@rubytech/create-maxy 1.0.883 → 1.0.885

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 (42) 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/mcp-eager/dist/index.d.ts +61 -0
  6. package/payload/platform/lib/mcp-eager/dist/index.d.ts.map +1 -0
  7. package/payload/platform/lib/mcp-eager/dist/index.js +49 -0
  8. package/payload/platform/lib/mcp-eager/dist/index.js.map +1 -0
  9. package/payload/platform/lib/mcp-eager/src/index.ts +78 -0
  10. package/payload/platform/lib/mcp-eager/tsconfig.json +8 -0
  11. package/payload/platform/package.json +2 -2
  12. package/payload/platform/plugins/admin/PLUGIN.md +1 -1
  13. package/payload/platform/plugins/admin/mcp/dist/index.js +48 -76
  14. package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
  15. package/payload/platform/plugins/contacts/mcp/dist/index.js +10 -9
  16. package/payload/platform/plugins/contacts/mcp/dist/index.js.map +1 -1
  17. package/payload/platform/plugins/docs/references/internals.md +13 -0
  18. package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
  19. package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
  20. package/payload/platform/plugins/email/mcp/dist/index.js +10 -9
  21. package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
  22. package/payload/platform/plugins/memory/mcp/dist/index.js +9 -8
  23. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  24. package/payload/platform/plugins/scheduling/mcp/dist/index.js +9 -8
  25. package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
  26. package/payload/platform/plugins/tasks/mcp/dist/index.js +15 -14
  27. package/payload/platform/plugins/tasks/mcp/dist/index.js.map +1 -1
  28. package/payload/platform/plugins/telegram/mcp/dist/index.js +4 -3
  29. package/payload/platform/plugins/telegram/mcp/dist/index.js.map +1 -1
  30. package/payload/platform/plugins/workflows/mcp/dist/index.js +9 -8
  31. package/payload/platform/plugins/workflows/mcp/dist/index.js.map +1 -1
  32. package/payload/platform/scripts/__tests__/logs-read-prefix.sh +85 -240
  33. package/payload/platform/scripts/log-adherence-check.sh +100 -0
  34. package/payload/platform/scripts/logs-read.sh +71 -141
  35. package/payload/platform/scripts/logs-read.test.sh +47 -104
  36. package/payload/premium-plugins/real-agency/BUNDLE.md +1 -1
  37. package/payload/server/chunk-5PQU2HW2.js +11672 -0
  38. package/payload/server/chunk-NPKQWE3S.js +1431 -0
  39. package/payload/server/chunk-ZVO5ASQA.js +11660 -0
  40. package/payload/server/client-pool-QUMX7OUT.js +34 -0
  41. package/payload/server/maxy-edge.js +2 -2
  42. package/payload/server/server.js +123 -121
@@ -1,31 +1,30 @@
1
1
  #!/usr/bin/env bash
2
2
  # logs-read.sh — Shell counterpart to the logs-read MCP tool.
3
3
  #
4
- # Task 532: stream logs are per-conversation, not per-day. The primary mode
5
- # now reads a single {prefix}-{conversationId}.log file — the reader gets a
6
- # continuous, conversation-scoped timeline from first [spawn] to final
7
- # [process-exit] without needing to filter.
8
- #
9
- # Task 671: first-turn sessions (pre 1→2-user-turn flush per Task 650) land
10
- # at {prefix}preflush-{sessionKey:0:12}.log. The per-conversation mode
11
- # resolves either shape from the same identifier — full first, preflush as
12
- # fallback.
4
+ # Task 1006: stream logs are per-session, not per-day or per-conversation.
5
+ # The primary mode reads a single {prefix}-{sessionKey}.log file — the
6
+ # operator types a sessionKey prefix and the surface returns the bytes from
7
+ # first [spawn] to final [process-exit] without filtering. sessionKey is
8
+ # the single identifier on disk; the earlier full vs preflush split is gone.
13
9
  #
14
10
  # Modes:
