@rubytech/create-realagent 1.0.840 → 1.0.843

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.d.ts +2 -0
  3. package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.d.ts.map +1 -0
  4. package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.js +88 -0
  5. package/payload/platform/lib/account-enumeration/dist/__tests__/enumerate.test.js.map +1 -0
  6. package/payload/platform/lib/account-enumeration/dist/index.d.ts +23 -0
  7. package/payload/platform/lib/account-enumeration/dist/index.d.ts.map +1 -0
  8. package/payload/platform/lib/account-enumeration/dist/index.js +96 -0
  9. package/payload/platform/lib/account-enumeration/dist/index.js.map +1 -0
  10. package/payload/platform/lib/account-enumeration/src/__tests__/enumerate.test.ts +94 -0
  11. package/payload/platform/lib/account-enumeration/src/index.ts +96 -0
  12. package/payload/platform/lib/account-enumeration/tsconfig.json +8 -0
  13. package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.d.ts +2 -0
  14. package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.d.ts.map +1 -0
  15. package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.js +165 -0
  16. package/payload/platform/lib/graph-write/dist/__tests__/account-id-gate.test.js.map +1 -0
  17. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js +15 -5
  18. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js.map +1 -1
  19. package/payload/platform/lib/graph-write/dist/index.d.ts +12 -0
  20. package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
  21. package/payload/platform/lib/graph-write/dist/index.js +25 -0
  22. package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
  23. package/payload/platform/lib/graph-write/src/__tests__/account-id-gate.test.ts +189 -0
  24. package/payload/platform/lib/graph-write/src/__tests__/action-provenance-gate.test.ts +16 -5
  25. package/payload/platform/lib/graph-write/src/index.ts +45 -1
  26. package/payload/platform/package.json +2 -2
  27. package/payload/platform/plugins/admin/PLUGIN.md +2 -1
  28. package/payload/platform/plugins/admin/mcp/dist/index.js +133 -1
  29. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  30. package/payload/platform/plugins/admin/skills/skill-builder/SKILL.md +18 -19
  31. package/payload/platform/plugins/docs/references/attachments.md +2 -2
  32. package/payload/platform/plugins/docs/references/internals.md +1 -1
  33. package/payload/platform/plugins/docs/references/plugins-guide.md +10 -0
  34. package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
  35. package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
  36. package/payload/platform/templates/agents/admin/IDENTITY.md +4 -0
  37. package/payload/server/adminuser-self-heal-RY4NFCI7.js +45 -0
  38. package/payload/server/chunk-2YG3AYAH.js +1508 -0
  39. package/payload/server/chunk-7DFOKDNM.js +2098 -0
  40. package/payload/server/chunk-DTWW35TK.js +667 -0
  41. package/payload/server/chunk-HTYXRFT6.js +727 -0
  42. package/payload/server/chunk-NPVEOM3D.js +1508 -0
  43. package/payload/server/chunk-OLP7LZDW.js +10119 -0
  44. package/payload/server/chunk-QGM4M3NI.js +37 -0
  45. package/payload/server/chunk-S27QCBFQ.js +10071 -0
  46. package/payload/server/chunk-TS6CKCGU.js +727 -0
  47. package/payload/server/chunk-XECKT3YB.js +10071 -0
  48. package/payload/server/client-pool-2WQ2Q3TF.js +32 -0
  49. package/payload/server/client-pool-SMWCZMZG.js +32 -0
  50. package/payload/server/cloudflare-task-tracker-NQK7A2EQ.js +20 -0
  51. package/payload/server/cloudflare-task-tracker-O4ZA4TAS.js +20 -0
  52. package/payload/server/cloudflare-task-tracker-XFGXO7ZV.js +20 -0
  53. package/payload/server/maxy-edge.js +2 -2
  54. package/payload/server/public/assets/{admin-D678VwpH.js → admin-CvwOOG4D.js} +1 -1
  55. package/payload/server/public/assets/{public-C7mCgRX0.js → public-Br9YjNs_.js} +2 -2
  56. package/payload/server/public/index.html +1 -1
  57. package/payload/server/public/public.html +1 -1
  58. package/payload/server/server.js +65 -41
@@ -77,37 +77,36 @@ Using the lean pattern from `references/lean-pattern.md`, compose:
77
77
 
78
78
  Show the user the complete SKILL.md content and each reference file. Ask them to review.
79
79
 
80
- ## Step 7: Save to Memory
80
+ ## Step 7: Save the Skill as a Plugin File
81
81
 
