@rubytech/create-maxy 1.0.881 → 1.0.884

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.
Files changed (67) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/lib/graph-mcp/dist/index.js +45 -0
  3. package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
  4. package/payload/platform/lib/graph-mcp/src/index.ts +47 -0
  5. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js +57 -9
  6. package/payload/platform/lib/graph-write/dist/__tests__/action-provenance-gate.test.js.map +1 -1
  7. package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts +26 -0
  8. package/payload/platform/lib/graph-write/dist/conversation-provenance.d.ts.map +1 -0
  9. package/payload/platform/lib/graph-write/dist/conversation-provenance.js +81 -0
  10. package/payload/platform/lib/graph-write/dist/conversation-provenance.js.map +1 -0
  11. package/payload/platform/lib/graph-write/dist/index.d.ts +38 -16
  12. package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
  13. package/payload/platform/lib/graph-write/dist/index.js +75 -35
  14. package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
  15. package/payload/platform/lib/graph-write/src/__tests__/action-provenance-gate.test.ts +59 -9
  16. package/payload/platform/lib/graph-write/src/conversation-provenance.ts +140 -0
  17. package/payload/platform/lib/graph-write/src/index.ts +76 -35
  18. package/payload/platform/lib/mcp-eager/dist/index.d.ts +61 -0
  19. package/payload/platform/lib/mcp-eager/dist/index.d.ts.map +1 -0
  20. package/payload/platform/lib/mcp-eager/dist/index.js +49 -0
  21. package/payload/platform/lib/mcp-eager/dist/index.js.map +1 -0
  22. package/payload/platform/lib/mcp-eager/src/index.ts +78 -0
  23. package/payload/platform/lib/mcp-eager/tsconfig.json +8 -0
  24. package/payload/platform/package.json +2 -2
  25. package/payload/platform/plugins/admin/mcp/dist/__tests__/plugin-read-skill-resolution.test.js +26 -2
  26. package/payload/platform/plugins/admin/mcp/dist/__tests__/plugin-read-skill-resolution.test.js.map +1 -1
  27. package/payload/platform/plugins/admin/mcp/dist/index.js +36 -33
  28. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  29. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts +5 -1
  30. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.d.ts.map +1 -1
  31. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js +24 -8
  32. package/payload/platform/plugins/admin/mcp/dist/skill-resolution.js.map +1 -1
  33. package/payload/platform/plugins/contacts/PLUGIN.md +8 -0
  34. package/payload/platform/plugins/contacts/mcp/dist/index.js +10 -9
  35. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
  36. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.d.ts.map +1 -1
  37. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js +17 -2
  38. package/payload/platform/plugins/contacts/mcp/dist/tools/contact-create.js.map +1 -1
  39. package/payload/platform/plugins/docs/references/internals.md +15 -2
  40. package/payload/platform/plugins/docs/references/plugins-guide.md +2 -0
  41. package/payload/platform/plugins/email/mcp/dist/index.js +10 -9
  42. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
  43. package/payload/platform/plugins/memory/PLUGIN.md +5 -3
  44. package/payload/platform/plugins/memory/mcp/dist/index.js +10 -9
  45. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  46. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  47. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +18 -1
  48. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  49. package/payload/platform/plugins/scheduling/mcp/dist/index.js +9 -8
  50. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  51. package/payload/platform/plugins/tasks/mcp/dist/index.js +15 -14
  52. package/payload/platform/plugins/tasks/mcp/dist/index.js.map +1 -1
  53. package/payload/platform/plugins/telegram/mcp/dist/index.js +4 -3
  54. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  55. package/payload/platform/plugins/workflows/mcp/dist/index.js +9 -8
  56. package/payload/platform/plugins/workflows/mcp/dist/index.js.map +1 -1
  57. package/payload/platform/scripts/__tests__/logs-read-prefix.sh +341 -0
  58. package/payload/platform/scripts/logs-read.sh +108 -41
  59. package/payload/platform/scripts/logs-read.test.sh +6 -2
  60. package/payload/platform/templates/agents/admin/IDENTITY.md +1 -1
  61. package/payload/premium-plugins/real-agency/BUNDLE.md +1 -1
  62. package/payload/server/chunk-5PQU2HW2.js +11672 -0
  63. package/payload/server/chunk-ECAQVMRA.js +759 -0
  64. package/payload/server/chunk-K7S5T4VG.js +11534 -0
  65. package/payload/server/cloudflare-task-tracker-JNZXLW32.js +22 -0
  66. package/payload/server/maxy-edge.js +2 -2
  67. package/payload/server/server.js +38 -6
