@rubytech/create-maxy-code 0.1.498 → 0.1.500

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 (88) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/active-rules/dist/index.d.ts +3 -7
  3. package/payload/platform/lib/active-rules/dist/index.d.ts.map +1 -1
  4. package/payload/platform/lib/active-rules/dist/index.js +20 -52
  5. package/payload/platform/lib/active-rules/dist/index.js.map +1 -1
  6. package/payload/platform/lib/active-rules/src/index.test.ts +35 -69
  7. package/payload/platform/lib/active-rules/src/index.ts +17 -58
  8. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  9. package/payload/platform/plugins/admin/hooks/__tests__/admin-authoring-observer.test.sh +135 -0
  10. package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-directive.test.sh +11 -2
  11. package/payload/platform/plugins/admin/hooks/prompt-optimiser-directive.sh +6 -3
  12. package/payload/platform/plugins/admin/mcp/dist/index.js +52 -107
  13. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  14. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts +8 -4
  15. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts.map +1 -1
  16. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js +67 -50
  17. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js.map +1 -1
  18. package/payload/platform/plugins/admin/mcp/dist/store-skill-core.d.ts +33 -0
  19. package/payload/platform/plugins/admin/mcp/dist/store-skill-core.d.ts.map +1 -0
  20. package/payload/platform/plugins/admin/mcp/dist/store-skill-core.js +122 -0
  21. package/payload/platform/plugins/admin/mcp/dist/store-skill-core.js.map +1 -0
  22. package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +11 -5
  23. package/payload/platform/plugins/contacts/.claude-plugin/plugin.json +1 -1
  24. package/payload/platform/plugins/contacts/PLUGIN.md +1 -1
  25. package/payload/platform/plugins/contacts/mcp/dist/index.js +3 -12
  26. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
  27. package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-lookup.test.js +105 -2
  28. package/payload/platform/plugins/contacts/mcp/dist/tools/__tests__/contact-lookup.test.js.map +1 -1
  29. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts +11 -1
  30. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.d.ts.map +1 -1
  31. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js +56 -4
  32. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-lookup.js.map +1 -1
  33. package/payload/platform/plugins/docs/references/contacts-guide.md +2 -0
  34. package/payload/platform/plugins/docs/references/internals.md +2 -2
  35. package/payload/platform/plugins/docs/references/outlook-guide.md +4 -0
  36. package/payload/platform/plugins/docs/references/plugins-guide.md +2 -2
  37. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-signature.test.d.ts +2 -0
  38. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-signature.test.d.ts.map +1 -0
  39. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-signature.test.js +53 -0
  40. package/payload/platform/plugins/email/mcp/dist/__tests__/compose-signature.test.js.map +1 -0
  41. package/payload/platform/plugins/email/mcp/dist/__tests__/signature.test.d.ts +2 -0
  42. package/payload/platform/plugins/email/mcp/dist/__tests__/signature.test.d.ts.map +1 -0
  43. package/payload/platform/plugins/email/mcp/dist/__tests__/signature.test.js +50 -0
  44. package/payload/platform/plugins/email/mcp/dist/__tests__/signature.test.js.map +1 -0
  45. package/payload/platform/plugins/email/mcp/dist/lib/compose.d.ts.map +1 -1
  46. package/payload/platform/plugins/email/mcp/dist/lib/compose.js +15 -2
  47. package/payload/platform/plugins/email/mcp/dist/lib/compose.js.map +1 -1
  48. package/payload/platform/plugins/email/mcp/dist/lib/signature.d.ts +29 -0
  49. package/payload/platform/plugins/email/mcp/dist/lib/signature.d.ts.map +1 -0
  50. package/payload/platform/plugins/email/mcp/dist/lib/signature.js +55 -0
  51. package/payload/platform/plugins/email/mcp/dist/lib/signature.js.map +1 -0
  52. package/payload/platform/plugins/email/references/email-reference.md +10 -0
  53. package/payload/platform/plugins/email/skills/email-composition/SKILL.md +9 -1
  54. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.js +10 -9
  55. package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/profile-read-active-rules.test.js.map +1 -1
  56. package/payload/platform/plugins/memory/skills/conversational-memory/SKILL.md +2 -0
  57. package/payload/platform/plugins/memory/skills/preference-audit/SKILL.md +16 -0
  58. package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +6 -0
  59. package/payload/platform/plugins/prompt-optimiser/skills/prompt-optimiser/SKILL.md +1 -1
  60. package/payload/platform/plugins/prompt-optimiser/skills/prompt-optimiser/__tests__/restatement-ladder.test.sh +2 -1
  61. package/payload/platform/services/claude-session-manager/dist/index.js +26 -1
  62. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  63. package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.d.ts +19 -19
  64. package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.d.ts.map +1 -1
  65. package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.js +28 -34
  66. package/payload/platform/services/claude-session-manager/dist/preference-reconciliation-audit.js.map +1 -1
  67. package/payload/platform/services/claude-session-manager/dist/skill-registration-drift.d.ts +15 -0
  68. package/payload/platform/services/claude-session-manager/dist/skill-registration-drift.d.ts.map +1 -1
  69. package/payload/platform/services/claude-session-manager/dist/skill-registration-drift.js +61 -0
  70. package/payload/platform/services/claude-session-manager/dist/skill-registration-drift.js.map +1 -1
  71. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  72. package/payload/server/public/assets/{AdminLoginScreens-Ce5aBBWF.js → AdminLoginScreens-CKg_Dlpv.js} +1 -1
  73. package/payload/server/public/assets/{admin-BWb8me-E.js → admin-wtSUU5N0.js} +1 -1
  74. package/payload/server/public/assets/chat-DRuIxGtE.js +1 -0
  75. package/payload/server/public/assets/data-BdZCvyvN.js +1 -0
  76. package/payload/server/public/assets/{operator-icJwMkZF.js → operator-DglDYmdV.js} +1 -1
  77. package/payload/server/public/assets/{page-iG57nKOg.js → page-C-5sTAE-.js} +2 -2
  78. package/payload/server/public/assets/{page-DUgrGrJp.js → page-C8G5p4jr.js} +1 -1
  79. package/payload/server/public/assets/{public-CkOumzpf.js → public-DqJjXR4p.js} +1 -1
  80. package/payload/server/public/assets/{useVoiceRecorder-Cu6zvuFx.js → useVoiceRecorder-Cj-VZDKO.js} +1 -1
  81. package/payload/server/public/chat.html +3 -3
  82. package/payload/server/public/data.html +3 -3
  83. package/payload/server/public/index.html +4 -4
  84. package/payload/server/public/operator.html +4 -4
  85. package/payload/server/public/public.html +3 -3
  86. package/payload/server/server.js +89 -63
  87. package/payload/server/public/assets/chat-qtOiJX-Y.js +0 -1
  88. package/payload/server/public/assets/data-BjqC4hyM.js +0 -1