82
- Use `memory-write` to save the composed skill to the knowledge graph:
82
+ Use `store-skill` to write the composed skill to disk as part of an admin-managed plugin. The tool computes the path internally — supply the names and content only.
83
+
84
+ Pick a `pluginName` that groups related operator-authored skills (e.g. `beacons-skills`, `my-utils`). Reuse the same `pluginName` across calls so the skill joins existing operator skills under one plugin. If this is the first skill in a new plugin, the tool creates the `PLUGIN.md` automatically.
83
85
 
84
86
  ```
85
- memory-write({
86
- labels: ["CreativeWork"],
87
- properties: {
88
- title: "Skill: {name}",
89
- abstract: "{full SKILL.md content including frontmatter}"
90
- }
87
+ store-skill({
88
+ pluginName: "{kebab-case plugin name}",
89
+ skillName: "{kebab-case skill name}",
90
+ description: "{the one-sentence description from Step 1}",
91
+ publicEmbed: false,
92
+ body: "{SKILL.md body content WITHOUT frontmatter — activation conditions, behaviour rules, references index}",
93
+ references: [
94
+ { filename: "{kebab-case-name}.md", content: "{full reference body}" }
95
+ ]
91
96
  })
92
97
  ```
93
98
 
94
- If the skill has reference files, save each as a separate `CreativeWork` node:
99
+ Set `publicEmbed: true` only when the user wants this skill surfaced to the public agent. Default to `false` for admin-only skills.
95
100
 
96
- ```
97
- memory-write({
98
- labels: ["CreativeWork"],
99
- properties: {
100
- title: "Skill Reference: {name}/{filename}",
101
- abstract: "{full reference file content}"
102
- }
103
- })
104
- ```
101
+ `references` is optional — pass `[]` (or omit) when the skill has no detailed procedures or templates.
105
102
 
106
103
  ## Step 8: Confirm
107
104
 
108
105
  Tell the user:
109
106
 
110
- > "Your skill has been saved to memory. I can recall it when the situation matches. You can update it at any timejust ask me to edit the skill."
107
+ > "Your skill is saved on disk as part of the `{pluginName}` plugin and is active right now for the admin agent. I can use it when the situation matches. To edit it later, ask me to update the skill I'll re-run `store-skill` with the new content."
108
+
109
+ If `publicEmbed: true`, add: "It will surface in the public agent on the next session start."
111
110
 
112
111
  ---
113
112
 
@@ -17,7 +17,7 @@ Anything else is refused at upload time with a message naming the type.
17
17
 
18
18
  ## Size caps
19
19
 
20
- - **Per file:** 20 MB. Enforced at the upload endpoint — files over this limit never reach disk.
20
+ - **Per file:** 50 MB. Enforced at the upload endpoint — files over this limit never reach disk.
21
21
  - **Per message:** up to 5 files.
22
22
  - **Uncompressed contents of a single zip:** 100 MB. A zip whose declared uncompressed total is over this limit is refused before any byte is extracted (decompression-bomb guard).
23
23
 
@@ -37,7 +37,7 @@ Nothing is ingested, sent, or acted on automatically. The extraction is local an
37
37
  - `tar`, `tar.gz`, `7z`, `rar` — zip only. If you have one of these, unzip/convert locally and upload the zip (or the extracted files directly).
38
38
  - Nested archives — a zip-inside-a-zip is extracted one level; you can ask the agent to unpack the inner one afterwards.
39
39
  - Password-protected zips — the agent will tell you to unlock locally and re-upload.
40
- - Uploads larger than 20 MB — split the archive, or upload the individual files.
40
+ - Uploads larger than 50 MB — split the archive, or upload the individual files.
41
41
 
42
42
  ## Where the files live
43
43
 
@@ -140,7 +140,7 @@ WHERE node.accountId = $accountId
140
140
 
141
141
  Multi-tenancy boundary. Every query is scoped to the requesting account. The `ACCOUNT_ID` environment variable is set at MCP server startup — it is not a tool parameter and cannot be overridden by the agent.
142
142
 