@@ -0,0 +1,341 @@
1
+ #!/usr/bin/env bash
2
+ # Task 998 — bash harness for logs-read.sh prefix-match resolution.
3
+ #
4
+ # Covers the contract from the task brief:
5
+ # 1. 8-char prefix → resolves full-UUID file (the original bug)
6
+ # 2. Multiple matches → reason=ambiguous-prefix, exit 1, never picks
7
+ # 3. More-specific prefix disambiguates
8
+ # 4. Zero matches → reason=file-not-found-in-either-shape with glob patterns
9
+ # 5. Full 36-char UUID still resolves (regression boundary)
10
+ # 6. Preflush prefix match
11
+ # 7. Preflush ambiguity (Pass 1 zero, Pass 2 multiple)
12
+ # 8. Every per-conversation type (agent-stream, session, error, public)
13
+ # resolves under prefix-match
14
+ #
15
+ # Companion to platform/scripts/logs-read.test.sh (Task 671's two-shape
16
+ # resolution harness).
17
+ set -euo pipefail
18
+
19
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
20
+ LOGS_READ="$SCRIPT_DIR/../logs-read.sh"
21
+ if [[ ! -x "$LOGS_READ" ]]; then
22
+ echo "FATAL: $LOGS_READ not executable" >&2
23
+ exit 1
24
+ fi
25
+
26
+ PASS=0
27
+ FAIL=0
28
+
29
+ # logs-read.sh resolves PLATFORM_ROOT from its own location; mirror the
30
+ # logs-read.test.sh convention by symlinking the script into an ephemeral
31
+ # install tree per case.
32
+ setup_install_tree() {
33
+ local root="$1"
34
+ mkdir -p "$root/platform/scripts"
35
+ mkdir -p "$root/data/accounts/acct-test/logs"
36
+ mkdir -p "$HOME/.$(basename "$root")/logs"
37
+ ln -sf "$LOGS_READ" "$root/platform/scripts/logs-read.sh"
38
+ echo "$root/platform/scripts/logs-read.sh"
39
+ }
40
+
41
+ cleanup_install_tree() {
42
+ local root="$1"
43
+ rm -rf "$root"
44
+ rm -rf "$HOME/.$(basename "$root")"
45
+ }
46
+
47
+ run_case() {
48
+ local name="$1"
49
+ shift
50
+ echo ""
51
+ echo "=== CASE: $name ==="
52
+ if "$@"; then
53
+ PASS=$((PASS + 1))
54
+ echo "PASS: $name"
55
+ else
56
+ FAIL=$((FAIL + 1))
57
+ echo "FAIL: $name"
58
+ fi
59
+ }
60
+
61
+ # --- Case 1: 8-char prefix resolves a full-UUID file (original bug) ---
62
+ case_prefix_resolves_full() {
63
+ local root="/tmp/maxy-logs-read-prefix-1-$$"
64
+ local script
65
+ script=$(setup_install_tree "$root")
66
+ local logdir="$root/data/accounts/acct-test/logs"
67
+ local full_uuid="3483269d-c793-4a07-98cc-556d936f2f4d"
68
+ local filename="claude-agent-stream-${full_uuid}.log"
69
+ echo "prefix-match-sentinel" > "$logdir/$filename"
70
+
71
+ local stdout stderr rc=0
72
+ stdout=$("$script" "3483269d" system 2>/tmp/logs-read-prefix-stderr-1-$$) || rc=$?
73
+ stderr=$(cat /tmp/logs-read-prefix-stderr-1-$$)
74
+ rm -f /tmp/logs-read-prefix-stderr-1-$$
75
+
76
+ cleanup_install_tree "$root"
77
+
78
+ if [[ $rc -ne 0 ]]; then echo " expected exit 0, got $rc"; return 1; fi
79
+ if [[ "$stdout" != *"prefix-match-sentinel"* ]]; then
80
+ echo " stdout missing sentinel: $stdout"; return 1
81
+ fi
82
+ if [[ "$stderr" != *"matched_shape=full"* ]]; then
83
+ echo " stderr missing matched_shape=full: $stderr"; return 1
84
+ fi
85
+ if [[ "$stdout" != *"$filename"* ]]; then
86
+ echo " header missing matched filename: $stdout"; return 1
87
+ fi
88
+ return 0
89
+ }
90
+
91
+ # --- Case 2: Multiple matches on Pass 1 → ambiguous-prefix, exit 1 ---
92
+ case_ambiguous_prefix() {
93
+ local root="/tmp/maxy-logs-read-prefix-2-$$"
94
+ local script
95
+ script=$(setup_install_tree "$root")
96
+ local logdir="$root/data/accounts/acct-test/logs"
97
+ echo "A" > "$logdir/claude-agent-stream-aaaaaaaa-1111-A.log"
98
+ echo "B" > "$logdir/claude-agent-stream-aaaaaaaa-2222-B.log"
99
+
100
+ local stdout stderr rc=0
101
+ stdout=$("$script" "aaaaaaaa" system 2>/tmp/logs-read-prefix-stderr-2-$$) || rc=$?
102
+ stderr=$(cat /tmp/logs-read-prefix-stderr-2-$$)
103
+ rm -f /tmp/logs-read-prefix-stderr-2-$$
104
+
105
+ cleanup_install_tree "$root"
106
+
107
+ if [[ $rc -ne 1 ]]; then echo " expected exit 1, got $rc"; return 1; fi
108
+ if [[ "$stderr" != *"reason=ambiguous-prefix"* ]]; then
109
+ echo " stderr missing reason=ambiguous-prefix: $stderr"; return 1
110
+ fi
111
+ if [[ "$stderr" != *"matches=2"* ]]; then
112
+ echo " stderr missing matches=2: $stderr"; return 1
113
+ fi
114
+ if [[ "$stderr" != *"claude-agent-stream-aaaaaaaa-1111-A.log"* ]]; then
115
+ echo " stderr missing candidate A: $stderr"; return 1
116
+ fi
117
+ if [[ "$stderr" != *"claude-agent-stream-aaaaaaaa-2222-B.log"* ]]; then
118
+ echo " stderr missing candidate B: $stderr"; return 1
119
+ fi
120
+ # Body must NOT contain either sentinel — refusal means no body emitted.
121
+ if [[ -n "$stdout" ]]; then
122
+ echo " stdout non-empty on ambiguous refusal: $stdout"; return 1
123
+ fi
124
+ return 0
125
+ }
126
+
127
+ # --- Case 3: more-specific prefix disambiguates ---
128
+ case_more_specific_disambiguates() {
129
+ local root="/tmp/maxy-logs-read-prefix-3-$$"
130
+ local script
131
+ script=$(setup_install_tree "$root")
132
+ local logdir="$root/data/accounts/acct-test/logs"
133
+ echo "ONE" > "$logdir/claude-agent-stream-aaaaaaaa-1111-A.log"
134
+ echo "TWO" > "$logdir/claude-agent-stream-aaaaaaaa-2222-B.log"
135
+
136
+ local stdout stderr rc=0
137
+ stdout=$("$script" "aaaaaaaa-1111" system 2>/tmp/logs-read-prefix-stderr-3-$$) || rc=$?
138
+ stderr=$(cat /tmp/logs-read-prefix-stderr-3-$$)
139
+ rm -f /tmp/logs-read-prefix-stderr-3-$$
140
+
141
+ cleanup_install_tree "$root"
142
+
143
+ if [[ $rc -ne 0 ]]; then echo " expected exit 0, got $rc"; return 1; fi
144
+ if [[ "$stdout" != *"ONE"* ]]; then echo " stdout missing ONE: $stdout"; return 1; fi
145
+ if [[ "$stdout" == *"TWO"* ]]; then echo " stdout contained TWO: $stdout"; return 1; fi
146
+ if [[ "$stderr" != *"matched_shape=full"* ]]; then
147
+ echo " stderr missing matched_shape=full: $stderr"; return 1
148
+ fi
149
+ return 0
150
+ }
151
+
152
+ # --- Case 4: zero matches → tried=[<glob>, <glob>] file-not-found ---
153
+ case_zero_matches() {
154
+ local root="/tmp/maxy-logs-read-prefix-4-$$"
155
+ local script
156
+ script=$(setup_install_tree "$root")
157
+
158
+ local stdout stderr rc=0
159
+ stdout=$("$script" "xxxxxxxx" system 2>/tmp/logs-read-prefix-stderr-4-$$) || rc=$?
160
+ stderr=$(cat /tmp/logs-read-prefix-stderr-4-$$)
161
+ rm -f /tmp/logs-read-prefix-stderr-4-$$
162
+
163
+ cleanup_install_tree "$root"
164
+
165
+ if [[ $rc -ne 1 ]]; then echo " expected exit 1, got $rc"; return 1; fi
166
+ if [[ "$stderr" != *"reason=file-not-found-in-either-shape"* ]]; then
167
+ echo " stderr missing reason=file-not-found-in-either-shape: $stderr"; return 1
168
+ fi
169
+ if [[ "$stderr" != *"tried=[claude-agent-stream-xxxxxxxx*.log, claude-agent-stream-preflush-xxxxxxxx*.log]"* ]]; then
170
+ echo " stderr missing tried=[<glob>, <glob>]: $stderr"; return 1
171
+ fi
172
+ return 0
173
+ }
174
+
175
+ # --- Case 5: full 36-char UUID still resolves (regression boundary) ---
176
+ case_full_uuid_regression() {
177
+ local root="/tmp/maxy-logs-read-prefix-5-$$"
178
+ local script
179
+ script=$(setup_install_tree "$root")
180
+ local logdir="$root/data/accounts/acct-test/logs"
181
+ local full_uuid="3483269d-c793-4a07-98cc-556d936f2f4d"
182
+ local filename="claude-agent-stream-${full_uuid}.log"
183
+ echo "regression-sentinel" > "$logdir/$filename"
184
+
185
+ local stdout stderr rc=0
186
+ stdout=$("$script" "$full_uuid" system 2>/tmp/logs-read-prefix-stderr-5-$$) || rc=$?
187
+ stderr=$(cat /tmp/logs-read-prefix-stderr-5-$$)
188
+ rm -f /tmp/logs-read-prefix-stderr-5-$$
189
+
190
+ cleanup_install_tree "$root"
191
+
192
+ if [[ $rc -ne 0 ]]; then echo " expected exit 0, got $rc"; return 1; fi
193
+ if [[ "$stdout" != *"regression-sentinel"* ]]; then
194
+ echo " stdout missing sentinel: $stdout"; return 1
195
+ fi
196
+ if [[ "$stderr" != *"matched_shape=full"* ]]; then
197
+ echo " stderr missing matched_shape=full: $stderr"; return 1
198
+ fi
199
+ return 0
200
+ }
201
+
202
+ # --- Case 6: preflush-only file, prefix-match (Pass 2) ---
203
+ case_preflush_prefix_match() {
204
+ local root="/tmp/maxy-logs-read-prefix-6-$$"
205
+ local script
206
+ script=$(setup_install_tree "$root")
207
+ local logdir="$root/data/accounts/acct-test/logs"
208
+ # Preflush filename uses sessionKey:0:12; mimic with a 12-char slice.
209
+ echo "preflush-prefix-sentinel" > "$logdir/claude-agent-stream-preflush-bbbbbbbb-ccc.log"
210
+
211
+ local stdout stderr rc=0
212
+ stdout=$("$script" "bbbbbbbb" system 2>/tmp/logs-read-prefix-stderr-6-$$) || rc=$?
213
+ stderr=$(cat /tmp/logs-read-prefix-stderr-6-$$)
214
+ rm -f /tmp/logs-read-prefix-stderr-6-$$
215
+
216
+ cleanup_install_tree "$root"
217
+
218
+ if [[ $rc -ne 0 ]]; then echo " expected exit 0, got $rc"; return 1; fi
219
+ if [[ "$stdout" != *"preflush-prefix-sentinel"* ]]; then
220
+ echo " stdout missing sentinel: $stdout"; return 1
221
+ fi
222
+ if [[ "$stderr" != *"matched_shape=preflush"* ]]; then
223
+ echo " stderr missing matched_shape=preflush: $stderr"; return 1
224
+ fi
225
+ return 0
226
+ }
227
+
228
+ # --- Case 7: preflush ambiguity (Pass 1 zero, Pass 2 multiple) ---
229
+ case_preflush_ambiguity() {
230
+ local root="/tmp/maxy-logs-read-prefix-7-$$"
231
+ local script
232
+ script=$(setup_install_tree "$root")
233
+ local logdir="$root/data/accounts/acct-test/logs"
234
+ echo "P1" > "$logdir/claude-agent-stream-preflush-cccccccc-111.log"
235
+ echo "P2" > "$logdir/claude-agent-stream-preflush-cccccccc-222.log"
236
+
237
+ local stdout stderr rc=0
238
+ stdout=$("$script" "cccccccc" system 2>/tmp/logs-read-prefix-stderr-7-$$) || rc=$?
239
+ stderr=$(cat /tmp/logs-read-prefix-stderr-7-$$)
240
+ rm -f /tmp/logs-read-prefix-stderr-7-$$
241
+
242
+ cleanup_install_tree "$root"
243
+
244
+ if [[ $rc -ne 1 ]]; then echo " expected exit 1, got $rc"; return 1; fi
245
+ if [[ "$stderr" != *"reason=ambiguous-prefix"* ]]; then
246
+ echo " stderr missing reason=ambiguous-prefix: $stderr"; return 1
247
+ fi
248
+ if [[ "$stderr" != *"matches=2"* ]]; then
249
+ echo " stderr missing matches=2: $stderr"; return 1
250
+ fi
251
+ if [[ "$stderr" != *"claude-agent-stream-preflush-cccccccc-111.log"* ]]; then
252
+ echo " stderr missing preflush candidate 1: $stderr"; return 1
253
+ fi
254
+ if [[ "$stderr" != *"claude-agent-stream-preflush-cccccccc-222.log"* ]]; then
255
+ echo " stderr missing preflush candidate 2: $stderr"; return 1
256
+ fi
257
+ return 0
258
+ }
259
+
260
+ # --- Case 8: every per-conversation type resolves under prefix-match ---
261
+ # Parameterised over the four prefix_for_type values:
262
+ # agent-stream → claude-agent-stream-
263
+ # session → sse-events-
264
+ # error → claude-agent-stderr-
265
+ # public → public-agent-stream-
266
+ case_all_types_prefix_match() {
267
+ local root="/tmp/maxy-logs-read-prefix-8-$$"
268
+ local script
269
+ script=$(setup_install_tree "$root")
270
+ local logdir="$root/data/accounts/acct-test/logs"
271
+ local full_uuid="7d49ef21-1234-4567-89ab-cdef01234567"
272
+
273
+ echo "sentinel-agent-stream" > "$logdir/claude-agent-stream-${full_uuid}.log"
274
+ echo "sentinel-session" > "$logdir/sse-events-${full_uuid}.log"
275
+ echo "sentinel-error" > "$logdir/claude-agent-stderr-${full_uuid}.log"
276
+ echo "sentinel-public" > "$logdir/public-agent-stream-${full_uuid}.log"
277
+
278
+ local pairs=(
279
+ "agent-stream|sentinel-agent-stream"
280
+ "session|sentinel-session"
281
+ "error|sentinel-error"
282
+ "public|sentinel-public"
283
+ )
284
+ local fail=0
285
+ local pair t expected_sentinel
286
+ for pair in "${pairs[@]}"; do
287
+ t="${pair%%|*}"
288
+ expected_sentinel="${pair##*|}"
289
+ local stdout stderr rc=0
290
+ stdout=$("$script" "7d49ef21" "$t" 2>/tmp/logs-read-prefix-stderr-8-$$) || rc=$?
291
+ stderr=$(cat /tmp/logs-read-prefix-stderr-8-$$)
292
+ rm -f /tmp/logs-read-prefix-stderr-8-$$
293
+ if [[ $rc -ne 0 ]]; then echo " [$t] expected exit 0, got $rc — stderr: $stderr"; fail=1; continue; fi
294
+ if [[ "$stdout" != *"$expected_sentinel"* ]]; then
295
+ echo " [$t] missing sentinel '$expected_sentinel': $stdout"; fail=1
296
+ fi
297
+ if [[ "$stderr" != *"matched_shape=full"* ]]; then
298
+ echo " [$t] stderr missing matched_shape=full: $stderr"; fail=1
299
+ fi
300
+ done
301
+
302
+ cleanup_install_tree "$root"
303
+ [[ $fail -eq 0 ]]
304
+ }
305
+
306
+ # --- Case 9: conv_id with shell metacharacters is rejected ---
307
+ case_metacharacter_rejected() {
308
+ local root="/tmp/maxy-logs-read-prefix-9-$$"
309
+ local script
310
+ script=$(setup_install_tree "$root")
311
+
312
+ local stdout stderr rc=0
313
+ # Use a backslash-escaped wildcard to feed it as a literal argument.
314
+ stdout=$("$script" "ab*cd" system 2>/tmp/logs-read-prefix-stderr-9-$$) || rc=$?
315
+ stderr=$(cat /tmp/logs-read-prefix-stderr-9-$$)
316
+ rm -f /tmp/logs-read-prefix-stderr-9-$$
317
+
318
+ cleanup_install_tree "$root"
319
+
320
+ if [[ $rc -ne 2 ]]; then echo " expected exit 2 (usage), got $rc"; return 1; fi
321
+ if [[ "$stderr" != *"invalid characters"* ]]; then
322
+ echo " stderr missing invalid-characters guard: $stderr"; return 1
323
+ fi
324
+ return 0
325
+ }
326
+
327
+ run_case "8-char prefix resolves full-UUID file" case_prefix_resolves_full
328
+ run_case "ambiguous prefix → refused, candidates listed" case_ambiguous_prefix
329
+ run_case "more-specific prefix disambiguates" case_more_specific_disambiguates
330
+ run_case "zero matches → file-not-found-in-either-shape" case_zero_matches
331
+ run_case "full 36-char UUID still resolves" case_full_uuid_regression
332
+ run_case "preflush file resolves by prefix" case_preflush_prefix_match
333
+ run_case "preflush ambiguity refused" case_preflush_ambiguity
334
+ run_case "every per-conversation type prefix-matches" case_all_types_prefix_match
335
+ run_case "shell metacharacters rejected" case_metacharacter_rejected
336
+
337
+ echo ""
338
+ echo "================================================"
339
+ echo " Passed: $PASS / Failed: $FAIL"
340
+ echo "================================================"
341
+ [[ $FAIL -eq 0 ]]
@@ -144,25 +144,42 @@ validate_type() {
144
144
  esac
145
145
  }