@@ -65,7 +65,7 @@ else
65
65
  fi
66
66
  rm -rf "$FAKEBIN"
67
67
 
68
- # Case 4 — both generated lists present: the ladder carries all three tiers, both
68
+ # Case 4 — both generated lists present: the ladder carries all four rungs, both
69
69
  # full lists, and the relocated outcome-only-brief contract. The hook reads the
70
70
  # lists from $PWD/agents/admin, so run it from a temp account dir.
71
71
  WD=$(mktemp -d); mkdir -p "$WD/agents/admin"
@@ -78,11 +78,20 @@ if printf '%s' "$ctx" | grep -q "specialists:content-producer" \
78
78
  && printf '%s' "$ctx" | grep -qE '(^|[^0-9])1\)' \
79
79
  && printf '%s' "$ctx" | grep -qE '(^|[^0-9])2\)' \
80
80
  && printf '%s' "$ctx" | grep -qE '(^|[^0-9])3\)' \
81
+ && printf '%s' "$ctx" | grep -qE '(^|[^0-9])4\)' \
81
82
  && printf '%s' "$ctx" | grep -qiE "freestyle|last resort"; then
82
- echo "PASS: both lists injected in three-tier ladder"; PASS=$((PASS+1))
83
+ echo "PASS: both lists injected in four-rung ladder"; PASS=$((PASS+1))
83
84
  else