143
- The read filter alone is not sufficient — it correctly *hides* alien-account nodes from every UI but does not prevent them existing. A writer that misresolves `accountId` (literal, undefined, or inferred-from-the-wrong-context) leaks nodes into the graph with no downstream symptom; the read filter then keeps them invisible indefinitely. The write-side doctrine is documented in `.docs/neo4j.md` "Account isolation invariant" — every writer that stamps `n.accountId` must verify the value against `${DATA_ROOT}/accounts/<id>/account.json` before write.
143
+ The read filter alone is not sufficient — it correctly *hides* alien-account nodes from every UI but does not prevent them existing. A writer that misresolves `accountId` (literal, undefined, or inferred-from-the-wrong-context) leaks nodes into the graph with no downstream symptom; the read filter then keeps them invisible indefinitely. The write-side doctrine is documented in `.docs/neo4j.md` "Account isolation invariant" — every writer that stamps `n.accountId` must verify the value against `${DATA_ROOT}/accounts/<id>/account.json` before write. The live floor is `writeNodeWithEdges` — every doctrine-primitive write is gated by an `accountId == process.env.ACCOUNT_ID` check (the spawning process validates `ACCOUNT_ID` at boot against the on-disk account set via the `account-enumeration` lib), with `[graph-write] reject reason=invalid-account-id …` as the rejection signal.
144
144
 
145
145
  ---
146
146
 
@@ -90,6 +90,16 @@ Tell {{productName}}:
90
90
 
91
91
  Ask {{productName}}: "What plugins do I have?" or "List my plugins."
92
92
 
93
+ ## Operator-Authored Plugins (skill-builder output)
94
+
95
+ Skills you create at runtime through the admin `skill-builder` skill are saved on disk as their own plugin under `data/accounts/{accountId}/plugins/{pluginName}/`. The admin agent calls `mcp__admin__store-skill`, which composes `PLUGIN.md` (on first call) and `skills/{skillName}/SKILL.md` plus any reference files. The agent supplies `pluginName`, `skillName`, `description`, `publicEmbed`, `body`, and optional references — the path is computed by the tool, never by the agent.
96
+
97
+ These operator-authored plugins survive reinstall because the installer's wipe zone excludes `data/`. At admin session start the platform mirrors `data/accounts/{accountId}/plugins/*` into the runtime plugins directory so the same `parsePluginFrontmatter` / `assemblePublicPluginContent` / `loadEmbeddedPlugins` loaders that read shipped and premium plugins also pick up operator-authored ones — no special-case loader path. The admin agent sees every operator skill by default; per-skill `publicEmbed: true|false` controls which skills surface to the public agent.
98
+
99
+ To edit an operator-authored skill later, ask {{productName}} to update it — the admin agent re-runs `store-skill` for the same `pluginName`/`skillName` and the new content overwrites in place. To remove one, delete the directory under `data/accounts/{accountId}/plugins/{pluginName}/skills/{skillName}/` (or the whole plugin) — the next session start re-mirrors the remaining skills only.
100
+
101
+ `pluginName` collisions with shipped plugin names are refused by `store-skill` with a structured error. See [.docs/agents.md](../../../../.docs/agents.md) § "Operator-authored skills as plugin files" for the full contract.
102
+
93
103
  ## Brand Templating (for plugin and skill authors)
94
104
 
95
105
  Skill content, plugin manifests, agent templates, and reference files reference the operator-visible brand name only via the literal `{{productName}}` placeholder. The platform substitutes from `brand.json.productName` at read time — Maxy installs render `Maxy`, Real Agent installs render `Real Agent`, all from the same source content.
@@ -58,7 +58,7 @@ tail -200 ~/.maxy/logs/maxy-ui.log | rg '\[remote-auth\].*resolvedKind='
58
58
  - Platform process has stopped — restart it
59
59
  - Network issue if accessing remotely — check your Cloudflare tunnel is running
60
60
 
61
- **If the chat shows a single `[agent-loop-stop] same error twice — aborting` line and stops:** {{productName}} hit the same structured tool failure twice in a row inside one turn (e.g. a permission gate refused the same write twice). The runtime aborted the turn after the second occurrence to save tokens instead of running until the SDK turn budget exhausted. The blocker text names the tool and the first line of the error. Resolve the underlying cause (re-run the named skill, fix the missing prerequisite, etc.) and send your next message — the next turn cold-starts with a fresh client. To see the diagnostic, ask {{productName}}: "Show me the most recent agent-loop-stop log line."
61
+ **If the chat shows a single `[agent-loop-stop] same error twice — aborting` line and stops:** {{productName}} hit the same structured tool failure twice in a row inside one turn (e.g. a permission gate refused the same write twice, or two `Read` calls hit the same missing file). The runtime aborted the turn after the second occurrence to save tokens instead of running until the SDK turn budget exhausted. The blocker text names the tool and the first line of the error. Resolve the underlying cause (re-run the named skill, fix the missing prerequisite, etc.) and send your next message — the next turn cold-starts with a fresh client AND carries a recovery briefing so {{productName}} picks up where it aborted, instead of cold-querying its own session list. To see the diagnostic, ask {{productName}}: "Show me the most recent agent-loop-stop log line." Greppable post-deploy invariants: `[agent-loop-stop] reason=identical-tool-failure tool=<name> errorSignature=<sha8> toolInputDigest=<sha8>` (the `toolInputDigest` discriminator means two `Read` calls on different `file_path` no longer collapse to one signature) followed by paired `[recovery-handoff] generated reason=agent-loop-stop` and `[recovery-handoff] consumed reason=agent-loop-stop`. A `[recovery-handoff] WARN missing-on-cold-create` line means the recovery briefing wasn't persisted — surface to support.
62
62
 