146
146
 
147
- # --- Per-conversation mode: read {prefix}{convId}.log, with preflush fallback ---
147
+ # --- Per-conversation mode: prefix-match {prefix}{convId-prefix}*.log ---
148
148
  #
149
- # Task 671: writer (Task 650) emits logs under two filename shapes
149
+ # Task 671 two filename shapes the writer emits:
150
150
  # FULL: {prefix}{conversationId}.log (post 1→2-user-turn flush)
151
151
  # PREFLUSH: {prefix}preflush-{id:0:12}.log (pre-flush, first turn; the
152
152
  # slice is of the sessionKey)
153
- # This mode tries FULL across every account dir first; if zero FULL hits,
154
- # falls back to PREFLUSH across every dir. If BOTH exist in the same dir,
155
- # FULL wins and the preflush sibling is logged to server.log as stale
156
- # (promotion-race housekeeping signal).
157
153
  #
158
- # Identity note: for the abrupt-exit case this fallback exists to solve,
154
+ # Task 998 operator surface accepts any unambiguous prefix of the id.
155
+ # The writer's filename is canonical (full 36-char UUID) but every agent
156
+ # line surfaces the 8-char id (`conversationId=3483269d…`). Pass 1 and
157
+ # Pass 2 each glob `${prefix}${conv_id}*.log` so an 8-char operator input
158
+ # resolves a full-UUID file. Three trailer outcomes:
159
+ # single hit → exit 0 + matched_shape=full|preflush (existing success)
160
+ # zero hits → exit 1 + reason=file-not-found-in-either-shape (existing miss)
161
+ # 2+ hits → exit 1 + reason=ambiguous-prefix candidates=[...]; never picks
162
+ #
163
+ # Stale-preflush housekeeping (Task 671): on a single FULL hit, glob the
164
+ # preflush pattern in the same dir and emit `[logs-read] stale-preflush-detected`
165
+ # to server.log for each sibling. Best-effort — retrieval doesn't fail if
166
+ # server.log is unwritable.
167
+ #
168
+ # Identity note: the preflush fallback exists for the abrupt-exit case where
159
169
  # the operator passes the sessionKey (no conversationId was ever assigned).