15
- # logs-read.sh <conversationId> [type] Read {prefix}-{convId}.log
16
- # logs-read.sh --tail [type] [lines] Tail most recent file of type
17
- # logs-read.sh --grep <sessionKey> [type] Legacy grep across log files
11
+ # logs-read.sh <sessionKey-or-prefix> [type] Read {prefix}-{sessionKey}.log
12
+ # logs-read.sh --tail [type] [lines] Tail most recent file of type
13
+ # logs-read.sh --grep <sessionKey> [type] Legacy grep across log files
18
14
  #
19
- # Types: agent-stream (canonical, per-conversation tool-use/tool-result archive;
15
+ # Types: agent-stream (canonical, per-session tool-use/tool-result archive;
20
16
  # `system` is kept as a backwards-compatible alias), session, error,
21
17
  # heartbeat, public, server, mcp, vnc
22
- # For per-conversation types (agent-stream/system/session/error/public): conversationId is
23
- # required in the first mode. For platform-scoped types (server/vnc/heartbeat):
24
- # conversationId is ignored.
18
+ # For per-session types (agent-stream/system/session/error/public): sessionKey
19
+ # is required in the first mode. For platform-scoped types (server/vnc/
20
+ # heartbeat): the id is ignored.
25
21
  #
26
22
  # Every invocation writes a one-line trailer describing files searched,
27
23
  # matches returned, and files rejected — empty output never leaves a reader
28
- # guessing why.
24
+ # guessing why. On a miss the script appends a one-line
25
+ # `[log-tee] missing-on-resolve sessionKey=<8> surface=logs-read-sh
26
+ # reason=...` entry to server.log so the writer-side existence contract is
27
+ # the authoritative signal.
29
28
  #
30
29
  # Exit codes:
31
30
  # 0 Success (output produced)
@@ -122,16 +121,16 @@ VALID_TYPES="agent-stream, system, session, error, heartbeat, public, server, mc
122
121
  usage() {
123
122
  cat >&2 <<EOF
124
123
  Usage:
125
- logs-read.sh <conversationId> [type] Read {prefix}-{convId}.log
126
- logs-read.sh --tail [type] [lines] Tail most recent log of type
127
- logs-read.sh --grep <sessionKey> [type] Legacy grep across log files
124
+ logs-read.sh <sessionKey-or-prefix> [type] Read {prefix}-{sessionKey}.log
125
+ logs-read.sh --tail [type] [lines] Tail most recent log of type
126
+ logs-read.sh --grep <sessionKey> [type] Legacy grep across log files
128
127
 
129
128
  Types: $VALID_TYPES
130
129
  Default type: agent-stream | Default lines: 50
131
130
 
132
- Per-conversation types (agent-stream, system, session, error, public) require conversationId
133
- in the first mode — the log file is named {prefix}-{conversationId}.log.
134
- Platform-scoped types (server, vnc, heartbeat) ignore conversationId.
131
+ Per-session types (agent-stream, system, session, error, public) require
132
+ sessionKey in the first mode — the log file is named {prefix}-{sessionKey}.log.
133
+ Platform-scoped types (server, vnc, heartbeat) ignore the id.
135
134
  EOF
136
135
  exit 2
137
136
  }
@@ -144,65 +143,48 @@ validate_type() {
144
143
  esac
145
144
  }
146
145
 
147
- # --- Per-conversation mode: prefix-match {prefix}{convId-prefix}*.log ---
146
+ # --- Per-session mode: prefix-match {prefix}{sessionKey-prefix}*.log ---
148
147
  #
149
- # Task 671two filename shapes the writer emits:
150
- # FULL: {prefix}{conversationId}.log (post 1→2-user-turn flush)
151
- # PREFLUSH: {prefix}preflush-{id:0:12}.log (pre-flush, first turn; the
152
- # slice is of the sessionKey)
148
+ # Task 1006one filename shape per session: {prefix}{sessionKey}.log.
149
+ # The operator types an 8-char-or-greater sessionKey prefix; we glob
150
+ # `${prefix}${session_key}*.log` across every account log dir. The earlier
151
+ # full vs preflush two-shape contract is gone: sessionKey is the single
152
+ # identifier on disk, mounted from turn 1.
153
153
  #
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)
154
+ # Three trailer outcomes:
155
+ # single hit → exit 0 (success)
156
+ # zero hits → exit 1 + appends `[log-tee] missing-on-resolve sessionKey=<8>
157
+ # surface=logs-read-sh reason=...` to server.log so
158
+ # the writer-side existence contract is the
159
+ # authoritative signal.
161
160
  # 2+ hits → exit 1 + reason=ambiguous-prefix candidates=[...]; never picks
