← Simulator.CompanyCONTENT HISTORY

Update to Simulator.Company

Snapshot Oct 24, 2026 · 13:31 UTC · version 2.10.0

Collection source: downloaded plugin package.

WHAT CHANGED

First saved snapshot

No earlier snapshot is available to establish a change.

Save Simulator.Company and check future changes in Following.

Up to 10 plugins free · no card required.

Compare saved observations

Download comparison JSON
Full technical diff · 0 changed fields
Full snapshot data
{
  "description": "Simulator.Company chat & messaging specialist — sending messages to a user, and creating or reusing p2p (1:1) and group chats. In Simulator a chat is an actor of the Events system form (data.chatType = \"p2p\" | \"group\"), its participants are the actor's access-rule members, and its messages are `comment` reactions. Use when the user wants to \"message\", \"write to\", \"DM\", \"send a message to\" someone, \"open a chat with\", \"start a conversation\", or \"reply in the chat\". Activate on \"write a message to user N\", \"send N a message\", \"open a chat with\", \"message someone\", \"напиши повідомлення користувачу\", \"надішли повідомлення\", \"відкрий чат з\", \"почни розмову з\", \"напиши юзеру\", \"напиши сообщение пользователю\", \"отправь сообщение\", \"открой чат с\", \"напиши в чат\". For comments/approvals on an arbitrary actor (not a chat) use `simulator-reactions`; for files use `simulator-attachments`; for sharing/access in general use `simulator-access`.\n",
  "included_files": [],
  "name": "simulator-chat",
  "skill_md_contents": "---\nname: simulator-chat\ndescription: >\n  Simulator.Company chat & messaging specialist — sending messages to a user, and\n  creating or reusing p2p (1:1) and group chats. In Simulator a chat is an actor of\n  the Events system form (data.chatType = \"p2p\" | \"group\"), its participants are the\n  actor's access-rule members, and its messages are `comment` reactions. Use when the\n  user wants to \"message\", \"write to\", \"DM\", \"send a message to\" someone, \"open a chat\n  with\", \"start a conversation\", or \"reply in the chat\". Activate on\n  \"write a message to user N\", \"send N a message\", \"open a chat with\", \"message someone\",\n  \"напиши повідомлення користувачу\", \"надішли повідомлення\", \"відкрий чат з\",\n  \"почни розмову з\", \"напиши юзеру\", \"напиши сообщение пользователю\",\n  \"отправь сообщение\", \"открой чат с\", \"напиши в чат\". For comments/approvals on an\n  arbitrary actor (not a chat) use `simulator-reactions`; for files use\n  `simulator-attachments`; for sharing/access in general use `simulator-access`.\n---\n\n> **Curated tool names (v2 server):** `searchUsers`, `getUsers`, `getSystemActor`,\n> `getForms`, `filterActors`, `createActor`, `getActorByRef`, `saveAccessRules`,\n> `createReaction`, `getReactions`. Call them by these exact names. There is **no**\n> dedicated \"create chat\" / \"send message\" tool — a chat is composed from these.\n\n# Simulator.Company Chat Specialist\n\nIn Simulator.Company a **chat is an actor of the `Events` system form**:\n\n- `data.chatType = \"p2p\"` → a 1:1 chat · `\"group\"` → a group chat · empty → a plain\n  event / SIP meeting (not a chat).\n- **Participants** = the chat actor's **access-rule members** (keyed by `userId`) — *not*\n  a data field. The creator is the implicit owner; other participants are added with\n  `saveAccessRules`.\n- **Messages** = **`comment` reactions** posted under the chat actor.\n\nThere is no server-side \"p2p has exactly two members / don't duplicate\" enforcement —\n**this skill owns that discipline**, and it must match the platform's standard p2p\nconvention or two clients will create duplicate chats for the same pair. The\nconvention:\n\n- **Deterministic `ref`** = `p2pConversation_<minUserId>_<maxUserId>` — both\n  participants' raw workspace userIds **sorted ascending**, joined with `_`. This is the\n  p2p dedup key; the UI finds/reuses a chat *only* by this ref.\n- **`title`** = `<nickA>:p2p:<nickB>` — nicks ordered by the same ascending userId sort.\n- **`data`** = `{ chatType:\"p2p\", startDate:<nowSec>, endDate:<nowSec>, scheduleMeeting:false }`.\n- **Participants** are added by a **separate** `saveAccessRules` call (never inline in\n  the create body); the creator is owner implicitly.\n\n> Read `$CLAUDE_PLUGIN_ROOT/docs/entities/chats.md` for the full model, the verbatim\n> Events-form field list, and the current-user constraint.\n\nReply to the user in **their own language**.\n\n---\n\n## Test case: \"write a message to user N\"\n\nThis is the canonical flow. Each step names the exact tool.\n\n### 1 — Resolve user N\n\n```\nsearchUsers(query=\"N\")          # accId defaults to the active workspace\n```\n\nTake N's `userId` from the result (e.g. `4210`). If several match, ask the user which\none (or show name + email). `getUsers` lists everyone if you need to browse.\n\n> `getSystemActor(objType=\"user\", objId=<userId>)` returns N's system \"twin\" actor. You\n> usually **do not need it** to chat — membership uses the raw `userId` — but it\n> get-or-creates the twin if some flow needs N represented as an actor.\n\n### 2 — Resolve the Events form id (per workspace)\n\n```\ngetForms(formTypes=\"system\")    # find the row whose title == \"Events\" → its id\n```\n\nThe Events form id differs per workspace, so don't hardcode it. (You may instead pass\n`formName=\"Events\"` to `createActor` and let it resolve the id.)\n\n### 3 — Build the canonical ref + title (reuse key)\n\nThe p2p dedup key needs **both** userIds. The recipient's came from step 1; resolve the\n**sender's** `userId` too (see the box below), then:\n\n```\nids   = sortAscending(senderUserId, N_userId)        # numeric ascending\nref   = \"p2pConversation_\" + ids[0] + \"_\" + ids[1]   # e.g. p2pConversation_4210_4310\ntitle = nick(ids[0]) + \":p2p:\" + nick(ids[1])        # e.g. olena:p2p:petro\n```\n\n> **Getting the sender's userId.** There is **no PAPI \"current user\" endpoint**, and the\n> auth token carries the caller's `saId`+`nick`, not the per-workspace `userId`. So:\n> ask the user, take it from context, or `getUsers` and match the token's `saId`/`nick`\n> to a member's `id`. **If you cannot get it,** fall back to discovery-by-member in\n> step 4 and skip the ref (note: a ref-less chat may be duplicated later by the web UI,\n> which keys reuse on ref).\n\n### 4 — Find an existing chat (reuse, don't duplicate)\n\nCanonical — by the deterministic ref, exactly as the web UI does:\n\n```\ngetActorByRef(formId=<EventsFormId>, ref=<ref>)     # found → reuse its id, skip to step 6\n```\n\nFallback when the sender's userId is unknown — discover by participant (still finds\nUI-created chats, since the recipient is an access member):\n\n```\nfilterActors(formId=<EventsFormId>, q=\"chatType=p2p\", members=\"<N_userId>:view\")\n```\n\n### 5 — Create the p2p chat (only if none exists)\n\n```\ncreateActor(\n  formName=\"Events\",                       # or formId=<EventsFormId>\n  title=<title>,                           # \"<nickA>:p2p:<nickB>\"\n  ref=<ref>,                               # p2pConversation_<minId>_<maxId> — REQUIRED for UI reuse\n  data={ \"chatType\": \"p2p\",\n         \"startDate\": <nowUnixSeconds>,\n         \"endDate\":   <nowUnixSeconds>,\n         \"scheduleMeeting\": false })        # plain unix SECONDS on Events — not a calendar object\n```\n\n`startDate`/`endDate` are **required** by the Events form. On a chat they are just\n\"now\" as integer unix seconds (the nested calendar `{startDate,endDate,...}` object used\nelsewhere does **not** apply here).\n\nThen add N as the second participant (the creator is owner implicitly — do **not** add a\nself access-rule):\n\n```\nsaveAccessRules(\n  objType=\"actor\", objId=\"<newChatId>\",\n  rules=[{ \"action\":\"create\",\n           \"data\":{ \"userId\": <N_userId>,\n                    \"privs\":{ \"view\":true, \"modify\":true, \"remove\":true } } }])\n```\n\n`saveAccessRules` is applied asynchronously and returns a `taskId`; posting the message\nnext still works.\n\n### 6 — Post the message (a `comment` reaction)\n\n```\ncreateReaction(type=\"comment\", actorId=\"<chatId>\", description=\"<the message text>\")\n```\n\nThat `comment` reaction under the chat actor **is** the chat message. To read the\nconversation back: `getReactions(actorId=\"<chatId>\", view=\"flat\", orderValue=\"ASC\")`.\n\n---\n\n## Variations\n\n- **Group chat.** Create with `data.chatType=\"group\"` and **no `ref`** (the UI gives\n  group chats no dedup), then `saveAccessRules` (or `bulkSaveAccessRules`) for every\n  participant `userId`. To find a group's members later, filter by them:\n  `filterActors(members=\"11:view,22:view,33:view\", q=\"chatType=group\")`.\n- **Reply in a thread.** Pass `parentId=<reactionId>` to `createReaction` (see\n  `simulator-reactions`).\n- **Attach a file to a message.** Upload first (`uploadBase64` → `attachId`), then\n  `createReaction(..., attachments=[{attachId:<id>}])` — see `simulator-attachments`.\n- **Post quietly (no notification).** `createReaction(..., notify=false)`.\n- **Hand the message to the AI agent.** `createReaction(..., extra={mcp:true})` runs the\n  platform AI agent on it under the requesting user's access (see `simulator-reactions`).\n\n## Reuse & safety rules\n\n- **Always look up (step 4) before creating (step 5).** Creating a second p2p Events\n  actor for the same pair silently produces a duplicate chat — the platform does not\n  dedupe; only the deterministic `ref` does.\n- **Always set the canonical `ref` on create** (`p2pConversation_<minId>_<maxId>`) so the\n  web UI reuses the same chat instead of making its own duplicate.\n- **Participants are access rules, never `data` fields.** Do not store member ids in\n  `data`, and do not add a self access-rule (the creator is owner implicitly).\n- **Confirm before messaging on the user's behalf** if the message is outward-facing —\n  a chat message notifies the recipient.\n- A chat needs `chatType` set; an Events actor without it is a plain event/meeting, and\n  comments on it are ordinary discussion, not chat messages.\n\n---\n\n## Relationship to the other skills\n\n| Skill | Boundary |\n|---|---|\n| `simulator-agents` | Delegating work to a person as an agent (assess who fits, then act / task / message) — it composes this p2p flow for its \"message the person\" outcome. |\n| `simulator-reactions` | Generic comments/approvals/ratings on **any** actor (a chat message is a `comment` reaction — that skill covers the reaction mechanics). |\n| `simulator-actors` | The Events actor's own `data` fields and the actor `data` value protocol. |\n| `simulator-access` | Access rules in general (chat participants are access-rule members). |\n| `simulator-attachments` | Files on a message/actor. |\n| `simulator-init` | Login + workspace selection (needed before any of this). |\n\n## Reference Documents\n\n| Path | When to read |\n|---|---|\n| `$CLAUDE_PLUGIN_ROOT/docs/entities/chats.md` | The chat model: Events form, chatType, members-as-access, messages-as-reactions |\n| `$CLAUDE_PLUGIN_ROOT/docs/entities/reactions.md` | Reaction types, tree/threading, AI-agent reactions |\n| `$CLAUDE_PLUGIN_ROOT/docs/entities/system-forms.md` | The Events system form among the built-ins |\n\n## Tips\n\n- `accId`/workspace defaults to the active one — make sure `simulator-init` ran first.\n- The Events form id is **per workspace** — resolve it (`getForms`/`formName`), never hardcode.\n- On Events, `startDate`/`endDate` are plain unix **seconds**, both \"now\" for a chat.\n- Canonical reuse = `getActorByRef(ref=\"p2pConversation_<minId>_<maxId>\")` (matches the web UI); it needs both userIds — the sender's isn't in the token (`saId`/`nick` only, no PAPI /me), so derive it or fall back to `filterActors(members=…, q=\"chatType=p2p\")`.\n- A message is just `createReaction(type=\"comment\", actorId=<chatId>, description=...)`.\n"
}

SHA-256 of public snapshot: 9b105ea1c82a64baf47a6ec94ac8efb714f8aa52f6a02d0f37ca042b11397902