@rubytech/create-maxy-code 0.1.148 → 0.1.151
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/lib/graph-style/dist/index.d.ts +5 -5
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +11 -9
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/index.ts +11 -9
- package/payload/platform/lib/graph-trash/dist/index.js +6 -6
- package/payload/platform/lib/graph-trash/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-trash/src/index.ts +6 -6
- package/payload/platform/neo4j/edge-annotations.json +4 -4
- package/payload/platform/neo4j/schema.cypher +107 -46
- package/payload/platform/plugins/admin/PLUGIN.md +1 -1
- package/payload/platform/plugins/admin/hooks/__tests__/{post-turn-graph-pass.test.sh → pre-turn-graph-pass.test.sh} +184 -112
- package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +2 -2
- package/payload/platform/plugins/admin/hooks/{post-turn-graph-pass.sh → pre-turn-graph-pass.sh} +83 -53
- package/payload/platform/plugins/docs/references/linkedin-extension.md +1 -1
- package/payload/platform/plugins/docs/references/platform.md +1 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +2 -2
- package/payload/platform/plugins/linkedin-import/skills/linkedin-import/SKILL.md +5 -5
- package/payload/platform/plugins/memory/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/memory/PLUGIN.md +4 -4
- package/payload/platform/plugins/memory/bin/conversation-archive-ingest.mjs +5 -5
- package/payload/platform/plugins/memory/bin/conversation-archive-ingest.sh +1 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +14 -14
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-citation-audit.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-orphans.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/dream-cycle/phase-orphans.js +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts +2 -2
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js +3 -4
- package/payload/platform/plugins/memory/mcp/dist/lib/llm-classifier.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js +6 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/typed-edge-schema.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js +2 -2
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/conversation-archive-derive-insights.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js +17 -17
- package/payload/platform/plugins/memory/mcp/dist/tools/__tests__/memory-ingest.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.d.ts +3 -3
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js +6 -6
- package/payload/platform/plugins/memory/mcp/dist/tools/conversation-archive-derive-insights.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +9 -3
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts +2 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-empty-trash.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts +6 -5
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js +30 -29
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-ingest.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js +4 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/memory-restore.js.map +1 -1
- package/payload/platform/plugins/memory/references/schema-base.md +2 -2
- package/payload/platform/plugins/memory/skills/conversation-archive/SKILL.md +12 -12
- package/payload/platform/plugins/scheduling/skills/daily-prep/SKILL.md +2 -2
- package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -1
- package/payload/platform/plugins/x-import/PLUGIN.md +3 -3
- package/payload/platform/plugins/x-import/skills/x-import/SKILL.md +7 -7
- package/payload/platform/scripts/seed-neo4j.sh +3 -3
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +22 -3
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +16 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +29 -3
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +7 -7
- package/payload/platform/templates/specialists/agents/database-operator.md +6 -6
- package/payload/platform/templates/specialists/agents/librarian.md +2 -2
- package/payload/server/public/assets/{admin-_pm0RRTE.js → admin-CnLGltuE.js} +1 -1
- package/payload/server/public/assets/{data-b6q64Ot4.js → data-MB2EWZUP.js} +1 -1
- package/payload/server/public/assets/{graph-D1SVlBLb.js → graph-B2FnDRz-.js} +1 -1
- package/payload/server/public/assets/graph-labels-DDYqDlu3.js +1 -0
- package/payload/server/public/assets/{page-DwFigpyR.js → page-BJKRa6Db.js} +1 -1
- package/payload/server/public/assets/{page-BV6E4J2q.js → page-Bx2p3W1k.js} +1 -1
- 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 +20 -18
- package/payload/server/public/assets/graph-labels-BiDGzVs2.js +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Task
|
|
2
|
+
# Task 425 — pre-turn-graph-pass UserPromptSubmit hook test suite.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
4
|
+
# Supersedes the Task 419 Stop-hook test suite. A local Python HTTP server
|
|
5
5
|
# stands in for the admin server:
|
|
6
6
|
# POST /api/admin/log-ingest → records every line emit_log sent.
|
|
7
7
|
# GET /api/admin/post-turn-context → returns the contents of $CTX_FILE,
|
|
@@ -10,30 +10,37 @@
|
|
|
10
10
|
# every POST body AND whether the GET was attempted at all.
|
|
11
11
|
#
|
|
12
12
|
# Contract under test:
|
|
13
|
-
# - Role/specialist gates: exit 0, single trigger-skipped log line,
|
|
14
|
-
#
|
|
15
|
-
# - MAXY_UI_INTERNAL_PORT unset: exit 0,
|
|
16
|
-
#
|
|
17
|
-
# /api/admin/log-ingest is unreachable;
|
|
18
|
-
# -
|
|
19
|
-
#
|
|
20
|
-
# - Dispatch path (admin + populated JSONL): exit
|
|
21
|
-
# `
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
13
|
+
# - Role/specialist gates: exit 0, single trigger-skipped log line, empty
|
|
14
|
+
# stdout (no additionalContext).
|
|
15
|
+
# - MAXY_UI_INTERNAL_PORT unset: exit 0, empty stdout. The hook emits its
|
|
16
|
+
# missing-env diagnostic on its own stderr channel because
|
|
17
|
+
# /api/admin/log-ingest is unreachable; loud-fail is acceptable.
|
|
18
|
+
# - Missing-transcript: exit 0, single trigger-skipped log line, empty
|
|
19
|
+
# stdout.
|
|
20
|
+
# - Dispatch path (admin + populated JSONL): exit 0, stdout is a JSON
|
|
21
|
+
# envelope `{"hookSpecificOutput":{"hookEventName":"UserPromptSubmit",
|
|
22
|
+
# "additionalContext":"..."}}` whose additionalContext begins with
|
|
23
|
+
# `[system: pre-turn-graph-pass-hook]` and contains `<conversation>` +
|
|
24
|
+
# `<prior-writes>` blocks, with conversationId interpolated from
|
|
25
|
+
# CONVERSATION_NODE_ID.
|
|
26
|
+
# - Prior-writes fetch fails (context endpoint 500): exit 0, dispatch
|
|
25
27
|
# still fires, `<prior-writes>` block resolves to `(none)`.
|
|
26
|
-
# - CONVERSATION_NODE_ID unset: exit
|
|
28
|
+
# - CONVERSATION_NODE_ID unset: exit 0, additionalContext names
|
|
27
29
|
# `conversationId: <unset>`, and no GET to /api/admin/post-turn-context
|
|
28
30
|
# is attempted.
|
|
29
31
|
# - Multi-block assistant turn: conversation walk concatenates `text`
|
|
30
32
|
# blocks; `tool_use` blocks dropped.
|
|
31
33
|
# - User turn whose only content is `tool_result` blocks: skipped in
|
|
32
34
|
# the conversation walk.
|
|
35
|
+
# - UserPromptSubmit stdin-shape regression: a synthetic UserPromptSubmit
|
|
36
|
+
# payload produces the documented stdout envelope; a stale Stop-shaped
|
|
37
|
+
# payload (no hook_event_name, no prompt) also produces the
|
|
38
|
+
# UserPromptSubmit envelope because the hook keys on
|
|
39
|
+
# session_id + transcript_path, not on input shape sniffing.
|
|
33
40
|
|
|
34
41
|
set -u
|
|
35
42
|
|
|
36
|
-
HOOK="$(cd "$(dirname "$0")/.." && pwd)/
|
|
43
|
+
HOOK="$(cd "$(dirname "$0")/.." && pwd)/pre-turn-graph-pass.sh"
|
|
37
44
|
if [[ ! -x "$HOOK" ]]; then
|
|
38
45
|
echo "FAIL: $HOOK not executable" >&2
|
|
39
46
|
exit 1
|
|
@@ -160,15 +167,32 @@ context_get_count() {
|
|
|
160
167
|
grep -E '^GET\s+/api/admin/post-turn-context' "$REQ_LOG" 2>/dev/null | wc -l | tr -d ' '
|
|
161
168
|
}
|
|
162
169
|
|
|
170
|
+
additional_context() {
|
|
171
|
+
printf '%s' "$HOOK_STDOUT" | python3 -c '
|
|
172
|
+
import sys, json
|
|
173
|
+
try:
|
|
174
|
+
d = json.load(sys.stdin)
|
|
175
|
+
out = (d.get("hookSpecificOutput") or {}).get("additionalContext", "")
|
|
176
|
+
sys.stdout.write(out)
|
|
177
|
+
except Exception:
|
|
178
|
+
pass
|
|
179
|
+
'
|
|
180
|
+
}
|
|
181
|
+
|
|
163
182
|
envelope_for() {
|
|
164
183
|
python3 -c '
|
|
165
184
|
import json, sys
|
|
166
|
-
print(json.dumps({
|
|
185
|
+
print(json.dumps({
|
|
186
|
+
"session_id": sys.argv[1],
|
|
187
|
+
"transcript_path": sys.argv[2],
|
|
188
|
+
"cwd": "/tmp",
|
|
189
|
+
"hook_event_name": "UserPromptSubmit",
|
|
190
|
+
"prompt": "test prompt body",
|
|
191
|
+
}))
|
|
167
192
|
' "$1" "$2"
|
|
168
193
|
}
|
|
169
194
|
|
|
170
195
|
write_simple_transcript() {
|
|
171
|
-
# write_simple_transcript <outfile> <user_text> <assistant_text>
|
|
172
196
|
local out="$1"; shift
|
|
173
197
|
python3 - "$out" "$@" <<'PY'
|
|
174
198
|
import sys, json
|
|
@@ -179,12 +203,12 @@ while i + 1 < len(sys.argv):
|
|
|
179
203
|
u, a = sys.argv[i], sys.argv[i+1]
|
|
180
204
|
records.append({
|
|
181
205
|
"type": "user",
|
|
182
|
-
"timestamp": "2026-05-
|
|
206
|
+
"timestamp": "2026-05-26T10:00:00.000Z",
|
|
183
207
|
"message": {"role": "user", "content": u},
|
|
184
208
|
})
|
|
185
209
|
records.append({
|
|
186
210
|
"type": "assistant",
|
|
187
|
-
"timestamp": "2026-05-
|
|
211
|
+
"timestamp": "2026-05-26T10:00:01.000Z",
|
|
188
212
|
"message": {
|
|
189
213
|
"id": "msg_a",
|
|
190
214
|
"role": "assistant",
|
|
@@ -198,8 +222,10 @@ with open(out, "w", encoding="utf-8") as f:
|
|
|
198
222
|
PY
|
|
199
223
|
}
|
|
200
224
|
|
|
225
|
+
DIRECTIVE_TOKEN='[system: pre-turn-graph-pass-hook]'
|
|
226
|
+
|
|
201
227
|
# ---------------------------------------------------------------------------
|
|
202
|
-
# Case 1: role != admin → trigger-skipped reason=role-not-admin
|
|
228
|
+
# Case 1: role != admin → trigger-skipped reason=role-not-admin
|
|
203
229
|
# ---------------------------------------------------------------------------
|
|
204
230
|
start_listener
|
|
205
231
|
TRANSCRIPT_BASIC=$(mktemp); TMPFILES+=("$TRANSCRIPT_BASIC")
|
|
@@ -208,8 +234,8 @@ ENV_BASIC=$(envelope_for "$OP_ID" "$TRANSCRIPT_BASIC")
|
|
|
208
234
|
: > "$REQ_LOG"
|
|
209
235
|
run_hook "public" "" "$LISTENER_PORT" "$CONV_ID" "$ENV_BASIC"
|
|
210
236
|
[[ "$HOOK_RC" -eq 0 ]] || fail "case-1 rc=$HOOK_RC"
|
|
211
|
-
if
|
|
212
|
-
fail "case-1
|
|
237
|
+
if [[ -n "$HOOK_STDOUT" ]]; then
|
|
238
|
+
fail "case-1 stdout must be empty on role-not-admin, got: $HOOK_STDOUT"
|
|
213
239
|
fi
|
|
214
240
|
if ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=role-not-admin$"; then
|
|
215
241
|
pass "case-1 role=public → trigger-skipped reason=role-not-admin"
|
|
@@ -223,8 +249,8 @@ fi
|
|
|
223
249
|
: > "$REQ_LOG"
|
|
224
250
|
run_hook "admin" "database-operator" "$LISTENER_PORT" "$CONV_ID" "$ENV_BASIC"
|
|
225
251
|
[[ "$HOOK_RC" -eq 0 ]] || fail "case-2 rc=$HOOK_RC"
|
|
226
|
-
if
|
|
227
|
-
fail "case-2
|
|
252
|
+
if [[ -n "$HOOK_STDOUT" ]]; then
|
|
253
|
+
fail "case-2 stdout must be empty on is-specialist, got: $HOOK_STDOUT"
|
|
228
254
|
fi
|
|
229
255
|
if ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=is-specialist specialist=database-operator$"; then
|
|
230
256
|
pass "case-2 specialist=database-operator → trigger-skipped reason=is-specialist"
|
|
@@ -233,15 +259,13 @@ else
|
|
|
233
259
|
fi
|
|
234
260
|
|
|
235
261
|
# ---------------------------------------------------------------------------
|
|
236
|
-
# Case 3: MAXY_UI_INTERNAL_PORT unset → exit 0,
|
|
237
|
-
# The hook prints a missing-env line on its own stderr (loud-fail);
|
|
238
|
-
# that's expected — /log-ingest is unreachable without the port.
|
|
262
|
+
# Case 3: MAXY_UI_INTERNAL_PORT unset → exit 0, empty stdout, loud stderr.
|
|
239
263
|
# ---------------------------------------------------------------------------
|
|
240
264
|
: > "$REQ_LOG"
|
|
241
265
|
run_hook "admin" "" "" "$CONV_ID" "$ENV_BASIC"
|
|
242
266
|
[[ "$HOOK_RC" -eq 0 ]] || fail "case-3 rc=$HOOK_RC"
|
|
243
|
-
if
|
|
244
|
-
fail "case-3
|
|
267
|
+
if [[ -n "$HOOK_STDOUT" ]]; then
|
|
268
|
+
fail "case-3 stdout must be empty when MAXY_UI_INTERNAL_PORT unset"
|
|
245
269
|
fi
|
|
246
270
|
if [[ "$(ingest_lines | wc -l | tr -d ' ')" == "0" ]]; then
|
|
247
271
|
pass "case-3 missing MAXY_UI_INTERNAL_PORT → exit 0, no dispatch"
|
|
@@ -250,117 +274,112 @@ else
|
|
|
250
274
|
fi
|
|
251
275
|
|
|
252
276
|
# ---------------------------------------------------------------------------
|
|
253
|
-
# Case 4:
|
|
254
|
-
# ---------------------------------------------------------------------------
|
|
255
|
-
: > "$REQ_LOG"
|
|
256
|
-
run_hook "admin" "" "$LISTENER_PORT" "$CONV_ID" ""
|
|
257
|
-
[[ "$HOOK_RC" -eq 0 ]] || fail "case-4 rc=$HOOK_RC"
|
|
258
|
-
if printf '%s' "$HOOK_STDERR" | grep -q '\[system: post-turn-graph-pass-stop-hook\]'; then
|
|
259
|
-
fail "case-4 stderr must not contain dispatch directive"
|
|
260
|
-
fi
|
|
261
|
-
if ingest_lines | grep -qE '^trigger-skipped sessionId=unknown reason=empty-stdin$'; then
|
|
262
|
-
pass "case-4 empty stdin → trigger-skipped reason=empty-stdin"
|
|
263
|
-
else
|
|
264
|
-
fail "case-4 expected empty-stdin, got: $(ingest_lines)"
|
|
265
|
-
fi
|
|
266
|
-
|
|
267
|
-
# ---------------------------------------------------------------------------
|
|
268
|
-
# Case 5: transcript_path missing on disk → trigger-skipped reason=missing-transcript
|
|
277
|
+
# Case 4: transcript_path missing on disk → trigger-skipped reason=missing-transcript
|
|
269
278
|
# ---------------------------------------------------------------------------
|
|
270
|
-
MISSING_PATH="/tmp/
|
|
279
|
+
MISSING_PATH="/tmp/pre-turn-graph-pass-does-not-exist-$$.jsonl"
|
|
271
280
|
rm -f "$MISSING_PATH"
|
|
272
281
|
ENV_MISSING=$(envelope_for "$OP_ID" "$MISSING_PATH")
|
|
273
282
|
: > "$REQ_LOG"
|
|
274
283
|
run_hook "admin" "" "$LISTENER_PORT" "$CONV_ID" "$ENV_MISSING"
|
|
275
|
-
[[ "$HOOK_RC" -eq 0 ]] || fail "case-
|
|
276
|
-
if
|
|
277
|
-
fail "case-
|
|
284
|
+
[[ "$HOOK_RC" -eq 0 ]] || fail "case-4 rc=$HOOK_RC"
|
|
285
|
+
if [[ -n "$HOOK_STDOUT" ]]; then
|
|
286
|
+
fail "case-4 stdout must be empty on missing-transcript"
|
|
278
287
|
fi
|
|
279
288
|
if ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=missing-transcript$"; then
|
|
280
|
-
pass "case-
|
|
289
|
+
pass "case-4 missing transcript_path → trigger-skipped reason=missing-transcript"
|
|
281
290
|
else
|
|
282
|
-
fail "case-
|
|
291
|
+
fail "case-4 expected missing-transcript, got: $(ingest_lines)"
|
|
283
292
|
fi
|
|
284
293
|
|
|
285
294
|
# ---------------------------------------------------------------------------
|
|
286
|
-
# Case
|
|
287
|
-
#
|
|
295
|
+
# Case 5: Dispatch path → exit 0, UserPromptSubmit JSON envelope, blocks
|
|
296
|
+
# interpolated, dispatch log emitted.
|
|
288
297
|
# ---------------------------------------------------------------------------
|
|
289
298
|
cat > "$CTX_FILE" <<'JSON'
|
|
290
299
|
{"writes":[{"elementId":"4:abc:1","labels":["KnowledgeDocument"],"properties":{"title":"prior note"}}]}
|
|
291
300
|
JSON
|
|
292
301
|
: > "$REQ_LOG"
|
|
293
302
|
run_hook "admin" "" "$LISTENER_PORT" "$CONV_ID" "$ENV_BASIC"
|
|
294
|
-
[[ "$HOOK_RC" -eq
|
|
295
|
-
|
|
296
|
-
|
|
303
|
+
[[ "$HOOK_RC" -eq 0 ]] || fail "case-5 expected rc=0, got $HOOK_RC"
|
|
304
|
+
STDOUT_OK=$(printf '%s' "$HOOK_STDOUT" | python3 -c '
|
|
305
|
+
import sys, json
|
|
306
|
+
try:
|
|
307
|
+
d = json.load(sys.stdin)
|
|
308
|
+
out = d["hookSpecificOutput"]
|
|
309
|
+
assert out["hookEventName"] == "UserPromptSubmit"
|
|
310
|
+
assert isinstance(out["additionalContext"], str)
|
|
311
|
+
print("OK")
|
|
312
|
+
except Exception as e:
|
|
313
|
+
print(f"BAD: {e}")
|
|
314
|
+
')
|
|
315
|
+
if [[ "$STDOUT_OK" != "OK" ]]; then
|
|
316
|
+
fail "case-5 stdout envelope malformed: $STDOUT_OK"
|
|
297
317
|
fi
|
|
298
|
-
|
|
299
|
-
|
|
318
|
+
CTX_BODY=$(additional_context)
|
|
319
|
+
if ! printf '%s' "$CTX_BODY" | head -n 1 | grep -qF "$DIRECTIVE_TOKEN"; then
|
|
320
|
+
fail "case-5 additionalContext must begin with $DIRECTIVE_TOKEN"
|
|
300
321
|
fi
|
|
301
|
-
if ! printf '%s' "$
|
|
302
|
-
fail "case-
|
|
322
|
+
if ! printf '%s' "$CTX_BODY" | grep -q '<conversation>'; then
|
|
323
|
+
fail "case-5 additionalContext missing <conversation> block"
|
|
303
324
|
fi
|
|
304
|
-
if ! printf '%s' "$
|
|
305
|
-
fail "case-
|
|
325
|
+
if ! printf '%s' "$CTX_BODY" | grep -q '<prior-writes>'; then
|
|
326
|
+
fail "case-5 additionalContext missing <prior-writes> block"
|
|
306
327
|
fi
|
|
307
|
-
if ! printf '%s' "$
|
|
308
|
-
fail "case-
|
|
328
|
+
if ! printf '%s' "$CTX_BODY" | grep -q "conversationId: ${CONV_ID}"; then
|
|
329
|
+
fail "case-5 additionalContext must interpolate conversationId=${CONV_ID}"
|
|
309
330
|
fi
|
|
310
|
-
if ! printf '%s' "$
|
|
311
|
-
fail "case-
|
|
331
|
+
if ! printf '%s' "$CTX_BODY" | grep -q '\[user: hello\]'; then
|
|
332
|
+
fail "case-5 additionalContext must carry user turn '[user: hello]'"
|
|
312
333
|
fi
|
|
313
|
-
if ! printf '%s' "$
|
|
314
|
-
fail "case-
|
|
334
|
+
if ! printf '%s' "$CTX_BODY" | grep -q '\[assistant: hi there\]'; then
|
|
335
|
+
fail "case-5 additionalContext must carry assistant turn '[assistant: hi there]'"
|
|
336
|
+
fi
|
|
337
|
+
if ! printf '%s' "$CTX_BODY" | grep -q 'KnowledgeDocument'; then
|
|
338
|
+
fail "case-5 additionalContext <prior-writes> must include fetched labels"
|
|
315
339
|
fi
|
|
316
340
|
if ingest_lines | grep -qE "^dispatch sessionId=${OP_ID} conversationId=${CONV_ID} "; then
|
|
317
|
-
pass "case-
|
|
341
|
+
pass "case-5 dispatch path → exit 0 + JSON envelope with conversation, prior-writes, conversationId"
|
|
318
342
|
else
|
|
319
|
-
fail "case-
|
|
343
|
+
fail "case-5 expected dispatch log line, got: $(ingest_lines)"
|
|
320
344
|
fi
|
|
321
345
|
|
|
322
346
|
# ---------------------------------------------------------------------------
|
|
323
|
-
# Case
|
|
324
|
-
# still fires, <prior-writes> resolves to `(none)`.
|
|
347
|
+
# Case 6: Prior-writes fetch fails (500) → exit 0, prior-writes=(none).
|
|
325
348
|
# ---------------------------------------------------------------------------
|
|
326
349
|
: > "$CTX_FILE"
|
|
327
350
|
: > "$REQ_LOG"
|
|
328
351
|
run_hook "admin" "" "$LISTENER_PORT" "$CONV_ID" "$ENV_BASIC"
|
|
329
|
-
[[ "$HOOK_RC" -eq
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
fi
|
|
333
|
-
# Extract the prior-writes block and check it contains only `(none)`.
|
|
334
|
-
PRIOR_BODY=$(printf '%s' "$HOOK_STDERR" | awk '/<prior-writes>/{flag=1;next}/<\/prior-writes>/{flag=0}flag' | tr -d '[:space:]')
|
|
352
|
+
[[ "$HOOK_RC" -eq 0 ]] || fail "case-6 expected rc=0, got $HOOK_RC"
|
|
353
|
+
CTX_BODY=$(additional_context)
|
|
354
|
+
PRIOR_BODY=$(printf '%s' "$CTX_BODY" | awk '/<prior-writes>/{flag=1;next}/<\/prior-writes>/{flag=0}flag' | tr -d '[:space:]')
|
|
335
355
|
if [[ "$PRIOR_BODY" == "(none)" ]]; then
|
|
336
|
-
pass "case-
|
|
356
|
+
pass "case-6 context endpoint 500 → dispatch still fires, prior-writes = (none)"
|
|
337
357
|
else
|
|
338
|
-
fail "case-
|
|
358
|
+
fail "case-6 expected prior-writes=(none), got [$PRIOR_BODY]"
|
|
339
359
|
fi
|
|
340
360
|
|
|
341
361
|
# ---------------------------------------------------------------------------
|
|
342
|
-
# Case
|
|
343
|
-
# `conversationId: <unset>`, no GET to /api/admin/post-turn-context.
|
|
362
|
+
# Case 7: CONVERSATION_NODE_ID unset → conversationId:<unset>, no context GET.
|
|
344
363
|
# ---------------------------------------------------------------------------
|
|
345
364
|
cat > "$CTX_FILE" <<'JSON'
|
|
346
365
|
{"writes":[]}
|
|
347
366
|
JSON
|
|
348
367
|
: > "$REQ_LOG"
|
|
349
368
|
run_hook "admin" "" "$LISTENER_PORT" "" "$ENV_BASIC"
|
|
350
|
-
[[ "$HOOK_RC" -eq
|
|
351
|
-
|
|
352
|
-
|
|
369
|
+
[[ "$HOOK_RC" -eq 0 ]] || fail "case-7 expected rc=0, got $HOOK_RC"
|
|
370
|
+
CTX_BODY=$(additional_context)
|
|
371
|
+
if ! printf '%s' "$CTX_BODY" | grep -q 'conversationId: <unset>'; then
|
|
372
|
+
fail "case-7 additionalContext must contain 'conversationId: <unset>'"
|
|
353
373
|
fi
|
|
354
374
|
GET_COUNT=$(context_get_count)
|
|
355
375
|
if [[ "$GET_COUNT" == "0" ]]; then
|
|
356
|
-
pass "case-
|
|
376
|
+
pass "case-7 CONVERSATION_NODE_ID unset → no GET /api/admin/post-turn-context attempted"
|
|
357
377
|
else
|
|
358
|
-
fail "case-
|
|
378
|
+
fail "case-7 expected zero context GETs, got: $GET_COUNT"
|
|
359
379
|
fi
|
|
360
380
|
|
|
361
381
|
# ---------------------------------------------------------------------------
|
|
362
|
-
# Case
|
|
363
|
-
# blocks; tool_use blocks dropped.
|
|
382
|
+
# Case 8: Multi-block assistant turn → text concatenated, tool_use dropped.
|
|
364
383
|
# ---------------------------------------------------------------------------
|
|
365
384
|
T_MULTI=$(mktemp); TMPFILES+=("$T_MULTI")
|
|
366
385
|
python3 - "$T_MULTI" <<'PY'
|
|
@@ -369,12 +388,12 @@ out = sys.argv[1]
|
|
|
369
388
|
records = [
|
|
370
389
|
{
|
|
371
390
|
"type": "user",
|
|
372
|
-
"timestamp": "2026-05-
|
|
391
|
+
"timestamp": "2026-05-26T10:00:00.000Z",
|
|
373
392
|
"message": {"role": "user", "content": "go"},
|
|
374
393
|
},
|
|
375
394
|
{
|
|
376
395
|
"type": "assistant",
|
|
377
|
-
"timestamp": "2026-05-
|
|
396
|
+
"timestamp": "2026-05-26T10:00:01.000Z",
|
|
378
397
|
"message": {
|
|
379
398
|
"id": "msg_a",
|
|
380
399
|
"role": "assistant",
|
|
@@ -396,20 +415,20 @@ cat > "$CTX_FILE" <<'JSON'
|
|
|
396
415
|
JSON
|
|
397
416
|
: > "$REQ_LOG"
|
|
398
417
|
run_hook "admin" "" "$LISTENER_PORT" "$CONV_ID" "$ENV_MULTI"
|
|
399
|
-
[[ "$HOOK_RC" -eq
|
|
400
|
-
|
|
401
|
-
|
|
418
|
+
[[ "$HOOK_RC" -eq 0 ]] || fail "case-8 expected rc=0, got $HOOK_RC"
|
|
419
|
+
CTX_BODY=$(additional_context)
|
|
420
|
+
if ! printf '%s' "$CTX_BODY" | grep -q '\[assistant: part-one part-two\]'; then
|
|
421
|
+
fail "case-8 expected concatenated assistant text '[assistant: part-one part-two]'"
|
|
402
422
|
fi
|
|
403
|
-
if printf '%s' "$
|
|
404
|
-
fail "case-
|
|
423
|
+
if printf '%s' "$CTX_BODY" | grep -q '"tool_use"'; then
|
|
424
|
+
fail "case-8 tool_use block must not appear in additionalContext"
|
|
405
425
|
fi
|
|
406
|
-
if printf '%s' "$
|
|
407
|
-
pass "case-
|
|
426
|
+
if printf '%s' "$CTX_BODY" | grep -q '\[assistant: part-one part-two\]'; then
|
|
427
|
+
pass "case-8 multi-block assistant turn → text concatenated, tool_use dropped"
|
|
408
428
|
fi
|
|
409
429
|
|
|
410
430
|
# ---------------------------------------------------------------------------
|
|
411
|
-
# Case
|
|
412
|
-
# the conversation walk (no `[user: ...]` line for that record).
|
|
431
|
+
# Case 9: User turn with only tool_result blocks → skipped in conversation walk.
|
|
413
432
|
# ---------------------------------------------------------------------------
|
|
414
433
|
T_TRONLY=$(mktemp); TMPFILES+=("$T_TRONLY")
|
|
415
434
|
python3 - "$T_TRONLY" <<'PY'
|
|
@@ -418,12 +437,12 @@ out = sys.argv[1]
|
|
|
418
437
|
records = [
|
|
419
438
|
{
|
|
420
439
|
"type": "user",
|
|
421
|
-
"timestamp": "2026-05-
|
|
440
|
+
"timestamp": "2026-05-26T10:00:00.000Z",
|
|
422
441
|
"message": {"role": "user", "content": "real user turn"},
|
|
423
442
|
},
|
|
424
443
|
{
|
|
425
444
|
"type": "assistant",
|
|
426
|
-
"timestamp": "2026-05-
|
|
445
|
+
"timestamp": "2026-05-26T10:00:01.000Z",
|
|
427
446
|
"message": {
|
|
428
447
|
"id": "msg_a",
|
|
429
448
|
"role": "assistant",
|
|
@@ -432,7 +451,7 @@ records = [
|
|
|
432
451
|
},
|
|
433
452
|
{
|
|
434
453
|
"type": "user",
|
|
435
|
-
"timestamp": "2026-05-
|
|
454
|
+
"timestamp": "2026-05-26T10:00:02.000Z",
|
|
436
455
|
"message": {
|
|
437
456
|
"role": "user",
|
|
438
457
|
"content": [
|
|
@@ -448,25 +467,78 @@ PY
|
|
|
448
467
|
ENV_TRONLY=$(envelope_for "$OP_ID" "$T_TRONLY")
|
|
449
468
|
: > "$REQ_LOG"
|
|
450
469
|
run_hook "admin" "" "$LISTENER_PORT" "$CONV_ID" "$ENV_TRONLY"
|
|
451
|
-
[[ "$HOOK_RC" -eq
|
|
452
|
-
|
|
470
|
+
[[ "$HOOK_RC" -eq 0 ]] || fail "case-9 expected rc=0, got $HOOK_RC"
|
|
471
|
+
CTX_BODY=$(additional_context)
|
|
472
|
+
CONV_BLOCK=$(printf '%s' "$CTX_BODY" | awk '/<conversation>/{flag=1;next}/<\/conversation>/{flag=0}flag')
|
|
453
473
|
if ! printf '%s' "$CONV_BLOCK" | grep -q '\[user: real user turn\]'; then
|
|
454
|
-
fail "case-
|
|
474
|
+
fail "case-9 conversation block must include real user turn"
|
|
455
475
|
fi
|
|
456
476
|
if printf '%s' "$CONV_BLOCK" | grep -q 'tool plumbing payload'; then
|
|
457
|
-
fail "case-
|
|
477
|
+
fail "case-9 tool_result-only user record must not appear in conversation walk"
|
|
458
478
|
fi
|
|
459
|
-
# The heredoc indents the conversation block; match leading whitespace.
|
|
460
479
|
TR_LINES=$(printf '%s\n' "$CONV_BLOCK" | grep -cE '\[user:' || true)
|
|
461
480
|
if [[ "$TR_LINES" == "1" ]]; then
|
|
462
|
-
pass "case-
|
|
481
|
+
pass "case-9 tool_result-only user record skipped in conversation walk"
|
|
482
|
+
else
|
|
483
|
+
fail "case-9 expected exactly one [user: ...] line, got $TR_LINES"
|
|
484
|
+
fi
|
|
485
|
+
|
|
486
|
+
# ---------------------------------------------------------------------------
|
|
487
|
+
# Case 10: UserPromptSubmit shape — directive on stdout only, never stderr;
|
|
488
|
+
# stdout envelope has hookEventName="UserPromptSubmit".
|
|
489
|
+
# ---------------------------------------------------------------------------
|
|
490
|
+
cat > "$CTX_FILE" <<'JSON'
|
|
491
|
+
{"writes":[]}
|
|
492
|
+
JSON
|
|
493
|
+
: > "$REQ_LOG"
|
|
494
|
+
run_hook "admin" "" "$LISTENER_PORT" "$CONV_ID" "$ENV_BASIC"
|
|
495
|
+
[[ "$HOOK_RC" -eq 0 ]] || fail "case-10 expected rc=0, got $HOOK_RC"
|
|
496
|
+
if printf '%s' "$HOOK_STDERR" | grep -qF "$DIRECTIVE_TOKEN"; then
|
|
497
|
+
fail "case-10 directive must not appear on stderr (UserPromptSubmit contract is stdout JSON)"
|
|
498
|
+
fi
|
|
499
|
+
HEN=$(printf '%s' "$HOOK_STDOUT" | python3 -c '
|
|
500
|
+
import sys, json
|
|
501
|
+
try:
|
|
502
|
+
d = json.load(sys.stdin)
|
|
503
|
+
sys.stdout.write(d["hookSpecificOutput"]["hookEventName"])
|
|
504
|
+
except Exception:
|
|
505
|
+
pass
|
|
506
|
+
')
|
|
507
|
+
if [[ "$HEN" == "UserPromptSubmit" ]]; then
|
|
508
|
+
pass "case-10 stdout envelope is well-formed JSON with hookEventName=UserPromptSubmit"
|
|
509
|
+
else
|
|
510
|
+
fail "case-10 expected hookEventName=UserPromptSubmit, got [$HEN]"
|
|
511
|
+
fi
|
|
512
|
+
|
|
513
|
+
# ---------------------------------------------------------------------------
|
|
514
|
+
# Case 11: Stop-shaped stdin regression — a CC Stop-hook payload carries
|
|
515
|
+
# hook_event_name="Stop". The hook must not silently emit a
|
|
516
|
+
# UserPromptSubmit envelope on a Stop fire. Expect exit 0 with
|
|
517
|
+
# empty stdout and a `wrong-hook-event` skip line.
|
|
518
|
+
# ---------------------------------------------------------------------------
|
|
519
|
+
ENV_STOP_SHAPED=$(python3 -c '
|
|
520
|
+
import json, sys
|
|
521
|
+
print(json.dumps({
|
|
522
|
+
"session_id": sys.argv[1],
|
|
523
|
+
"transcript_path": sys.argv[2],
|
|
524
|
+
"hook_event_name": "Stop",
|
|
525
|
+
}))
|
|
526
|
+
' "$OP_ID" "$TRANSCRIPT_BASIC")
|
|
527
|
+
: > "$REQ_LOG"
|
|
528
|
+
run_hook "admin" "" "$LISTENER_PORT" "$CONV_ID" "$ENV_STOP_SHAPED"
|
|
529
|
+
[[ "$HOOK_RC" -eq 0 ]] || fail "case-11 expected rc=0, got $HOOK_RC"
|
|
530
|
+
if [[ -n "$HOOK_STDOUT" ]]; then
|
|
531
|
+
fail "case-11 stdout must be empty on wrong-hook-event Stop, got: $HOOK_STDOUT"
|
|
532
|
+
fi
|
|
533
|
+
if ingest_lines | grep -qE "^trigger-skipped sessionId=${OP_ID} reason=wrong-hook-event event=Stop$"; then
|
|
534
|
+
pass "case-11 Stop-shaped stdin rejected with wrong-hook-event skip"
|
|
463
535
|
else
|
|
464
|
-
fail "case-
|
|
536
|
+
fail "case-11 expected wrong-hook-event skip, got: $(ingest_lines)"
|
|
465
537
|
fi
|
|
466
538
|
|
|
467
539
|
# ---------------------------------------------------------------------------
|
|
468
540
|
# Summary
|
|
469
541
|
# ---------------------------------------------------------------------------
|
|
470
542
|
echo
|
|
471
|
-
echo "
|
|
543
|
+
echo "pre-turn-graph-pass tests: $PASS passed, $FAIL failed"
|
|
472
544
|
[[ "$FAIL" -eq 0 ]]
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# blocking the legacy MCP deviation tools mechanically. The retired
|
|
9
9
|
# `whatsapp-export-insight-pass` tool is replaced by Phase 2:
|
|
10
10
|
# `mcp__memory__conversation-archive-derive-insights` — a read-only tool that
|
|
11
|
-
# walks :Section chunks of one named
|
|
11
|
+
# walks :Section chunks of one named :ConversationArchive in
|
|
12
12
|
# pages and emits per-row proposals. The new tool is NOT in any BLOCK list
|
|
13
13
|
# (the gate is allow-by-default for unrecognised tools) — its writes go
|
|
14
14
|
# through the existing graph-cypher-write surface, gated by the operator per
|
|
@@ -154,7 +154,7 @@ fi
|
|
|
154
154
|
case "$TOOL_NAME" in
|
|
155
155
|
mcp__memory__whatsapp-export-parse|mcp__memory__whatsapp-export-insight-write|mcp__memory__whatsapp-export-insight-pass|mcp__memory__whatsapp-export-preview)
|
|
156
156
|
emit_decision "block" "denied-mcp-legacy" \
|
|
157
|
-
"Blocked: ${TOOL_NAME} is a retired path. generalised conversation-archive ingest — invoke 'bash platform/plugins/memory/bin/conversation-archive-ingest.sh <archive> --source whatsapp --owner-element-id <id> --participant-person-ids <csv> --scope <admin|public>' once. Normalise, sessionize, classify (mode=chat), and memory-ingest (writes :
|
|
157
|
+
"Blocked: ${TOOL_NAME} is a retired path. generalised conversation-archive ingest — invoke 'bash platform/plugins/memory/bin/conversation-archive-ingest.sh <archive> --source whatsapp --owner-element-id <id> --participant-person-ids <csv> --scope <admin|public>' once. Normalise, sessionize, classify (mode=chat), and memory-ingest (writes :ConversationArchive keyed on conversationIdentity, source=<enum>) all run in-process."
|
|
158
158
|
;;
|
|
159
159
|
esac
|
|
160
160
|
|