162
161
  #
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
169
- # the operator passes the sessionKey (no conversationId was ever assigned).
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.
172
- #
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.
162
+ # MIRROR keep these three sites in lockstep:
163
+ # - platform/ui/app/lib/logs-read-resolve.ts (TS helper, exact-match)
164
+ # - platform/plugins/admin/mcp/src/index.ts (MCP tool, exact-match)
183
165
  per_conversation_mode() {
184
- local conv_id="$1"
166
+ local session_key="$1"
185
167
  local filter_type="${2:-agent-stream}"
186
168
 
187
- if [[ -z "$conv_id" ]]; then
188
- echo "Error: conversationId cannot be empty" >&2
169
+ if [[ -z "$session_key" ]]; then
170
+ echo "Error: sessionKey cannot be empty" >&2
189
171
  exit 2
190
172
  fi
191
173
 
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
174
+ # Reject shell metacharacters in session_key — the prefix-match glob below
175
+ # would otherwise turn user input into a shell pattern. SessionKeys only
176
+ # contain [a-zA-Z0-9-].
177
+ if [[ ! "$session_key" =~ ^[a-zA-Z0-9-]+$ ]]; then
178
+ echo "Error: sessionKey contains invalid characters (allowed: a-z, A-Z, 0-9, -)" >&2
197
179
  exit 2
198
180
  fi
199
181
 
200
182
  validate_type "$filter_type"
201
183
 
202
- # Platform-scoped types shortcut to their fixed files regardless of convId.
184
+ # Platform-scoped types shortcut to their fixed files regardless of id.
203
185
  case "$filter_type" in
204
186
  server|vnc|heartbeat|mcp)
205
- echo "# note: type=$filter_type is platform-scoped; conversationId ignored — showing fixed file" >&2
187
+ echo "# note: type=$filter_type is platform-scoped; sessionKey ignored — showing fixed file" >&2
206
188
  tail_mode "$filter_type" "50"
207
189
  return
208
190
  ;;