63
63
  **Agent searches the filesystem after uploading a zip.** If you uploaded a zip and the agent burns several turns running `find` / `Glob` instead of unzipping, that is the symptom of the recovery-retry attachment-context regression (now closed by the recovery context preservation contract in `.docs/agents.md`). Greppable confirmation is the `[context-overflow-recovery] retry … attachmentsCarried=<n>` line in the conversation stream log. If you see `[context-overflow-recovery] WARN attachment-context-lost`, the regression has returned — surface to support.
64
64
 
@@ -53,7 +53,7 @@ When per-group activation is `mention`, the agent fires only if the inbound mess
53
53
 
54
54
  Every `messages.upsert` event (both `notify` and `append`, both `fromMe` directions) writes a `:Message:WhatsAppMessage` row to Neo4j attached to the sessionKey-keyed `:Conversation`. A single capture site at `platform/ui/app/lib/whatsapp/manager.ts` covers inbound, outbound (Baileys echoes agent-sent messages back through `messages.upsert` with `fromMe=true`), and owner-mirror — without touching `outbound/send.ts`. `messageId` namespace is `whatsapp-live:<waName>:<remoteJid>:<msg.key.id>` where `<waName>` is the Baileys credential dirname (e.g. `default`); distinct from the `:Section:Conversation` chunks written by the source-agnostic `conversation-archive` skill — live and archive live in disjoint label spaces. Persist failures are loud (`[whatsapp-persist] FAIL …`) and never block dispatch — silent loss is the worse failure mode.
55
55
 
56
- **`accountId` contract.** `n.accountId` on every `:Conversation`, `:Person`, and `:Message:WhatsAppMessage` row stamped by this plugin is the **platform-side UUID** resolved by [`resolvePlatformAccountId()`](../../ui/app/lib/whatsapp/platform-account-id.ts) from `data/accounts/<uuid>/account.json` — NOT the Baileys credential dirname (which is only used as the `messageId`/`sessionKey` namespace token). The boot-time line `[whatsapp-persist] resolved-account-id waname=<dir> uuid=<uuid>` records the resolution. Doctrine: see `.docs/neo4j.md` "Account isolation invariant" — every writer that stamps `n.accountId` must verify the value against `${DATA_ROOT}/accounts/<id>/account.json` before write. The helper loud-throws on zero or multi accounts (Phase 0 single-account invariant), aborting the WhatsApp connection start before any write can occur.
56
+ **`accountId` contract.** `n.accountId` on every `:Conversation`, `:Person`, and `:Message:WhatsAppMessage` row stamped by this plugin is the **platform-side UUID** resolved by [`resolvePlatformAccountId()`](../../ui/app/lib/whatsapp/platform-account-id.ts) from `data/accounts/<uuid>/account.json` — NOT the Baileys credential dirname (which is only used as the `messageId`/`sessionKey` namespace token). The boot-time line `[whatsapp-persist] resolved-account-id waname=<dir> uuid=<uuid>` records the resolution. Doctrine: see `.docs/neo4j.md` "Account isolation invariant" — every writer that stamps `n.accountId` must verify the value against `${DATA_ROOT}/accounts/<id>/account.json` before write. The helper loud-throws on zero or multi accounts (Phase 0 single-account invariant), aborting the WhatsApp connection start before any write can occur. The same boot-validated identity (`process.env.ACCOUNT_ID`) backs the central live floor at [`writeNodeWithEdges`](../../lib/graph-write/src/index.ts) — any write whose `accountId` differs from the spawning process's `ACCOUNT_ID` is rejected by the gate; the WhatsApp helper is the writer-side discipline, the gate is the universal floor.
57
57
 
58
58
  ## Skills
59
59
 
