@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
|
@@ -18,4 +18,17 @@ export interface PluginReadHint {
|
|
|
18
18
|
}
|
|
19
19
|
export declare function findSkillOwners(platformRoot: string, skillName: string): SkillFindResult;
|
|
20
20
|
export declare function computePluginReadHint(platformRoot: string, pluginName: string, file: string): PluginReadHint | null;
|
|
21
|
+
export type SkillLoadOutcome = {
|
|
22
|
+
status: "unique";
|
|
23
|
+
pluginName: string;
|
|
24
|
+
file: string;
|
|
25
|
+
absolutePath: string;
|
|
26
|
+
body: string;
|
|
27
|
+
} | {
|
|
28
|
+
status: "ambiguous";
|
|
29
|
+
candidates: SkillOwner[];
|
|
30
|
+
} | {
|
|
31
|
+
status: "not-found";
|
|
32
|
+
};
|
|
33
|
+
export declare function loadSkill(platformRoot: string, skillName: string): Promise<SkillLoadOutcome>;
|
|
21
34
|
//# sourceMappingURL=skill-resolution.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-resolution.d.ts","sourceRoot":"","sources":["../src/skill-resolution.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"skill-resolution.d.ts","sourceRoot":"","sources":["../src/skill-resolution.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,UAAU,EAAE,CAAC,UAAU,CAAC,CAAA;CAAE,GAC9C;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,UAAU,EAAE,CAAA;CAAE,GACjD;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,EAAE,CAAA;CAAE,CAAC;AAU5C,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,eAAe,CAAC,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,eAAe,CA4BxF;AAcD,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,GACX,cAAc,GAAG,IAAI,CASvB;AASD,MAAM,MAAM,gBAAgB,GACxB;IAAE,MAAM,EAAE,QAAQ,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAC1F;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,EAAE,UAAU,EAAE,CAAA;CAAE,GACjD;IAAE,MAAM,EAAE,WAAW,CAAA;CAAE,CAAC;AAE5B,wBAAsB,SAAS,CAC7B,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,gBAAgB,CAAC,CAY3B"}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// (plugin dir → skill dir), never recursive, so deeper skill subtrees and
|
|
11
11
|
// sibling references/ directories are ignored.
|
|
12
12
|
import { existsSync, readdirSync, statSync } from "node:fs";
|
|
13
|
+
import { readFile } from "node:fs/promises";
|
|
13
14
|
import { resolve } from "node:path";
|
|
14
15
|
const SKILL_FILE_PATH = /^skills\/([a-z0-9][a-z0-9-]*)\//;
|
|
15
16
|
// Task 1000 — the symmetric misuse: skill slug buried inside `pluginName`
|
|
@@ -75,4 +76,17 @@ export function computePluginReadHint(platformRoot, pluginName, file) {
|
|
|
75
76
|
return null;
|
|
76
77
|
return { pluginName: owner.pluginName, file: correctedFile };
|
|
77
78
|
}
|
|
79
|
+
export async function loadSkill(platformRoot, skillName) {
|
|
80
|
+
const result = findSkillOwners(platformRoot, skillName);
|
|
81
|
+
if (result.status === "ambiguous") {
|
|
82
|
+
return { status: "ambiguous", candidates: result.candidates };
|
|
83
|
+
}
|
|
84
|
+
if (result.status === "not-found") {
|
|
85
|
+
return { status: "not-found" };
|
|
86
|
+
}
|
|
87
|
+
const [{ pluginName, file }] = result.candidates;
|
|
88
|
+
const absolutePath = resolve(platformRoot, "plugins", pluginName, file);
|
|
89
|
+
const body = await readFile(absolutePath, "utf-8");
|
|
90
|
+
return { status: "unique", pluginName, file, absolutePath, body };
|
|
91
|
+
}
|
|
78
92
|
//# sourceMappingURL=skill-resolution.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill-resolution.js","sourceRoot":"","sources":["../src/skill-resolution.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"skill-resolution.js","sourceRoot":"","sources":["../src/skill-resolution.ts"],"names":[],"mappings":"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"}
|
|
@@ -72,7 +72,7 @@ Call `premium-deliver` with `pluginName` set to the plugin name. The tool handle
|
|
|
72
72
|
- Scans for public agent templates
|
|
73
73
|
- Returns a structured result with per-sub-plugin status and available templates
|
|
74
74
|
|
|
75
|
-
Report the tool's result to the user. If templates are found, present them and ask whether the user wants to create any now. For template creation,
|
|
75
|
+
Report the tool's result to the user. If templates are found, present them and ask whether the user wants to create any now. For template creation, run `skill-load skillName=public-agent-manager` and follow the "Create from Template" instructions, passing the template directory path from the tool's result.
|
|
76
76
|
|
|
77
77
|
If the delivered plugin has specialist agents (files in `agents/` with the `{plugin-name}--{agent-name}.md` naming convention, distinct from template directories), load the specialist management skill and follow the premium plugin agent activation instructions.
|
|
78
78
|
|
|
@@ -129,7 +129,7 @@ Skill content, plugin manifests, agent templates, and reference files reference
|
|
|
129
129
|
|
|
130
130
|
**Author rule:** never write the literal string `Maxy` (or any brand name) in shipped skill, plugin, or template content. Use `{{productName}}` whenever the operator should see the brand name. The audit grep `grep -rn "\bMaxy\b" platform/plugins/admin/skills/ platform/plugins/*/skills/ platform/templates/agents/` must return zero matches; a literal brand name is a defect, not a stylistic choice.
|
|
131
131
|
|
|
132
|
-
The runtime substitution happens at
|
|
132
|
+
The runtime substitution happens at every read site that flows content into a system prompt or operator-visible UI: the admin agent's `plugin-read` tool (references + `PLUGIN.md`), the `skill-load` tool (SKILL.md by skill name — one-call resolver+reader, the canonical primitive for SKILL.md), the public agent's recursive plugin assembly, and `IDENTITY` / `SOUL` / `AGENTS` / `KNOWLEDGE` markdown reads. Missing or empty `productName` hard-fails — there is no fallback to a default brand string. See [.docs/agents.md](../../.docs/agents.md) § "Brand templating" for the full contract.
|
|
133
133
|
|
|
134
134
|
## MCP Plugin Observability (for plugin authors)
|
|
135
135
|
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
|
|
5
5
|
**Symptom:** Operator opens a new admin session, sends one turn, sees the agent reply, then `logs-read sessionKey=<…>` returns `file-not-found` or zero bytes.
|
|
6
6
|
|
|
7
|
-
**Invariant:** For every new session, the stream-log file exists on disk and contains the token bytes from the moment the first token returns to the operator. 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 — pass iff file present and bytes present.
|
|
7
|
+
**Invariant:** For every new session, the stream-log file exists on disk iff at least one token byte has been emitted, and contains the token bytes from the moment the first token returns to the operator. The single-writer mandate (2026-05-14) mechanically enforces both halves of the contract: the single writer module at `platform/ui/app/lib/claude-agent/stream-log-writer.ts` opens the file lazily on `streamLog.writeToken` (the SDK first-byte site at [`stream-parser.ts:296`](../../../ui/app/lib/claude-agent/stream-parser.ts#L296)), and the build gate `platform/ui/scripts/check-stream-log-writer.mjs` rejects every external `appendFileSync`/`createWriteStream` against the `claude-agent-stream-*` pattern at CI time. 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 — pass iff file present and bytes present. The hourly adherence runner `platform/scripts/log-adherence-check.sh` extends the device-side check with a duplicate-basename diagnostic (`dup-basenames=N` in the `[log-tee] adherence-check` line); `dup>0` is a P0 page meaning the writer collapse regressed.
|
|
8
8
|
|
|
9
|
-
**Diagnose if it ever recurs:** run `bash platform/scripts/__tests__/first-token-creates-stream-log.test.sh` from the install. Pass = invariant holds; any other exit = the writer-side existence contract is broken and one `[log-tee] missing-on-resolve sessionKey=<8> surface=<…>` line on `server.log` is the operator-visible signal (P0).
|
|
9
|
+
**Diagnose if it ever recurs:** run `bash platform/scripts/__tests__/first-token-creates-stream-log.test.sh` from the install. Pass = invariant holds; any other exit = the writer-side existence contract is broken and one `[log-tee] missing-on-resolve sessionKey=<8> surface=<…>` line on `server.log` is the operator-visible signal (P0). For the duplicate-file class specifically (the 2026-05-14 recurrence trigger), `bash platform/scripts/log-adherence-check.sh` returns non-zero whenever any sessionKey has more than one `claude-agent-stream-<sk>.log` across account dirs.
|
|
10
10
|
|
|
11
11
|
## Browser navigation to a local file (`file://`) used to time out for two minutes
|
|
12
12
|
|
|
@@ -188,6 +188,8 @@ replaced the ttyd/xterm admin terminal with a detached action runner. Upgrades a
|
|
|
188
188
|
|
|
189
189
|
**"Connection lost — reconnecting…" banner appears during an upgrade.** On post-Task-666 bundles this should never appear while the upgrade is in flight — the routes live on the always-on edge. If you see the banner during steps 8→11 of an upgrade on a current bundle, it is a **regression**, not expected behaviour: the routes have drifted back onto `maxy.service` or the edge's Hono dispatcher is not intercepting them. Check `~/.maxy/logs/edge.log` for `[edge-admin]` entries during the window; absence means the edge never received the request. The prebuild gate `platform/ui/scripts/check-edge-admin-routes.mjs` exists specifically to catch this drift before it ships.
|
|
190
190
|
|
|
191
|
+
**Agent does not respond to any message after a fresh install or restart.** The chat ingress quartet surfaces where the request silently stopped. From admin chat, ask the agent to read the chat-ingress timeline for the last 5 minutes — internally it runs `logs-read.sh --tail chat-attempts 5`, which cross-greps `edge.log` and `server.log` for the four checkpoints (edge inbound, admin-auth verdict, handler entry, edge outcome). Any missing row localises the failure: no edge inbound = request never reached the device (network, DNS, or cloudflare tunnel); inbound present + no auth verdict = handler unreachable (Hono routing regression); auth verdict = reject + a `code=` value the client banner matches; auth = accept + no handler entry = post-auth routing regression; all three rows present + no edge outcome = upstream crash or socket abort. See the developer doc `.docs/web-chat.md#chat-ingress-quartet` for the full state-machine and the prefix taxonomy.
|
|
192
|
+
|
|
191
193
|
**Heartbeat stalled** (log panel header shows rising `silent Ns` amber badge).
|
|
192
194
|
|
|
193
195
|
- Open the log panel header: `state: <systemd_state>` tells you the unit's current state.
|
|
@@ -524,3 +526,5 @@ sudo systemctl --user start maxy-ui
|
|
|
524
526
|
**`.trash/` retention:** Archived directories are kept indefinitely. The platform never auto-empties `.trash/`. When you're confident the archived orphans are truly obsolete, remove the directory manually: `rm -rf ~/maxy/data/accounts/.trash/<uuid>-<ts>/`.
|
|
525
527
|
|
|
526
528
|
**Installer aborted with "identity-match FAILED":** Multi-account installs where no sibling matches `users.json[0].userId` abort loud — the installer refuses to pick one and refuses to sweep. Resolution: inspect `account.json` in each candidate dir (listed in the abort output), identify the correct owner, move the other(s) aside manually, then re-run the installer.
|
|
529
|
+
|
|
530
|
+
**A chat turn looks broken — assistant bubble never rendered:** Open `claude-agent-stream-<sessionKey>.log` and grep for `[sse-client]`. The five phases (`connected`, `event_received`, `render_complete`, `error`, `close`) tell the story in order. Missing `connected` = the chat fetch never returned 200; missing `event_received` = the server emitted nothing or the client lost the stream before the first frame; missing `render_complete` = the reducer never committed the assistant bubble (persist_ack never arrived). The operator can also click "Report this turn" in the chat menu to append a `[failure-report]` line carrying the same sessionKey + conversationId + clientSnapshot, plus copy the payload to the clipboard.
|
|
@@ -87,7 +87,7 @@ Graph hygiene is **agent-directed, case by case** — no autonomous rule engine,
|
|
|
87
87
|
|
|
88
88
|
## Conversational Memory
|
|
89
89
|
|
|
90
|
-
The owner's profile and preferences accumulate organically from conversation — never from questionnaires.
|
|
90
|
+
The owner's profile and preferences accumulate organically from conversation — never from questionnaires. Run `skill-load skillName=conversational-memory` for full guidance on when to observe preferences, how to handle remember/forget requests, and how to answer "what do you know about me?" transparently with confidence scores and evidence trail.
|
|
91
91
|
|
|
92
92
|
## UserProfile + Person Field Management
|
|
93
93
|
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Task 1015 — build-time gate: rejects prose that tells an agent to load a
|
|
3
|
+
// SKILL.md via `plugin-read`. `skill-load skillName=<name>` is the canonical
|
|
4
|
+
// resolve+read primitive for skills; `plugin-read` retains ownership of
|
|
5
|
+
// `references/*` and `PLUGIN.md` (both inherently plugin-scoped).
|
|
6
|
+
//
|
|
7
|
+
// Two phrasing classes regressed historically (corrections ledger:
|
|
8
|
+
// `path-as-pluginName`, count >= 1):
|
|
9
|
+
// 1. Literal-path: "Load `<plugin>/skills/<slug>/SKILL.md` via `plugin-read`"
|
|
10
|
+
// 2. Manifest-pointer: "load via `plugin-read` (find its path in the manifest ...)"
|
|
11
|
+
// Both surface the same failure — the LLM packs the path into the `pluginName`
|
|
12
|
+
// argument and hits the not-found branch. This gate fails CI if either phrasing
|
|
13
|
+
// reappears in `platform/templates/` or in any plugin's `SKILL.md` / `PLUGIN.md`.
|
|
14
|
+
//
|
|
15
|
+
// Scope: prose-only Markdown the agent reads as instructions. Code files are
|
|
16
|
+
// out of scope (TypeScript / shell already lint elsewhere). References under
|
|
17
|
+
// `skills/*/references/` are out of scope because they document end-user
|
|
18
|
+
// behaviour, not agent routing. The MCP server's `index.ts` and tests are
|
|
19
|
+
// covered by their own type checks + the `skill-load.test.ts` suite.
|
|
20
|
+
|
|
21
|
+
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
|
|
22
|
+
import { dirname, join, basename } from 'node:path'
|
|
23
|
+
import { fileURLToPath } from 'node:url'
|
|
24
|
+
|
|
25
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url))
|
|
26
|
+
const REPO_ROOT = join(SCRIPT_DIR, '..', '..')
|
|
27
|
+
|
|
28
|
+
const SCAN_ROOTS = [
|
|
29
|
+
join(REPO_ROOT, 'platform', 'templates'),
|
|
30
|
+
join(REPO_ROOT, 'platform', 'plugins'),
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
// Phrasing 1 — literal SKILL.md path with `via plugin-read`. Matches every
|
|
34
|
+
// historical site that named a path string.
|
|
35
|
+
const PATH_PHRASING_RE = /Load\s+[`"]?[\w/-]*skills\/[a-z0-9-]+\/SKILL\.md[`"]?\s+via\s+`?plugin-read`?/i
|
|
36
|
+
|
|
37
|
+
// Phrasing 2 — manifest-pointer "via plugin-read (find its path in the manifest ...)"
|
|
38
|
+
// from sites that referred the agent to the manifest instead of naming a path.
|
|
39
|
+
const MANIFEST_PHRASING_RE = /via\s+`?plugin-read`?\s*\(\s*find its path in the manifest/i
|
|
40
|
+
|
|
41
|
+
// Only scan files that an agent reads as routing instructions.
|
|
42
|
+
function shouldScanFile(filePath) {
|
|
43
|
+
const base = basename(filePath)
|
|
44
|
+
if (base === 'SKILL.md' || base === 'PLUGIN.md' || base === 'IDENTITY.md' || base === 'SOUL.md') return true
|
|
45
|
+
// Specialist agent files live under platform/templates/specialists/agents/*.md
|
|
46
|
+
if (filePath.includes('/templates/specialists/agents/') && filePath.endsWith('.md')) return true
|
|
47
|
+
// Other markdown under templates/agents/ (admin/public IDENTITY.md handled by base check)
|
|
48
|
+
if (filePath.includes('/templates/agents/') && filePath.endsWith('.md')) return true
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const SKIP_DIR_NAMES = new Set(['node_modules', 'dist', '.git', 'references', 'archive'])
|
|
53
|
+
|
|
54
|
+
function* walk(root) {
|
|
55
|
+
if (!existsSync(root)) return
|
|
56
|
+
for (const entry of readdirSync(root)) {
|
|
57
|
+
if (SKIP_DIR_NAMES.has(entry)) continue
|
|
58
|
+
const full = join(root, entry)
|
|
59
|
+
let stat
|
|
60
|
+
try { stat = statSync(full) } catch { continue }
|
|
61
|
+
if (stat.isDirectory()) {
|
|
62
|
+
yield* walk(full)
|
|
63
|
+
} else if (stat.isFile()) {
|
|
64
|
+
yield full
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const violations = []
|
|
70
|
+
|
|
71
|
+
for (const root of SCAN_ROOTS) {
|
|
72
|
+
for (const file of walk(root)) {
|
|
73
|
+
if (!shouldScanFile(file)) continue
|
|
74
|
+
let text
|
|
75
|
+
try { text = readFileSync(file, 'utf-8') } catch { continue }
|
|
76
|
+
const lines = text.split('\n')
|
|
77
|
+
for (let i = 0; i < lines.length; i++) {
|
|
78
|
+
const line = lines[i]
|
|
79
|
+
if (PATH_PHRASING_RE.test(line)) {
|
|
80
|
+
violations.push({ file, line: i + 1, phrasing: 'literal-path', text: line.trim() })
|
|
81
|
+
} else if (MANIFEST_PHRASING_RE.test(line)) {
|
|
82
|
+
violations.push({ file, line: i + 1, phrasing: 'manifest-pointer', text: line.trim() })
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (violations.length > 0) {
|
|
89
|
+
console.error(`check-skill-load-coverage: ${violations.length} prose site(s) still tell the agent to load a skill via plugin-read.`)
|
|
90
|
+
console.error(`Rewrite each to: Run \`skill-load skillName=<name>\``)
|
|
91
|
+
console.error(``)
|
|
92
|
+
for (const v of violations) {
|
|
93
|
+
const rel = v.file.replace(REPO_ROOT + '/', '')
|
|
94
|
+
console.error(` ${rel}:${v.line} [${v.phrasing}]`)
|
|
95
|
+
console.error(` ${v.text.slice(0, 200)}${v.text.length > 200 ? '…' : ''}`)
|
|
96
|
+
}
|
|
97
|
+
process.exit(1)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
console.log(`check-skill-load-coverage: ok (0 prose sites use legacy plugin-read for skills)`)
|
|
@@ -91,10 +91,35 @@ for k in "${UNIQUE[@]:-}"; do
|
|
|
91
91
|
fi
|
|
92
92
|
done
|
|
93
93
|
|
|
94
|
-
|
|
95
|
-
|
|
94
|
+
# Task 1013 — duplicate-basename check. Two stream-log files for one
|
|
95
|
+
# sessionKey across account dirs is the structural-failure signature this
|
|
96
|
+
# task exists to make impossible. The build gate prevents the writer-side
|
|
97
|
+
# regression that produces it (sessions.ts:568 wrong-identifier path); this
|
|
98
|
+
# diagnostic is the runtime backstop on every device, every hour. `dup=0`
|
|
99
|
+
# is the steady state; `dup>0` is a P0 page (the writer collapse regressed).
|
|
100
|
+
declare -A BASENAME_COUNT=()
|
|
101
|
+
for log_dir in "$ACCOUNTS_DIR"/*/logs; do
|
|
102
|
+
for f in "$log_dir"/claude-agent-stream-*.log; do
|
|
103
|
+
if [[ -f "$f" ]]; then
|
|
104
|
+
base=$(basename "$f")
|
|
105
|
+
key="${base#claude-agent-stream-}"
|
|
106
|
+
key="${key%.log}"
|
|
107
|
+
BASENAME_COUNT[$key]=$(( ${BASENAME_COUNT[$key]:-0} + 1 ))
|
|
108
|
+
fi
|
|
109
|
+
done
|
|
110
|
+
done
|
|
111
|
+
DUP_BASENAMES=0
|
|
112
|
+
for k in "${!BASENAME_COUNT[@]}"; do
|
|
113
|
+
if [[ ${BASENAME_COUNT[$k]} -gt 1 ]]; then
|
|
114
|
+
DUP_BASENAMES=$((DUP_BASENAMES + 1))
|
|
115
|
+
echo "${TS} [log-tee] dup-basename sessionKey=${k:0:8} count=${BASENAME_COUNT[$k]} surface=adherence-script — Task 1013 P0: writer collapse regressed" >> "$SERVER_LOG" 2>/dev/null || true
|
|
116
|
+
fi
|
|
117
|
+
done
|
|
118
|
+
|
|
119
|
+
echo "${TS} [log-tee] adherence-check window=${WINDOW_HOURS}h sessions=${SESSIONS} misses=${MISSES} dup-basenames=${DUP_BASENAMES} surface=script ts=${TS}" >> "$SERVER_LOG" 2>/dev/null || true
|
|
120
|
+
echo "[log-adherence-check] sessions=${SESSIONS} misses=${MISSES} dup-basenames=${DUP_BASENAMES} window=${WINDOW_HOURS}h"
|
|
96
121
|
|
|
97
|
-
if [[ $MISSES -gt 0 ]]; then
|
|
122
|
+
if [[ $MISSES -gt 0 || $DUP_BASENAMES -gt 0 ]]; then
|
|
98
123
|
exit 1
|
|
99
124
|
fi
|
|
100
125
|
exit 0
|
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
#
|
|
15
15
|
# Types: agent-stream (canonical, per-session tool-use/tool-result archive;
|
|
16
16
|
# `system` is kept as a backwards-compatible alias), session, error,
|
|
17
|
-
# heartbeat, public, server, mcp, vnc
|
|
17
|
+
# heartbeat, public, server, mcp, vnc, chat-attempts
|
|
18
18
|
# For per-session types (agent-stream/system/session/error/public): sessionKey
|
|
19
19
|
# is required in the first mode. For platform-scoped types (server/vnc/
|
|
20
|
-
# heartbeat): the id is ignored.
|
|
20
|
+
# heartbeat): the id is ignored. chat-attempts is --tail-only (Task 1018):
|
|
21
|
+
# cross-grep of edge.log + server.log for the chat-ingress quartet within
|
|
22
|
+
# the most recent N minutes.
|
|
21
23
|
#
|
|
22
24
|
# Every invocation writes a one-line trailer describing files searched,
|
|
23
25
|
# matches returned, and files rejected — empty output never leaves a reader
|
|
@@ -116,7 +118,13 @@ is_per_conversation_type() {
|
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
SEARCH_TYPES="agent-stream error session public server mcp vnc"
|
|
119
|
-
VALID_TYPES="agent-stream, system, session, error, heartbeat, public, server, mcp, vnc"
|
|
121
|
+
VALID_TYPES="agent-stream, system, session, error, heartbeat, public, server, mcp, vnc, chat-attempts"
|
|
122
|
+
|
|
123
|
+
# Task 1018 — chat-attempts cross-grep prefixes. One row per chat-ingress
|
|
124
|
+
# checkpoint emitted across edge.log + server.log: edge-side inbound/outcome,
|
|
125
|
+
# admin-auth gate verdict, chat-route handler entry. Absence of a row at any
|
|
126
|
+
# checkpoint localises the failure to one stage of the ingress pipeline.
|
|
127
|
+
CHAT_ATTEMPT_GREP='\[edge-admin\] inbound|\[edge-admin\] outcome|\[admin-auth\] outcome|\[chat-route\] entered'
|
|
120
128
|
|
|
121
129
|
# Authoritative sessionKey character set.
|
|
122
130
|
# Writer-side (Task 1008) emits `sk_<hex16>` filenames; the underscore must
|
|
@@ -133,14 +141,19 @@ usage() {
|
|
|
133
141
|
Usage:
|
|
134
142
|
logs-read.sh <sessionKey-or-prefix> [type] Read {prefix}-{sessionKey}.log
|
|
135
143
|
logs-read.sh --tail [type] [lines] Tail most recent log of type
|
|
144
|
+
logs-read.sh --tail chat-attempts [min] Cross-grep edge.log + server.log
|
|
145
|
+
for the chat-ingress quartet
|
|
136
146
|
logs-read.sh --grep <sessionKey> [type] Legacy grep across log files
|
|
137
147
|
|
|
138
148
|
Types: $VALID_TYPES
|
|
139
|
-
Default type: agent-stream | Default lines: 50
|
|
149
|
+
Default type: agent-stream | Default lines: 50 | Default chat-attempts minutes: 5
|
|
140
150
|
|
|
141
151
|
Per-session types (agent-stream, system, session, error, public) require
|
|
142
152
|
sessionKey in the first mode — the log file is named {prefix}-{sessionKey}.log.
|
|
143
153
|
Platform-scoped types (server, vnc, heartbeat) ignore the id.
|
|
154
|
+
chat-attempts is --tail-only — it aggregates the four ingress checkpoints
|
|
155
|
+
([edge-admin] inbound, [admin-auth] outcome, [chat-route] entered,
|
|
156
|
+
[edge-admin] outcome) into one timeline.
|
|
144
157
|
EOF
|
|
145
158
|
exit 2
|
|
146
159
|
}
|
|
@@ -148,11 +161,93 @@ EOF
|
|
|
148
161
|
validate_type() {
|
|
149
162
|
local t="$1"
|
|
150
163
|
case "$t" in
|
|
151
|
-
agent-stream|system|session|error|heartbeat|public|server|mcp|vnc) return 0 ;;
|
|
164
|
+
agent-stream|system|session|error|heartbeat|public|server|mcp|vnc|chat-attempts) return 0 ;;
|
|
152
165
|
*) echo "Error: invalid type '$t'. Valid types: $VALID_TYPES" >&2; exit 2 ;;
|
|
153
166
|
esac
|
|
154
167
|
}
|
|
155
168
|
|
|
169
|
+
# Task 1018 — chat-attempts cross-grep mode. Combines edge.log + server.log
|
|
170
|
+
# lines matching the chat-ingress quartet, filtered to the last N minutes,
|
|
171
|
+
# sorted chronologically. Default window: 5 minutes (matches the typical
|
|
172
|
+
# "agent doesn't respond" report window — operator sees the silence,
|
|
173
|
+
# screenshots the chat, then runs this within a few minutes).
|
|
174
|
+
#
|
|
175
|
+
# Output preserves source-file prefix so operators can see whether the line
|
|
176
|
+
# came from the edge process (edge.log) or maxy-ui (server.log). On a
|
|
177
|
+
# successful chat POST the timeline reads:
|
|
178
|
+
#
|
|
179
|
+
# [edge-admin] inbound method=POST path=/api/admin/chat token=present … (edge.log)
|
|
180
|
+
# [admin-auth] outcome=accept … (server.log)
|
|
181
|
+
# [chat-route] entered route=admin method=POST cacheKey=… … (server.log)
|
|
182
|
+
# [edge-admin] outcome method=POST path=/api/admin/chat status=200 … (edge.log)
|
|
183
|
+
#
|
|
184
|
+
# Any missing row in that sequence is itself diagnostic. Three-strike rule:
|
|
185
|
+
# zero edge inbound = browser/network problem; inbound present + admin-auth
|
|
186
|
+
# missing = handler unreachable; admin-auth=reject = auth failure; entered
|
|
187
|
+
# missing after accept = Hono routing regression; outcome missing = upstream
|
|
188
|
+
# proxy crash.
|
|
189
|
+
chat_attempts_mode() {
|
|
190
|
+
local minutes="${1:-5}"
|
|
191
|
+
if ! [[ "$minutes" =~ ^[0-9]+$ ]]; then
|
|
192
|
+
echo "Error: minutes must be a number, got '$minutes'" >&2
|
|
193
|
+
exit 2
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
local EDGE_LOG="$CONFIG_DIR/logs/edge.log"
|
|
197
|
+
local since_epoch
|
|
198
|
+
since_epoch=$(date -u -v-"${minutes}"M +%s 2>/dev/null || date -u -d "${minutes} minutes ago" +%s 2>/dev/null || true)
|
|
199
|
+
if [[ -z "$since_epoch" ]]; then
|
|
200
|
+
echo "Error: could not compute time window — neither BSD nor GNU date available" >&2
|
|
201
|
+
exit 2
|
|
202
|
+
fi
|
|
203
|
+
|
|
204
|
+
# Each log line carries an ISO-8601 timestamp at the head (server.log via
|
|
205
|
+
# pino, edge.log via console — both written line-buffered with leading
|
|
206
|
+
# `YYYY-MM-DDTHH:MM:SS` then either `.sssZ` or a space). awk filters to
|
|
207
|
+
# the time window; sed prefixes the source filename so the timeline
|
|
208
|
+
# remains attributable; sort -k1 keeps chronological order.
|
|
209
|
+
local filter_awk='
|
|
210
|
+
BEGIN { since = '"$since_epoch"' }
|
|
211
|
+
{
|
|
212
|
+
ts = substr($0, 1, 19)
|
|
213
|
+
gsub("T", " ", ts)
|
|
214
|
+
cmd = "date -u -j -f \"%Y-%m-%d %H:%M:%S\" \"" ts "\" +%s 2>/dev/null || date -u -d \"" ts "\" +%s 2>/dev/null"
|
|
215
|
+
cmd | getline epoch
|
|
216
|
+
close(cmd)
|
|
217
|
+
if (epoch >= since) print
|
|
218
|
+
}
|
|
219
|
+
'
|
|
220
|
+
|
|
221
|
+
local hits=0
|
|
222
|
+
local tmp
|
|
223
|
+
tmp=$(mktemp -t chat-attempts.XXXXXX)
|
|
224
|
+
trap 'rm -f "$tmp"' EXIT
|
|
225
|
+
|
|
226
|
+
if [[ -f "$EDGE_LOG" ]]; then
|
|
227
|
+
grep -E "$CHAT_ATTEMPT_GREP" "$EDGE_LOG" 2>/dev/null | awk "$filter_awk" | sed 's|^|edge.log |' >> "$tmp" || true
|
|
228
|
+
fi
|
|
229
|
+
if [[ -f "$SERVER_LOG" ]]; then
|
|
230
|
+
grep -E "$CHAT_ATTEMPT_GREP" "$SERVER_LOG" 2>/dev/null | awk "$filter_awk" | sed 's|^|server.log|' >> "$tmp" || true
|
|
231
|
+
fi
|
|
232
|
+
|
|
233
|
+
hits=$(wc -l < "$tmp" | tr -d ' ')
|
|
234
|
+
if [[ "$hits" -eq 0 ]]; then
|
|
235
|
+
echo "# chat-attempts (last ${minutes} min) — no events" >&2
|
|
236
|
+
echo "-- trailer: type=chat-attempts window_min=${minutes} matches=0 searched=edge.log,server.log" >&2
|
|
237
|
+
exit 1
|
|
238
|
+
fi
|
|
239
|
+
|
|
240
|
+
echo "# chat-attempts (last ${minutes} min — chronological)"
|
|
241
|
+
echo ""
|
|
242
|
+
# Sort by the timestamp column that begins immediately after the source-file
|
|
243
|
+
# prefix. Prefix is "edge.log " or "server.log" — both 10 chars before the
|
|
244
|
+
# timestamp begins, so column 11 onward is the chronological key.
|
|
245
|
+
sort -k1.11 "$tmp"
|
|
246
|
+
echo "" >&2
|
|
247
|
+
echo "-- trailer: type=chat-attempts window_min=${minutes} matches=${hits} searched=edge.log,server.log" >&2
|
|
248
|
+
exit 0
|
|
249
|
+
}
|
|
250
|
+
|
|
156
251
|
# --- Per-session mode: prefix-match {prefix}{sessionKey-prefix}*.log ---
|
|
157
252
|
#
|
|
158
253
|
# Task 1006 — one filename shape per session: {prefix}{sessionKey}.log.
|
|
@@ -368,6 +463,14 @@ tail_mode() {
|
|
|
368
463
|
local log_type="${1:-agent-stream}"
|
|
369
464
|
local lines="${2:-50}"
|
|
370
465
|
|
|
466
|
+
# Task 1018 — chat-attempts is cross-grep + time-windowed, not single-file
|
|
467
|
+
# tail. Dispatch before validate_type's numeric `lines` check (which would
|
|
468
|
+
# be wrong here — argument 2 is `minutes`, not `lines`).
|
|
469
|
+
if [[ "$log_type" == "chat-attempts" ]]; then
|
|
470
|
+
chat_attempts_mode "${lines:-5}"
|
|
471
|
+
return
|
|
472
|
+
fi
|
|
473
|
+
|
|
371
474
|
validate_type "$log_type"
|
|
372
475
|
|
|
373
476
|
if ! [[ "$lines" =~ ^[0-9]+$ ]]; then
|
|
@@ -115,7 +115,7 @@ Everything else is out of bounds. You do not drive Cloudflare's dashboard via Pl
|
|
|
115
115
|
|
|
116
116
|
When a sanctioned surface fails, report the failure with the exact output, cite the matching recovery step from `reset-guide.md`, and stop. Improvisation — "let me try a different flag", "let me check the dashboard myself", "let me call the Cloudflare API to list zones" — is the behaviour this rule exists to prevent. The cost of stopping is low; the cost of improvising is a state-corruption cascade that takes the operator far longer to unwind than a clean report of failure would.
|
|
117
117
|
|
|
118
|
-
|
|
118
|
+
Run `skill-load skillName=setup-tunnel` on the first Cloudflare-related request of a session. The skill names the four sanctioned surfaces and the exact inputs each script requires.
|
|
119
119
|
|
|
120
120
|
## Questions
|
|
121
121
|
|
|
@@ -129,9 +129,9 @@ Operationalises the CONCISE prerogative for clarification. Three rules:
|
|
|
129
129
|
|
|
130
130
|
## Tool Routing
|
|
131
131
|
|
|
132
|
-
**Bundled-SKILL access contract.** Plugin skills and references live in the bundled npm package, not on disk under `<accountDir>/agents/admin/plugins/`. Load
|
|
132
|
+
**Bundled-SKILL access contract.** Plugin skills and references live in the bundled npm package, not on disk under `<accountDir>/agents/admin/plugins/`. Load skills with `mcp__admin__skill-load skillName="<name>"` (one call: resolves the owning plugin and returns the SKILL.md body with brand placeholders substituted). Load references and `PLUGIN.md` with `mcp__admin__plugin-read` (plugin-scoped by their nature) — the `<plugin-manifest>` `Skills:` and `References:` lines name the exact arguments 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.
|
|
133
133
|
|
|
134
|
-
**Plain-English precision pass.**
|
|
134
|
+
**Plain-English precision pass.** Run `skill-load skillName=plainly` on the first turn of a session where the user asks to explain, define, or pre-empts one of the trigger phrases ("explain in plain English", "what does X mean", "define X", "I don't understand", "what is this"), AND on the first turn where you are about to return a reply containing a term not in the operator's prior turn. Apply the AI-tells strip + recursive-rule pass to the reply before sending it. The skill applies to prose returned to the operator, not to MCP tool arguments — agent-to-machine payloads pass through unmodified.
|
|
135
135
|
|
|
136
136
|
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.
|
|
137
137
|
|
|
@@ -162,7 +162,7 @@ Consult LEARNINGS.md before repeating an approach that failed or interacting in
|
|
|
162
162
|
|
|
163
163
|
## Capabilities
|
|
164
164
|
|
|
165
|
-
Your capabilities come from **MCP tools**, **specialist subagents** listed in `agents/admin/AGENTS.md`, and **native Claude Code tools** (Read, Grep, Glob for observation; Write, Edit for agent configuration). The `<plugin-manifest>` contains two sections: a `<specialist-domains>` block mapping intent domains to specialists, and full entries for plugins the admin agent handles directly. Use the manifest to find admin-owned plugin resources
|
|
165
|
+
Your capabilities come from **MCP tools**, **specialist subagents** listed in `agents/admin/AGENTS.md`, and **native Claude Code tools** (Read, Grep, Glob for observation; Write, Edit for agent configuration). The `<plugin-manifest>` contains two sections: a `<specialist-domains>` block mapping intent domains to specialists, and full entries for plugins the admin agent handles directly. Use the manifest to find admin-owned plugin resources — load skills via `skill-load skillName=<name>` and references via `plugin-read`.
|
|
166
166
|
|
|
167
167
|
When the user asks what you can do, answer from the specialist domains, admin-owned plugins, and business context in the graph. Only describe active capabilities in response to general queries. When the user's stated intent matches a dormant plugin's purpose, you may mention the dormant capability — see Dormant Plugin Nudges below.
|
|
168
168
|
|
|
@@ -171,7 +171,7 @@ When the user asks what you can do, answer from the specialist domains, admin-ow
|
|
|
171
171
|
- Be proactive in surfacing what needs attention. Never proactive in acting on it — the Intent Gate applies.
|
|
172
172
|
- When the owner asks what needs doing, or when context suggests a status check is appropriate, assess the state of the business from the graph and report what needs attention. Check the `businessType` property on the `LocalBusiness` node — when set, it determines which vertical schema reference to load alongside `schema-base.md` for structured writes.
|
|
173
173
|
- When the business context in the graph is sparse or missing, learn the business, understand the stage, gather customer details, build a comprehensive picture.
|
|
174
|
-
- When operational data is missing or incomplete on the LocalBusiness node, load
|
|
174
|
+
- When operational data is missing or incomplete on the LocalBusiness node, run `skill-load skillName=business-profile`.
|
|
175
175
|
- Think strategically. Surface problems before they become urgent. Recommend actions based on what you know.
|
|
176
176
|
- Never state a future commitment ("I'll flag", "I'll check", "I'll remind") without immediately creating the mechanism to fulfil it — a scheduled event, a task, or a workflow trigger. A commitment without a backing mechanism is a broken promise.
|
|
177
177
|
- Store everything you learn about the business in the graph — not in files.
|
|
@@ -194,7 +194,7 @@ This is distinct from the rule about your own commitments (above). That rule say
|
|
|
194
194
|
|
|
195
195
|
## Conversational Memory
|
|
196
196
|
|
|
197
|
-
You learn about the owner through conversation — never through questionnaires or onboarding forms.
|
|
197
|
+
You learn about the owner through conversation — never through questionnaires or onboarding forms. Run `skill-load skillName=conversational-memory` for full guidance. The essentials:
|
|
198
198
|
|
|
199
199
|
- When the owner reveals a preference or working pattern, store it via `profile-update`. Explicit statements get `source: "explicit"`; behavioural patterns observed at least twice get `source: "behavioural"`. Always include `conversationId` for evidence linking.
|
|
200
200
|
- When the owner says "remember this" or "forget that", use `profile-update` or `profile-delete` accordingly and confirm what you did.
|
|
@@ -208,7 +208,7 @@ Purchased premium plugins are automatically delivered from the staging area at e
|
|
|
208
208
|
|
|
209
209
|
## Public Agent Management
|
|
210
210
|
|
|
211
|
-
Any request to create, edit, clone, list, preview, or delete a public agent must go through the public agent management skill.
|
|
211
|
+
Any request to create, edit, clone, list, preview, or delete a public agent must go through the public agent management skill. Run `skill-load skillName=public-agent-manager`. Do not duplicate or improvise agent operations outside the skill. Public agents are optional — the platform works without any.
|
|
212
212
|
|
|
213
213
|
## Admin User Management
|
|
214
214
|
|
|
@@ -229,7 +229,7 @@ Only the owner (or any current admin) can manage admins. Role is a label only
|
|
|
229
229
|
|
|
230
230
|
## Plugin and Account Management
|
|
231
231
|
|
|
232
|
-
Plugin installation, removal, and account settings changes are managed via conversation.
|
|
232
|
+
Plugin installation, removal, and account settings changes are managed via conversation. Run `skill-load skillName=plugin-management` for the relevant skill.
|
|
233
233
|
|
|
234
234
|
## WhatsApp Configuration
|
|
235
235
|
|
|
@@ -290,7 +290,7 @@ The `<specialist-domains>` block lists every specialist-owned tool with a descri
|
|
|
290
290
|
- **No matching tool in the manifest** — fall back to memory-search. Only if memory-search cannot answer, and the capability is clearly admin-owned, use ToolSearch as a last resort.
|
|
291
291
|
- After a specialist run, synthesise its results into the final response to the user.
|
|
292
292
|
- Retain all task management. Specialists do not create, update, or complete tasks.
|
|
293
|
-
- To install or remove specialists, load
|
|
293
|
+
- To install or remove specialists, run `skill-load skillName=specialist-management`. Keep `agents/admin/AGENTS.md` in sync.
|
|
294
294
|
|
|
295
295
|
## Browser
|
|
296
296
|
|
|
@@ -306,7 +306,7 @@ The document-editor's markdown parser fails silently on these specific typograph
|
|
|
306
306
|
|
|
307
307
|
## Plugins and Skills
|
|
308
308
|
|
|
309
|
-
Your behaviour is defined by your loaded plugins and specialist domains. The `<plugin-manifest>` contains a `<specialist-domains>` block (domains owned by specialists — delegate, don't load skills) and full entries for admin-owned plugins
|
|
309
|
+
Your behaviour is defined by your loaded plugins and specialist domains. The `<plugin-manifest>` contains a `<specialist-domains>` block (domains owned by specialists — delegate, don't load skills) and full entries for admin-owned plugins. To load a skill for an admin-owned plugin, run `skill-load skillName=<name>` — one call resolves the owning plugin and returns the SKILL.md body. To load a reference or `PLUGIN.md`, use `plugin-read` with the plugin's directory name and the file path from the manifest. Do not call either tool for specialist-owned domains — the specialists have domain knowledge embedded in their system prompts.
|
|
310
310
|
|
|
311
311
|
## Dormant Plugin Nudges
|
|
312
312
|
|
|
@@ -51,6 +51,6 @@ When you need to clarify intent before acting, follow two rules.
|
|
|
51
51
|
|
|
52
52
|
## Plain-English precision pass
|
|
53
53
|
|
|
54
|
-
|
|
54
|
+
Run `skill-load skillName=plainly` on the first text-producing turn of every session. Apply the AI-tells strip and the recursive plain-English rule to every reply you return to the visitor before emitting it — this is a prime-directive prerogative that overrides every other formatting preference. Customers are the highest-stakes audience this platform serves; plain English is not a polish step, it is the contract.
|
|
55
55
|
|
|
56
56
|
The skill applies to prose returned to the visitor and to text content passed through `render-component`. It does NOT apply to the structured arguments you pass into `render-component` (component name, option lists, IDs) — those are agent-to-machine payloads.
|
|
@@ -74,7 +74,7 @@ When a brief carries a "host this website" / "publish this site" / "put this onl
|
|
|
74
74
|
|
|
75
75
|
The chain (≤4 turns, total):
|
|
76
76
|
|
|
77
|
-
1. **Read the skill texts**
|
|
77
|
+
1. **Read the skill texts** — run `skill-load skillName=unzip-attachment` then `skill-load skillName=publish-site` to load both into context. Both ship invariants (zip-slip guard, declared-uncompressed cap, slug regex, refusal taxonomy) that are mechanically enforced by their shell primitives — read, do not paraphrase.
|
|
78
78
|
2. **Extract** the attached archive via the `unzip-attachment` skill's deterministic Bash flow. Output lands at `<accountDir>/extracted/<attachmentId>/`. Refusals (oversize, zip-slip, symlink, unreadable) are loud-fail; relay the operator message verbatim and stop.
|
|
79
79
|
3. **Confirm the slug** with the operator if not already explicit. The slug is one or more `/`-separated segments under `<accountDir>/sites/`; each segment matches `/^[a-z0-9_][a-z0-9_.-]{0,99}$/i` and no segment starts with `.` or equals `..`. Never invent a slug.
|
|
80
80
|
4. **Publish** via the `publish-site` skill's deterministic Bash flow — single `mv` from the extracted tree into `<accountDir>/sites/<slug>/`. Refusals (`unsafe-slug`, `destination-occupied`, `symlink-in-source`, `zero-html`, `ambiguous-html`) are loud-fail; relay the operator message verbatim and stop.
|
|
@@ -99,6 +99,6 @@ When a tool returns an error, surface the failure and its diagnostic context bef
|
|
|
99
99
|
|
|
100
100
|
## Plain-English precision pass
|
|
101
101
|
|
|
102
|
-
|
|
102
|
+
Run `skill-load skillName=plainly` on the first turn of every session. Apply the AI-tells strip + recursive plain-English rule to every prose artifact you return to the admin agent — captions, brochure prose, summaries, the "What you did" / "Summary" output contract above. This is a prime-directive prerogative; do not wait for admin to ask.
|
|
103
103
|
|
|
104
104
|
**Receiving-endpoint carve-out.** Plainly applies to prose returned to admin or to `render-component` text content. It does NOT apply to arguments passed to `image-generate` — those are agent-to-machine payloads where technical descriptors (`backlit, shallow DOF, 35mm grain, recraft-v4 design composition`) are required vocabulary, not jargon to strip. Pass image prompts through verbatim.
|
|
@@ -110,7 +110,7 @@ You ingest two classes of input into the graph: **unstructured documents** (PDFs
|
|
|
110
110
|
|
|
111
111
|
### Branch A — unstructured documents (universal `document-ingest` skill)
|
|
112
112
|
|
|
113
|
-
For any unstructured document, load
|
|
113
|
+
For any unstructured document, run `skill-load skillName=document-ingest` and follow it. The skill is generic — there are no per-doctype skills (no `cv-import`, no `contract-import`, no `transcript-import`). It loads the active ontology (`schema-base.md` + the active vertical schema named on the LocalBusiness `businessType` property), confirms the document subject (the anchor node) with the operator if the brief is ambiguous, runs Haiku-driven section classification via `memory-classify`, and writes typed graph nodes via `memory-ingest` with the natural edges named in the ontology.
|
|
114
114
|
|
|
115
115
|
The classifier maps document sections to typed ontology labels. It does not invent labels — every returned `kind` is verified against the loaded ontology label set; sections whose `kind` is not in the ontology are written as generic `:Section` nodes (the legacy fallback) with one `[document-ingest] unmapped-section` log line per. The operator sees ontology gaps named in the log; the document still completes.
|
|
116
116
|
|
|
@@ -118,7 +118,7 @@ The classifier maps document sections to typed ontology labels. It does not inve
|
|
|
118
118
|
|
|
119
119
|
Per-source archive imports keep their own skill because their CSVs already encode entity types deterministically and need no LLM classifier. Currently shipped:
|
|
120
120
|
|
|
121
|
-
- **linkedin-import** — LinkedIn Basic Data Export. Ships with references for `Profile.csv` and `Connections.csv`; additional CSVs land as new references inside the same plugin over time.
|
|
121
|
+
- **linkedin-import** — LinkedIn Basic Data Export. Ships with references for `Profile.csv` and `Connections.csv`; additional CSVs land as new references inside the same plugin over time. Run `skill-load skillName=linkedin-import` before any ingestion.
|
|
122
122
|
- **conversation-archive** — Conversation transcripts (any source) ingest via the `conversation-archive` skill. One skill, one bash entry, one writer, with `--source <enum>` selecting the per-source normaliser (`whatsapp`, `telegram`, `signal`, `linkedin-messages`, `zoom-transcript`, `meeting-minutes`, `imessage`, `slack`, `other`). Phase 0 ships only `whatsapp`; other normalisers land per-source. Pipeline: operator confirms owner + every distinct sender → `bash platform/plugins/memory/bin/conversation-archive-ingest.sh <archive> --source <enum> --owner-element-id <id> --participant-person-ids <id1>,<id2>,... --scope <admin|public>`. The script normalises (per source), sessionizes at gap-hours boundaries (default 12h), classifies each session via Haiku (`memory-classify` with `mode='chat'`) into topic-bounded `:Section:Conversation` chunks, and writes them under a parent `:ConversationArchive` MERGEd on `conversationIdentity = sha256(accountId + ":" + sortedParticipantElementIds)`. Re-imports are delta-append; the writer is bound to the operator-confirmed sender set (parser-miss = LOUD-FAIL). SKILL: `platform/plugins/memory/skills/conversation-archive/SKILL.md`. Distinct from the live `whatsapp` plugin (Baileys QR pairing, in-memory store).
|
|
123
123
|
- **conversation-archive-enrich** — Phase 2 over a named `:ConversationArchive`. Source-agnostic — runs against any archive produced by the `conversation-archive` skill regardless of source. Operator-triggered only (never auto-fires on Phase 1 completion). Walks `:Section:Conversation` chunks in pages via the read-only MCP tool `mcp__memory__conversation-archive-derive-insights` (which calls Haiku per chunk on OAuth, NOT the API key) and surfaces high-confidence claims for per-row operator gate (`wire / skip / reject`) over four kinds — `mention`, `task`, `preference`, `observed-relationship`. Idempotent on `(elementId(chunk), kind, contentHash)`: re-runs collapse identical claims via MERGE. Load: `platform/plugins/memory/skills/conversation-archive-enrich/SKILL.md`. Trigger phrasing: operator names a specific archive AND asks for insights / enrichment / derived claims — never the ingest skill's completion event.
|
|
124
124
|
|
|
@@ -194,6 +194,6 @@ When a tool returns an error, surface the failure and its diagnostic context bef
|
|
|
194
194
|
|
|
195
195
|
## Plain-English precision pass
|
|
196
196
|
|
|
197
|
-
|
|
197
|
+
Run `skill-load skillName=plainly` on the first turn of every session. Apply the AI-tells strip + recursive plain-English rule to every prose return payload — ingest summaries, derived-insight reports, dedupe results, every textual report back to admin. This is a prime-directive prerogative; do not wait for admin to ask.
|
|
198
198
|
|
|
199
199
|
**Receiving-endpoint carve-out.** Plainly applies to prose returned to admin. It does NOT apply to arguments passed to `memory-write`, `memory-classify`, `memory-ingest`, `memory-update`, or any `cypher-shell` invocation — those are agent-to-machine payloads where node labels (`:Person:LocalBusiness`), edge types (`PARTICIPANT`, `MENTIONS`), and Cypher tokens are required vocabulary, not jargon to strip. Pass Cypher statements and structured tool arguments through verbatim.
|
|
@@ -63,7 +63,7 @@ Manages events, appointments, and recurring triggers in the graph.
|
|
|
63
63
|
|
|
64
64
|
Guides setting up a Cloudflare Tunnel so the platform is reachable via a custom domain. The Cloudflare plugin exposes zero MCP tools; every operation runs through one of four sanctioned surfaces — `setup-tunnel.sh` (autonomous), `reset-tunnel.sh` (reset), `manual-setup.md` (runbook), or `dashboard-guide.md` (click-paths the operator runs in their browser). The operator's logged-in Cloudflare dashboard is the single source of truth; the agent never reads or mutates account state via any API path.
|
|
65
65
|
|
|
66
|
-
**Skill + references.**
|
|
66
|
+
**Skill + references.** Run `skill-load skillName=setup-tunnel` on the first Cloudflare-related turn. It names the four surfaces and the inputs to collect before invoking the autonomous script.
|
|
67
67
|
|
|
68
68
|
**Autonomous setup.** Collect the admin hostname (and optionally public + apex hostnames), then invoke `~/setup-tunnel.sh <brand> <port> <admin-hostname> [<public-hostname>] [<apex-hostname>]` via Bash. The script handles OAuth login, tunnel creation, DNS routing, config + state files, service restart, and post-restart verification. When an apex hostname is passed, it prints an `ACTION REQUIRED` block — relay it verbatim so the operator edits the exact dashboard record the CLI cannot create.
|
|
69
69
|
|
|
@@ -204,6 +204,6 @@ When a tool returns an error (email send, WhatsApp send, browser navigate, Teleg
|
|
|
204
204
|
|
|
205
205
|
## Plain-English precision pass
|
|
206
206
|
|
|
207
|
-
|
|
207
|
+
Run `skill-load skillName=plainly` on the first turn of every session. Apply the AI-tells strip + recursive plain-English rule to every prose return payload — every report you send back to admin, every email body or WhatsApp text you compose for the owner to send, every status summary. This is a prime-directive prerogative; do not wait for admin to ask.
|
|
208
208
|
|
|
209
209
|
**Receiving-endpoint carve-out.** Plainly applies to prose returned to admin and to message bodies destined for human readers. It does NOT apply to MCP tool arguments — `email-send` subject/body fields that target a human reader DO get plainly; structured arguments to `schedule-create` or any browser-automation call do NOT.
|