@rubytech/create-maxy 1.0.889 → 1.0.891
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.
- package/package.json +2 -2
- package/payload/platform/plugins/admin/PLUGIN.md +8 -3
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load.test.d.ts +2 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load.test.d.ts.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load.test.js +88 -0
- package/payload/platform/plugins/admin/mcp/dist/__tests__/skill-load.test.js.map +1 -0
- package/payload/platform/plugins/admin/mcp/dist/index.js +62 -5
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts +13 -0
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js +14 -0
- package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js.map +1 -1
- package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +1 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +6 -2
- package/payload/platform/plugins/memory/PLUGIN.md +1 -1
- package/payload/platform/scripts/check-skill-load-coverage.mjs +100 -0
- package/payload/platform/scripts/log-adherence-check.sh +28 -3
- package/payload/platform/scripts/logs-read.sh +108 -5
- package/payload/platform/templates/agents/admin/IDENTITY.md +10 -10
- package/payload/platform/templates/agents/public/IDENTITY.md +1 -1
- package/payload/platform/templates/specialists/agents/content-producer.md +2 -2
- package/payload/platform/templates/specialists/agents/database-operator.md +3 -3
- package/payload/platform/templates/specialists/agents/personal-assistant.md +2 -2
- package/payload/platform/templates/specialists/agents/project-manager.md +1 -1
- package/payload/platform/templates/specialists/agents/research-assistant.md +1 -1
- package/payload/server/chunk-FBTNBSB4.js +2282 -0
- package/payload/server/chunk-NZ6D7QJM.js +3571 -0
- package/payload/server/chunk-O7DTMDJM.js +759 -0
- package/payload/server/chunk-OD4LSAB2.js +9770 -0
- package/payload/server/chunk-RICR2PXW.js +9771 -0
- package/payload/server/chunk-TUCK5CI2.js +3566 -0
- package/payload/server/client-pool-E3OU5NYU.js +34 -0
- package/payload/server/client-pool-PG23WNFG.js +34 -0
- package/payload/server/cloudflare-task-tracker-KCIUQYB5.js +22 -0
- package/payload/server/maxy-edge.js +29 -5
- package/payload/server/public/assets/{admin-BN_z-2Bm.js → admin-BM3orGyK.js} +31 -31
- package/payload/server/public/index.html +1 -1
- package/payload/server/server.js +452 -314
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rubytech/create-maxy",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.891",
|
|
4
4
|
"description": "Install Maxy — AI for Productive People",
|
|
5
5
|
"bin": {
|
|
6
6
|
"create-maxy": "./dist/index.js"
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"build": "tsc",
|
|
11
11
|
"bundle": "node scripts/bundle.js",
|
|
12
12
|
"test": "npm run build && node --test 'dist/__tests__/*.test.js'",
|
|
13
|
-
"prepublishOnly": "bash ../../platform/scripts/verify-skill-tool-surface.sh && node ../../platform/scripts/check-no-task-id-leaks.mjs && node ../../platform/ui/scripts/check-route-wiring.mjs && node ../../platform/ui/scripts/check-edge-admin-routes.mjs && npm run build && node --test 'dist/__tests__/*.test.js' && chmod +x dist/index.js && npm run bundle && node ../../platform/ui/scripts/check-bundle-node-imports.mjs --dir=./payload/server/public/assets"
|
|
13
|
+
"prepublishOnly": "bash ../../platform/scripts/verify-skill-tool-surface.sh && node ../../platform/scripts/check-no-task-id-leaks.mjs && node ../../platform/scripts/check-skill-load-coverage.mjs && node ../../platform/ui/scripts/check-route-wiring.mjs && node ../../platform/ui/scripts/check-edge-admin-routes.mjs && npm run build && node --test 'dist/__tests__/*.test.js' && chmod +x dist/index.js && npm run bundle && node ../../platform/ui/scripts/check-bundle-node-imports.mjs --dir=./payload/server/public/assets"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"dist",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: admin
|
|
3
|
-
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver — single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), render-component, session-reset, session-resume, file-attach, wifi, adherence-read (attention-weighted adherence ledger), and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform."
|
|
3
|
+
description: "Platform administration plugin. Provides system-status, public-hostname (deterministic Cloudflare public-URL resolver — single call returning the operator's canonical hostname so agents never guess property names on :CloudflareHostname nodes), brand-settings, account-manage, account-update, admin-add, admin-remove, admin-list, admin-update-pin, agent-list, agent-config-read, logs-read, plugin-read, skill-load (one-call resolve+read for SKILL.md by skill name — the canonical primitive for loading a named skill; plugin-read remains the reader for references/* and PLUGIN.md), store-skill (deterministic write counterpart to plugin-read; persists operator-authored skills as plugin files under the active account), render-component, session-reset, session-resume, file-attach, wifi, adherence-read (attention-weighted adherence ledger), and action-approval tools (action-pending, action-approve, action-reject, action-edit) for managing the Maxy platform."
|
|
4
4
|
tools:
|
|
5
5
|
- system-status
|
|
6
6
|
- public-hostname
|
|
@@ -15,6 +15,7 @@ tools:
|
|
|
15
15
|
- agent-config-read
|
|
16
16
|
- logs-read
|
|
17
17
|
- plugin-read
|
|
18
|
+
- skill-load
|
|
18
19
|
- store-skill
|
|
19
20
|
- render-component
|
|
20
21
|
- session-reset
|
|
@@ -42,7 +43,7 @@ embed: false
|
|
|
42
43
|
|
|
43
44
|
# Admin
|
|
44
45
|
|
|
45
|
-
Platform management tools for both the admin and public agents. The `plugin-read` tool is available to the public agent (read-only) for loading plugin definitions and references. All other tools are admin-only.
|
|
46
|
+
Platform management tools for both the admin and public agents. The `plugin-read` tool is available to the public agent (read-only) for loading plugin definitions and references. `skill-load skillName=<name>` is the canonical resolver+reader for SKILL.md by skill name — one call returns the body with brand placeholders substituted; `plugin-read` retains exclusive ownership of `references/*` and `PLUGIN.md` reads (both inherently plugin-scoped). All other tools are admin-only.
|
|
46
47
|
|
|
47
48
|
Tools are available via the `admin` MCP server.
|
|
48
49
|
|
|
@@ -52,7 +53,7 @@ Tools are available via the `admin` MCP server.
|
|
|
52
53
|
|
|
53
54
|
`logs-read { type: "agent-stream" }` is the canonical name for the per-conversation tool-use/tool-result archive previously called `system`; both names work and the legacy alias is preserved.
|
|
54
55
|
|
|
55
|
-
**Stream log is the primary diagnostic surface for an in-progress session.** Every stream log is named by sessionKey and the stream-log file exists from the first token. The first-token invariant is bound by `platform/scripts/__tests__/first-token-creates-stream-log.test.sh`: one operator turn, one token, `claude-agent-stream-<sessionKey>.log` exists and contains the token bytes after the byte returns to the operator. The parent-process console fan-out tee in [`platform/ui/app/lib/claude-agent/logging.ts`](../../ui/app/lib/claude-agent/logging.ts) appends every `[<tag>]`-prefixed `console.error` / `console.log` line to every active session's stream log alongside `server.log`. For diagnosing an in-session issue (WhatsApp inbound, Cloudflare action, persist write, baileys error), call `logs-read { sessionKey: "<…>" }` first — the stream log carries both the agent lifecycle AND the parent-process events that occurred during the session window. The `conversationId` parameter remains a legacy alias resolving to the same sessionKey-named file. `logs-read { type: "server" }` is the cross-session escape hatch for events outside any single session window.
|
|
56
|
+
**Stream log is the primary diagnostic surface for an in-progress session.** Every stream log is named by sessionKey and the stream-log file exists from the first token. The single-writer mandate (2026-05-14) mechanically enforces this contract: the single writer module at [`platform/ui/app/lib/claude-agent/stream-log-writer.ts`](../../ui/app/lib/claude-agent/stream-log-writer.ts) owns every open of `claude-agent-stream-*.log`, opens the file lazily on the first SDK token byte (via `streamLog.writeToken`), and buffers pre-token markers in memory while duplicating them to `server.log` via `console.error` so zero-token sessions retain observability. The build gate `platform/ui/scripts/check-stream-log-writer.mjs` rejects any external `appendFileSync`/`createWriteStream` against the `claude-agent-stream-*` pattern outside the writer module, plus any `claude-agent-stream-${conversationId}` literal (the pre-1013 wrong-identifier signature that produced two files for one session on the Beacons Pi 2026-05-14). The first-token invariant is bound by `platform/scripts/__tests__/first-token-creates-stream-log.test.sh`: one operator turn, one token, `claude-agent-stream-<sessionKey>.log` exists and contains the token bytes after the byte returns to the operator. The parent-process console fan-out tee in [`platform/ui/app/lib/claude-agent/logging.ts`](../../ui/app/lib/claude-agent/logging.ts) appends every `[<tag>]`-prefixed `console.error` / `console.log` line to every active session's stream log alongside `server.log`. For diagnosing an in-session issue (WhatsApp inbound, Cloudflare action, persist write, baileys error), call `logs-read { sessionKey: "<…>" }` first — the stream log carries both the agent lifecycle AND the parent-process events that occurred during the session window. The `conversationId` parameter remains a legacy alias resolving to the same sessionKey-named file. `logs-read { type: "server" }` is the cross-session escape hatch for events outside any single session window.
|
|
56
57
|
|
|
57
58
|
## Skills
|
|
58
59
|
|
|
@@ -75,3 +76,7 @@ Tools are available via the `admin` MCP server.
|
|
|
75
76
|
- `hooks/pre-tool-use.sh` — enforces admin agent write boundaries
|
|
76
77
|
- `hooks/playwright-file-guard.sh` — rewrites file:// URLs to a backgrounded loopback http.server before Playwright sees them
|
|
77
78
|
- `hooks/webfetch-preflight.mjs` — short-circuits WebFetch on JS-SPA shells with a structured `WEBFETCH_CANNOT_READ_JS_SPA` error so the agent surfaces a loud failure to the owner instead of paying the 60s extraction timeout. Fail-open on any internal error.
|
|
79
|
+
|
|
80
|
+
## Failure-report breadcrumbs
|
|
81
|
+
|
|
82
|
+
`POST /api/admin/sse-telemetry` and `POST /api/admin/failure-report` write `[sse-client]` and `[failure-report]` lines to `claude-agent-stream-<sessionKey>.log` so one grep against that file reconstructs the failing turn. The admin agent reads these via `logs-read`; the operator surfaces them by clicking "Report this turn" in the chat menu.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-load.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/skill-load.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Task 1015 — `skill-load` MCP tool acceptance gate.
|
|
2
|
+
//
|
|
3
|
+
// Locks in the contract for `loadSkill` (the resolve+read helper backing
|
|
4
|
+
// the new `skill-load` MCP tool registered in index.ts). Tests build a
|
|
5
|
+
// temp PLATFORM_ROOT tree, seed SKILL.md files, and assert exact outcome
|
|
6
|
+
// shapes — no stdio transport, no SDK harness.
|
|
7
|
+
//
|
|
8
|
+
// Brand substitution is exercised in production via the same
|
|
9
|
+
// `substituteBrandPlaceholders` call as `plugin-read` (index.ts) and
|
|
10
|
+
// covered by brand-templating's own tests. Case 2 below asserts that
|
|
11
|
+
// `loadSkill` returns the raw bytes (placeholder intact) so the handler
|
|
12
|
+
// can substitute deterministically.
|
|
13
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
14
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { loadSkill } from "../skill-resolution.js";
|
|
18
|
+
let root;
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
root = mkdtempSync(join(tmpdir(), "skill-load-"));
|
|
21
|
+
});
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
rmSync(root, { recursive: true, force: false });
|
|
24
|
+
});
|
|
25
|
+
function seedSkill(plugin, skill, body) {
|
|
26
|
+
const dir = join(root, "plugins", plugin, "skills", skill);
|
|
27
|
+
mkdirSync(dir, { recursive: true });
|
|
28
|
+
writeFileSync(join(dir, "SKILL.md"), body);
|
|
29
|
+
}
|
|
30
|
+
describe("loadSkill", () => {
|
|
31
|
+
it("unique → returns SKILL.md body verbatim with absolute path", async () => {
|
|
32
|
+
seedSkill("admin", "plainly", "# Plainly\n\nStrip AI tells.\n");
|
|
33
|
+
const outcome = await loadSkill(root, "plainly");
|
|
34
|
+
expect(outcome.status).toBe("unique");
|
|
35
|
+
if (outcome.status !== "unique")
|
|
36
|
+
return;
|
|
37
|
+
expect(outcome.pluginName).toBe("admin");
|
|
38
|
+
expect(outcome.file).toBe("skills/plainly/SKILL.md");
|
|
39
|
+
expect(outcome.body).toBe("# Plainly\n\nStrip AI tells.\n");
|
|
40
|
+
expect(outcome.absolutePath.endsWith("plugins/admin/skills/plainly/SKILL.md")).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
it("unique → returns raw bytes with brand placeholder intact (handler runs substitution)", async () => {
|
|
43
|
+
seedSkill("admin", "branded", "# Welcome to <<product-name>>\n\nThe placeholder must reach the handler unmodified.\n");
|
|
44
|
+
const outcome = await loadSkill(root, "branded");
|
|
45
|
+
expect(outcome.status).toBe("unique");
|
|
46
|
+
if (outcome.status !== "unique")
|
|
47
|
+
return;
|
|
48
|
+
expect(outcome.body).toContain("<<product-name>>");
|
|
49
|
+
});
|
|
50
|
+
it("ambiguous → returns ambiguous status with candidate list", async () => {
|
|
51
|
+
seedSkill("admin", "shared-name", "# A\n");
|
|
52
|
+
seedSkill("contacts", "shared-name", "# B\n");
|
|
53
|
+
const outcome = await loadSkill(root, "shared-name");
|
|
54
|
+
expect(outcome.status).toBe("ambiguous");
|
|
55
|
+
if (outcome.status !== "ambiguous")
|
|
56
|
+
return;
|
|
57
|
+
expect(outcome.candidates).toHaveLength(2);
|
|
58
|
+
});
|
|
59
|
+
it("not-found → returns not-found status", async () => {
|
|
60
|
+
seedSkill("admin", "plainly", "# Plainly\n");
|
|
61
|
+
const outcome = await loadSkill(root, "absent-skill");
|
|
62
|
+
expect(outcome.status).toBe("not-found");
|
|
63
|
+
});
|
|
64
|
+
it("kebab-case validator rejects path-shaped input like admin/skills/plainly", () => {
|
|
65
|
+
// Mirrors the zod regex at the MCP tool registration in index.ts —
|
|
66
|
+
// the validator runs before the handler so loadSkill is never called
|
|
67
|
+
// with a path-shaped skillName.
|
|
68
|
+
const KEBAB = /^[a-z0-9][a-z0-9-]*$/;
|
|
69
|
+
expect(KEBAB.test("admin/skills/plainly")).toBe(false);
|
|
70
|
+
expect(KEBAB.test("skills/plainly")).toBe(false);
|
|
71
|
+
expect(KEBAB.test("admin/plainly")).toBe(false);
|
|
72
|
+
expect(KEBAB.test("plainly")).toBe(true);
|
|
73
|
+
expect(KEBAB.test("setup-tunnel")).toBe(true);
|
|
74
|
+
});
|
|
75
|
+
it("ambiguous candidates include every owning plugin (set comparison, no order dependency)", async () => {
|
|
76
|
+
seedSkill("admin", "plainly", "# A\n");
|
|
77
|
+
seedSkill("contacts", "plainly", "# B\n");
|
|
78
|
+
seedSkill("memory", "plainly", "# C\n");
|
|
79
|
+
const outcome = await loadSkill(root, "plainly");
|
|
80
|
+
expect(outcome.status).toBe("ambiguous");
|
|
81
|
+
if (outcome.status !== "ambiguous")
|
|
82
|
+
return;
|
|
83
|
+
const owners = outcome.candidates.map(c => c.pluginName).sort();
|
|
84
|
+
expect(owners).toEqual(["admin", "contacts", "memory"]);
|
|
85
|
+
expect(outcome.candidates.every(c => c.file === "skills/plainly/SKILL.md")).toBe(true);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
//# sourceMappingURL=skill-load.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skill-load.test.js","sourceRoot":"","sources":["../../src/__tests__/skill-load.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -20,7 +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 { getOnboardingState, completeOnboardingStep } from "./lib/onboarding.js";
|
|
23
|
-
import { findSkillOwners, computePluginReadHint } from "./skill-resolution.js";
|
|
23
|
+
import { findSkillOwners, computePluginReadHint, loadSkill } from "./skill-resolution.js";
|
|
24
24
|
import { resolvePublicHostname } from "./lib/public-hostname.js";
|
|
25
25
|
const server = new McpServer({
|
|
26
26
|
name: "admin",
|
|
@@ -1664,9 +1664,13 @@ eagerTool(server, "plugin-read", "Read a plugin definition (PLUGIN.md) or one of
|
|
|
1664
1664
|
// Task 964 — agent-side `Glob` fallthrough when "set up cloudflare" maps a
|
|
1665
1665
|
// SKILL.md to the wrong plugin: skill-find walks
|
|
1666
1666
|
// `${PLATFORM_ROOT}/plugins/*/skills/<skillName>/SKILL.md` once and returns
|
|
1667
|
-
// the owner. With one tool call the agent learns the correct plugin name
|
|
1668
|
-
//
|
|
1669
|
-
|
|
1667
|
+
// the owner. With one tool call the agent learns the correct plugin name.
|
|
1668
|
+
//
|
|
1669
|
+
// Task 1015 — `skill-load` (below) is the canonical single-call surface that
|
|
1670
|
+
// most agents want (resolve + read body in one shot). `skill-find` is kept
|
|
1671
|
+
// for callers that need only the path (e.g. tooling that lists owners
|
|
1672
|
+
// without reading the body).
|
|
1673
|
+
server.tool("skill-find", "Find which plugin owns a skill by name. Walks plugins/*/skills/<skillName>/SKILL.md and returns the owning plugin (unique), every candidate (ambiguous), or not-found. For the common resolve-and-read case, prefer `skill-load skillName=<name>` — it returns the SKILL.md body in the same call.", {
|
|
1670
1674
|
skillName: z.string().regex(/^[a-z0-9][a-z0-9-]*$/, "skillName must be lowercase kebab-case (a-z, 0-9, -)"),
|
|
1671
1675
|
}, async ({ skillName }) => {
|
|
1672
1676
|
const start = Date.now();
|
|
@@ -1678,7 +1682,7 @@ server.tool("skill-find", "Find which plugin owns a skill by name. Walks plugins
|
|
|
1678
1682
|
return {
|
|
1679
1683
|
content: [{
|
|
1680
1684
|
type: "text",
|
|
1681
|
-
text: `pluginName="${pluginName}" file="${file}"\
|
|
1685
|
+
text: `pluginName="${pluginName}" file="${file}"\nTo read the body in one call, use: skill-load skillName="${skillName}"`,
|
|
1682
1686
|
}],
|
|
1683
1687
|
};
|
|
1684
1688
|
}
|
|
@@ -1696,6 +1700,59 @@ server.tool("skill-find", "Find which plugin owns a skill by name. Walks plugins
|
|
|
1696
1700
|
isError: true,
|
|
1697
1701
|
};
|
|
1698
1702
|
});
|
|
1703
|
+
// Task 1015 — `skill-load` collapses skill resolve+read into one call.
|
|
1704
|
+
//
|
|
1705
|
+
// Why: 21 prose call sites previously said *"Load `admin/skills/plainly/SKILL.md`
|
|
1706
|
+
// via `plugin-read`"*. The LLM kept passing the path as `pluginName`, tripping
|
|
1707
|
+
// the not-found branch with the corrected-pluginName hint that Task 1000 added.
|
|
1708
|
+
// The recurring failure is recorded in the corrections ledger. One slug-arg
|
|
1709
|
+
// tool removes the path-format ambiguity at the source.
|
|
1710
|
+
//
|
|
1711
|
+
// Shape mirrors `plugin-read` (eagerTool registration, same brand-substitution
|
|
1712
|
+
// call) so SKILL.md bodies are byte-identical regardless of which tool reads
|
|
1713
|
+
// them. `plugin-read` is retained for `references/*` and `PLUGIN.md` reads —
|
|
1714
|
+
// those are plugin-scoped by nature and the path-as-pluginName failure pattern
|
|
1715
|
+
// is specific to skills (addressed by slug in prose).
|
|
1716
|
+
eagerTool(server, "skill-load", "Load a plugin skill's SKILL.md body by skill name. One call: resolves the owning plugin and reads the body, with brand placeholders substituted. Returns the SKILL.md body when exactly one plugin owns the skill, a candidate list when multiple plugins own it (ambiguous), or an error when no plugin owns it.", {
|
|
1717
|
+
skillName: z.string().regex(/^[a-z0-9][a-z0-9-]*$/, "skillName must be lowercase kebab-case (a-z, 0-9, -)"),
|
|
1718
|
+
}, async ({ skillName }) => {
|
|
1719
|
+
const start = Date.now();
|
|
1720
|
+
try {
|
|
1721
|
+
const outcome = await loadSkill(PLATFORM_ROOT, skillName);
|
|
1722
|
+
if (outcome.status === "ambiguous") {
|
|
1723
|
+
const ms = Date.now() - start;
|
|
1724
|
+
console.log(`[skill-load] skillName=${skillName} result=ambiguous pluginName=none file=none bodyBytes=0 ms=${ms}`);
|
|
1725
|
+
const lines = outcome.candidates.map(c => ` pluginName="${c.pluginName}" file="${c.file}"`).join("\n");
|
|
1726
|
+
return {
|
|
1727
|
+
content: [{
|
|
1728
|
+
type: "text",
|
|
1729
|
+
text: `Ambiguous — skill "${skillName}" exists in ${outcome.candidates.length} plugins:\n${lines}\nUse plugin-read with one of the listed pluginName/file pairs.`,
|
|
1730
|
+
}],
|
|
1731
|
+
isError: true,
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
if (outcome.status === "not-found") {
|
|
1735
|
+
const ms = Date.now() - start;
|
|
1736
|
+
console.log(`[skill-load] skillName=${skillName} result=not-found pluginName=none file=none bodyBytes=0 ms=${ms}`);
|
|
1737
|
+
return {
|
|
1738
|
+
content: [{ type: "text", text: `not_found — no plugin owns a skill named "${skillName}"` }],
|
|
1739
|
+
isError: true,
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
const content = substituteBrandPlaceholders(outcome.body, outcome.absolutePath);
|
|
1743
|
+
const ms = Date.now() - start;
|
|
1744
|
+
console.log(`[skill-load] skillName=${skillName} result=unique pluginName=${outcome.pluginName} file=${outcome.file} bodyBytes=${content.length} ms=${ms}`);
|
|
1745
|
+
return { content: [{ type: "text", text: content }] };
|
|
1746
|
+
}
|
|
1747
|
+
catch (err) {
|
|
1748
|
+
const ms = Date.now() - start;
|
|
1749
|
+
console.log(`[skill-load] skillName=${skillName} result=read-error pluginName=unknown file=unknown bodyBytes=0 ms=${ms}`);
|
|
1750
|
+
return {
|
|
1751
|
+
content: [{ type: "text", text: `Failed to load skill "${skillName}": ${err instanceof Error ? err.message : String(err)}` }],
|
|
1752
|
+
isError: true,
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
});
|
|
1699
1756
|
// Task 940 — `"rendered"` is a sentinel, not a persistence ack. The platform
|
|
1700
1757
|
// UI's stream-parser intercepts this tool_use upstream of the SDK reply
|
|
1701
1758
|
// (platform/ui/app/lib/claude-agent/stream-parser.ts:362) and emits a typed
|