@@ -121,6 +121,8 @@ Operationalises the CONCISE prerogative for clarification. Three rules:
121
121
 
122
122
  ## Tool Routing
123
123
 
124
+ **Bundled-SKILL access contract.** Plugin skills and references live in the bundled npm package, not on disk under `<accountDir>/agents/admin/plugins/`. Load them with `mcp__admin__plugin-read` only — the `<plugin-manifest>` `Skills:` and `References:` lines name the exact `pluginName` and file path to pass. `Read`, `Glob`, and `Bash` against those paths will fail with `File does not exist`, and the runtime agent-loop-stop interceptor cannot distinguish a generic-error retry from a tool-routing mistake. Subagents inherit this contract via the parent system prompt — never dispatch an `Agent` subagent with a `Read` directive against a bundled SKILL path.
125
+
124
126
  Plugins provide domain-specific tools that query their own data stores directly. `memory-search` is a general-purpose semantic search across the entire knowledge graph — it finds nodes by vector similarity, which means results are ranked by semantic closeness to the query, not by domain relevance. A query containing the word "email" will surface product documentation *about* email features before it surfaces actual Email nodes whose content is unrelated to the query wording.
125
127
 
126
128
  When the user's intent maps to a specific plugin's domain, use that plugin's tools — not `memory-search`. The `<plugin-manifest>` groups tools by plugin and describes each plugin's purpose and retrieval paths. The `<specialist-domains>` block within it lists every specialist-owned tool by name. Match user intent to a tool in these registries first; fall back to `memory-search` only when the query genuinely spans multiple domains or no tool in the manifest matches the intent.
@@ -245,6 +247,8 @@ When `<previous-context>` is present:
245
247
 
246
248
  When `<previous-context>` is absent, Neo4j was unreachable or no prior context exists — proceed normally, using tool calls to establish state.
247
249
 
250
+ A separate `<recovery-context>` block on the user-message side appears only when the previous turn was aborted by `agent-loop-stop` or `main-stream-stalled` and the platform persisted a continuation briefing. Treat it as the authoritative description of what failed and what was incomplete — do not re-execute the failed work, do not call `session-list` to figure out what was happening, and do not re-research the blocker. The block coexists with `<previous-context>` (system-prompt session summary) on the recovery turn; the two are not duplicates — `<previous-context>` orients you to the session, `<recovery-context>` orients you to the specific failed turn.
251
+
248
252
  In managed context mode, conversation history is provided within `<conversation-history>` tags. Use `session-compact-status` to retrieve older archived context if needed.
249
253
 
250
254
  ## Tasks
@@ -0,0 +1,45 @@
1
+ import "./chunk-QGM4M3NI.js";
2
+
3
+ // app/lib/adminuser-self-heal.ts
4
+ var UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5
+ async function selfHealAdminUser(args) {
6
+ const { driver, userId, accountId } = args;
7
+ if (!UUID_REGEX.test(accountId)) {
8
+ throw new Error(
9
+ `[adminuser-self-heal] refusing to run: accountId is not a UUID (got: '${accountId.slice(0, 12)}\u2026')`
10
+ );
11
+ }
12
+ if (!UUID_REGEX.test(userId)) {
13
+ throw new Error(
14
+ `[adminuser-self-heal] refusing to run: userId is not a UUID (got: '${userId.slice(0, 12)}\u2026')`
15
+ );
16
+ }
17
+ const session = driver.session();
18
+ try {
19
+ const result = await session.run(
20
+ `MATCH (au:AdminUser {userId: $userId})
21
+ WHERE au.accountId IS NULL OR au.role IS NULL
22
+ SET au.accountId = COALESCE(au.accountId, $accountId),
23
+ au.role = COALESCE(au.role, 'owner'),
24
+ au.updatedAt = $now
25
+ RETURN count(au) AS healed`,
26
+ { userId, accountId, now: (/* @__PURE__ */ new Date()).toISOString() }
27
+ );
28
+ const raw = result.records[0]?.get("healed");
29
+ let healed = 0;
30
+ if (typeof raw === "number") {
31
+ healed = raw;
32
+ } else if (raw && typeof raw.toNumber === "function") {
33
+ healed = raw.toNumber();
34
+ }
35
+ console.error(
36
+ `[adminuser-self-heal] healed=${healed} userId=${userId.slice(0, 8)} accountId=${accountId.slice(0, 8)}`
37
+ );
38
+ return { healed };
39
+ } finally {
40
+ await session.close();
41
+ }
42
+ }
43
+ export {
44
+ selfHealAdminUser
45
+ };