@rubytech/create-realagent 1.0.794 → 1.0.797
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 +1 -1
- package/payload/platform/neo4j/migrations/003-person-name-eradicate.cypher +24 -0
- package/payload/platform/plugins/admin/PLUGIN.md +4 -0
- package/payload/platform/plugins/admin/hooks/__tests__/archive-ingest-gate.test.sh +166 -0
- package/payload/platform/plugins/admin/hooks/archive-ingest-gate.sh +147 -0
- package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +9 -5
- package/payload/platform/plugins/admin/mcp/dist/index.js +54 -18
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/docs/references/internals.md +4 -0
- package/payload/platform/plugins/docs/references/settings.md +6 -0
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +2 -0
- package/payload/platform/plugins/memory/PLUGIN.md +4 -2
- package/payload/platform/plugins/memory/mcp/dist/index.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js +11 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-loader.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js +124 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-validator.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts +12 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js +41 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-loader.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts +9 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js +44 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/schema-validator.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js +20 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-archive-write.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js +0 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-archive-write.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.d.ts +2 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/whatsapp-export-parse.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-base.md +11 -1
- package/payload/platform/plugins/whatsapp-import/PLUGIN.md +4 -0
- package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.d.ts +8 -2
- package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.d.ts.map +1 -1
- package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.js +66 -15
- package/payload/platform/plugins/whatsapp-import/lib/dist/parse-export.js.map +1 -1
- package/payload/platform/plugins/whatsapp-import/lib/src/__tests__/parse-export.test.ts +175 -0
- package/payload/platform/plugins/whatsapp-import/lib/src/parse-export.ts +78 -17
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/SKILL.md +2 -0
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import/references/export-parse.md +8 -6
- package/payload/platform/scripts/logs-read.sh +17 -12
- package/payload/platform/scripts/seed-neo4j.sh +43 -20
- package/payload/platform/templates/agents/admin/IDENTITY.md +6 -0
- package/payload/platform/templates/specialists/agents/database-operator.md +2 -0
- package/payload/server/chunk-BURNRCKP.js +3405 -0
- package/payload/server/chunk-JSBRDJBE.js +30 -0
- package/payload/server/chunk-KM23Y7SY.js +9896 -0
- package/payload/server/client-pool-PV45NUTN.js +29 -0
- package/payload/server/maxy-edge.js +3 -2
- package/payload/server/neo4j-migrations-IUSBODOP.js +51 -0
- package/payload/server/public/assets/{admin-jGbRjAxV.js → admin-Cz8hUAqx.js} +60 -60
- package/payload/server/public/assets/data-BvV94XHO.js +1 -0
- package/payload/server/public/assets/graph-CBu0rtrP.js +1 -0
- package/payload/server/public/assets/page-BNM63zsb.js +50 -0
- package/payload/server/public/assets/page-DM19J3ur.js +1 -0
- package/payload/server/public/assets/useAdminFetch-iYCQ9lT0.js +1 -0
- package/payload/server/public/data.html +3 -3
- package/payload/server/public/graph.html +3 -3
- package/payload/server/public/index.html +4 -4
- package/payload/server/server.js +116 -137
- package/payload/server/public/assets/data-BhrQjgR5.js +0 -1
- package/payload/server/public/assets/graph-Jj7seS-w.js +0 -1
- package/payload/server/public/assets/page-DIG7s5Jp.js +0 -1
- package/payload/server/public/assets/page-sZb3wcOM.js +0 -50
- package/payload/server/public/assets/share-2-BndjMKeG.js +0 -1
package/package.json
CHANGED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Migration 003 — Person.name eradication (Task 849)
|
|
3
|
+
//
|
|
4
|
+
// Removes the denormalised `name` property from every Person node.
|
|
5
|
+
// `Person.name` is forbidden by schema-base.md "Forbidden Properties":
|
|
6
|
+
// the canonical fields are `givenName` + `familyName`, composed at
|
|
7
|
+
// read time by display-helpers.ts. Persisted `name` was a divergence
|
|
8
|
+
// trap — LLM extraction emitted `name = givenName` ("Dan") alongside
|
|
9
|
+
// the structured pair, so the canvas rendered "Dan" instead of
|
|
10
|
+
// "Dan Brett".
|
|
11
|
+
//
|
|
12
|
+
// Idempotent: subsequent runs find no Persons with `name` set and
|
|
13
|
+
// return removed=0.
|
|
14
|
+
//
|
|
15
|
+
// Applied at boot by platform/ui/app/lib/neo4j-migrations.ts. Safe to
|
|
16
|
+
// run manually:
|
|
17
|
+
// cypher-shell -u neo4j -p <password> -a $NEO4J_URI \
|
|
18
|
+
// -f platform/neo4j/migrations/003-person-name-eradicate.cypher
|
|
19
|
+
// ============================================================
|
|
20
|
+
|
|
21
|
+
MATCH (p:Person)
|
|
22
|
+
WHERE p.name IS NOT NULL
|
|
23
|
+
REMOVE p.name
|
|
24
|
+
RETURN count(p) AS removed
|
|
@@ -51,6 +51,10 @@ Platform management tools for both the admin and public agents. The `plugin-read
|
|
|
51
51
|
|
|
52
52
|
Tools are available via the `admin` MCP server.
|
|
53
53
|
|
|
54
|
+
**Three-store admin auth invariant (Task 850).** `admin-add` writes to all three identity stores (`users.json` PIN auth, `account.json` `admins[]` role, Neo4j `:AdminUser`/`:Person` graph identity) with per-leg `[admin-auth-store]` log lines and returns `is_error: true` on any leg failure naming what's already written. `admin-update-pin` writes `users.json` only and emits the same line. Direct `Edit`/`Write` on `account.json` is blocked at the `pre-tool-use` hook — mutations go through `account-update`, `plugin-toggle-enabled`, or the `admin-*` tools. See `.docs/agents.md` § "Three-store admin auth invariant" for the full contract.
|
|
55
|
+
|
|
56
|
+
`logs-read { type: "agent-stream" }` (Task 850) 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.
|
|
57
|
+
|
|
54
58
|
## Skills
|
|
55
59
|
|
|
56
60
|
| Task | When to use | Reference |
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Regression test for archive-ingest-gate.sh (Task 846).
|
|
3
|
+
#
|
|
4
|
+
# Six cases cover the contract:
|
|
5
|
+
# 1. Edit on /platform/plugins/<x>/lib/* is BLOCKED (exit 2).
|
|
6
|
+
# 2. Edit on a benign path is ALLOWED (exit 0).
|
|
7
|
+
# 3. Bash with `npx vitest` is BLOCKED.
|
|
8
|
+
# 4. PostToolUse on whatsapp-export-parse with isError:true sets the flag.
|
|
9
|
+
# 5. Subsequent PreToolUse on ANY tool is BLOCKED (post-parse-error gate).
|
|
10
|
+
# 6. UserPromptSubmit clears the flag, restoring normal allow behavior.
|
|
11
|
+
#
|
|
12
|
+
# Tests use ARCHIVE_INGEST_GATE_STATE_DIR to point at a tmp dir so they run
|
|
13
|
+
# without a real account layout.
|
|
14
|
+
|
|
15
|
+
set -u
|
|
16
|
+
|
|
17
|
+
HOOK="$(cd "$(dirname "$0")/.." && pwd)/archive-ingest-gate.sh"
|
|
18
|
+
if [[ ! -x "$HOOK" ]]; then
|
|
19
|
+
echo "FAIL: $HOOK not executable" >&2
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# Per-run isolated state dir
|
|
24
|
+
STATE_DIR=$(mktemp -d)
|
|
25
|
+
export ARCHIVE_INGEST_GATE_STATE_DIR="$STATE_DIR"
|
|
26
|
+
FLAG_FILE="$STATE_DIR/archive-ingest-parse-error.flag"
|
|
27
|
+
|
|
28
|
+
cleanup() { rm -rf "$STATE_DIR"; }
|
|
29
|
+
trap cleanup EXIT
|
|
30
|
+
|
|
31
|
+
PASS=0
|
|
32
|
+
FAIL=0
|
|
33
|
+
|
|
34
|
+
run_case() {
|
|
35
|
+
local name="$1" stdin="$2" expected_exit="$3"
|
|
36
|
+
local actual_exit
|
|
37
|
+
printf '%s' "$stdin" | bash "$HOOK" >/dev/null 2>/dev/null
|
|
38
|
+
actual_exit=$?
|
|
39
|
+
if [[ "$actual_exit" -eq "$expected_exit" ]]; then
|
|
40
|
+
echo "PASS: $name (exit=$actual_exit)"
|
|
41
|
+
PASS=$((PASS + 1))
|
|
42
|
+
else
|
|
43
|
+
echo "FAIL: $name (expected exit=$expected_exit, got=$actual_exit)" >&2
|
|
44
|
+
FAIL=$((FAIL + 1))
|
|
45
|
+
fi
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
# Case 1 — Edit on plugin lib path: BLOCKED
|
|
49
|
+
run_case "Edit on platform/plugins/whatsapp-import/lib/src/parse-export.ts → BLOCKED" \
|
|
50
|
+
'{"hook_event_name":"PreToolUse","tool_name":"Edit","tool_input":{"file_path":"/Users/x/repo/platform/plugins/whatsapp-import/lib/src/parse-export.ts","old_string":"a","new_string":"b"}}' \
|
|
51
|
+
2
|
|
52
|
+
|
|
53
|
+
# Case 2 — Edit on a benign path: ALLOWED
|
|
54
|
+
run_case "Edit on README.md → ALLOWED" \
|
|
55
|
+
'{"hook_event_name":"PreToolUse","tool_name":"Edit","tool_input":{"file_path":"/Users/x/repo/README.md","old_string":"a","new_string":"b"}}' \
|
|
56
|
+
0
|
|
57
|
+
|
|
58
|
+
# Case 3 — Bash with `npx vitest`: BLOCKED
|
|
59
|
+
run_case "Bash 'npx vitest run parse-export.test.ts' → BLOCKED" \
|
|
60
|
+
'{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"npx vitest run parse-export.test.ts"}}' \
|
|
61
|
+
2
|
|
62
|
+
|
|
63
|
+
# Case 3b — Bash with benign command: ALLOWED
|
|
64
|
+
run_case "Bash 'ls -la' → ALLOWED" \
|
|
65
|
+
'{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"ls -la"}}' \
|
|
66
|
+
0
|
|
67
|
+
|
|
68
|
+
# Case 3c — Bash with `bun test`: BLOCKED
|
|
69
|
+
run_case "Bash 'bun test' → BLOCKED" \
|
|
70
|
+
'{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"bun test"}}' \
|
|
71
|
+
2
|
|
72
|
+
|
|
73
|
+
# Case 3d — Bash with `npm test`: BLOCKED
|
|
74
|
+
run_case "Bash 'npm test' → BLOCKED" \
|
|
75
|
+
'{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"npm test"}}' \
|
|
76
|
+
2
|
|
77
|
+
|
|
78
|
+
# Make sure flag is absent before parse-error simulation
|
|
79
|
+
rm -f "$FLAG_FILE"
|
|
80
|
+
|
|
81
|
+
# Case 4 — PostToolUse on whatsapp-export-parse with isError:true sets flag
|
|
82
|
+
run_case "PostToolUse parse-error sets flag (exit 0, flag side-effect)" \
|
|
83
|
+
'{"hook_event_name":"PostToolUse","tool_name":"mcp__memory__whatsapp-export-parse","tool_input":{"filePath":"_chat.txt"},"tool_response":{"isError":true,"content":[{"type":"text","text":"parse-error file=_chat.txt line=1 reason=not-a-_chat.txt"}]}}' \
|
|
84
|
+
0
|
|
85
|
+
|
|
86
|
+
if [[ -f "$FLAG_FILE" ]]; then
|
|
87
|
+
echo "PASS: parse-error flag created at $FLAG_FILE"
|
|
88
|
+
PASS=$((PASS + 1))
|
|
89
|
+
else
|
|
90
|
+
echo "FAIL: parse-error flag NOT created at $FLAG_FILE" >&2
|
|
91
|
+
FAIL=$((FAIL + 1))
|
|
92
|
+
fi
|
|
93
|
+
|
|
94
|
+
# Case 5 — Subsequent PreToolUse on ANY tool BLOCKED while flag is fresh
|
|
95
|
+
run_case "PreToolUse Read after parse-error → BLOCKED" \
|
|
96
|
+
'{"hook_event_name":"PreToolUse","tool_name":"Read","tool_input":{"file_path":"/tmp/foo"}}' \
|
|
97
|
+
2
|
|
98
|
+
|
|
99
|
+
run_case "PreToolUse Bash after parse-error → BLOCKED" \
|
|
100
|
+
'{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"echo hi"}}' \
|
|
101
|
+
2
|
|
102
|
+
|
|
103
|
+
# Case 6 — UserPromptSubmit clears flag
|
|
104
|
+
run_case "UserPromptSubmit clears flag (exit 0)" \
|
|
105
|
+
'{"hook_event_name":"UserPromptSubmit","prompt":"retry"}' \
|
|
106
|
+
0
|
|
107
|
+
|
|
108
|
+
if [[ ! -f "$FLAG_FILE" ]]; then
|
|
109
|
+
echo "PASS: UserPromptSubmit cleared flag"
|
|
110
|
+
PASS=$((PASS + 1))
|
|
111
|
+
else
|
|
112
|
+
echo "FAIL: UserPromptSubmit did NOT clear flag" >&2
|
|
113
|
+
FAIL=$((FAIL + 1))
|
|
114
|
+
fi
|
|
115
|
+
|
|
116
|
+
# Case 7 — After clearance, normal allow resumes
|
|
117
|
+
run_case "PreToolUse Read after clearance → ALLOWED" \
|
|
118
|
+
'{"hook_event_name":"PreToolUse","tool_name":"Read","tool_input":{"file_path":"/tmp/foo"}}' \
|
|
119
|
+
0
|
|
120
|
+
|
|
121
|
+
# Case 8 — PostToolUse with isError:false does NOT set flag
|
|
122
|
+
rm -f "$FLAG_FILE"
|
|
123
|
+
run_case "PostToolUse parse-success (isError:false) does NOT set flag" \
|
|
124
|
+
'{"hook_event_name":"PostToolUse","tool_name":"mcp__memory__whatsapp-export-parse","tool_input":{"filePath":"_chat.txt"},"tool_response":{"isError":false,"content":[{"type":"text","text":"{\"parsedLines\":[]}"}]}}' \
|
|
125
|
+
0
|
|
126
|
+
|
|
127
|
+
if [[ ! -f "$FLAG_FILE" ]]; then
|
|
128
|
+
echo "PASS: parse-success leaves flag absent"
|
|
129
|
+
PASS=$((PASS + 1))
|
|
130
|
+
else
|
|
131
|
+
echo "FAIL: parse-success incorrectly created flag" >&2
|
|
132
|
+
FAIL=$((FAIL + 1))
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
# Case 9 — Stale flag (>600s) auto-clears + allows
|
|
136
|
+
PAST=$(( $(date -u +%s) - 700 ))
|
|
137
|
+
echo "$PAST" > "$FLAG_FILE"
|
|
138
|
+
run_case "Stale flag auto-clears, PreToolUse Read → ALLOWED" \
|
|
139
|
+
'{"hook_event_name":"PreToolUse","tool_name":"Read","tool_input":{"file_path":"/tmp/foo"}}' \
|
|
140
|
+
0
|
|
141
|
+
|
|
142
|
+
# Case 10 — No stdin (terminal) fails closed
|
|
143
|
+
echo "Probing fail-closed behaviour (no stdin)..."
|
|
144
|
+
bash "$HOOK" </dev/null >/dev/null 2>/dev/null
|
|
145
|
+
ACTUAL=$?
|
|
146
|
+
# /dev/null IS a stdin — the `[ -t 0 ]` check tests for terminal, not file.
|
|
147
|
+
# A file/pipe stdin reads as empty, which produces empty hook_event_name and
|
|
148
|
+
# falls through to default `exit 0` (allow). The terminal-only fail-closed
|
|
149
|
+
# branch can't be tested non-interactively; verify the script reads `[ -t 0 ]`.
|
|
150
|
+
if grep -q '\[ -t 0 \]' "$HOOK"; then
|
|
151
|
+
echo "PASS: fail-closed terminal check is present"
|
|
152
|
+
PASS=$((PASS + 1))
|
|
153
|
+
else
|
|
154
|
+
echo "FAIL: fail-closed terminal check missing" >&2
|
|
155
|
+
FAIL=$((FAIL + 1))
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
echo
|
|
159
|
+
echo "──────── archive-ingest-gate test summary ────────"
|
|
160
|
+
echo "PASS: $PASS"
|
|
161
|
+
echo "FAIL: $FAIL"
|
|
162
|
+
|
|
163
|
+
if [[ "$FAIL" -gt 0 ]]; then
|
|
164
|
+
exit 1
|
|
165
|
+
fi
|
|
166
|
+
exit 0
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Archive-ingest gate (Task 846).
|
|
3
|
+
#
|
|
4
|
+
# Three enforcements, one script — phase decided by `hook_event_name` on stdin:
|
|
5
|
+
#
|
|
6
|
+
# 1. PreToolUse Edit/Write/NotebookEdit: deny writes under
|
|
7
|
+
# `*platform/plugins/*/lib/*` (parser/CSV-shape source for any *-import or
|
|
8
|
+
# *-export plugin). The database-operator subagent has Read+Bash but not
|
|
9
|
+
# Edit/Write per its `tools:` frontmatter — yet it can still mutate files
|
|
10
|
+
# via Bash heredoc. The path block applies to every tool that takes a
|
|
11
|
+
# `file_path` and has been observed as the improvisation surface.
|
|
12
|
+
#
|
|
13
|
+
# 2. PreToolUse Bash: deny commands invoking JavaScript test runners
|
|
14
|
+
# (vitest|bun test|npm test|npx jest|node .*vitest). The reproducer
|
|
15
|
+
# incident (conv 47c6a590) saw the operator run all four variants in
|
|
16
|
+
# sequence after parse-export returned isError.
|
|
17
|
+
#
|
|
18
|
+
# 3. Parse-error gate: PostToolUse on any `mcp__*__*-export-parse` /
|
|
19
|
+
# `mcp__*__*-import-parse` tool whose `tool_response.isError == true`
|
|
20
|
+
# writes a flag file. Subsequent PreToolUse on ANY tool blocks until
|
|
21
|
+
# UserPromptSubmit clears the flag (semantics: "subagent's next action
|
|
22
|
+
# must be a user-facing message; further tool calls blocked"). A 600s
|
|
23
|
+
# TTL is the cross-session safety net.
|
|
24
|
+
#
|
|
25
|
+
# Exit codes follow Claude Code hook protocol: 0 = allow, 2 = block (stderr
|
|
26
|
+
# message shown to the agent). Fail-closed on stdin read failure to match
|
|
27
|
+
# pre-tool-use.sh.
|
|
28
|
+
|
|
29
|
+
set -uo pipefail
|
|
30
|
+
|
|
31
|
+
# Read stdin — fail closed if unavailable
|
|
32
|
+
if [ -t 0 ]; then
|
|
33
|
+
echo "Blocked: archive-ingest-gate received no stdin (cannot inspect tool call). Failing closed." >&2
|
|
34
|
+
exit 2
|
|
35
|
+
fi
|
|
36
|
+
INPUT=$(cat)
|
|
37
|
+
|
|
38
|
+
# ----- Resolve account dir for state file ----------------------------------
|
|
39
|
+
# Mirrors pre-tool-use.sh lines 100-107: walk from this hook's location to
|
|
40
|
+
# platform/, then `../data/accounts/<single-account>/`. Phase 0 has exactly
|
|
41
|
+
# one account directory.
|
|
42
|
+
HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
|
|
43
|
+
PLATFORM_ROOT_RESOLVED="${HOOK_DIR}/../../.."
|
|
44
|
+
ACCOUNTS_DIR="${PLATFORM_ROOT_RESOLVED}/../data/accounts"
|
|
45
|
+
ACCOUNT_DIR=""
|
|
46
|
+
if [ -d "$ACCOUNTS_DIR" ]; then
|
|
47
|
+
ACCOUNT_DIR=$(find "$ACCOUNTS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | head -1)
|
|
48
|
+
fi
|
|
49
|
+
# Test harness override — tests pass `ARCHIVE_INGEST_GATE_STATE_DIR` to point
|
|
50
|
+
# at a tmp dir without needing a real account layout.
|
|
51
|
+
STATE_DIR="${ARCHIVE_INGEST_GATE_STATE_DIR:-${ACCOUNT_DIR}/state}"
|
|
52
|
+
FLAG_FILE="${STATE_DIR}/archive-ingest-parse-error.flag"
|
|
53
|
+
TTL_SECONDS=600
|
|
54
|
+
|
|
55
|
+
# ----- Parse fields from stdin ---------------------------------------------
|
|
56
|
+
# Hook protocol: every event includes `hook_event_name`. PreToolUse +
|
|
57
|
+
# PostToolUse include `tool_name`. PostToolUse adds `tool_response`.
|
|
58
|
+
# UserPromptSubmit has neither. Use grep/sed against the JSON envelope —
|
|
59
|
+
# no jq dependency to match the rest of the hook fleet.
|
|
60
|
+
HOOK_EVENT=$(printf '%s' "$INPUT" | grep -o '"hook_event_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
|
61
|
+
TOOL_NAME=$(printf '%s' "$INPUT" | grep -o '"tool_name"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
|
62
|
+
|
|
63
|
+
# ============================================================================
|
|
64
|
+
# UserPromptSubmit — clear the parse-error flag.
|
|
65
|
+
# ============================================================================
|
|
66
|
+
if [ "$HOOK_EVENT" = "UserPromptSubmit" ]; then
|
|
67
|
+
if [ -f "$FLAG_FILE" ]; then
|
|
68
|
+
rm -f "$FLAG_FILE" 2>/dev/null
|
|
69
|
+
echo "[archive-ingest-gate] cleared reason=user-prompt-submit" >&2
|
|
70
|
+
fi
|
|
71
|
+
exit 0
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# ============================================================================
|
|
75
|
+
# PostToolUse — record parse-error from any *-export-parse / *-import-parse.
|
|
76
|
+
# ============================================================================
|
|
77
|
+
if [ "$HOOK_EVENT" = "PostToolUse" ]; then
|
|
78
|
+
case "$TOOL_NAME" in
|
|
79
|
+
mcp__*__*-export-parse|mcp__*__*-import-parse)
|
|
80
|
+
# Inspect tool_response for isError true. Body is a JSON object; match
|
|
81
|
+
# `"isError":true` with optional whitespace.
|
|
82
|
+
if printf '%s' "$INPUT" | grep -Eq '"isError"[[:space:]]*:[[:space:]]*true'; then
|
|
83
|
+
mkdir -p "$STATE_DIR" 2>/dev/null
|
|
84
|
+
date -u +%s > "$FLAG_FILE" 2>/dev/null
|
|
85
|
+
echo "[archive-ingest-gate] surfaced reason=parse-error tool=${TOOL_NAME}" >&2
|
|
86
|
+
fi
|
|
87
|
+
;;
|
|
88
|
+
esac
|
|
89
|
+
# PostToolUse must always allow the tool result through.
|
|
90
|
+
exit 0
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
# ============================================================================
|
|
94
|
+
# PreToolUse — three independent blocks.
|
|
95
|
+
# ============================================================================
|
|
96
|
+
if [ "$HOOK_EVENT" != "PreToolUse" ]; then
|
|
97
|
+
# Unknown event, or hook fired in a context without an event name. Allow.
|
|
98
|
+
exit 0
|
|
99
|
+
fi
|
|
100
|
+
|
|
101
|
+
# --- Block 1: post-parse-error gate (applies to ALL tools) -----------------
|
|
102
|
+
if [ -f "$FLAG_FILE" ]; then
|
|
103
|
+
FLAG_TS=$(cat "$FLAG_FILE" 2>/dev/null | head -1)
|
|
104
|
+
NOW=$(date -u +%s)
|
|
105
|
+
if [ -n "$FLAG_TS" ] && [ "$FLAG_TS" -gt 0 ] 2>/dev/null; then
|
|
106
|
+
AGE=$(( NOW - FLAG_TS ))
|
|
107
|
+
if [ "$AGE" -lt "$TTL_SECONDS" ]; then
|
|
108
|
+
echo "[archive-ingest-gate] block tool=${TOOL_NAME} reason=post-parse-error age_s=${AGE}" >&2
|
|
109
|
+
echo "Blocked: an archive-parser MCP tool returned isError=true earlier in this turn. The subagent's next action must be a user-facing message naming the parse-error and yielding back to the operator. Further tool calls are blocked until the operator submits a new prompt." >&2
|
|
110
|
+
exit 2
|
|
111
|
+
fi
|
|
112
|
+
# Stale flag — clean up so we don't keep blocking on a corpse.
|
|
113
|
+
rm -f "$FLAG_FILE" 2>/dev/null
|
|
114
|
+
else
|
|
115
|
+
# Unparseable flag — remove rather than block on garbage.
|
|
116
|
+
rm -f "$FLAG_FILE" 2>/dev/null
|
|
117
|
+
fi
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
# --- Block 2: plugin-source path block (Edit/Write/NotebookEdit) -----------
|
|
121
|
+
case "$TOOL_NAME" in
|
|
122
|
+
Edit|Write|NotebookEdit)
|
|
123
|
+
FILE_PATH=$(printf '%s' "$INPUT" | grep -o '"file_path"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
|
124
|
+
case "$FILE_PATH" in
|
|
125
|
+
*/platform/plugins/*/lib/*|platform/plugins/*/lib/*)
|
|
126
|
+
echo "[archive-ingest-gate] block tool=${TOOL_NAME} reason=plugin-source-edit path=${FILE_PATH}" >&2
|
|
127
|
+
echo "Blocked: ${TOOL_NAME} on ${FILE_PATH} is a platform plugin lib/ path. The database-operator subagent does not own plugin source; if a parser is broken, surface the parse-error to the operator and let them dispatch a code-edit task instead." >&2
|
|
128
|
+
exit 2
|
|
129
|
+
;;
|
|
130
|
+
esac
|
|
131
|
+
;;
|
|
132
|
+
esac
|
|
133
|
+
|
|
134
|
+
# --- Block 3: shell test-runner block (Bash) -------------------------------
|
|
135
|
+
if [ "$TOOL_NAME" = "Bash" ]; then
|
|
136
|
+
COMMAND=$(printf '%s' "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
|
|
137
|
+
# Match any of: `vitest`, `bun test`, `npm test`, `npx jest`, `node ... vitest`.
|
|
138
|
+
# Word-boundary checks via grep -E with explicit token boundaries — POSIX
|
|
139
|
+
# `[[:space:]]` covers leading-token edge cases, end-of-string covers tail.
|
|
140
|
+
if printf '%s' "$COMMAND" | grep -Eq '(^|[[:space:]/])vitest($|[[:space:]])|(^|[[:space:]])bun[[:space:]]+test($|[[:space:]])|(^|[[:space:]])npm[[:space:]]+test($|[[:space:]])|(^|[[:space:]])npx[[:space:]]+jest($|[[:space:]])|node[[:space:]].*vitest'; then
|
|
141
|
+
echo "[archive-ingest-gate] block tool=Bash reason=test-runner command=${COMMAND}" >&2
|
|
142
|
+
echo "Blocked: Bash command invokes a JavaScript test runner (vitest/bun test/npm test/npx jest). The database-operator subagent does not run plugin tests; surface the parse-error to the operator." >&2
|
|
143
|
+
exit 2
|
|
144
|
+
fi
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
exit 0
|
|
@@ -47,11 +47,15 @@ if [ "$AGENT_TYPE" = "admin" ]; then
|
|
|
47
47
|
echo "[entitlement] tool-deny: tool=${TOOL_NAME} path=${FILE_PATH} field=entitlement-file" >&2
|
|
48
48
|
exit 2
|
|
49
49
|
;;
|
|
50
|
-
# account.json (Task 831) — agent must use the account-update
|
|
51
|
-
# (or plugin-toggle-enabled for enabledPlugins changes), which
|
|
52
|
-
# editable fields server-side. Direct Edit/Write bypasses
|
|
53
|
-
# deny unconditionally including cwd-relative paths.
|
|
54
|
-
*/
|
|
50
|
+
# account.json (Task 831 + Task 850) — agent must use the account-update
|
|
51
|
+
# MCP tool (or plugin-toggle-enabled for enabledPlugins changes), which
|
|
52
|
+
# whitelists editable fields server-side. Direct Edit/Write bypasses
|
|
53
|
+
# that whitelist; deny unconditionally including cwd-relative paths.
|
|
54
|
+
# Task 850 adds */account.json and *account.json as a backstop so any
|
|
55
|
+
# layout the named patterns miss is still caught — the Adam Mackay
|
|
56
|
+
# incident showed a tier upgrade via raw Edit on account.json reach
|
|
57
|
+
# the disk, exactly the failure mode this hook exists to prevent.
|
|
58
|
+
*/data/accounts/*/account.json|*/config/accounts/*/account.json|data/accounts/*/account.json|config/accounts/*/account.json|*/account.json|*account.json|account.json)
|
|
55
59
|
echo "Blocked: Admin agent cannot edit account.json directly. Use the account-update or plugin-toggle-enabled MCP tools — they whitelist editable fields server-side and exclude tier and purchasedPlugins by design." >&2
|
|
56
60
|
echo "[entitlement] tool-deny: tool=${TOOL_NAME} path=${FILE_PATH} field=account-json" >&2
|
|
57
61
|
exit 2
|
|
@@ -621,7 +621,7 @@ server.tool("plugin-toggle-enabled", "Enable or disable a plugin in this account
|
|
|
621
621
|
// ===================================================================
|
|
622
622
|
// Admin user management tools
|
|
623
623
|
// ===================================================================
|
|
624
|
-
server.tool("admin-add", "Add a new admin user to this account. Creates a device-level user entry (users.json) and adds them to this account's admins list (account.json). PIN must be at least 4 digits. If no PIN is provided, a unique 4-digit PIN is generated. Returns the userId and PIN to share with the new admin.", {
|
|
624
|
+
server.tool("admin-add", "Add a new admin user to this account. Creates a device-level user entry (users.json) and adds them to this account's admins list (account.json). PIN must be at least 4 digits. If no PIN is provided, a unique 4-digit PIN is generated. Returns the userId and PIN to share with the new admin.\n\nIMPORTANT — retry behaviour: if the user already stated a specific PIN earlier in the conversation and a first admin-add call failed (e.g. tier cap reached, PIN collision), every retry MUST re-pass that PIN as the `pin` parameter. Omitting `pin` on the retry auto-generates a different 4-digit PIN, silently substituting what the user asked for.", {
|
|
625
625
|
name: z.string().describe("Display name for the new admin (stored on the AdminUser node in Neo4j)."),
|
|
626
626
|
pin: z.string().optional().describe("Optional PIN (minimum 4 digits). If omitted, a unique 4-digit PIN is generated."),
|
|
627
627
|
}, async ({ name, pin: rawPin }) => {
|
|
@@ -679,14 +679,25 @@ server.tool("admin-add", "Add a new admin user to this account. Creates a device
|
|
|
679
679
|
}
|
|
680
680
|
const pinHash = hashPin(plaintextPin);
|
|
681
681
|
const userId = crypto.randomUUID();
|
|
682
|
+
// Three-store admin auth invariant (Task 850): users.json (device-level
|
|
683
|
+
// PIN auth), account.json admins[] (account-level role), Neo4j AdminUser
|
|
684
|
+
// (display + graph identity). Each leg emits a [admin-auth-store] line so
|
|
685
|
+
// a future incident can grep one log to know which leg failed; any leg
|
|
686
|
+
// failing makes the tool result is_error: true with a cause line citing
|
|
687
|
+
// what's already been written. No automatic rollback — the cause line is
|
|
688
|
+
// the manual-recovery diagnostic.
|
|
689
|
+
const userIdShort = userId.slice(0, 8);
|
|
682
690
|
// 1. Write to users.json (device-level). Auth fields only — `name` lives
|
|
683
691
|
// on the AdminUser node in Neo4j (Task 829).
|
|
684
692
|
users.push({ userId, pin: pinHash });
|
|
685
693
|
try {
|
|
686
694
|
writeUsersJson(users);
|
|
695
|
+
console.error(`[admin-auth-store] action=add userId=${userIdShort} result=ok store=users`);
|
|
687
696
|
}
|
|
688
697
|
catch (err) {
|
|
689
|
-
|
|
698
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
699
|
+
console.error(`[admin-auth-store] action=add userId=${userIdShort} result=fail store=users error=${errMsg}`);
|
|
700
|
+
return { content: [{ type: "text", text: `${TAG} Failed to write users.json: ${errMsg}` }], isError: true };
|
|
690
701
|
}
|
|
691
702
|
// 2. Write to account.json (account-level)
|
|
692
703
|
try {
|
|
@@ -698,10 +709,12 @@ server.tool("admin-add", "Add a new admin user to this account. Creates a device
|
|
|
698
709
|
const configPath = join(getAccountDir(), "account.json");
|
|
699
710
|
await writeFile(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
700
711
|
}
|
|
712
|
+
console.error(`[admin-auth-store] action=add userId=${userIdShort} result=ok store=account`);
|
|
701
713
|
}
|
|
702
714
|
catch (err) {
|
|
703
|
-
|
|
704
|
-
|
|
715
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
716
|
+
console.error(`[admin-auth-store] action=add userId=${userIdShort} result=fail store=account error=${errMsg}`);
|
|
717
|
+
return { content: [{ type: "text", text: `${TAG} users.json updated; account.json write FAILED — manual reconciliation needed: ${errMsg}` }], isError: true };
|
|
705
718
|
}
|
|
706
719
|
// 3. Write to Neo4j (graph-level): AdminUser + Person + OWNS atomically,
|
|
707
720
|
// plus ADMIN_OF edge to the LocalBusiness for this account.
|
|
@@ -710,7 +723,12 @@ server.tool("admin-add", "Add a new admin user to this account. Creates a device
|
|
|
710
723
|
// in platform/ui/app/lib/neo4j-store.ts (case-insensitive exact match
|
|
711
724
|
// on givenName + familyName; partial-name ambiguity does NOT match —
|
|
712
725
|
// rationalisation is a separate agent-mediated concern).
|
|
713
|
-
|
|
726
|
+
// Task 850 — Neo4j-leg failure now returns is_error: true with the
|
|
727
|
+
// [admin-auth-store] line; previously it set a soft warning string and
|
|
728
|
+
// returned success, which is what hid the Adam Mackay incident from
|
|
729
|
+
// the recovery agent. The user is still functional via users.json +
|
|
730
|
+
// account.json, but the graph state is divergent and the operator
|
|
731
|
+
// must know.
|
|
714
732
|
let personReused = false;
|
|
715
733
|
try {
|
|
716
734
|
const session = getSession();
|
|
@@ -759,18 +777,25 @@ server.tool("admin-add", "Add a new admin user to this account. Creates a device
|
|
|
759
777
|
finally {
|
|
760
778
|
await session.close();
|
|
761
779
|
}
|
|
780
|
+
console.error(`[admin-auth-store] action=add userId=${userIdShort} result=ok store=neo4j personReused=${personReused}`);
|
|
762
781
|
}
|
|
763
782
|
catch (err) {
|
|
764
783
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
765
|
-
console.error(
|
|
766
|
-
|
|
784
|
+
console.error(`[admin-auth-store] action=add userId=${userIdShort} result=fail store=neo4j error=${errMsg}`);
|
|
785
|
+
return {
|
|
786
|
+
content: [{
|
|
787
|
+
type: "text",
|
|
788
|
+
text: `${TAG} users.json + account.json updated for userId ${userId} (PIN: ${plaintextPin}); Neo4j sync FAILED — manual reconciliation needed: ${errMsg}`,
|
|
789
|
+
}],
|
|
790
|
+
isError: true,
|
|
791
|
+
};
|
|
767
792
|
}
|
|
768
|
-
console.error(`${TAG} [admin-identity] adminuser-bound userId=${
|
|
793
|
+
console.error(`${TAG} [admin-identity] adminuser-bound userId=${userIdShort} name=${name.trim()} personReused=${personReused}`);
|
|
769
794
|
console.error(`${TAG} admin added: userId=${userId} userName=${name.trim()} accountId=${ACCOUNT_ID} role=admin addedBy=${callerUserId ?? "unknown"}`);
|
|
770
795
|
return {
|
|
771
796
|
content: [{
|
|
772
797
|
type: "text",
|
|
773
|
-
text: `Admin added successfully.\n\n- **Name:** ${name.trim()}\n- **userId:** ${userId}\n- **PIN:** ${plaintextPin}\n- **Role:** admin\n\nShare the PIN with ${name.trim()} so they can log in
|
|
798
|
+
text: `Admin added successfully.\n\n- **Name:** ${name.trim()}\n- **userId:** ${userId}\n- **PIN:** ${plaintextPin}\n- **Role:** admin\n\nShare the PIN with ${name.trim()} so they can log in.`,
|
|
774
799
|
}],
|
|
775
800
|
};
|
|
776
801
|
});
|
|
@@ -912,18 +937,22 @@ server.tool("admin-update-pin", "Update an existing admin user's PIN. Defaults t
|
|
|
912
937
|
users = readUsersJson();
|
|
913
938
|
}
|
|
914
939
|
catch (err) {
|
|
940
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
915
941
|
console.error(`${TAG} userId=${userIdLabel} result=user-not-found reason=users-json-read-failed`);
|
|
916
|
-
|
|
942
|
+
console.error(`[admin-auth-store] action=update-pin userId=${userIdLabel} result=fail store=users error=${errMsg}`);
|
|
943
|
+
return { content: [{ type: "text", text: `${TAG} ${errMsg}` }], isError: true };
|
|
917
944
|
}
|
|
918
945
|
const targetIndex = users.findIndex(u => u.userId === userId);
|
|
919
946
|
if (targetIndex === -1) {
|
|
920
947
|
console.error(`${TAG} userId=${userIdLabel} result=user-not-found`);
|
|
948
|
+
console.error(`[admin-auth-store] action=update-pin userId=${userIdLabel} result=fail store=users error=user-not-found`);
|
|
921
949
|
return { content: [{ type: "text", text: `${TAG} User ${userId} not found in users.json.` }], isError: true };
|
|
922
950
|
}
|
|
923
951
|
const newHash = hashPin(newPin);
|
|
924
952
|
const collidesWithOther = users.some((u, i) => i !== targetIndex && u.pin === newHash);
|
|
925
953
|
if (collidesWithOther) {
|
|
926
954
|
console.error(`${TAG} userId=${userIdLabel} result=collision`);
|
|
955
|
+
console.error(`[admin-auth-store] action=update-pin userId=${userIdLabel} result=fail store=users error=pin-collision`);
|
|
927
956
|
return { content: [{ type: "text", text: `${TAG} That PIN is already in use by another user. Choose a different PIN.` }], isError: true };
|
|
928
957
|
}
|
|
929
958
|
users[targetIndex].pin = newHash;
|
|
@@ -931,9 +960,12 @@ server.tool("admin-update-pin", "Update an existing admin user's PIN. Defaults t
|
|
|
931
960
|
writeUsersJson(users);
|
|
932
961
|
}
|
|
933
962
|
catch (err) {
|
|
934
|
-
|
|
963
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
964
|
+
console.error(`[admin-auth-store] action=update-pin userId=${userIdLabel} result=fail store=users error=${errMsg}`);
|
|
965
|
+
return { content: [{ type: "text", text: `${TAG} Failed to write users.json: ${errMsg}` }], isError: true };
|
|
935
966
|
}
|
|
936
967
|
console.error(`${TAG} userId=${userIdLabel} result=ok`);
|
|
968
|
+
console.error(`[admin-auth-store] action=update-pin userId=${userIdLabel} result=ok store=users`);
|
|
937
969
|
const self = userId === callerUserId;
|
|
938
970
|
return {
|
|
939
971
|
content: [{ type: "text", text: `PIN updated${self ? "" : ` for userId ${userId}`}. The new PIN takes effect on the next login.` }],
|
|
@@ -1147,8 +1179,8 @@ server.tool("agent-list", "List all public (non-admin) agents with their full co
|
|
|
1147
1179
|
};
|
|
1148
1180
|
}
|
|
1149
1181
|
});
|
|
1150
|
-
server.tool("logs-read", "Read recent logs. Task 532: the stream logs (type=
|
|
1151
|
-
type: z.enum(["system", "session", "error", "heartbeat", "public", "server", "mcp", "vnc", "review"]).optional(),
|
|
1182
|
+
server.tool("logs-read", "Read recent logs. Task 532: the stream logs (type=agent-stream/error/session/public) are now per-conversation — pass `conversationId` to retrieve a single conversation's log from first [spawn] to final [process-exit]. type=agent-stream: per-conversation tool-use/tool-result archive (every `[tool-use]` and `[tool-result]` pair with full input + output JSON, plus raw Claude stream-json, agent events, and MCP server stderr via tee). USE THIS when investigating what an agent ACTUALLY did with its tools — server.log only carries `[persist] tool-call persisted` markers, not bodies. (`type=system` is a backwards-compatible alias for the same archive.) type=session: SSE events sent to client. type=error: Claude subprocess stderr (raw — NODE_DEBUG HTTP/NET/UNDICI traces land in agent-stream via the stream tee, not here). type=heartbeat: platform event dispatcher (check-due-events cron). type=public: public agent diagnostic log. type=server: platform server log. type=mcp: MCP server stderr (per-plugin raw). type=vnc: VNC browser viewer lifecycle. type=review: log-review detector decisions. sessionKey: grep legacy sessionKey-tagged lines across all logs (useful for pre-Task-532 artefacts). When conversationId is provided, reads the single per-conversation file for the requested type (or dumps all type files for that conversationId if type is omitted).", {
|
|
1183
|
+
type: z.enum(["agent-stream", "system", "session", "error", "heartbeat", "public", "server", "mcp", "vnc", "review"]).optional(),
|
|
1152
1184
|
lines: z.number().optional(),
|
|
1153
1185
|
sessionKey: z.string().optional(),
|
|
1154
1186
|
conversationId: z.string().optional(),
|
|
@@ -1181,16 +1213,17 @@ server.tool("logs-read", "Read recent logs. Task 532: the stream logs (type=syst
|
|
|
1181
1213
|
return { content: [{ type: "text", text: `Log directory does not exist: ${LOG_DIR}` }] };
|
|
1182
1214
|
}
|
|
1183
1215
|
const prefixMap = {
|
|
1184
|
-
|
|
1216
|
+
"agent-stream": "claude-agent-stream",
|
|
1217
|
+
system: "claude-agent-stream", // Task 850 — backwards-compatible alias for agent-stream
|
|
1185
1218
|
error: "claude-agent-stderr",
|
|
1186
1219
|
session: "sse-events",
|
|
1187
1220
|
public: "public-agent-stream",
|
|
1188
1221
|
};
|
|
1189
|
-
const resolvedType = type ?? "
|
|
1222
|
+
const resolvedType = type ?? "agent-stream";
|
|
1190
1223
|
const prefix = prefixMap[resolvedType];
|
|
1191
1224
|
if (!prefix) {
|
|
1192
1225
|
return {
|
|
1193
|
-
content: [{ type: "text", text: `type=${resolvedType} is not per-conversation. Valid per-conversation types:
|
|
1226
|
+
content: [{ type: "text", text: `type=${resolvedType} is not per-conversation. Valid per-conversation types: agent-stream, error, session, public. For platform-scoped types (server, vnc, review, heartbeat, mcp) omit conversationId.` }],
|
|
1194
1227
|
isError: true,
|
|
1195
1228
|
};
|
|
1196
1229
|
}
|
|
@@ -1234,7 +1267,8 @@ server.tool("logs-read", "Read recent logs. Task 532: the stream logs (type=syst
|
|
|
1234
1267
|
// --- Session-key filtered mode: grep across log files ---
|
|
1235
1268
|
if (sessionKey) {
|
|
1236
1269
|
const prefixes = {
|
|
1237
|
-
|
|
1270
|
+
"agent-stream": "claude-agent-stream-",
|
|
1271
|
+
system: "claude-agent-stream-", // Task 850 — backwards-compatible alias
|
|
1238
1272
|
error: "claude-agent-stderr-",
|
|
1239
1273
|
session: "sse-events-",
|
|
1240
1274
|
public: "public-agent-stream-",
|
|
@@ -1332,7 +1366,7 @@ server.tool("logs-read", "Read recent logs. Task 532: the stream logs (type=syst
|
|
|
1332
1366
|
return { content: [{ type: "text", text: `# Session timeline: ${sessionKey}\n\n${sections.join("\n\n")}` }] };
|
|
1333
1367
|
}
|
|
1334
1368
|
// --- Standard mode: tail most recent file of the requested type ---
|
|
1335
|
-
const resolvedType = type ?? "
|
|
1369
|
+
const resolvedType = type ?? "agent-stream";
|
|
1336
1370
|
// Heartbeat log is a single fixed file, not prefix-based
|
|
1337
1371
|
if (resolvedType === "heartbeat") {
|
|
1338
1372
|
const logFile = resolve(LOG_DIR, "check-due-events.log");
|
|
@@ -1381,6 +1415,8 @@ server.tool("logs-read", "Read recent logs. Task 532: the stream logs (type=syst
|
|
|
1381
1415
|
}).toString();
|
|
1382
1416
|
return { content: [{ type: "text", text: `# review.log\n\n${result}` }] };
|
|
1383
1417
|
}
|
|
1418
|
+
// Task 850 — agent-stream and the legacy system alias both map to
|
|
1419
|
+
// claude-agent-stream-. The fall-through default also covers them.
|
|
1384
1420
|
const prefix = resolvedType === "error" ? "claude-agent-stderr-"
|
|
1385
1421
|
: resolvedType === "session" ? "sse-events-"
|
|
1386
1422
|
: resolvedType === "public" ? "public-agent-stream-"
|