@rubytech/create-maxy 1.0.885 → 1.0.887
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/schema.cypher +7 -0
- package/payload/platform/plugins/admin/PLUGIN.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +8 -0
- package/payload/platform/scripts/__tests__/first-token-creates-stream-log.test.sh +37 -0
- package/payload/platform/scripts/__tests__/logs-read-prefix.sh +51 -0
- package/payload/platform/scripts/check-no-conversation-id-leaks.mjs +165 -0
- package/payload/platform/scripts/conversation-id-allowlist.txt +151 -0
- package/payload/platform/scripts/logs-read.sh +24 -4
- package/payload/platform/scripts/seed-neo4j.sh +46 -0
- package/payload/server/chunk-IFMZ5I3E.js +1460 -0
- package/payload/server/chunk-MOAY7KG2.js +11667 -0
- package/payload/server/client-pool-M6NS5G2U.js +34 -0
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/server.js +61 -19
package/package.json
CHANGED
|
@@ -498,9 +498,16 @@ OPTIONS {
|
|
|
498
498
|
CREATE CONSTRAINT message_id_unique IF NOT EXISTS
|
|
499
499
|
FOR (m:Message) REQUIRE m.messageId IS UNIQUE;
|
|
500
500
|
|
|
501
|
+
// Task 1007 — Message.sessionKey is the canonical conversation pointer.
|
|
502
|
+
// The legacy m.conversationId index stays until follow-up Task 1011 (final
|
|
503
|
+
// cutover, after the full code-rename slice lands and no path reads
|
|
504
|
+
// m.conversationId).
|
|
501
505
|
CREATE INDEX message_conversation IF NOT EXISTS
|
|
502
506
|
FOR (m:Message) ON (m.conversationId);
|
|
503
507
|
|
|
508
|
+
CREATE INDEX message_session_key IF NOT EXISTS
|
|
509
|
+
FOR (m:Message) ON (m.sessionKey);
|
|
510
|
+
|
|
504
511
|
CREATE VECTOR INDEX message_embedding IF NOT EXISTS
|
|
505
512
|
FOR (m:Message) ON (m.embedding)
|
|
506
513
|
OPTIONS {
|
|
@@ -52,7 +52,7 @@ Tools are available via the `admin` MCP server.
|
|
|
52
52
|
|
|
53
53
|
`logs-read { type: "agent-stream" }` is the canonical name for the per-conversation tool-use/tool-result archive previously called `system`; both names work and the legacy alias is preserved.
|
|
54
54
|
|
|
55
|
-
**Stream log is the primary diagnostic surface for an in-progress session.** Every stream log is named by sessionKey and exists from the first token. The parent-process console fan-out tee in [`platform/ui/app/lib/claude-agent/logging.ts`](../../ui/app/lib/claude-agent/logging.ts) appends every `[<tag>]`-prefixed `console.error` / `console.log` line to every active session's stream log alongside `server.log`. For diagnosing an in-session issue (WhatsApp inbound, Cloudflare action, persist write, baileys error), call `logs-read { sessionKey: "<…>" }` first — the stream log carries both the agent lifecycle AND the parent-process events that occurred during the session window. The `conversationId` parameter remains a legacy alias resolving to the same sessionKey-named file. `logs-read { type: "server" }` is the cross-session escape hatch for events outside any single session window.
|
|
55
|
+
**Stream log is the primary diagnostic surface for an in-progress session.** Every stream log is named by sessionKey and the stream-log file exists from the first token. The first-token invariant is bound by `platform/scripts/__tests__/first-token-creates-stream-log.test.sh`: one operator turn, one token, `claude-agent-stream-<sessionKey>.log` exists and contains the token bytes after the byte returns to the operator. The parent-process console fan-out tee in [`platform/ui/app/lib/claude-agent/logging.ts`](../../ui/app/lib/claude-agent/logging.ts) appends every `[<tag>]`-prefixed `console.error` / `console.log` line to every active session's stream log alongside `server.log`. For diagnosing an in-session issue (WhatsApp inbound, Cloudflare action, persist write, baileys error), call `logs-read { sessionKey: "<…>" }` first — the stream log carries both the agent lifecycle AND the parent-process events that occurred during the session window. The `conversationId` parameter remains a legacy alias resolving to the same sessionKey-named file. `logs-read { type: "server" }` is the cross-session escape hatch for events outside any single session window.
|
|
56
56
|
|
|
57
57
|
## Skills
|
|
58
58
|
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Troubleshooting
|
|
2
2
|
|
|
3
|
+
## Stream-log file for a fresh session is absent or empty
|
|
4
|
+
|
|
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
|
+
|
|
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.
|
|
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).
|
|
10
|
+
|
|
3
11
|
## Browser navigation to a local file (`file://`) used to time out for two minutes
|
|
4
12
|
|
|
5
13
|
**Symptom:** Older versions of the platform's admin agent would attempt `browser_navigate file:///path/to.html`, hit Playwright's silent two-minute timeout, then guess fixed ports (8080 / 3000 / 8000 / 9000) and report `ERR_CONNECTION_REFUSED` for each before someone manually started a local HTTP server.
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Task 1011 — the binding test for the first-token invariant.
|
|
3
|
+
#
|
|
4
|
+
# Invariant: for every new session, at the moment the first token is
|
|
5
|
+
# emitted, the stream-log file for that session exists on disk and contains
|
|
6
|
+
# that token's bytes. Tasks 1006/1008/1010 each shipped a partial slice;
|
|
7
|
+
# this test fixes the invariant itself to one assertion.
|
|
8
|
+
#
|
|
9
|
+
# The .test.sh entrypoint is the operator-facing surface — runnable from a
|
|
10
|
+
# bash prompt, from boot smoke, and from any CI runner that has `node` on
|
|
11
|
+
# PATH. The contract assertions live in the vitest spec at:
|
|
12
|
+
# platform/ui/server/routes/admin/__tests__/first-token-creates-stream-log.test.ts
|
|
13
|
+
#
|
|
14
|
+
# That spec exercises the live chat-route handler, the live appendFileSync
|
|
15
|
+
# writer, and a live filesystem (mkdtempSync tmpdir). The only seams
|
|
16
|
+
# substituted are upstream of the writer: the agent SDK invocation, the
|
|
17
|
+
# session-store identity lookups, and the Neo4j createConversation
|
|
18
|
+
# roundtrip. None of those are the writer the invariant binds.
|
|
19
|
+
#
|
|
20
|
+
# Exit codes:
|
|
21
|
+
# 0 test passed — file present, token bytes present
|
|
22
|
+
# 1 test failed — file absent OR token bytes absent
|
|
23
|
+
# 2 vitest binary not resolvable from platform/ui
|
|
24
|
+
|
|
25
|
+
set -euo pipefail
|
|
26
|
+
|
|
27
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
28
|
+
UI_DIR="$(cd "$SCRIPT_DIR/../../ui" && pwd)"
|
|
29
|
+
SPEC_REL="server/routes/admin/__tests__/first-token-creates-stream-log.test.ts"
|
|
30
|
+
|
|
31
|
+
if [[ ! -f "$UI_DIR/$SPEC_REL" ]]; then
|
|
32
|
+
echo "FATAL: vitest spec missing at $UI_DIR/$SPEC_REL" >&2
|
|
33
|
+
exit 2
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
cd "$UI_DIR"
|
|
37
|
+
exec npx --no-install vitest run "$SPEC_REL" --reporter=verbose
|
|
@@ -172,12 +172,63 @@ case_every_per_session_type() {
|
|
|
172
172
|
return 0
|
|
173
173
|
}
|
|
174
174
|
|
|
175
|
+
# Task 1010 — sk_<hex16> writer-side format from Task 1008 must resolve via
|
|
176
|
+
# the underscore-bearing regex `^[a-zA-Z0-9_-]+$`. Pre-1010 the regex
|
|
177
|
+
# rejected `_`, leaving every post-1008 stream log unretrievable.
|
|
178
|
+
case_sk_underscore_prefix_resolves() {
|
|
179
|
+
local root="/tmp/maxy-prefix-test-7-$$"
|
|
180
|
+
local script
|
|
181
|
+
script=$(setup_install_tree "$root")
|
|
182
|
+
local logdir="$root/data/accounts/acct-test/logs"
|
|
183
|
+
local sk="sk_16e0eeb035cf52ef"
|
|
184
|
+
echo "sentinel-sk" > "$logdir/claude-agent-stream-${sk}.log"
|
|
185
|
+
|
|
186
|
+
local rc=0
|
|
187
|
+
local stdout
|
|
188
|
+
stdout=$("$script" "sk_16e0e" agent-stream 2>/dev/null) || rc=$?
|
|
189
|
+
cleanup_install_tree "$root"
|
|
190
|
+
|
|
191
|
+
[[ $rc -eq 0 ]] || { echo " expected exit 0, got $rc"; return 1; }
|
|
192
|
+
[[ "$stdout" == *"sentinel-sk"* ]] || { echo " missing sentinel"; return 1; }
|
|
193
|
+
return 0
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Task 1010 — shell metacharacters must still be rejected. The regex widen
|
|
197
|
+
# adds `_` only; `;`, `*`, `$`, `?`, `[` etc. must still hit the reject path
|
|
198
|
+
# and exit 2, otherwise user input would escape literal matching in the glob.
|
|
199
|
+
# Also asserts the `[logs-read] regex-rejected` observability line lands in
|
|
200
|
+
# server.log so the adherence runner can see writer/reader divergence.
|
|
201
|
+
case_shell_metachar_still_rejected() {
|
|
202
|
+
local root="/tmp/maxy-prefix-test-8-$$"
|
|
203
|
+
local script
|
|
204
|
+
script=$(setup_install_tree "$root")
|
|
205
|
+
local server_log="$HOME/.$(basename "$root")/logs/server.log"
|
|
206
|
+
: > "$server_log"
|
|
207
|
+
|
|
208
|
+
local rc=0
|
|
209
|
+
"$script" "abc;rm" agent-stream >/dev/null 2>/tmp/prefix-stderr-8-$$ || rc=$?
|
|
210
|
+
local stderr
|
|
211
|
+
stderr=$(cat /tmp/prefix-stderr-8-$$)
|
|
212
|
+
rm -f /tmp/prefix-stderr-8-$$
|
|
213
|
+
local server_log_contents
|
|
214
|
+
server_log_contents=$(cat "$server_log" 2>/dev/null || echo "")
|
|
215
|
+
cleanup_install_tree "$root"
|
|
216
|
+
|
|
217
|
+
[[ $rc -eq 2 ]] || { echo " expected exit 2, got $rc"; return 1; }
|
|
218
|
+
[[ "$stderr" == *"invalid characters"* ]] || { echo " stderr missing 'invalid characters': $stderr"; return 1; }
|
|
219
|
+
[[ "$server_log_contents" == *"[logs-read] regex-rejected"* ]] || { echo " server.log missing regex-rejected line: $server_log_contents"; return 1; }
|
|
220
|
+
[[ "$server_log_contents" == *"sample=;"* ]] || { echo " server.log missing sample=;: $server_log_contents"; return 1; }
|
|
221
|
+
return 0
|
|
222
|
+
}
|
|
223
|
+
|
|
175
224
|
run_case "8-char prefix resolves full sessionKey file" case_8char_prefix_resolves_full
|
|
176
225
|
run_case "ambiguous prefix refuses to pick" case_ambiguous_prefix_refuses
|
|
177
226
|
run_case "more-specific prefix disambiguates" case_more_specific_prefix_resolves
|
|
178
227
|
run_case "zero matches → file-not-found" case_zero_match_miss
|
|
179
228
|
run_case "full 36-char sessionKey resolves" case_full_sessionkey_resolves
|
|
180
229
|
run_case "every per-session type resolves" case_every_per_session_type
|
|
230
|
+
run_case "Task 1010: sk_ underscore prefix resolves" case_sk_underscore_prefix_resolves
|
|
231
|
+
run_case "Task 1010: shell metacharacter still rejected" case_shell_metachar_still_rejected
|
|
181
232
|
|
|
182
233
|
echo ""
|
|
183
234
|
echo "================================================"
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Task 1007 build-time gate: prevents NEW files from gaining a `conversationId` /
|
|
3
|
+
// `sessionId` reference while the 146-file rename slice is in progress.
|
|
4
|
+
//
|
|
5
|
+
// The legacy identifiers are being collapsed into the single canonical
|
|
6
|
+
// `sessionKey`. This sprint shipped the schema-side load-bearing pieces
|
|
7
|
+
// (backfill, new index, boot-time adherence probe) plus the core lib rename.
|
|
8
|
+
// The mechanical tail (routes, MCP plugin parameters, UI props, WhatsApp,
|
|
9
|
+
// tests, specialist templates) is split across follow-up tasks 1009 and 1010.
|
|
10
|
+
//
|
|
11
|
+
// During the transition window, every file currently containing the legacy
|
|
12
|
+
// identifiers is named in `conversation-id-allowlist.txt`. Each follow-up
|
|
13
|
+
// rename PR removes file paths from the allowlist as those files are migrated.
|
|
14
|
+
// This gate fails if:
|
|
15
|
+
// (a) a file NOT in the allowlist contains any of the four legacy tokens, or
|
|
16
|
+
// (b) the allowlist references a path that no longer exists (stale entry).
|
|
17
|
+
//
|
|
18
|
+
// Once tasks 1009 and 1010 land, the allowlist is empty; the gate then enforces
|
|
19
|
+
// the task's outcome contract (zero hits anywhere outside .tasks/archive/).
|
|
20
|
+
|
|
21
|
+
import { readFileSync, readdirSync, statSync, existsSync } from 'node:fs'
|
|
22
|
+
import { dirname, join, relative, 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
|
+
const ALLOWLIST_PATH = join(SCRIPT_DIR, 'conversation-id-allowlist.txt')
|
|
28
|
+
|
|
29
|
+
const SCAN_ROOTS = [
|
|
30
|
+
join(REPO_ROOT, 'platform'),
|
|
31
|
+
join(REPO_ROOT, 'packages'),
|
|
32
|
+
join(REPO_ROOT, 'brands'),
|
|
33
|
+
join(REPO_ROOT, '.docs'),
|
|
34
|
+
join(REPO_ROOT, '.claude', 'skills'),
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
const ALLOWED_EXTS = new Set([
|
|
38
|
+
'.sh', '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
|
|
39
|
+
'.md', '.cypher', '.json',
|
|
40
|
+
])
|
|
41
|
+
const SKIP_DIR_NAMES = new Set([
|
|
42
|
+
'node_modules', 'dist', 'build', '.next', 'payload',
|
|
43
|
+
])
|
|
44
|
+
const SKIP_PATH_FRAGMENTS = ['.tasks/archive/']
|
|
45
|
+
|
|
46
|
+
// Whole-word matches only — avoids hits inside longer identifiers like
|
|
47
|
+
// `agentSessionId` (Claude Agent SDK per-process session, intentionally kept)
|
|
48
|
+
// and `conversationIdentity` (ConversationArchive hash key, unrelated).
|
|
49
|
+
const LEGACY_TOKEN_RE = /\b(?:conversationId|sessionId|CONVERSATION_ID|SESSION_ID)\b/
|
|
50
|
+
|
|
51
|
+
// Comment-line skip — matches the convention in check-no-task-id-leaks.mjs.
|
|
52
|
+
// Comments that *describe* the legacy field (inside the migration code itself,
|
|
53
|
+
// or inline docstrings explaining the rename) are not new executing code
|
|
54
|
+
// introducing the token, so they don't trip the gate.
|
|
55
|
+
function isCommentLine(line) {
|
|
56
|
+
const trimmed = line.replace(/^\s+/, '')
|
|
57
|
+
if (trimmed.startsWith('#')) return true
|
|
58
|
+
if (trimmed.startsWith('//')) return true
|
|
59
|
+
if (trimmed.startsWith('/*')) return true
|
|
60
|
+
if (trimmed.startsWith('*')) return true
|
|
61
|
+
if (trimmed.startsWith('{/*')) return true
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function loadAllowlist() {
|
|
66
|
+
if (!existsSync(ALLOWLIST_PATH)) return new Set()
|
|
67
|
+
const text = readFileSync(ALLOWLIST_PATH, 'utf-8')
|
|
68
|
+
return new Set(
|
|
69
|
+
text.split('\n')
|
|
70
|
+
.map(l => l.trim())
|
|
71
|
+
.filter(l => l && !l.startsWith('#'))
|
|
72
|
+
)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function shouldScanFile(filePath) {
|
|
76
|
+
const name = basename(filePath)
|
|
77
|
+
const ext = name.includes('.') ? '.' + name.split('.').pop() : ''
|
|
78
|
+
if (!ALLOWED_EXTS.has(ext)) return false
|
|
79
|
+
const rel = relative(REPO_ROOT, filePath)
|
|
80
|
+
for (const frag of SKIP_PATH_FRAGMENTS) {
|
|
81
|
+
if (rel.includes(frag)) return false
|
|
82
|
+
}
|
|
83
|
+
return true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function* walk(root) {
|
|
87
|
+
let entries
|
|
88
|
+
try { entries = readdirSync(root) } catch { return }
|
|
89
|
+
for (const name of entries) {
|
|
90
|
+
if (SKIP_DIR_NAMES.has(name)) continue
|
|
91
|
+
const full = join(root, name)
|
|
92
|
+
let st
|
|
93
|
+
try { st = statSync(full) } catch { continue }
|
|
94
|
+
if (st.isDirectory()) {
|
|
95
|
+
yield* walk(full)
|
|
96
|
+
} else if (st.isFile()) {
|
|
97
|
+
yield full
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const allowlist = loadAllowlist()
|
|
103
|
+
const newLeaks = []
|
|
104
|
+
const hitFiles = new Set()
|
|
105
|
+
|
|
106
|
+
for (const root of SCAN_ROOTS) {
|
|
107
|
+
if (!existsSync(root)) continue
|
|
108
|
+
for (const file of walk(root)) {
|
|
109
|
+
if (!shouldScanFile(file)) continue
|
|
110
|
+
let content
|
|
111
|
+
try { content = readFileSync(file, 'utf-8') } catch { continue }
|
|
112
|
+
if (!LEGACY_TOKEN_RE.test(content)) continue
|
|
113
|
+
const rel = relative(REPO_ROOT, file)
|
|
114
|
+
const lines = content.split('\n')
|
|
115
|
+
// For non-allowlisted files: only flag hits in non-comment lines. For
|
|
116
|
+
// allowlisted files: any hit counts (so the allowlist accurately tracks
|
|
117
|
+
// the surface still using the legacy name, comments and code alike).
|
|
118
|
+
if (allowlist.has(rel)) {
|
|
119
|
+
hitFiles.add(rel)
|
|
120
|
+
continue
|
|
121
|
+
}
|
|
122
|
+
let firstLine = 0, firstText = ''
|
|
123
|
+
for (let i = 0; i < lines.length; i++) {
|
|
124
|
+
if (!LEGACY_TOKEN_RE.test(lines[i])) continue
|
|
125
|
+
if (isCommentLine(lines[i])) continue
|
|
126
|
+
firstLine = i + 1
|
|
127
|
+
firstText = lines[i].trim().slice(0, 120)
|
|
128
|
+
break
|
|
129
|
+
}
|
|
130
|
+
if (firstLine > 0) {
|
|
131
|
+
newLeaks.push({ file: rel, line: firstLine, content: firstText })
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Stale allowlist entries: files in the allowlist that no longer have a hit
|
|
137
|
+
// (renamed already, or no longer exist) — remove them.
|
|
138
|
+
const staleAllowlistEntries = [...allowlist].filter(p => !hitFiles.has(p))
|
|
139
|
+
|
|
140
|
+
let failed = false
|
|
141
|
+
|
|
142
|
+
if (newLeaks.length > 0) {
|
|
143
|
+
failed = true
|
|
144
|
+
console.error(`check-no-conversation-id-leaks: ${newLeaks.length} NEW file(s) introduced legacy identifier(s):`)
|
|
145
|
+
for (const leak of newLeaks) {
|
|
146
|
+
console.error(` ${leak.file}:${leak.line}: ${leak.content}`)
|
|
147
|
+
}
|
|
148
|
+
console.error('')
|
|
149
|
+
console.error('The single canonical identifier is `sessionKey`. New code must use it.')
|
|
150
|
+
console.error('Legacy tokens: conversationId, sessionId, CONVERSATION_ID, SESSION_ID.')
|
|
151
|
+
console.error('Follow-up tasks 1009 + 1010 are migrating existing files off the allowlist.')
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (staleAllowlistEntries.length > 0) {
|
|
155
|
+
failed = true
|
|
156
|
+
console.error('')
|
|
157
|
+
console.error(`check-no-conversation-id-leaks: ${staleAllowlistEntries.length} stale allowlist entry(ies) — remove from platform/scripts/conversation-id-allowlist.txt:`)
|
|
158
|
+
for (const entry of staleAllowlistEntries) {
|
|
159
|
+
console.error(` ${entry}`)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (failed) process.exit(1)
|
|
164
|
+
|
|
165
|
+
console.error(`check-no-conversation-id-leaks: ok (${allowlist.size} files in transition allowlist; 0 new leaks)`)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
.docs/agents.md
|
|
2
|
+
.docs/conversation-archive.md
|
|
3
|
+
.docs/deployment.md
|
|
4
|
+
.docs/mcp-servers.md
|
|
5
|
+
.docs/neo4j.md
|
|
6
|
+
.docs/observability.md
|
|
7
|
+
.docs/platform.md
|
|
8
|
+
.docs/web-chat.md
|
|
9
|
+
.docs/whatsapp.md
|
|
10
|
+
platform/lib/graph-mcp/src/index.ts
|
|
11
|
+
platform/lib/graph-trash/src/index.ts
|
|
12
|
+
platform/lib/graph-write/src/__tests__/audit.test.ts
|
|
13
|
+
platform/lib/graph-write/src/audit.ts
|
|
14
|
+
platform/neo4j/schema.cypher
|
|
15
|
+
platform/plugins/admin/PLUGIN.md
|
|
16
|
+
platform/plugins/admin/hooks/__tests__/archive-ingest-surface-gate.test.sh
|
|
17
|
+
platform/plugins/admin/mcp/src/index.ts
|
|
18
|
+
platform/plugins/admin/skills/onboarding/SKILL.md
|
|
19
|
+
platform/plugins/admin/skills/stream-log-review/SKILL.md
|
|
20
|
+
platform/plugins/admin/skills/stream-log-review/references/analysis-patterns.md
|
|
21
|
+
platform/plugins/business-assistant/references/profiling.md
|
|
22
|
+
platform/plugins/contacts/mcp/src/index.ts
|
|
23
|
+
platform/plugins/contacts/mcp/src/tools/contact-erase.ts
|
|
24
|
+
platform/plugins/contacts/mcp/src/tools/contact-export.ts
|
|
25
|
+
platform/plugins/contacts/mcp/src/tools/group-create.ts
|
|
26
|
+
platform/plugins/contacts/mcp/src/tools/group-manage.ts
|
|
27
|
+
platform/plugins/docs/references/admin-session.md
|
|
28
|
+
platform/plugins/docs/references/contacts-guide.md
|
|
29
|
+
platform/plugins/docs/references/internals.md
|
|
30
|
+
platform/plugins/docs/references/platform.md
|
|
31
|
+
platform/plugins/docs/references/plugins-guide.md
|
|
32
|
+
platform/plugins/docs/references/troubleshooting.md
|
|
33
|
+
platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md
|
|
34
|
+
platform/plugins/linkedin-import/skills/linkedin-import/references/connections.md
|
|
35
|
+
platform/plugins/memory/bin/conversation-archive-ingest.mjs
|
|
36
|
+
platform/plugins/memory/bin/conversation-archive-ingest.sh
|
|
37
|
+
platform/plugins/memory/mcp/scripts/boot-smoke.sh
|
|
38
|
+
platform/plugins/memory/mcp/scripts/graph/fixture.cypher
|
|
39
|
+
platform/plugins/memory/mcp/src/index.ts
|
|
40
|
+
platform/plugins/memory/mcp/src/lib/graph-prune.ts
|
|
41
|
+
platform/plugins/memory/mcp/src/tools/conversation-archive-derive-insights.ts
|
|
42
|
+
platform/plugins/memory/mcp/src/tools/conversation-archive-enrich-rejection.ts
|
|
43
|
+
platform/plugins/memory/mcp/src/tools/conversation-memory-expunge.ts
|
|
44
|
+
platform/plugins/memory/mcp/src/tools/memory-archive-write.ts
|
|
45
|
+
platform/plugins/memory/mcp/src/tools/memory-find-candidates.ts
|
|
46
|
+
platform/plugins/memory/mcp/src/tools/memory-ingest.ts
|
|
47
|
+
platform/plugins/memory/mcp/src/tools/profile-update.ts
|
|
48
|
+
platform/plugins/memory/references/graph-primitives.md
|
|
49
|
+
platform/plugins/memory/references/schema-base.md
|
|
50
|
+
platform/plugins/memory/skills/conversation-archive/SKILL.md
|
|
51
|
+
platform/plugins/memory/skills/conversational-memory/SKILL.md
|
|
52
|
+
platform/plugins/memory/skills/document-ingest/SKILL.md
|
|
53
|
+
platform/plugins/scheduling/mcp/src/index.ts
|
|
54
|
+
platform/plugins/tasks/.mcp.json
|
|
55
|
+
platform/plugins/tasks/mcp/src/index.ts
|
|
56
|
+
platform/plugins/tasks/mcp/src/tools/project-create.ts
|
|
57
|
+
platform/plugins/tasks/mcp/src/tools/session-list.ts
|
|
58
|
+
platform/plugins/waitlist/mcp/src/index.ts
|
|
59
|
+
platform/plugins/waitlist/mcp/src/tools/__tests__/waitlist-persist.test.ts
|
|
60
|
+
platform/plugins/waitlist/mcp/src/tools/waitlist-heal.ts
|
|
61
|
+
platform/plugins/waitlist/mcp/src/tools/waitlist-list.ts
|
|
62
|
+
platform/plugins/waitlist/mcp/src/tools/waitlist-persist.ts
|
|
63
|
+
platform/plugins/waitlist/mcp/src/tools/waitlist-scan.ts
|
|
64
|
+
platform/plugins/waitlist/mcp/src/tools/waitlist-setup.ts
|
|
65
|
+
platform/plugins/whatsapp/PLUGIN.md
|
|
66
|
+
platform/plugins/whatsapp/mcp/src/index.ts
|
|
67
|
+
platform/plugins/workflows/mcp/src/index.ts
|
|
68
|
+
platform/scripts/__tests__/admin-persist-audit.test.ts
|
|
69
|
+
platform/scripts/admin-conversation-recover.mjs
|
|
70
|
+
platform/scripts/admin-persist-audit.ts
|
|
71
|
+
platform/scripts/check-no-conversation-id-leaks.mjs
|
|
72
|
+
platform/scripts/check-sdk-oauth.mjs
|
|
73
|
+
platform/scripts/component-knowledgedoc-backfill.ts
|
|
74
|
+
platform/scripts/seed-neo4j.sh
|
|
75
|
+
platform/templates/agents/admin/IDENTITY.md
|
|
76
|
+
platform/templates/specialists/agents/database-operator.md
|
|
77
|
+
platform/ui/__tests__/form-spawn-tailer.test.ts
|
|
78
|
+
platform/ui/app/AdminModals.tsx
|
|
79
|
+
platform/ui/app/ChatActionsMenu.tsx
|
|
80
|
+
platform/ui/app/ChatFooter.tsx
|
|
81
|
+
platform/ui/app/ConversationRowActions.tsx
|
|
82
|
+
platform/ui/app/Sidebar.tsx
|
|
83
|
+
platform/ui/app/__tests__/buildSessionLogsUrl.test.ts
|
|
84
|
+
platform/ui/app/admin/components/CloudflareSetupForm.tsx
|
|
85
|
+
platform/ui/app/graph/__tests__/display-helpers.test.ts
|
|
86
|
+
platform/ui/app/graph/__tests__/zoom-tier-colour-stability.test.ts
|
|
87
|
+
platform/ui/app/graph/display-helpers.ts
|
|
88
|
+
platform/ui/app/lib/__tests__/admin-conversation-create.test.ts
|
|
89
|
+
platform/ui/app/lib/__tests__/admin-persist-observability.test.ts
|
|
90
|
+
platform/ui/app/lib/__tests__/claude-agent-loop-stop.test.ts
|
|
91
|
+
platform/ui/app/lib/__tests__/conversation-deferred-persistence.test.ts
|
|
92
|
+
platform/ui/app/lib/__tests__/ensure-conversation-handled-by.test.ts
|
|
93
|
+
platform/ui/app/lib/__tests__/graph-trash-conversation-cascade.test.ts
|
|
94
|
+
platform/ui/app/lib/__tests__/jsonl-replay.test.ts
|
|
95
|
+
platform/ui/app/lib/__tests__/load-user-profile-sparse.test.ts
|
|
96
|
+
platform/ui/app/lib/__tests__/neo4j-store-autolabel.test.ts
|
|
97
|
+
platform/ui/app/lib/__tests__/neo4j-store-persist-message.test.ts
|
|
98
|
+
platform/ui/app/lib/admin-sse-registry.ts
|
|
99
|
+
platform/ui/app/lib/attachments.ts
|
|
100
|
+
platform/ui/app/lib/claude-agent/admin-agent.ts
|
|
101
|
+
platform/ui/app/lib/claude-agent/compaction.ts
|
|
102
|
+
platform/ui/app/lib/claude-agent/dispatcher.ts
|
|
103
|
+
platform/ui/app/lib/claude-agent/jsonl-replay.ts
|
|
104
|
+
platform/ui/app/lib/claude-agent/logging.ts
|
|
105
|
+
platform/ui/app/lib/claude-agent/memory-context.ts
|
|
106
|
+
platform/ui/app/lib/claude-agent/public-agent.ts
|
|
107
|
+
platform/ui/app/lib/claude-agent/session-store.ts
|
|
108
|
+
platform/ui/app/lib/claude-agent/spawn-env.ts
|
|
109
|
+
platform/ui/app/lib/claude-agent/stream-parser.ts
|
|
110
|
+
platform/ui/app/lib/client-event.ts
|
|
111
|
+
platform/ui/app/lib/cloudflare-setup-types.ts
|
|
112
|
+
platform/ui/app/lib/cloudflare-task-tracker.ts
|
|
113
|
+
platform/ui/app/lib/logs-read-resolve.ts
|
|
114
|
+
platform/ui/app/lib/neo4j-store.ts
|
|
115
|
+
platform/ui/app/lib/paths.ts
|
|
116
|
+
platform/ui/app/lib/useConversationRename.ts
|
|
117
|
+
platform/ui/app/lib/useLogsPopover.ts
|
|
118
|
+
platform/ui/app/lib/vnc.ts
|
|
119
|
+
platform/ui/app/lib/whatsapp/__tests__/ensure-conversation.test.ts
|
|
120
|
+
platform/ui/app/lib/whatsapp/__tests__/persist-message.test.ts
|
|
121
|
+
platform/ui/app/lib/whatsapp/ensure-conversation.ts
|
|
122
|
+
platform/ui/app/lib/whatsapp/manager.ts
|
|
123
|
+
platform/ui/app/lib/whatsapp/persist-message.ts
|
|
124
|
+
platform/ui/app/page.tsx
|
|
125
|
+
platform/ui/app/public/MessageList.tsx
|
|
126
|
+
platform/ui/app/public/page.tsx
|
|
127
|
+
platform/ui/app/public/types.ts
|
|
128
|
+
platform/ui/app/public/useSession.ts
|
|
129
|
+
platform/ui/app/useAdminAuth.ts
|
|
130
|
+
platform/ui/app/useAdminChat.ts
|
|
131
|
+
platform/ui/server/index.ts
|
|
132
|
+
platform/ui/server/routes/__tests__/whatsapp-conversation-graph-state.test.ts
|
|
133
|
+
platform/ui/server/routes/_helpers.ts
|
|
134
|
+
platform/ui/server/routes/admin/__tests__/browser-iframe-route.test.ts
|
|
135
|
+
platform/ui/server/routes/admin/__tests__/chat-stream-log-path.test.ts
|
|
136
|
+
platform/ui/server/routes/admin/__tests__/cloudflare-tunnels-zero.test.ts
|
|
137
|
+
platform/ui/server/routes/admin/__tests__/graph-search.test.ts
|
|
138
|
+
platform/ui/server/routes/admin/__tests__/graph-subgraph.test.ts
|
|
139
|
+
platform/ui/server/routes/admin/__tests__/logs.test.ts
|
|
140
|
+
platform/ui/server/routes/admin/__tests__/sessions.test.ts
|
|
141
|
+
platform/ui/server/routes/admin/browser-iframe.ts
|
|
142
|
+
platform/ui/server/routes/admin/chat-failure.ts
|
|
143
|
+
platform/ui/server/routes/admin/chat.ts
|
|
144
|
+
platform/ui/server/routes/admin/cloudflare.ts
|
|
145
|
+
platform/ui/server/routes/admin/graph-subgraph.ts
|
|
146
|
+
platform/ui/server/routes/admin/logs.ts
|
|
147
|
+
platform/ui/server/routes/admin/session.ts
|
|
148
|
+
platform/ui/server/routes/admin/sessions.ts
|
|
149
|
+
platform/ui/server/routes/group.ts
|
|
150
|
+
platform/ui/server/routes/session.ts
|
|
151
|
+
platform/ui/server/routes/whatsapp.ts
|
|
@@ -118,6 +118,16 @@ is_per_conversation_type() {
|
|
|
118
118
|
SEARCH_TYPES="agent-stream error session public server mcp vnc"
|
|
119
119
|
VALID_TYPES="agent-stream, system, session, error, heartbeat, public, server, mcp, vnc"
|
|
120
120
|
|
|
121
|
+
# Authoritative sessionKey character set.
|
|
122
|
+
# Writer-side (Task 1008) emits `sk_<hex16>` filenames; the underscore must
|
|
123
|
+
# stay in this regex or every post-1008 file becomes unretrievable
|
|
124
|
+
# (Task 1010). UUID v4 sessionKeys (legacy and current alternate format)
|
|
125
|
+
# also pass. Widening past this set is unsafe: session_key is concatenated
|
|
126
|
+
# into a glob below (`${prefix}${session_key}*.log`), so any character bash
|
|
127
|
+
# treats as a metacharacter (`*`, `?`, `[`, `;`, `$`, etc.) would escape
|
|
128
|
+
# literal matching.
|
|
129
|
+
SESSIONKEY_REGEX='^[a-zA-Z0-9_-]+$'
|
|
130
|
+
|
|
121
131
|
usage() {
|
|
122
132
|
cat >&2 <<EOF
|
|
123
133
|
Usage:
|
|
@@ -172,10 +182,20 @@ per_conversation_mode() {
|
|
|
172
182
|
fi
|
|
173
183
|
|
|
174
184
|
# Reject shell metacharacters in session_key — the prefix-match glob below
|
|
175
|
-
# would otherwise turn user input into a shell pattern. SessionKeys
|
|
176
|
-
#
|
|
177
|
-
|
|
178
|
-
|
|
185
|
+
# would otherwise turn user input into a shell pattern. SessionKeys are
|
|
186
|
+
# `sk_<hex16>` (Task 1008) or UUID v4; both fit $SESSIONKEY_REGEX defined
|
|
187
|
+
# near the top of the script. Task 1010 widened this from `[a-zA-Z0-9-]`
|
|
188
|
+
# after the Task 1008 underscore-bearing format made every post-1008
|
|
189
|
+
# session unretrievable.
|
|
190
|
+
if [[ ! "$session_key" =~ $SESSIONKEY_REGEX ]]; then
|
|
191
|
+
# Observability: surface the rejection before the operator-visible error
|
|
192
|
+
# so the adherence runner can prove writer/reader divergence.
|
|
193
|
+
# Best-effort append; mirrors the missing-on-resolve idiom below.
|
|
194
|
+
local rej_ts bad_char
|
|
195
|
+
rej_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
196
|
+
bad_char=$(printf '%s' "$session_key" | tr -d 'a-zA-Z0-9_-' | head -c 1)
|
|
197
|
+
echo "${rej_ts} [logs-read] regex-rejected sessionKey-prefix=${session_key:0:12} regex=${SESSIONKEY_REGEX} sample=${bad_char}" >> "$SERVER_LOG" 2>/dev/null || true
|
|
198
|
+
echo "Error: sessionKey contains invalid characters (allowed: a-z, A-Z, 0-9, _, -)" >&2
|
|
179
199
|
exit 2
|
|
180
200
|
fi
|
|
181
201
|
|
|
@@ -490,6 +490,52 @@ fi
|
|
|
490
490
|
|
|
491
491
|
echo "$SCHEMA_CYPHER" | "$CYPHER_SHELL" -u "$NEO4J_USER" -p "$NEO4J_PASSWORD" -a "$NEO4J_URI"
|
|
492
492
|
|
|
493
|
+
# ------------------------------------------------------------------
|
|
494
|
+
# Task 1007 — sessionKey backfill (idempotent, runs every seed pass).
|
|
495
|
+
#
|
|
496
|
+
# Conversation and Message rows minted before Task 985 carry only
|
|
497
|
+
# `conversationId`. The new canonical identifier is `sessionKey` (same
|
|
498
|
+
# UUID shape). This block copies conversationId → sessionKey on every
|
|
499
|
+
# row where sessionKey is still null, leaving rows that already carry
|
|
500
|
+
# sessionKey untouched. Re-runs are no-ops. The legacy
|
|
501
|
+
# `conversation_id_unique` constraint and `m.conversationId` index
|
|
502
|
+
# stay until the full code-rename slice lands and no path reads the
|
|
503
|
+
# legacy field; the drop happens in Task 1007's open completion scope.
|
|
504
|
+
# ------------------------------------------------------------------
|
|
505
|
+
echo "==> Backfilling sessionKey on :Conversation and :Message..."
|
|
506
|
+
BACKFILL_RESULT=$("$CYPHER_SHELL" -u "$NEO4J_USER" -p "$NEO4J_PASSWORD" -a "$NEO4J_URI" --format plain << 'BACKFILL_EOF'
|
|
507
|
+
MATCH (c:Conversation)
|
|
508
|
+
WHERE c.sessionKey IS NULL AND c.conversationId IS NOT NULL
|
|
509
|
+
SET c.sessionKey = c.conversationId
|
|
510
|
+
RETURN count(c) AS conversations_backfilled;
|
|
511
|
+
MATCH (m:Message)
|
|
512
|
+
WHERE m.sessionKey IS NULL AND m.conversationId IS NOT NULL
|
|
513
|
+
SET m.sessionKey = m.conversationId
|
|
514
|
+
RETURN count(m) AS messages_backfilled;
|
|
515
|
+
MATCH (c:Conversation) WHERE c.sessionKey IS NULL RETURN count(c) AS conversations_null_after;
|
|
516
|
+
MATCH (m:Message) WHERE m.sessionKey IS NULL RETURN count(m) AS messages_null_after;
|
|
517
|
+
BACKFILL_EOF
|
|
518
|
+
)
|
|
519
|
+
# Emit the migration-1007 adherence lines on stderr so they land in server.log
|
|
520
|
+
# the same as every other log line. Non-zero null counts after backfill are P0:
|
|
521
|
+
# they indicate orphan rows that carry neither identifier and need operator
|
|
522
|
+
# attention before the constraint flip in Task 1011.
|
|
523
|
+
echo "$BACKFILL_RESULT" | python3 -c "
|
|
524
|
+
import sys, re
|
|
525
|
+
rows = [r.strip() for r in sys.stdin.read().splitlines() if r.strip() and not r.strip().startswith(('conversations_', 'messages_'))]
|
|
526
|
+
nums = [int(r) for r in rows if re.fullmatch(r'\d+', r)]
|
|
527
|
+
if len(nums) >= 4:
|
|
528
|
+
conv_bf, msg_bf, conv_null, msg_null = nums[0], nums[1], nums[2], nums[3]
|
|
529
|
+
print(f'[migration-1007] conversation-row-backfill rows={conv_bf} outcome=ok', file=sys.stderr)
|
|
530
|
+
print(f'[migration-1007] message-row-backfill rows={msg_bf} outcome=ok', file=sys.stderr)
|
|
531
|
+
print(f'[migration-1007] adherence-check label=Conversation rows-with-null-sessionkey={conv_null}', file=sys.stderr)
|
|
532
|
+
print(f'[migration-1007] adherence-check label=Message rows-with-null-sessionkey={msg_null}', file=sys.stderr)
|
|
533
|
+
if conv_null > 0 or msg_null > 0:
|
|
534
|
+
print(f'[migration-1007] adherence-check P0=true reason=null-sessionkey-after-backfill', file=sys.stderr)
|
|
535
|
+
else:
|
|
536
|
+
print(f'[migration-1007] backfill output unparseable rows={len(nums)}', file=sys.stderr)
|
|
537
|
+
"
|
|
538
|
+
|
|
493
539
|
# ------------------------------------------------------------------
|
|
494
540
|
# 3. Create AdminUser node + ADMIN_OF relationship
|
|
495
541
|
# ------------------------------------------------------------------
|