84
85
  echo "FAIL: ladder/lists missing from additionalContext: $ctx" >&2; FAIL=$((FAIL+1))
85
86
  fi
87
+ # Task 1959: recurring-deliverable rung sits before the freestyle last resort.
88
+ if printf '%s' "$ctx" | grep -qi "skill-builder" \
89
+ && printf '%s' "$ctx" | grep -qiE "recurs|issues repeatedly|repeatable" \
90
+ && printf '%s' "$ctx" | grep -qi "agent-builder"; then
91
+ echo "PASS: recurring-deliverable rung present in ladder"; PASS=$((PASS+1))
92
+ else
93
+ echo "FAIL: recurring-deliverable rung missing from ladder: $ctx" >&2; FAIL=$((FAIL+1))
94
+ fi
86
95
  # Relocated outcome-only-brief contract.
87
96
  if printf '%s' "$ctx" | grep -qi "outcome" \
88
97
  && printf '%s' "$ctx" | grep -qi "binding constraints" \
@@ -1,13 +1,15 @@
1
1
  #!/usr/bin/env bash
2
2
  # UserPromptSubmit hook: inject the standing prompt-optimiser directive plus the
3
- # per-turn three-tier routing ladder as additionalContext on every turn, so the
3
+ # per-turn four-rung routing ladder as additionalContext on every turn, so the
4
4
  # agent restates each non-trivial prompt and routes it before acting. Mirrors
5
5
  # doctrine.sh: emit hookSpecificOutput.additionalContext via python3, fail-open
6
6
  # with exit 0.
7
7
  #
8
8
  # The ladder ranks, in order: (1) delegate to a specialist that owns the
9
9
  # deliverable via the Agent tool; (2) only if none fits, load an admin-usable
10
- # skill; (3) only if neither fits, author inline freestyle is the named last
10
+ # skill; (3) only if the deliverable recurs and no specialist or skill owns it,
11
+ # author its owning skill via skill-builder (or agent-builder) and produce
12
+ # through it; (4) only if it is a genuine one-off, author inline as the last
11
13
  # resort. The full agent roster and admin-usable skills list are re-emitted every
12
14
  # turn by reading two generated files from the account dir; the hook never walks
13
15
  # the plugins tree per turn (Task 698).
@@ -102,7 +104,8 @@ IFS= read -r -d '' DIRECTIVE_MID <<'DIRECTIVE_EOF' || true
102
104
  DIRECTIVE_EOF
103
105
 
104
106
  IFS= read -r -d '' DIRECTIVE_TAIL <<'DIRECTIVE_EOF' || true
105
- 3) Only if neither a specialist nor an admin skill fits, author it directly. Freestyle inline authoring is the last resort, not a peer of the two routes above.
107
+ 3) Only if neither a specialist nor an admin skill already owns it, decide whether the deliverable recurs. A deliverable the business issues repeatedly, or one the operator gives a fixed template, numbering, naming, or filing rule for, is authored once as its own skill: load skill-builder with skill-load (or agent-builder for a worker agent), create the owning skill, then produce the deliverable through it. This rung owns every repeatable business document or process; hand-building a recurring deliverable here is not a route.
108
+ 4) Only if the deliverable is a genuine one-off, author it directly. Freestyle inline authoring is the last resort, not a peer of the routes above.
106
109
  DIRECTIVE_EOF
107
110
 