@@ -215,103 +197,51 @@ per_conversation_mode() {
215
197
  exit 2
216
198
  fi
217
199
 
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"
221
-
200
+ local glob="${prefix}${session_key}*.log"
222
201
  local searched=0
223
- local matched_shape=""
224
202
 
225
- # Pass 1: FULL prefix glob across every log dir; aggregate hits.
226
- local -a full_hits=()
227
- local -a full_dirs=()
203
+ local -a hits=()
204
+ local -a hit_dirs=()
228
205
  shopt -s nullglob
229
206
  for log_dir in "${ACCOUNT_LOG_DIRS[@]}"; do
230
207
  searched=$((searched + 1))
231
- for f in "$log_dir"/${prefix}${conv_id}*.log; do
232
- full_hits+=("$f")
233
- full_dirs+=("$log_dir")
208
+ for f in "$log_dir"/${prefix}${session_key}*.log; do
209
+ hits+=("$f")
210
+ hit_dirs+=("$log_dir")
234
211
  done
235
212
  done
236
213
  shopt -u nullglob
237
214
 
238
- # Pass 1 ambiguous → refuse, never silently pick.
239
- if [[ ${#full_hits[@]} -gt 1 ]]; then
215
+ if [[ ${#hits[@]} -gt 1 ]]; then
240
216
  local -a candidate_names=()
241
217
  local cf
242
- for cf in "${full_hits[@]}"; do
218
+ for cf in "${hits[@]}"; do
243
219
  candidate_names+=("$(basename "$cf")")
244
220
  done
245
221
  local joined
246
222
  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
223
+ echo "-- trailer: sessionKey=$session_key type=$filter_type searched=$searched matches=${#hits[@]} reason=ambiguous-prefix candidates=[${joined}]" >&2
248
224
  exit 1
249
225
  fi
250
226
 
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")"
258
- cat "$full_path"
259
- found=1
260
- matched_shape="full"
261
-
262
- shopt -s nullglob
263
- local stale_path
264
- for stale_path in "$match_dir"/${prefix}preflush-${conv_id:0:12}*.log; do
265
- local stale_ts
266
- stale_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
267
- echo "${stale_ts} [logs-read] stale-preflush-detected path=${stale_path}" >> "$SERVER_LOG" 2>/dev/null || true
268
- done
269
- shopt -u nullglob
227
+ if [[ ${#hits[@]} -eq 1 ]]; then
228
+ local hit_path="${hits[0]}"
229
+ local match_dir="${hit_dirs[0]}"
230
+ echo "## $(basename "$hit_path") ($filter_type)$(account_suffix "$match_dir")"
231
+ cat "$hit_path"
232
+ echo "" >&2
233
+ echo "-- trailer: sessionKey=$session_key type=$filter_type searched=$searched matched=1" >&2
234
+ exit 0
270
235
  fi
271
236
 
272
- # Pass 2: PREFLUSH prefix glob, only if Pass 1 had zero hits.
273
- if [[ $found -eq 0 ]]; then
274
- local -a preflush_hits=()
275
- local -a preflush_dirs=()
276
- shopt -s nullglob
277
- for log_dir in "${ACCOUNT_LOG_DIRS[@]}"; do
278
- searched=$((searched + 1))
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
237
+ # Task 1006 missing-on-resolve emission. Best-effort; never fails the
238
+ # invocation when server.log is unwritable.
239
+ local miss_ts
240
+ miss_ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
241
+ echo "${miss_ts} [log-tee] missing-on-resolve sessionKey=${session_key:0:8} surface=logs-read-sh reason=\"file-not-found type=${filter_type}\"" >> "$SERVER_LOG" 2>/dev/null || true
297
242
 
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]}")"
301
- cat "$preflush_path"
302
- found=1
303
- matched_shape="preflush"
304
- fi
305
- fi
306
-
307
- # Trailer: empty output never leaves the reader guessing why.
308
- if [[ $found -eq 0 ]]; then
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
310
- exit 1
311
- fi
312
- echo "" >&2
313
- echo "-- trailer: conversationId=$conv_id type=$filter_type searched=$searched matched=$found matched_shape=$matched_shape" >&2
314
- exit 0
243
+ echo "-- trailer: sessionKey=$session_key type=$filter_type searched=$searched found=0 tried=[${glob}] reason=file-not-found" >&2
244
+ exit 1
315
245
  }
316
246
 
317
247
  # --- Legacy grep mode (backward compatibility with sessionKey-tagged lines) ---
@@ -1,14 +1,13 @@
1
1
  #!/usr/bin/env bash
2
- # Task 671 — bash harness for logs-read.sh two-shape preflush resolution.
2
+ # Task 1006 — bash harness for logs-read.sh single-shape resolution.
3
3
  #
4
- # Covers the four cases from the task brief:
5
- # 1. Full-UUID file present → matched_shape=full
6
- # 2. Preflush file present, full absent matched_shape=preflush
7
- # 3. Neither present trailer lists both filenames, exit 1
8
- # 4. Both present (promotion race) → full wins + stale-preflush signal to server.log
4
+ # Covers the three cases the new contract demands:
5
+ # 1. sessionKey-named file present → bytes returned, exit 0.
6
+ # 2. No file exit 1 + missing-on-resolve appended to server.log.
7
+ # 3. Ambiguous prefix (multiple matches) exit 1, never picks silently.
9
8
  #
10
9
  # MIRROR: see platform/ui/app/lib/logs-read-resolve.ts for the canonical
11
- # two-shape contract and its TS unit tests.
10
+ # single-shape contract and its TS unit tests.
12
11
  set -euo pipefail
13
12
 
14
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -18,28 +17,17 @@ if [[ ! -x "$LOGS_READ" ]]; then
18
17
  exit 1
19
18
  fi
20
19
 
21
- CONV_ID="8c264cfb-441f-48bf-9811-9fa3ad3b51dc"
22
- PREFLUSH_SLICE="8c264cfb-441"
23
- FULL_NAME="claude-agent-stream-${CONV_ID}.log"
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"
20
+ SESSION_KEY="8c264cfb-441f-48bf-9811-9fa3ad3b51dc"
21
+ FILE_NAME="claude-agent-stream-${SESSION_KEY}.log"
29
22
 
30
23
  PASS=0
31
24
  FAIL=0
32
25
 
33
- # logs-read.sh resolves PLATFORM_ROOT from the script location, and uses
34
- # INSTALL_DIR = $PLATFORM_ROOT/.. and ACCOUNTS_DIR = $INSTALL_DIR/data/accounts.
35
- # The test creates a fake install tree with the real script symlinked in.
36
26
  setup_install_tree() {
37
27
  local root="$1"
38
28
  mkdir -p "$root/platform/scripts"
39
29
  mkdir -p "$root/data/accounts/acct-test/logs"
40
30
  mkdir -p "$HOME/.$(basename "$root")/logs"
41
- # Symlink the script under test into the fake tree so PLATFORM_ROOT resolves
42
- # to $root/platform and the account logs are discovered.
43
31
  ln -sf "$LOGS_READ" "$root/platform/scripts/logs-read.sh"
44
32
  echo "$root/platform/scripts/logs-read.sh"
45
33
  }
@@ -50,8 +38,6 @@ cleanup_install_tree() {
50
38
  rm -rf "$HOME/.$(basename "$root")"
51
39
  }
52
40
 
53
- # Each case uses its own ephemeral install tree. Install-tree basename is
54
- # unique so configDir (~/.{installDir}) does not collide.
55
41
  run_case() {
56
42
  local name="$1"
57
43
  shift
@@ -66,16 +52,16 @@ run_case() {
66
52
  fi
67
53
  }
68
54
 
69
- # --- Case 1: full only ---
70
- case_full_only() {
55
+ # --- Case 1: sessionKey-named file present → exit 0 ---
56
+ case_file_present() {
71
57
  local root="/tmp/maxy-logs-read-test-1-$$"
72
58
  local script
73
59
  script=$(setup_install_tree "$root")
74
60
  local logdir="$root/data/accounts/acct-test/logs"
75
- echo "full-content-sentinel" > "$logdir/$FULL_NAME"
61
+ echo "sessionkey-content-sentinel" > "$logdir/$FILE_NAME"
76
62
 
77
63
  local stdout stderr rc
78
- stdout=$("$script" "$CONV_ID" system 2>/tmp/maxy-logs-read-stderr-1-$$) || rc=$?
64
+ stdout=$("$script" "$SESSION_KEY" system 2>/tmp/maxy-logs-read-stderr-1-$$) || rc=$?
79
65
  rc=${rc:-0}
80
66
  stderr=$(cat /tmp/maxy-logs-read-stderr-1-$$)
81
67
  rm -f /tmp/maxy-logs-read-stderr-1-$$
@@ -86,56 +72,66 @@ case_full_only() {
86
72
  echo " expected exit 0, got $rc"
87
73
  return 1
88
74
  fi
89
- if [[ "$stdout" != *"full-content-sentinel"* ]]; then
75
+ if [[ "$stdout" != *"sessionkey-content-sentinel"* ]]; then
90
76
  echo " stdout missing sentinel"
91
77
  return 1
92
78
  fi
93
- if [[ "$stderr" != *"matched_shape=full"* ]]; then
94
- echo " stderr missing matched_shape=full: $stderr"
79
+ if [[ "$stderr" != *"matched=1"* ]]; then
80
+ echo " stderr missing matched=1: $stderr"
95
81
  return 1
96
82
  fi
97
83
  return 0
98
84
  }
99
85
 
100
- # --- Case 2: preflush only ---
101
- case_preflush_only() {
86
+ # --- Case 2: no file → exit 1 + missing-on-resolve in server.log ---
87
+ case_missing() {
102
88
  local root="/tmp/maxy-logs-read-test-2-$$"
103
89
  local script
104
90
  script=$(setup_install_tree "$root")
105
- local logdir="$root/data/accounts/acct-test/logs"
106
- echo "preflush-content-sentinel" > "$logdir/$PREFLUSH_NAME"
91
+ local server_log="$HOME/.$(basename "$root")/logs/server.log"
92
+ : > "$server_log"
107
93
 
108
- local stdout stderr rc
109
- stdout=$("$script" "$CONV_ID" system 2>/tmp/maxy-logs-read-stderr-2-$$) || rc=$?
110
- rc=${rc:-0}
94
+ local rc=0
95
+ "$script" "$SESSION_KEY" system >/dev/null 2>/tmp/maxy-logs-read-stderr-2-$$ || rc=$?
96
+ local stderr
111
97
  stderr=$(cat /tmp/maxy-logs-read-stderr-2-$$)
112
98
  rm -f /tmp/maxy-logs-read-stderr-2-$$
99
+ local server_log_contents
100
+ server_log_contents=$(cat "$server_log" 2>/dev/null || echo "")
113
101
 
114
102
  cleanup_install_tree "$root"
115
103
 
116
- if [[ $rc -ne 0 ]]; then
117
- echo " expected exit 0, got $rc"
104
+ if [[ $rc -ne 1 ]]; then
105
+ echo " expected exit 1, got $rc"
118
106
  return 1
119
107
  fi
120
- if [[ "$stdout" != *"preflush-content-sentinel"* ]]; then
121
- echo " stdout missing sentinel"
108
+ if [[ "$stderr" != *"reason=file-not-found"* ]]; then
109
+ echo " stderr missing reason=file-not-found: $stderr"
122
110
  return 1
123
111
  fi
124
- if [[ "$stderr" != *"matched_shape=preflush"* ]]; then
125
- echo " stderr missing matched_shape=preflush: $stderr"
112
+ if [[ "$server_log_contents" != *"missing-on-resolve"* ]]; then
113
+ echo " server.log missing missing-on-resolve emission: $server_log_contents"
114
+ return 1
115
+ fi
116
+ if [[ "$server_log_contents" != *"surface=logs-read-sh"* ]]; then
117
+ echo " server.log emission missing surface tag"
126
118
  return 1
127
119
  fi
128
120
  return 0
129
121
  }
130
122
 
131
- # --- Case 3: neither presentmiss trailer ---
132
- case_neither() {
123
+ # --- Case 3: ambiguous prefixexit 1 without picking ---
124
+ case_ambiguous() {
133
125
  local root="/tmp/maxy-logs-read-test-3-$$"
134
126
  local script
135
127
  script=$(setup_install_tree "$root")
128
+ local logdir="$root/data/accounts/acct-test/logs"
129
+ echo "a" > "$logdir/claude-agent-stream-deadbeef-aaaa.log"
130
+ echo "b" > "$logdir/claude-agent-stream-deadbeef-bbbb.log"
136
131
 
137
- local stdout stderr rc=0
138
- stdout=$("$script" "$CONV_ID" system 2>/tmp/maxy-logs-read-stderr-3-$$) || rc=$?
132
+ local rc=0
133
+ "$script" "deadbeef" system >/dev/null 2>/tmp/maxy-logs-read-stderr-3-$$ || rc=$?
134
+ local stderr
139
135
  stderr=$(cat /tmp/maxy-logs-read-stderr-3-$$)
140
136
  rm -f /tmp/maxy-logs-read-stderr-3-$$
141
137
 
@@ -145,69 +141,16 @@ case_neither() {
145
141
  echo " expected exit 1, got $rc"
146
142
  return 1
147
143
  fi
148
- if [[ "$stderr" != *"tried=[${FULL_GLOB}, ${PREFLUSH_GLOB}]"* ]]; then
149
- echo " stderr missing tried=[…,…] glob patterns: $stderr"
150
- return 1
151
- fi
152
- if [[ "$stderr" != *"reason=file-not-found-in-either-shape"* ]]; then
153
- echo " stderr missing reason=file-not-found-in-either-shape: $stderr"
154
- return 1
155
- fi
156
- return 0
157
- }
158
-
159
- # --- Case 4: both present (promotion race) → full wins + stale signal ---
160
- case_both_present() {
161
- local root="/tmp/maxy-logs-read-test-4-$$"
162
- local script
163
- script=$(setup_install_tree "$root")
164
- local logdir="$root/data/accounts/acct-test/logs"
165
- echo "full-content-sentinel-4" > "$logdir/$FULL_NAME"
166
- echo "preflush-content-sentinel-4" > "$logdir/$PREFLUSH_NAME"
167
- local server_log="$HOME/.$(basename "$root")/logs/server.log"
168
- : > "$server_log"
169
-
170
- local stdout stderr rc=0
171
- stdout=$("$script" "$CONV_ID" system 2>/tmp/maxy-logs-read-stderr-4-$$) || rc=$?
172
- stderr=$(cat /tmp/maxy-logs-read-stderr-4-$$)
173
- rm -f /tmp/maxy-logs-read-stderr-4-$$
174
-
175
- local server_log_contents
176
- server_log_contents=$(cat "$server_log" 2>/dev/null || echo "")
177
-
178
- cleanup_install_tree "$root"
179
-
180
- if [[ $rc -ne 0 ]]; then
181
- echo " expected exit 0, got $rc"
182
- return 1
183
- fi
184
- if [[ "$stdout" != *"full-content-sentinel-4"* ]]; then
185
- echo " stdout missing full sentinel (full must win): $stdout"
186
- return 1
187
- fi
188
- if [[ "$stdout" == *"preflush-content-sentinel-4"* ]]; then
189
- echo " stdout contained preflush sentinel — preflush must be ignored when full exists"
190
- return 1
191
- fi
192
- if [[ "$stderr" != *"matched_shape=full"* ]]; then
193
- echo " stderr missing matched_shape=full"
194
- return 1
195
- fi
196
- if [[ "$server_log_contents" != *"stale-preflush-detected"* ]]; then
197
- echo " server.log missing stale-preflush-detected signal: $server_log_contents"
198
- return 1
199
- fi
200
- if [[ "$server_log_contents" != *"$PREFLUSH_NAME"* ]]; then
201
- echo " server.log stale signal missing preflush path"
144
+ if [[ "$stderr" != *"reason=ambiguous-prefix"* ]]; then
145
+ echo " stderr missing ambiguous-prefix trailer: $stderr"
202
146
  return 1
203
147
  fi
204
148
  return 0
205
149
  }
206
150
 
207
- run_case "full onlymatched_shape=full" case_full_only
208
- run_case "preflush onlymatched_shape=preflush" case_preflush_only
209
- run_case "neithertrailer enumerates both filenames" case_neither
210
- run_case "both present → full wins + stale signal" case_both_present
151
+ run_case "sessionKey file present exit 0" case_file_present
152
+ run_case "no fileexit 1 + missing-on-resolve" case_missing
153
+ run_case "ambiguous prefix exit 1, refuses to pick" case_ambiguous
211
154
 
212
155
  echo ""
213
156
  echo "================================================"
@@ -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`. 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.
20
+ Premium plugin bundle for UK estate agency professionals. Purchasing this bundle grants access to all 11 sub-plugins, each independently activatable via `enabledPlugins`. Every admin boot reconciles `account.json` `enabledPlugins` against on-disk presence: any sub-plugin from the `plugins:` list above whose `platform/plugins/<sub>/PLUGIN.md` exists gets stamped via an idempotent set-union, so `mcp__loop__*` tools and the other sub-plugin surfaces are visible to the admin agent from the first turn after install — and on first boot post-deploy for accounts that already had the bundle on disk from a prior 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