160
- # For post-flush retrievals the operator passes the conversationId and the
161
- # FULL file is expected to exist.
170
+ # For post-flush retrievals the operator passes the conversationId (or any
171
+ # unambiguous prefix of it) and the FULL file is expected to exist.
162
172
  #
163
- # MIRROR: keep the two-shape contract in sync with:
164
- # - platform/ui/app/lib/logs-read-resolve.ts (canonical TS helper + tests)
165
- # - platform/plugins/admin/mcp/src/index.ts (conversationId branch)
173
+ # MIRROR intentional divergence (Task 998):
174
+ # - platform/ui/app/lib/logs-read-resolve.ts (canonical TS helper)
175
+ # EXACT-MATCH on `${prefix}${conversationId}.log`.
176
+ # - platform/plugins/admin/mcp/src/index.ts (conversationId branch) —
177
+ # EXACT-MATCH, same shape.
178
+ # Both serve agents that always hold the full conversationId. This shell
179
+ # counterpart is the operator surface; operators read 8-char ids from agent
180
+ # lines and type them at the SSH prompt. Do not "fix" the divergence by
181
+ # adding prefix-match to TS/MCP — it would mask agent bugs that pass
182
+ # partial ids.
166
183
  per_conversation_mode() {
167
184
  local conv_id="$1"
168
185
  local filter_type="${2:-agent-stream}"
@@ -172,6 +189,14 @@ per_conversation_mode() {
172
189
  exit 2
173
190
  fi
174
191
 
192
+ # Reject shell metacharacters in conv_id — the prefix-match glob below
193
+ # would otherwise turn user input into a shell pattern. UUIDs and
194
+ # sessionKeys only contain [a-zA-Z0-9-].
195
+ if [[ ! "$conv_id" =~ ^[a-zA-Z0-9-]+$ ]]; then
196
+ echo "Error: conversationId contains invalid characters (allowed: a-z, A-Z, 0-9, -)" >&2
197
+ exit 2
198
+ fi
199
+
175
200
  validate_type "$filter_type"
176
201
 
177
202
  # Platform-scoped types shortcut to their fixed files regardless of convId.
@@ -190,56 +215,98 @@ per_conversation_mode() {
190
215
  exit 2
191
216
  fi
192
217
 
193
- # Build the two filenames the two-shape contract expects.
194
- local full_name="${prefix}${conv_id}.log"
195
- local preflush_name="${prefix}preflush-${conv_id:0:12}.log"
218
+ # Glob patterns the two-shape contract expects (Task 998: prefix match).
219
+ local full_glob="${prefix}${conv_id}*.log"
220
+ local preflush_glob="${prefix}preflush-${conv_id:0:12}*.log"
196
221
 
197
- local found=0
198
222
  local searched=0
199
223
  local matched_shape=""
200
224
 
201
- # Pass 1: FULL across every log dir. Records stale preflush siblings.
225
+ # Pass 1: FULL prefix glob across every log dir; aggregate hits.
226
+ local -a full_hits=()
227
+ local -a full_dirs=()
228
+ shopt -s nullglob
202
229
  for log_dir in "${ACCOUNT_LOG_DIRS[@]}"; do
203
- local full_path="$log_dir/$full_name"
204
230
  searched=$((searched + 1))
205
- if [[ ! -f "$full_path" ]]; then
206
- continue
207
- fi
208
- [[ $found -gt 0 ]] && echo ""
209
- echo "## $(basename "$full_path") ($filter_type)$(account_suffix "$log_dir")"
231
+ for f in "$log_dir"/${prefix}${conv_id}*.log; do
232
+ full_hits+=("$f")
233
+ full_dirs+=("$log_dir")
234
+ done
235
+ done
236
+ shopt -u nullglob
237
+
238
+ # Pass 1 ambiguous → refuse, never silently pick.
239
+ if [[ ${#full_hits[@]} -gt 1 ]]; then
240
+ local -a candidate_names=()
241
+ local cf
242
+ for cf in "${full_hits[@]}"; do
243
+ candidate_names+=("$(basename "$cf")")
244
+ done
245
+ local joined
246
+ joined=$(IFS=','; printf '%s' "${candidate_names[*]}")
247
+ echo "-- trailer: conversationId=$conv_id type=$filter_type searched=$searched matches=${#full_hits[@]} reason=ambiguous-prefix candidates=[${joined}]" >&2
248
+ exit 1
249
+ fi
250
+
251
+ local found=0
252
+
253
+ # Pass 1 single hit → emit body, run stale-preflush detection in matched dir.
254
+ if [[ ${#full_hits[@]} -eq 1 ]]; then
255
+ local full_path="${full_hits[0]}"
256
+ local match_dir="${full_dirs[0]}"
257
+ echo "## $(basename "$full_path") ($filter_type)$(account_suffix "$match_dir")"
210
258
  cat "$full_path"
211
- found=$((found + 1))
259
+ found=1
212
260
  matched_shape="full"
213
- # Stale-preflush detection: if the post-flush rename left the preflush
214
- # sibling on disk, emit a housekeeping signal to server.log. Best-effort
215
- # — retrieval must not fail if server.log is unwritable.
216
- local stale_path="$log_dir/$preflush_name"
217
- if [[ -f "$stale_path" ]]; then
261
+
262
+ shopt -s nullglob
263
+ local stale_path
264
+ for stale_path in "$match_dir"/${prefix}preflush-${conv_id:0:12}*.log; do
218
265
  local stale_ts
219
266
  stale_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
220
267
  echo "${stale_ts} [logs-read] stale-preflush-detected path=${stale_path}" >> "$SERVER_LOG" 2>/dev/null || true
221
- fi
222
- done
268
+ done
269
+ shopt -u nullglob
270
+ fi
223
271
 
224
- # Pass 2: PREFLUSH across every dir. Only runs if zero FULL hits.
272
+ # Pass 2: PREFLUSH prefix glob, only if Pass 1 had zero hits.
225
273
  if [[ $found -eq 0 ]]; then
274
+ local -a preflush_hits=()
275
+ local -a preflush_dirs=()
276
+ shopt -s nullglob
226
277
  for log_dir in "${ACCOUNT_LOG_DIRS[@]}"; do
227
- local preflush_path="$log_dir/$preflush_name"
228
278
  searched=$((searched + 1))
229
- if [[ ! -f "$preflush_path" ]]; then
230
- continue
231
- fi
232
- [[ $found -gt 0 ]] && echo ""
233
- echo "## $(basename "$preflush_path") ($filter_type)$(account_suffix "$log_dir")"
279
+ for f in "$log_dir"/${prefix}preflush-${conv_id:0:12}*.log; do
280
+ preflush_hits+=("$f")
281
+ preflush_dirs+=("$log_dir")
282
+ done
283
+ done
284
+ shopt -u nullglob
285
+
286
+ if [[ ${#preflush_hits[@]} -gt 1 ]]; then
287
+ local -a candidate_names=()
288
+ local pf
289
+ for pf in "${preflush_hits[@]}"; do
290
+ candidate_names+=("$(basename "$pf")")
291
+ done
292
+ local joined
293
+ joined=$(IFS=','; printf '%s' "${candidate_names[*]}")
294
+ echo "-- trailer: conversationId=$conv_id type=$filter_type searched=$searched matches=${#preflush_hits[@]} reason=ambiguous-prefix candidates=[${joined}]" >&2
295
+ exit 1
296
+ fi
297
+
298
+ if [[ ${#preflush_hits[@]} -eq 1 ]]; then
299
+ local preflush_path="${preflush_hits[0]}"
300
+ echo "## $(basename "$preflush_path") ($filter_type)$(account_suffix "${preflush_dirs[0]}")"
234
301
  cat "$preflush_path"
235
- found=$((found + 1))
302
+ found=1
236
303
  matched_shape="preflush"
237
- done
304
+ fi
238
305
  fi
239
306
 
240
307
  # Trailer: empty output never leaves the reader guessing why.
241
308
  if [[ $found -eq 0 ]]; then
242
- echo "-- trailer: conversationId=$conv_id type=$filter_type searched=$searched found=0 tried=[${full_name}, ${preflush_name}] reason=file-not-found-in-either-shape" >&2
309
+ echo "-- trailer: conversationId=$conv_id type=$filter_type searched=$searched found=0 tried=[${full_glob}, ${preflush_glob}] reason=file-not-found-in-either-shape" >&2
243
310
  exit 1
244
311
  fi
245
312
  echo "" >&2
@@ -22,6 +22,10 @@ CONV_ID="8c264cfb-441f-48bf-9811-9fa3ad3b51dc"
22
22
  PREFLUSH_SLICE="8c264cfb-441"
23
23
  FULL_NAME="claude-agent-stream-${CONV_ID}.log"
24
24
  PREFLUSH_NAME="claude-agent-stream-preflush-${PREFLUSH_SLICE}.log"
25
+ # Task 998 — miss trailer now lists glob patterns, not literal filenames,
26
+ # because Pass 1 / Pass 2 prefix-glob `${prefix}${conv_id}*.log`.
27
+ FULL_GLOB="claude-agent-stream-${CONV_ID}*.log"
28
+ PREFLUSH_GLOB="claude-agent-stream-preflush-${PREFLUSH_SLICE}*.log"
25
29
 
26
30
  PASS=0
27
31
  FAIL=0
@@ -141,8 +145,8 @@ case_neither() {
141
145
  echo " expected exit 1, got $rc"
142
146
  return 1
143
147
  fi
144
- if [[ "$stderr" != *"tried=[${FULL_NAME}, ${PREFLUSH_NAME}]"* ]]; then
145
- echo " stderr missing tried=[…,…]: $stderr"
148
+ if [[ "$stderr" != *"tried=[${FULL_GLOB}, ${PREFLUSH_GLOB}]"* ]]; then
149
+ echo " stderr missing tried=[…,…] glob patterns: $stderr"
146
150
  return 1
147
151
  fi
148
152
  if [[ "$stderr" != *"reason=file-not-found-in-either-shape"* ]]; then
@@ -55,7 +55,7 @@ Your personalisation is in `agents/admin/SOUL.md`. Read it and apply it. SOUL.md
55
55
  - Professional, direct, action-oriented. Concise and precise. British English unless SOUL.md specifies otherwise.
56
56
  - Do not use emoji characters in any response. Plain text and punctuation only.
57
57
  - Every URL in a response must be a clickable markdown link — never raw text. Use `[visible label](url)` format. The label should be the meaningful part of the URL (hostname, path, or a short description), not the full raw URL repeated.
58
- - **Internal scaffolding never appears in user-facing replies.** Tool descriptions, error strings, skill prompts, and graph schemas may name internal contracts (e.g. `producedByTaskId`, `:PRODUCED`, `Write blocked (no-admin-user)`, `missing-action-provenance`, `archiveSha256`, `lastIngestedMessageHash`) so you know how to act. Translate them into plain English for the user. Forbidden in user-facing replies: literal tool names with `mcp__` prefix, `Task NNN` references, doctrine names ("process-provenance", "write doctrine", "graph-write gate"), MCP-tool-description quotes, raw error discriminators. Forbidden phrasings include: "the LocalBusiness write requires a producedByTaskId per Task 885", "the write gate is blocking", "I'll call ToolSearch". Permitted equivalents: "I need to set up the business profile first before recording that — let me run that now", "the platform requires me to anchor this to an action record — creating one now". Internal language stays in your reasoning, not your prose to the operator.
58
+ - **Internal scaffolding never appears in user-facing replies.** Tool descriptions, error strings, skill prompts, and graph schemas may name internal contracts (e.g. `producedByTaskId`, `:PRODUCED`, `Write blocked (no-admin-user)`, `missing-provenance`, `archiveSha256`, `lastIngestedMessageHash`) so you know how to act. Translate them into plain English for the user. Forbidden in user-facing replies: literal tool names with `mcp__` prefix, `Task NNN` references, doctrine names ("process-provenance", "write doctrine", "graph-write gate"), MCP-tool-description quotes, raw error discriminators. Forbidden phrasings include: "the LocalBusiness write requires a producedByTaskId per Task 885", "the write gate is blocking", "I'll call ToolSearch". Permitted equivalents: "I need to set up the business profile first before recording that — let me run that now", "the platform requires me to anchor this to an action record — creating one now". Internal language stays in your reasoning, not your prose to the operator.
59
59
 
60
60
  ## Information Sourcing
61
61
 
@@ -17,7 +17,7 @@ plugins:
17
17
 
18
18
  # Real Agency
19
19
 
20
- Premium plugin bundle for UK estate agency professionals. Purchasing this bundle grants access to all 11 sub-plugins, each independently activatable via `enabledPlugins`. The bundle also delivers three specialist roles — negotiator, valuer, and compliance — that embed domain knowledge from the sub-plugins and operate autonomously with Loop CRM tools.
20
+ Premium plugin bundle for UK estate agency professionals. Purchasing this bundle grants access to all 11 sub-plugins, each independently activatable via `enabledPlugins`. When the bundle is auto-delivered at admin session startup (or explicitly via `premium-deliver`), every sub-plugin in the `plugins:` list above is stamped into `account.json` `enabledPlugins` in one idempotent set-union — delivery implies enablement, so `mcp__loop__*` tools and the other sub-plugin surfaces are visible to the agent from the first turn after install. Disable individual sub-plugins with `plugin-toggle-enabled`. The bundle also delivers three specialist roles — negotiator, valuer, and compliance — that embed domain knowledge from the sub-plugins and operate autonomously with Loop CRM tools.
21
21
 
22
22
  ## Specialist Roles
23
23