108
111
  DIRECTIVE=$(printf '%s\n%s\n%s\n%s\n%s' \
@@ -5,7 +5,7 @@ import { z } from "zod";
5
5
  import { readFile, writeFile } from "node:fs/promises";
6
6
  import { resolve, join } from "node:path";
7
7
  import { execFile, execFileSync } from "node:child_process";
8
- import { appendFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
8
+ import { appendFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
9
9
  import { writeAdminEntry, removeAdminFromAccount, logUsersAudit, formatAuditRowIds } from "../../../../lib/admins-write/dist/index.js";
10
10
  import { setAccessPassword as setAccessPasswordHash, clearAccessPassword as clearAccessPasswordHash, accessPasswordCollides } from "../../../../lib/admin-access-password/dist/index.js";
11
11
  import { resolveCredentialTarget } from "./credential-identity.js";
@@ -20,6 +20,7 @@ import { homedir, hostname as osHostname } from "node:os";
20
20
  import QRCode from "qrcode";
21
21
  import { getSession, closeDriver } from "./lib/neo4j.js";
22
22
  import { findSkillOwners, searchSkills, computePluginReadHint, loadSkill, parseRequiredInputs, adminAuthoringSkillBlock, skillSlugFromReadArgs, AUTHORING_DISPATCH_HINT } from "./skill-resolution.js";
23
+ import { storeSkillToAccount } from "./store-skill-core.js";
23
24
  import { resolvePublicHostname } from "./lib/public-hostname.js";
24
25
  import { resolveAdminHostname } from "./lib/admin-hostname.js";
25
26
  import { publishSite } from "./tools/publish-site.js";
@@ -1808,15 +1809,16 @@ eagerTool(server, "cpu-triage-read", "Read a cpu-triage run started by cpu-triag
1808
1809
  }
1809
1810
  return { content: [{ type: "text", text: JSON.stringify(out, null, 2) }] };
1810
1811
  });
1811
- // operator-authored skills are written to disk as a plugin so the
1812
- // existing plugin-manifest loader picks them up (parsePluginFrontmatter +
1813
- // assemblePublicPluginContent + loadEmbeddedPlugins all read from
1814
- // <PLATFORM_ROOT>/plugins/<dir>/). Canonical = <accountDir>/plugins/<plugin>
1815
- // survives installer rmSync (data/ excluded); the mirror under
1816
- // <PLATFORM_ROOT>/plugins/<plugin> is rehydrated at admin session start by
1817
- // autoDeliverUserPlugins. The agent supplies pluginName/skillName/body
1818
- // path is computed by this tool from ACCOUNT_ID. Symmetric write counterpart
1819
- // to plugin-read.
1812
+ // Task 1962 — operator-authored skills are written as a plugin under the
1813
+ // per-account root <accountDir>/plugins/<plugin>/ ONLY. That copy survives the
1814
+ // installer wipe (data/ is excluded) and is loaded directly by the spawn
1815
+ // manifest (computeActivePlugins) and the admin skill resolvers, which walk the
1816
+ // account root. Nothing is written to the immutable PLATFORM_ROOT/plugins tree
1817
+ // (chattr +i) and there is no platform mirror. The write+resolve core lives in
1818
+ // store-skill-core.ts and fails closed if the written skill does not resolve,
1819
+ // so success is never reported for a skill that cannot load. The agent supplies
1820
+ // pluginName/skillName/body — the path is computed from ACCOUNT_ID. Symmetric
1821
+ // write counterpart to plugin-read.
1820
1822
  eagerTool(server, "store-skill", "Save an operator-authored skill on disk as part of an admin-managed plugin. " +
1821
1823
  "The skill becomes immediately discoverable by the admin agent (and the public agent if publicEmbed=true). " +
1822
1824
  "Path is computed internally from the active account; the agent supplies content + names only. " +
@@ -1850,97 +1852,31 @@ eagerTool(server, "store-skill", "Save an operator-authored skill on disk as par
1850
1852
  }
1851
1853
  }
1852
1854
  const accountDir = getAccountDir();
1853
- const canonicalPluginDir = resolve(accountDir, "plugins", pluginName);
1854
- const mirrorPluginDir = resolve(PLATFORM_ROOT, "plugins", pluginName);
1855
- const canonicalPluginMd = resolve(canonicalPluginDir, "PLUGIN.md");
1856
- const mirrorPluginMd = resolve(mirrorPluginDir, "PLUGIN.md");
1857
- // Collision: mirror dir exists from a shipped plugin (no canonical mirror).
1858
- // Refuse rather than silently shadow.
1859
- if (existsSync(mirrorPluginMd) && !existsSync(canonicalPluginMd)) {
1860
- console.error(`[store-skill] ERROR pluginName=${pluginName} skillName=${skillName} reason=collision-with-shipped path=${mirrorPluginDir}`);
1861
- return { content: [{ type: "text", text: `pluginName "${pluginName}" collides with a shipped plugin. Choose a different pluginName.` }], isError: true };
1862
- }
1863
- const skillDir = resolve(canonicalPluginDir, "skills", skillName);
1864
- const action = existsSync(resolve(skillDir, "SKILL.md")) ? "overwrite" : "create";
1865
- try {
1866
- mkdirSync(canonicalPluginDir, { recursive: true });
1867
- // Compose PLUGIN.md only on first write. embed=["admin","public"] +
1868
- // optional=false matches assemblePublicPluginContent / loadEmbeddedPlugins
1869
- // expectations. metadata is single-line JSON (parsePluginFrontmatter at
1870
- // plugin-manifest.ts:67 expects single-line) — multi-line silently falls
1871
- // back to platform={}.
1872
- if (!existsSync(canonicalPluginMd)) {
1873
- const pluginMd = `---
1874
- name: ${pluginName}
1875
- description: "Operator-authored plugin"
1876
- tools: []
1877
- hidden: []
1878
- requires: []
1879
- metadata: {"platform":{"embed":["admin","public"],"optional":false}}
1880
- ---
1881
-
1882
- # ${pluginName}
1883
-
1884
- Operator-authored plugin. Skills under \`skills/\` were created via the admin skill-builder.
1885
- `;
1886
- writeFileSync(canonicalPluginMd, pluginMd);
1887
- }
1888
- // Reset skill dir before write so removed reference files don't linger.
1889
- if (existsSync(skillDir)) {
1890
- rmSync(skillDir, { recursive: true, force: true });
1891
- }
1892
- mkdirSync(skillDir, { recursive: true });
1893
- // Normalise description — strip newlines and tabs to keep YAML
1894
- // frontmatter on a single line. parsePluginFrontmatter uses a
1895
- // single-line regex; an embedded newline (or `\n---\n`) would
1896
- // otherwise corrupt the frontmatter parse and silently drop body
1897
- // content from the agent prompt.
1898
- const singleLineDescription = description.replace(/[\r\n\t]+/g, " ").trim();
1899
- const escapedDescription = singleLineDescription.replace(/"/g, '\\"');
1900
- const skillMd = `---
1901
- name: ${skillName}
1902
- description: "${escapedDescription}"
1903
- publicEmbed: ${publicEmbed}
1904
- ---
1905
-
1906
- ${body}
1907
- `;
1908
- writeFileSync(resolve(skillDir, "SKILL.md"), skillMd);
1909
- let refCount = 0;
1910
- if (references.length > 0) {
1911
- const refsDir = resolve(skillDir, "references");
1912
- mkdirSync(refsDir, { recursive: true });
1913
- for (const ref of references) {
1914
- writeFileSync(resolve(refsDir, ref.filename), ref.content);
1915
- refCount++;
1916
- }
1917
- }
1918
- // Write the `.user-mirror` marker to CANONICAL first — cpSync will
1919
- // carry it into the platform-side mirror as part of the same recursive
1920
- // copy. This eliminates the post-cpSync writeFileSync race: if cpSync
1921
- // succeeds, the marker is already in target; if cpSync fails partway,
1922
- // canonical's marker is intact and the next autoDeliverUserPlugins
1923
- // call retries the copy. autoDeliverUserPlugins relies on the marker
1924
- // arriving with the source tree — never writes its own marker.
1925
- writeFileSync(resolve(canonicalPluginDir, ".user-mirror"), `pluginName=${pluginName}\nsource=${canonicalPluginDir}\n`);
1926
- // Mirror to <PLATFORM_ROOT>/plugins/<pluginName>/ so the change is
1927
- // visible to the loader without a session restart.
1928
- mkdirSync(mirrorPluginDir, { recursive: true });
1929
- cpSync(canonicalPluginDir, mirrorPluginDir, { recursive: true, force: true });
1930
- const bodyBytes = Buffer.byteLength(body, "utf-8");
1931
- console.log(`[store-skill] write pluginName=${pluginName} skillName=${skillName} publicEmbed=${publicEmbed} path=${canonicalPluginDir} bodyBytes=${bodyBytes} referenceCount=${refCount} action=${action}`);
1932
- return {
1933
- content: [{
1934
- type: "text",
1935
- text: `Skill "${skillName}" saved to plugin "${pluginName}" (${action}). publicEmbed=${publicEmbed}, references=${refCount}. Active immediately for the admin agent${publicEmbed ? "; surfaces in the public agent on next session start" : ""}.`,
1936
- }],
1937
- };
1938
- }
1939
- catch (err) {
1940
- const msg = err instanceof Error ? err.message : String(err);
1941
- console.error(`[store-skill] ERROR pluginName=${pluginName} skillName=${skillName} reason=${msg.slice(0, 120)} path=${canonicalPluginDir}`);
1942
- return { content: [{ type: "text", text: `Failed to write skill: ${msg}` }], isError: true };
1943
- }
1855
+ const result = await storeSkillToAccount({
1856
+ accountDir,
1857
+ platformRoot: PLATFORM_ROOT,
1858
+ pluginName,
1859
+ skillName,
1860
+ description,
1861
+ publicEmbed,
1862
+ body,
1863
+ references,
1864
+ });
1865
+ if (!result.ok) {
1866
+ const isCollision = result.reason === "collision-with-shipped";
1867
+ console.error(`[store-skill] op=store-skill root=account path=${result.canonicalPluginDir} resolved=false reason=${result.reason} pluginName=${pluginName} skillName=${skillName} detail=${result.detail.slice(0, 120)}`);
1868
+ const text = isCollision
1869
+ ? `pluginName "${pluginName}" collides with a shipped plugin. Choose a different pluginName.`
1870
+ : `Skill "${skillName}" was written to "${pluginName}" but did not register: ${result.detail}. Not activating.`;
1871
+ return { content: [{ type: "text", text }], isError: true };
1872
+ }
1873
+ console.log(`[store-skill] op=store-skill root=account path=${result.canonicalPluginDir} resolved=true pluginName=${pluginName} skillName=${skillName} publicEmbed=${publicEmbed} bodyBytes=${result.bodyBytes} referenceCount=${result.refCount} action=${result.action}`);
1874
+ return {
1875
+ content: [{
1876
+ type: "text",
1877
+ text: `Skill "${skillName}" saved to plugin "${pluginName}" (${result.action}). publicEmbed=${publicEmbed}, references=${result.refCount}. Active immediately for the admin agent${publicEmbed ? "; surfaces in the public agent on next session start" : ""}.`,
1878
+ }],
1879
+ };
1944
1880
  });
1945
1881
  eagerTool(server, "plugin-read", "Read a plugin definition (PLUGIN.md) or one of its reference files.", {
1946
1882
  pluginName: z.string().describe("Name of the plugin directory (e.g. 'sales', 'business-assistant')"),
@@ -1960,14 +1896,23 @@ eagerTool(server, "plugin-read", "Read a plugin definition (PLUGIN.md) or one of
1960
1896
  console.log(`[admin-skill-gate] role=admin skill=${authoringSlug} decision=block reason=content-producer-owned`);
1961
1897
  return { content: [{ type: "text", text: AUTHORING_DISPATCH_HINT }], isError: true };
1962
1898
  }
1963
- const pluginPath = resolve(PLATFORM_ROOT, "plugins", pluginName, resolvedFile);
1899
+ // Task 1962 operator-authored plugins live under the account root, not
1900
+ // the platform tree. Prefer the platform copy; fall back to the account
1901
+ // root so an operator plugin's PLUGIN.md / SKILL.md / references resolve.
1902
+ const accountPluginsDir = resolve(getAccountDir(), "plugins");
1903
+ const platformPath = resolve(PLATFORM_ROOT, "plugins", pluginName, resolvedFile);
1904
+ const accountPath = resolve(accountPluginsDir, pluginName, resolvedFile);
1905
+ const pluginPath = existsSync(platformPath) ? platformPath : accountPath;
1906
+ const pluginRootDir = existsSync(platformPath)
1907
+ ? resolve(PLATFORM_ROOT, "plugins", pluginName)
1908
+ : resolve(accountPluginsDir, pluginName);
1964
1909
  if (!existsSync(pluginPath)) {
1965
1910
  // when the agent's pluginName/file combination misses
1966
1911
  // a skill that DOES live under `plugins/*/skills/<slug>/SKILL.md`,
1967
1912
  // surface the corrected pluginName AND the canonical file path so
1968
1913
  // the next call is deterministic. Both wrong-`file` and wrong-`pluginName`
1969
1914
  // shapes feed through `computePluginReadHint`.
1970
- const hint = computePluginReadHint(PLATFORM_ROOT, pluginName, resolvedFile);
1915
+ const hint = computePluginReadHint(PLATFORM_ROOT, pluginName, resolvedFile, accountPluginsDir);
1971
1916
  const hintText = hint ? ` Did you mean pluginName="${hint.pluginName}", file="${hint.file}"?` : "";
1972
1917
  console.log(`[plugin-read] ${pluginName}/${resolvedFile} not-found hint=${hint ? hint.pluginName : "none"}`);
1973
1918
  return {
@@ -1984,7 +1929,7 @@ eagerTool(server, "plugin-read", "Read a plugin definition (PLUGIN.md) or one of
1984
1929
  // Append file inventory on default calls (PLUGIN.md) so agents can discover
1985
1930
  // skill and reference paths without guessing
1986
1931
  if (resolvedFile === "PLUGIN.md") {
1987
- const pluginRoot = resolve(PLATFORM_ROOT, "plugins", pluginName);
1932
+ const pluginRoot = pluginRootDir;
1988
1933
  const skills = [];
1989
1934
  const references = [];
1990
1935
  const scanDir = (base, prefix, target) => {
@@ -2044,7 +1989,7 @@ lifelineTool(server, "skill-find", "Find which plugin owns a skill by name. Walk
2044
1989
  if (!ACCOUNT_ID)
2045
1990
  return refuseNoAccount("skill-find");
2046
1991
  const start = Date.now();
2047
- const result = findSkillOwners(PLATFORM_ROOT, skillName);
1992
+ const result = findSkillOwners(PLATFORM_ROOT, skillName, resolve(getAccountDir(), "plugins"));
2048
1993
  const ms = Date.now() - start;
2049
1994
  console.log(`[skill-find] skillName=${skillName} result=${result.status} candidates=${result.candidates.length} ms=${ms}`);
2050
1995
  if (result.status === "unique") {
@@ -2085,7 +2030,7 @@ eagerTool(server, "skill-search", "Find skills by free-text intent when you do n
2085
2030
  if (!ACCOUNT_ID)
2086
2031
  return refuseNoAccount("skill-search");
2087
2032
  const start = Date.now();
2088
- const hits = searchSkills(PLATFORM_ROOT, query);
2033
+ const hits = searchSkills(PLATFORM_ROOT, query, 5, resolve(getAccountDir(), "plugins"));
2089
2034
  const ms = Date.now() - start;
2090
2035
  const top = hits.length > 0 ? `${hits[0].pluginName}:${hits[0].skillName}` : "none";
2091
2036
  console.log(`[skill-search] query=${JSON.stringify(query)} hits=${hits.length} top=${top} ms=${ms}`);
@@ -2134,7 +2079,7 @@ eagerTool(server, "skill-load", "Load a plugin skill's SKILL.md body by skill na
2134
2079
  }
2135
2080
  const start = Date.now();
2136
2081
  try {
2137
- const outcome = await loadSkill(PLATFORM_ROOT, skillName);
2082
+ const outcome = await loadSkill(PLATFORM_ROOT, skillName, resolve(getAccountDir(), "plugins"));
2138
2083
  if (outcome.status === "ambiguous") {
2139
2084
  const ms = Date.now() - start;
2140
2085
  console.log(`[skill-load] skillName=${skillName} result=ambiguous pluginName=none file=none bodyBytes=0 ms=${ms}`);