@rubytech/create-maxy-code 0.1.223 → 0.1.225
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/dist/__tests__/installer-settings-permissions.test.js +112 -0
- package/dist/index.js +13 -0
- package/dist/permissions-seed.js +76 -0
- package/package.json +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/file-presentation/SKILL.md +9 -7
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +18 -4
- package/payload/platform/plugins/admin/skills/professional-document/SKILL.md +1 -1
- package/payload/platform/plugins/docs/references/admin-ui.md +11 -1
- package/payload/platform/plugins/docs/references/deployment.md +1 -1
- package/payload/platform/plugins/docs/references/platform.md +4 -0
- package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.d.ts.map +1 -1
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js +7 -5
- package/payload/platform/plugins/graph-viewer/mcp/dist/tools/graph-render.js.map +1 -1
- package/payload/platform/plugins/graph-viewer/skills/render-graph/SKILL.md +6 -6
- package/payload/platform/plugins/scheduling/PLUGIN.md +2 -2
- package/payload/platform/plugins/scheduling/mcp/dist/index.js +2 -2
- package/payload/platform/plugins/scheduling/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-export-ics.d.ts.map +1 -1
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-export-ics.js +3 -2
- package/payload/platform/plugins/scheduling/mcp/dist/tools/schedule-export-ics.js.map +1 -1
- package/payload/platform/scripts/check-plugin-references.mjs +256 -0
- package/payload/platform/scripts/logs-read-jsonl.test.sh +307 -0
- package/payload/platform/scripts/logs-read.sh +435 -32
- package/payload/platform/scripts/setup-account.sh +20 -8
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +0 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js +11 -10
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
- package/payload/platform/templates/specialists/agents/content-producer.md +1 -1
- package/payload/platform/templates/specialists/agents/personal-assistant.md +1 -1
- package/payload/premium-plugins/teaching/PLUGIN.md +7 -7
- package/payload/premium-plugins/venture-studio/skills/brand-pack/SKILL.md +1 -1
- package/payload/server/public/assets/data-BzRzOKWv.js +1 -0
- package/payload/server/public/data.html +1 -1
- package/payload/server/server.js +13 -6
- package/payload/server/public/assets/data-CHdaTjgn.js +0 -1
|
@@ -1,14 +1,22 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# logs-read.sh — Shell counterpart to the logs-read MCP tool.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
4
|
+
# On rc-spawn / claude.ai-code installs the per-session evidence is the Claude
|
|
5
|
+
# Code JSONL transcript under the configDir, NOT a per-account stream log.
|
|
6
|
+
# So the bare <key> mode (Task 585) accepts a claude.ai `session_<id>` (or its
|
|
7
|
+
# bare suffix, or the intrinsic <uuid> / a uuid prefix), maps it to the local
|
|
8
|
+
# <uuid> via sessions/<pid>.json (or the <uuid>.meta.json bridgeIds, or a
|
|
9
|
+
# content scan), and prints one timestamp-ordered timeline merging the parent
|
|
10
|
+
# transcript with every subagents/agent-*.jsonl — flagging each subagent
|
|
11
|
+
# is_error tool_result inline (agentType + failing tool + error text). An
|
|
12
|
+
# explicit <key> <type> still reads the legacy per-account stream log.
|
|
9
13
|
#
|
|
10
14
|
# Modes:
|
|
11
|
-
# logs-read.sh <sessionKey
|
|
15
|
+
# logs-read.sh <sessionKey> Merged JSONL session+subagent timeline
|
|
16
|
+
# logs-read.sh <sessionKey> <type> Legacy per-account stream-log read
|
|
17
|
+
# logs-read.sh --scan-subagent-errors [N] List subagent transcripts with is_error
|
|
18
|
+
# results (silent-failure audit; N = most-
|
|
19
|
+
# recent N transcripts, default all)
|
|
12
20
|
# logs-read.sh --tail [type] [lines] Tail most recent file of type
|
|
13
21
|
# logs-read.sh --grep <sessionKey> [type] Legacy grep across log files
|
|
14
22
|
#
|
|
@@ -50,32 +58,54 @@ CONFIG_DIR="$HOME/.$INSTALL_DIR_NAME"
|
|
|
50
58
|
SERVER_LOG="$CONFIG_DIR/logs/server.log"
|
|
51
59
|
VNC_LOG="$CONFIG_DIR/logs/vnc-boot.log"
|
|
52
60
|
|
|
61
|
+
# --- Resolve the Claude Code state tree (Task 585) ---
|
|
62
|
+
# rc-spawn per-session evidence is NOT under data/accounts/<id>/logs/ — it is
|
|
63
|
+
# the Claude Code JSONL transcript tree under the configDir. The session
|
|
64
|
+
# manager exports CLAUDE_CONFIG_DIR for the spawned `claude` processes; an
|
|
65
|
+
# operator shell usually has it unset, so derive the brand-isolated default
|
|
66
|
+
# ($CONFIG_DIR/.claude) and honour an explicit override (mirrors
|
|
67
|
+
# jsonl-path.ts's claudeStateRoot). Layout consumed by the <key> and scan
|
|
68
|
+
# modes:
|
|
69
|
+
# projects/<slug>/<uuid>.jsonl — parent transcript
|
|
70
|
+
# projects/<slug>/<uuid>/subagents/agent-<hex>.jsonl — child transcripts
|
|
71
|
+
# projects/<slug>/<uuid>/subagents/agent-<hex>.meta.json — {agentType,...}
|
|
72
|
+
# projects/<slug>/<uuid>.meta.json — sidecar (carries bridgeIds)
|
|
73
|
+
# sessions/<pid>.json — {sessionId, bridgeSessionId}
|
|
74
|
+
CLAUDE_TREE="${CLAUDE_CONFIG_DIR:-$CONFIG_DIR/.claude}"
|
|
75
|
+
PROJECTS_ROOT="$CLAUDE_TREE/projects"
|
|
76
|
+
SESSIONS_DIR="$CLAUDE_TREE/sessions"
|
|
77
|
+
|
|
78
|
+
# --- Resolve account log dirs (best-effort; required only by stream-log modes) ---
|
|
79
|
+
# On rc-spawn installs data/accounts/<id>/ exists but carries no logs/ subdir,
|
|
80
|
+
# so this array is legitimately empty. The per-session JSONL <key> mode and the
|
|
81
|
+
# --scan-subagent-errors mode do NOT touch it; the stream-log tail/grep/
|
|
82
|
+
# explicit-type modes call require_account_logs() before iterating it, which
|
|
83
|
+
# preserves their original exit-2 "missing directory" contract.
|
|
53
84
|
ACCOUNTS_DIR="$INSTALL_DIR/data/accounts"
|
|
54
|
-
if [[ ! -d "$ACCOUNTS_DIR" ]]; then
|
|
55
|
-
echo "Error: accounts directory does not exist: $ACCOUNTS_DIR" >&2
|
|
56
|
-
exit 2
|
|
57
|
-
fi
|
|
58
|
-
|
|
59
|
-
# Glob for account dirs — may be >1 on multi-account devices
|
|
60
|
-
shopt -s nullglob
|
|
61
|
-
ACCOUNT_DIRS=("$ACCOUNTS_DIR"/*/)
|
|
62
|
-
shopt -u nullglob
|
|
63
|
-
if [[ ${#ACCOUNT_DIRS[@]} -eq 0 ]]; then
|
|
64
|
-
echo "Error: no account directories found in $ACCOUNTS_DIR" >&2
|
|
65
|
-
exit 2
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
# Build array of account log directories that actually exist.
|
|
69
85
|
ACCOUNT_LOG_DIRS=()
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
86
|
+
if [[ -d "$ACCOUNTS_DIR" ]]; then
|
|
87
|
+
shopt -s nullglob
|
|
88
|
+
for _adir in "$ACCOUNTS_DIR"/*/; do
|
|
89
|
+
[[ -d "${_adir}logs" ]] && ACCOUNT_LOG_DIRS+=("${_adir}logs")
|
|
90
|
+
done
|
|
91
|
+
shopt -u nullglob
|
|
76
92
|
fi
|
|
77
93
|
MULTI_ACCOUNT=$(( ${#ACCOUNT_LOG_DIRS[@]} > 1 ? 1 : 0 ))
|
|
78
94
|
|
|
95
|
+
# Stream-log modes require at least one account log dir; the JSONL <key> mode
|
|
96
|
+
# and the scan mode do not. Called at the entry of the stream-log readers so
|
|
97
|
+
# the configDir-only modes never dead-end on a missing data/accounts tree.
|
|
98
|
+
require_account_logs() {
|
|
99
|
+
if [[ ! -d "$ACCOUNTS_DIR" ]]; then
|
|
100
|
+
echo "Error: accounts directory does not exist: $ACCOUNTS_DIR" >&2
|
|
101
|
+
exit 2
|
|
102
|
+
fi
|
|
103
|
+
if [[ ${#ACCOUNT_LOG_DIRS[@]} -eq 0 ]]; then
|
|
104
|
+
echo "Error: no account log directories found in $ACCOUNTS_DIR" >&2
|
|
105
|
+
exit 2
|
|
106
|
+
fi
|
|
107
|
+
}
|
|
108
|
+
|
|
79
109
|
# Account ID suffix for headers — only shown when multiple accounts exist.
|
|
80
110
|
account_suffix() {
|
|
81
111
|
if [[ $MULTI_ACCOUNT -eq 1 ]]; then
|
|
@@ -139,7 +169,10 @@ SESSIONKEY_REGEX='^[a-zA-Z0-9_-]+$'
|
|
|
139
169
|
usage() {
|
|
140
170
|
cat >&2 <<EOF
|
|
141
171
|
Usage:
|
|
142
|
-
logs-read.sh <sessionKey
|
|
172
|
+
logs-read.sh <sessionKey> Merged JSONL session+subagent timeline
|
|
173
|
+
logs-read.sh <sessionKey> <type> Legacy per-account stream-log read
|
|
174
|
+
logs-read.sh --scan-subagent-errors [N] List subagent transcripts carrying an
|
|
175
|
+
is_error result (silent-failure audit)
|
|
143
176
|
logs-read.sh --tail [type] [lines] Tail most recent log of type
|
|
144
177
|
logs-read.sh --tail chat-attempts [min] Cross-grep edge.log + server.log
|
|
145
178
|
for the chat-ingress quartet
|
|
@@ -148,9 +181,14 @@ Usage:
|
|
|
148
181
|
Types: $VALID_TYPES
|
|
149
182
|
Default type: agent-stream | Default lines: 50 | Default chat-attempts minutes: 5
|
|
150
183
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
184
|
+
A bare <sessionKey> reads the Claude Code JSONL transcript tree under the
|
|
185
|
+
configDir (CLAUDE_CONFIG_DIR, default \$HOME/.<brand>/.claude): it accepts a
|
|
186
|
+
claude.ai session_<id>, its bare suffix, or the intrinsic <uuid> (or a uuid
|
|
187
|
+
prefix), and prints the parent transcript merged with every subagent
|
|
188
|
+
transcript, flagging subagent is_error results inline. An explicit <type>
|
|
189
|
+
keeps the legacy per-account stream-log read.
|
|
190
|
+
Per-session stream-log types (agent-stream, system, session, error, public)
|
|
191
|
+
require sessionKey; platform-scoped types (server, vnc, heartbeat) ignore the id.
|
|
154
192
|
chat-attempts is --tail-only — it aggregates the four ingress checkpoints
|
|
155
193
|
([edge-admin] inbound, [admin-auth] outcome, [chat-route] entered,
|
|
156
194
|
[edge-admin] outcome) into one timeline.
|
|
@@ -305,6 +343,10 @@ per_conversation_mode() {
|
|
|
305
343
|
;;
|
|
306
344
|
esac
|
|
307
345
|
|
|
346
|
+
# Explicit-type per-session reads come from the per-account stream logs;
|
|
347
|
+
# preserve the original exit-2 contract when that tree is absent.
|
|
348
|
+
require_account_logs
|
|
349
|
+
|
|
308
350
|
local prefix
|
|
309
351
|
prefix=$(prefix_for_type "$filter_type")
|
|
310
352
|
if [[ -z "$prefix" ]]; then
|
|
@@ -369,6 +411,9 @@ grep_mode() {
|
|
|
369
411
|
exit 2
|
|
370
412
|
fi
|
|
371
413
|
|
|
414
|
+
# grep mode reads per-account stream logs; preserve its missing-dir contract.
|
|
415
|
+
require_account_logs
|
|
416
|
+
|
|
372
417
|
if [[ -n "$filter_type" ]]; then
|
|
373
418
|
validate_type "$filter_type"
|
|
374
419
|
fi
|
|
@@ -479,6 +524,7 @@ tail_mode() {
|
|
|
479
524
|
fi
|
|
480
525
|
|
|
481
526
|
if [[ "$log_type" == "heartbeat" ]]; then
|
|
527
|
+
require_account_logs
|
|
482
528
|
local hb_file=""
|
|
483
529
|
for log_dir in "${ACCOUNT_LOG_DIRS[@]}"; do
|
|
484
530
|
local candidate="${log_dir}/check-due-events.log"
|
|
@@ -520,6 +566,10 @@ tail_mode() {
|
|
|
520
566
|
exit 0
|
|
521
567
|
fi
|
|
522
568
|
|
|
569
|
+
# Reached only for per-session stream-log types (agent-stream/error/session/
|
|
570
|
+
# public/mcp); these live under the per-account log dirs.
|
|
571
|
+
require_account_logs
|
|
572
|
+
|
|
523
573
|
local prefix
|
|
524
574
|
prefix=$(prefix_for_type "$log_type")
|
|
525
575
|
|
|
@@ -550,6 +600,349 @@ tail_mode() {
|
|
|
550
600
|
exit 0
|
|
551
601
|
}
|
|
552
602
|
|
|
603
|
+
# === rc-spawn JSONL retrieval (Task 585) ===================================
|
|
604
|
+
# The bare <key> mode and --scan-subagent-errors read the Claude Code JSONL
|
|
605
|
+
# transcript tree under the configDir, not the per-account stream logs.
|
|
606
|
+
# python3 is the JSON dependency, deliberately not node: the systemd unit
|
|
607
|
+
# PATH includes /usr/bin but not nvm's node bin, and python3 is already
|
|
608
|
+
# required by setup-account.sh / seed-neo4j.sh, so this survives a minimal
|
|
609
|
+
# non-interactive SSH PATH. argv[1] selects the mode (timeline|scan); the
|
|
610
|
+
# program is fed via -c so logs-read.sh stays a single self-contained file.
|
|
611
|
+
JSONL_PY=$(cat <<'PY'
|
|
612
|
+
import sys, os, re, json, glob
|
|
613
|
+
|
|
614
|
+
MODE = sys.argv[1] if len(sys.argv) > 1 else ''
|
|
615
|
+
|
|
616
|
+
UUID_RE = re.compile(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', re.I)
|
|
617
|
+
HEXPREFIX_RE = re.compile(r'^[0-9a-f]{8,}$', re.I)
|
|
618
|
+
|
|
619
|
+
def clip(s, n):
|
|
620
|
+
if not isinstance(s, str):
|
|
621
|
+
s = '' if s is None else str(s)
|
|
622
|
+
s = s.replace('\n', ' ').strip()
|
|
623
|
+
return s if len(s) <= n else s[:n-1] + '…'
|
|
624
|
+
|
|
625
|
+
def load_jsonl(path):
|
|
626
|
+
rows = []
|
|
627
|
+
try:
|
|
628
|
+
with open(path, 'r', errors='replace') as f:
|
|
629
|
+
for line in f:
|
|
630
|
+
line = line.strip()
|
|
631
|
+
if not line:
|
|
632
|
+
continue
|
|
633
|
+
try:
|
|
634
|
+
rows.append(json.loads(line))
|
|
635
|
+
except Exception:
|
|
636
|
+
continue
|
|
637
|
+
except OSError:
|
|
638
|
+
pass
|
|
639
|
+
return rows
|
|
640
|
+
|
|
641
|
+
def load_json(path):
|
|
642
|
+
try:
|
|
643
|
+
with open(path, 'r', errors='replace') as f:
|
|
644
|
+
return json.load(f)
|
|
645
|
+
except Exception:
|
|
646
|
+
return None
|
|
647
|
+
|
|
648
|
+
def parent_jsonl_exact(projects_root, uuid):
|
|
649
|
+
for p in glob.glob(os.path.join(projects_root, '*', uuid + '.jsonl')):
|
|
650
|
+
return p
|
|
651
|
+
for p in glob.glob(os.path.join(projects_root, '*', 'archive', uuid + '.jsonl')):
|
|
652
|
+
return p
|
|
653
|
+
return None
|
|
654
|
+
|
|
655
|
+
def parent_jsonls_by_prefix(projects_root, prefix):
|
|
656
|
+
out = set()
|
|
657
|
+
for p in glob.glob(os.path.join(projects_root, '*', prefix + '*.jsonl')):
|
|
658
|
+
if UUID_RE.match(os.path.basename(p)[:-6]):
|
|
659
|
+
out.add(p)
|
|
660
|
+
return sorted(out)
|
|
661
|
+
|
|
662
|
+
def agent_type(af):
|
|
663
|
+
o = load_json(af[:-len('.jsonl')] + '.meta.json')
|
|
664
|
+
if isinstance(o, dict) and isinstance(o.get('agentType'), str) and o['agentType']:
|
|
665
|
+
return o['agentType']
|
|
666
|
+
return None
|
|
667
|
+
|
|
668
|
+
def fmt_input(d):
|
|
669
|
+
if isinstance(d, dict):
|
|
670
|
+
for k in ('command', 'file_path', 'pattern', 'path', 'description', 'prompt'):
|
|
671
|
+
v = d.get(k)
|
|
672
|
+
if isinstance(v, str) and v:
|
|
673
|
+
return '%s=%s' % (k, v)
|
|
674
|
+
try:
|
|
675
|
+
return json.dumps(d, ensure_ascii=False)
|
|
676
|
+
except Exception:
|
|
677
|
+
return str(d)
|
|
678
|
+
return '' if d is None else str(d)
|
|
679
|
+
|
|
680
|
+
def result_text(block, row):
|
|
681
|
+
c = block.get('content')
|
|
682
|
+
if isinstance(c, str) and c.strip():
|
|
683
|
+
return c
|
|
684
|
+
if isinstance(c, list):
|
|
685
|
+
parts = [x.get('text') if isinstance(x, dict) else x for x in c]
|
|
686
|
+
joined = ' '.join(p for p in parts if isinstance(p, str))
|
|
687
|
+
if joined.strip():
|
|
688
|
+
return joined
|
|
689
|
+
tur = row.get('toolUseResult')
|
|
690
|
+
return tur if isinstance(tur, str) else ''
|
|
691
|
+
|
|
692
|
+
# Map a claude.ai session key to the local intrinsic <uuid> + its parent JSONL.
|
|
693
|
+
# Order: (a) the key already IS a uuid (or a hex prefix of one) on disk;
|
|
694
|
+
# (b) a sessions/<pid>.json whose bridgeSessionId matches (live/recent);
|
|
695
|
+
# (c) a <uuid>.meta.json whose bridgeIds carries the suffix (persistent, so an
|
|
696
|
+
# ENDED session whose PID file was deleted still resolves); (d) content grep
|
|
697
|
+
# of top-level transcripts (last resort). Returns (uuid, parent_path, via) or
|
|
698
|
+
# (None, None, reason).
|
|
699
|
+
def resolve_uuid(projects_root, sessions_dir, key):
|
|
700
|
+
suffix = key[len('session_'):] if key.startswith('session_') else key
|
|
701
|
+
candidates = [key] if key == suffix else [key, suffix]
|
|
702
|
+
for c in candidates:
|
|
703
|
+
p = parent_jsonl_exact(projects_root, c)
|
|
704
|
+
if p:
|
|
705
|
+
return (os.path.basename(p)[:-6], p, 'uuid-exact')
|
|
706
|
+
if HEXPREFIX_RE.match(c):
|
|
707
|
+
hits = parent_jsonls_by_prefix(projects_root, c)
|
|
708
|
+
if len(hits) == 1:
|
|
709
|
+
return (os.path.basename(hits[0])[:-6], hits[0], 'uuid-prefix')
|
|
710
|
+
if len(hits) > 1:
|
|
711
|
+
return (None, None, 'ambiguous-prefix candidates=%d' % len(hits))
|
|
712
|
+
for pf in glob.glob(os.path.join(sessions_dir, '*.json')):
|
|
713
|
+
o = load_json(pf)
|
|
714
|
+
if not isinstance(o, dict):
|
|
715
|
+
continue
|
|
716
|
+
sid, bsid = o.get('sessionId'), o.get('bridgeSessionId')
|
|
717
|
+
if not (isinstance(sid, str) and sid):
|
|
718
|
+
continue
|
|
719
|
+
if isinstance(bsid, str) and bsid and (
|
|
720
|
+
bsid == key or bsid == 'session_' + suffix or bsid.replace('session_', '') == suffix
|
|
721
|
+
):
|
|
722
|
+
return (sid, parent_jsonl_exact(projects_root, sid), 'pid-bridge')
|
|
723
|
+
for mf in glob.glob(os.path.join(projects_root, '*', '*.meta.json')):
|
|
724
|
+
o = load_json(mf)
|
|
725
|
+
if isinstance(o, dict) and isinstance(o.get('bridgeIds'), list) and suffix in o['bridgeIds']:
|
|
726
|
+
uuid = os.path.basename(mf)[:-len('.meta.json')]
|
|
727
|
+
if UUID_RE.match(uuid):
|
|
728
|
+
return (uuid, parent_jsonl_exact(projects_root, uuid), 'bridgeids')
|
|
729
|
+
# Match the verbatim key (the full session_<suffix> the operator passed, or
|
|
730
|
+
# the bare token) — not a loose bare-suffix substring, which could collide
|
|
731
|
+
# with unrelated body text. Refuse on multiple matches rather than silently
|
|
732
|
+
# pick the wrong session (faithfulness over a best-effort guess).
|
|
733
|
+
matches = []
|
|
734
|
+
for p in glob.glob(os.path.join(projects_root, '*', '*.jsonl')):
|
|
735
|
+
if not UUID_RE.match(os.path.basename(p)[:-6]):
|
|
736
|
+
continue
|
|
737
|
+
try:
|
|
738
|
+
with open(p, 'r', errors='replace') as f:
|
|
739
|
+
blob = f.read()
|
|
740
|
+
except OSError:
|
|
741
|
+
continue
|
|
742
|
+
if key in blob:
|
|
743
|
+
matches.append(p)
|
|
744
|
+
if len(matches) == 1:
|
|
745
|
+
return (os.path.basename(matches[0])[:-6], matches[0], 'content-grep')
|
|
746
|
+
if len(matches) > 1:
|
|
747
|
+
return (None, None, 'ambiguous-content-match candidates=%d' % len(matches))
|
|
748
|
+
return (None, None, 'not-found')
|
|
749
|
+
|
|
750
|
+
def subagent_dir(projects_root, uuid):
|
|
751
|
+
for d in glob.glob(os.path.join(projects_root, '*', uuid, 'subagents')):
|
|
752
|
+
return d
|
|
753
|
+
return None
|
|
754
|
+
|
|
755
|
+
# Append (ts, source, kind, text) tuples for one transcript. tool_use_id is
|
|
756
|
+
# matched back to the assistant tool_use that issued it, so an is_error
|
|
757
|
+
# tool_result names the failing tool. is_sub distinguishes a subagent's
|
|
758
|
+
# failure (the task's target, flagged 'SUBAGENT ERROR' and counted) from the
|
|
759
|
+
# parent session's own tool error (shown plainly, never counted as a subagent
|
|
760
|
+
# error). Returns the count of SUBAGENT errors found (0 for the parent).
|
|
761
|
+
def collect_events(path, source, events, is_sub):
|
|
762
|
+
rows = load_jsonl(path)
|
|
763
|
+
id2tool = {}
|
|
764
|
+
for r in rows:
|
|
765
|
+
if not isinstance(r, dict):
|
|
766
|
+
continue
|
|
767
|
+
msg = r.get('message') if isinstance(r.get('message'), dict) else {}
|
|
768
|
+
content = msg.get('content')
|
|
769
|
+
if isinstance(content, list):
|
|
770
|
+
for b in content:
|
|
771
|
+
if isinstance(b, dict) and b.get('type') == 'tool_use' and b.get('id'):
|
|
772
|
+
id2tool[b['id']] = b.get('name')
|
|
773
|
+
n_err = 0
|
|
774
|
+
for r in rows:
|
|
775
|
+
if not isinstance(r, dict):
|
|
776
|
+
continue
|
|
777
|
+
ts = r.get('timestamp') or ''
|
|
778
|
+
t = r.get('type')
|
|
779
|
+
msg = r.get('message') if isinstance(r.get('message'), dict) else {}
|
|
780
|
+
content = msg.get('content')
|
|
781
|
+
if t == 'assistant':
|
|
782
|
+
if isinstance(content, list):
|
|
783
|
+
for b in content:
|
|
784
|
+
if not isinstance(b, dict):
|
|
785
|
+
continue
|
|
786
|
+
if b.get('type') == 'text' and isinstance(b.get('text'), str) and b['text'].strip():
|
|
787
|
+
events.append((ts, source, 'text', 'assistant: ' + clip(b['text'], 160)))
|
|
788
|
+
elif b.get('type') == 'tool_use':
|
|
789
|
+
events.append((ts, source, 'tool', 'tool_use: %s(%s)' % (b.get('name'), clip(fmt_input(b.get('input')), 120))))
|
|
790
|
+
elif isinstance(content, str) and content.strip():
|
|
791
|
+
events.append((ts, source, 'text', 'assistant: ' + clip(content, 160)))
|
|
792
|
+
elif t == 'user':
|
|
793
|
+
if isinstance(content, list):
|
|
794
|
+
for b in content:
|
|
795
|
+
if isinstance(b, dict) and b.get('type') == 'tool_result' and b.get('is_error'):
|
|
796
|
+
tool = id2tool.get(b.get('tool_use_id'))
|
|
797
|
+
flag = '‼ SUBAGENT ERROR' if is_sub else '‼ tool error'
|
|
798
|
+
events.append((ts, source, 'error', '%s tool=%s error="%s"' % (flag, tool or '?', clip(result_text(b, r), 200))))
|
|
799
|
+
if is_sub:
|
|
800
|
+
n_err += 1
|
|
801
|
+
elif isinstance(content, str) and content.strip():
|
|
802
|
+
events.append((ts, source, 'text', 'user: ' + clip(content, 160)))
|
|
803
|
+
return n_err
|
|
804
|
+
|
|
805
|
+
def timeline(projects_root, sessions_dir, key):
|
|
806
|
+
uuid, parent_path, via = resolve_uuid(projects_root, sessions_dir, key)
|
|
807
|
+
if not uuid:
|
|
808
|
+
sys.stderr.write('-- trailer: key=%s resolved=no reason=%s\n' % (key, via))
|
|
809
|
+
return 1
|
|
810
|
+
events = []
|
|
811
|
+
if parent_path and os.path.exists(parent_path):
|
|
812
|
+
collect_events(parent_path, 'parent', events, False)
|
|
813
|
+
sdir = subagent_dir(projects_root, uuid)
|
|
814
|
+
sub_count = err_count = 0
|
|
815
|
+
if sdir:
|
|
816
|
+
for af in sorted(glob.glob(os.path.join(sdir, 'agent-*.jsonl'))):
|
|
817
|
+
sub_count += 1
|
|
818
|
+
err_count += collect_events(af, agent_type(af) or os.path.basename(af)[:-6], events, True)
|
|
819
|
+
events.sort(key=lambda e: e[0])
|
|
820
|
+
print('# session %s (resolved via %s)' % (uuid, via))
|
|
821
|
+
print('# parent: %s' % (parent_path or '(no parent transcript on disk)'))
|
|
822
|
+
print('# subagents: %d subagent-errors: %d' % (sub_count, err_count))
|
|
823
|
+
print('')
|
|
824
|
+
for ts, source, _kind, text in events:
|
|
825
|
+
print('%s [%s] %s' % (ts or ('-' * 24), source, text))
|
|
826
|
+
sys.stderr.write('-- trailer: key=%s resolved=%s uuid=%s subagents=%d errors=%d\n' % (key, via, uuid, sub_count, err_count))
|
|
827
|
+
return 0
|
|
828
|
+
|
|
829
|
+
def scan(projects_root, limit):
|
|
830
|
+
files = glob.glob(os.path.join(projects_root, '*', '*', 'subagents', 'agent-*.jsonl'))
|
|
831
|
+
if limit > 0:
|
|
832
|
+
files = sorted(files, key=os.path.getmtime, reverse=True)[:limit]
|
|
833
|
+
files = sorted(files)
|
|
834
|
+
findings = []
|
|
835
|
+
for af in files:
|
|
836
|
+
rows = load_jsonl(af)
|
|
837
|
+
id2tool = {}
|
|
838
|
+
for r in rows:
|
|
839
|
+
if not isinstance(r, dict):
|
|
840
|
+
continue
|
|
841
|
+
msg = r.get('message') if isinstance(r.get('message'), dict) else {}
|
|
842
|
+
content = msg.get('content')
|
|
843
|
+
if isinstance(content, list):
|
|
844
|
+
for b in content:
|
|
845
|
+
if isinstance(b, dict) and b.get('type') == 'tool_use' and b.get('id'):
|
|
846
|
+
id2tool[b['id']] = b.get('name')
|
|
847
|
+
errs = []
|
|
848
|
+
for r in rows:
|
|
849
|
+
if not isinstance(r, dict):
|
|
850
|
+
continue
|
|
851
|
+
msg = r.get('message') if isinstance(r.get('message'), dict) else {}
|
|
852
|
+
content = msg.get('content')
|
|
853
|
+
if isinstance(content, list):
|
|
854
|
+
for b in content:
|
|
855
|
+
if isinstance(b, dict) and b.get('type') == 'tool_result' and b.get('is_error'):
|
|
856
|
+
errs.append((id2tool.get(b.get('tool_use_id')), result_text(b, r)))
|
|
857
|
+
if errs:
|
|
858
|
+
parent_uuid = os.path.basename(os.path.dirname(os.path.dirname(af)))
|
|
859
|
+
findings.append((af, agent_type(af), parent_uuid, errs))
|
|
860
|
+
print('# subagent-error scan: %d transcript(s) scanned, %d with errors' % (len(files), len(findings)))
|
|
861
|
+
print('')
|
|
862
|
+
for af, atype, parent_uuid, errs in findings:
|
|
863
|
+
print('‼ %s agentType=%s parent=%s' % (os.path.basename(af), atype or '?', parent_uuid[:8]))
|
|
864
|
+
for tool, etext in errs:
|
|
865
|
+
low = (etext or '').lower()
|
|
866
|
+
extra = ' [exit-127 / command-not-found]' if ('command not found' in low or 'exit code 127' in low) else ''
|
|
867
|
+
print(' tool=%s error="%s"%s' % (tool or '?', clip(etext, 200), extra))
|
|
868
|
+
sys.stderr.write('-- trailer: scan scanned=%d with_errors=%d\n' % (len(files), len(findings)))
|
|
869
|
+
return 0
|
|
870
|
+
|
|
871
|
+
if MODE == 'timeline':
|
|
872
|
+
sys.exit(timeline(sys.argv[2], sys.argv[3], sys.argv[4]))
|
|
873
|
+
elif MODE == 'scan':
|
|
874
|
+
lim = 0
|
|
875
|
+
if len(sys.argv) > 3 and sys.argv[3]:
|
|
876
|
+
try:
|
|
877
|
+
lim = int(sys.argv[3])
|
|
878
|
+
except ValueError:
|
|
879
|
+
lim = 0
|
|
880
|
+
sys.exit(scan(sys.argv[2], lim))
|
|
881
|
+
else:
|
|
882
|
+
sys.stderr.write('jsonl helper: unknown mode %r\n' % MODE)
|
|
883
|
+
sys.exit(2)
|
|
884
|
+
PY
|
|
885
|
+
)
|
|
886
|
+
|
|
887
|
+
# Best-effort missing-on-resolve marker, mirroring per_conversation_mode so the
|
|
888
|
+
# writer-side existence contract still records a <key> miss on server.log.
|
|
889
|
+
emit_missing_on_resolve() {
|
|
890
|
+
local key="$1" reason="$2" ts
|
|
891
|
+
ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
892
|
+
echo "${ts} [log-tee] missing-on-resolve sessionKey=${key:0:8} surface=logs-read-sh reason=\"${reason}\"" >> "$SERVER_LOG" 2>/dev/null || true
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
# Bare <key> mode: resolve the claude.ai session key against the configDir
|
|
896
|
+
# Claude tree and print a merged parent+subagent timeline with every subagent
|
|
897
|
+
# is_error tool_result flagged inline.
|
|
898
|
+
jsonl_timeline_mode() {
|
|
899
|
+
local key="$1"
|
|
900
|
+
if [[ -z "$key" ]]; then
|
|
901
|
+
echo "Error: sessionKey cannot be empty" >&2
|
|
902
|
+
exit 2
|
|
903
|
+
fi
|
|
904
|
+
if [[ ! "$key" =~ $SESSIONKEY_REGEX ]]; then
|
|
905
|
+
echo "Error: sessionKey contains invalid characters (allowed: a-z, A-Z, 0-9, _, -)" >&2
|
|
906
|
+
exit 2
|
|
907
|
+
fi
|
|
908
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
909
|
+
echo "Error: python3 is required to read the JSONL session timeline" >&2
|
|
910
|
+
exit 2
|
|
911
|
+
fi
|
|
912
|
+
if [[ ! -d "$PROJECTS_ROOT" ]]; then
|
|
913
|
+
echo "Error: Claude project tree not found at $PROJECTS_ROOT" >&2
|
|
914
|
+
echo " (set CLAUDE_CONFIG_DIR if this install uses a non-default config dir)" >&2
|
|
915
|
+
emit_missing_on_resolve "$key" "projects-root-missing"
|
|
916
|
+
exit 1
|
|
917
|
+
fi
|
|
918
|
+
local rc=0
|
|
919
|
+
python3 -c "$JSONL_PY" timeline "$PROJECTS_ROOT" "$SESSIONS_DIR" "$key" || rc=$?
|
|
920
|
+
[[ $rc -ne 0 ]] && emit_missing_on_resolve "$key" "jsonl-not-resolved"
|
|
921
|
+
exit $rc
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
# Standing scan: list every subagents/agent-*.jsonl carrying an is_error
|
|
925
|
+
# tool_result, so a silently-failed delivery (e.g. exit-127 / command-not-found
|
|
926
|
+
# on a nonexistent tool name) is discoverable without reproduction. Optional N
|
|
927
|
+
# limits the scan to the N most-recently-modified subagent transcripts.
|
|
928
|
+
scan_subagent_errors_mode() {
|
|
929
|
+
local limit="${1:-}"
|
|
930
|
+
if [[ -n "$limit" && ! "$limit" =~ ^[0-9]+$ ]]; then
|
|
931
|
+
echo "Error: scan limit must be a number, got '$limit'" >&2
|
|
932
|
+
exit 2
|
|
933
|
+
fi
|
|
934
|
+
if ! command -v python3 >/dev/null 2>&1; then
|
|
935
|
+
echo "Error: python3 is required for the subagent-error scan" >&2
|
|
936
|
+
exit 2
|
|
937
|
+
fi
|
|
938
|
+
if [[ ! -d "$PROJECTS_ROOT" ]]; then
|
|
939
|
+
echo "Error: Claude project tree not found at $PROJECTS_ROOT" >&2
|
|
940
|
+
exit 1
|
|
941
|
+
fi
|
|
942
|
+
python3 -c "$JSONL_PY" scan "$PROJECTS_ROOT" "${limit:-}"
|
|
943
|
+
exit $?
|
|
944
|
+
}
|
|
945
|
+
|
|
553
946
|
if [[ $# -eq 0 ]]; then
|
|
554
947
|
usage
|
|
555
948
|
fi
|
|
@@ -564,6 +957,10 @@ case "$1" in
|
|
|
564
957
|
[[ $# -eq 0 ]] && usage
|
|
565
958
|
grep_mode "$1" "${2:-}"
|
|
566
959
|
;;
|
|
960
|
+
--scan-subagent-errors)
|
|
961
|
+
shift
|
|
962
|
+
scan_subagent_errors_mode "${1:-}"
|
|
963
|
+
;;
|
|
567
964
|
--help|-h)
|
|
568
965
|
usage
|
|
569
966
|
;;
|
|
@@ -572,6 +969,12 @@ case "$1" in
|
|
|
572
969
|
usage
|
|
573
970
|
;;
|
|
574
971
|
*)
|
|
575
|
-
|
|
972
|
+
# Bare <key> resolves the configDir JSONL timeline (Task 585). An explicit
|
|
973
|
+
# <key> <type> keeps the legacy per-account stream-log read unchanged.
|
|
974
|
+
if [[ -n "${2:-}" ]]; then
|
|
975
|
+
per_conversation_mode "$1" "$2"
|
|
976
|
+
else
|
|
977
|
+
jsonl_timeline_mode "$1"
|
|
978
|
+
fi
|
|
576
979
|
;;
|
|
577
980
|
esac
|
|
@@ -44,15 +44,22 @@ mkdir -p "$ACCOUNT_DIR/agents/admin" "$ACCOUNT_DIR/agents/public" "$ACCOUNT_DIR/
|
|
|
44
44
|
[ -d "$ACCOUNT_DIR/.git" ] || git init -q "$ACCOUNT_DIR"
|
|
45
45
|
|
|
46
46
|
# ------------------------------------------------------------------
|
|
47
|
-
# 2. Write project-level .claude/settings.json (
|
|
47
|
+
# 2. Write project-level .claude/settings.json (permissions + hooks)
|
|
48
48
|
# ------------------------------------------------------------------
|
|
49
|
-
# Task 453
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
49
|
+
# Task 453 removed `permissions.allow` because its short-prefix entries
|
|
50
|
+
# (mcp__memory__*, mcp__contacts__*, …) never matched the plugin-manifest
|
|
51
|
+
# long-prefix tool names Claude Code actually emits (mcp__plugin_memory_memory__…),
|
|
52
|
+
# so every Task-subagent plugin call was denied at use-time.
|
|
53
|
+
#
|
|
54
|
+
# Task 583 reinstates a `permissions` block — but as wildcard
|
|
55
|
+
# `allow: ["*"]` plus `defaultMode: "bypassPermissions"`. The wildcard
|
|
56
|
+
# bypasses the prefix-matching defect Task 453 was avoiding (a list of one
|
|
57
|
+
# entry that matches everything cannot mismatch), and `bypassPermissions`
|
|
58
|
+
# short-circuits Stage 1 of the local matcher so claude.ai/code sessions
|
|
59
|
+
# never fall through to the Anthropic remote auto-classifier (which routes
|
|
60
|
+
# Agent dispatch to `ask` and surfaces a permission prompt no unattended
|
|
61
|
+
# operator clicks). Per-agent frontmatter `tools:` lists remain the
|
|
62
|
+
# permission surface for what each subagent is allowed to invoke.
|
|
56
63
|
#
|
|
57
64
|
# Hook commands use $PLATFORM_ROOT (set by claude-agent.ts at spawn time)
|
|
58
65
|
# to locate scripts relative to the platform installation, not the account dir.
|
|
@@ -61,6 +68,11 @@ HOOKS_PATH="\$PLATFORM_ROOT/plugins/admin/hooks"
|
|
|
61
68
|
WRITER_CRAFT_HOOKS_PATH="\$PLATFORM_ROOT/../premium-plugins/writer-craft/hooks"
|
|
62
69
|
cat > "$ACCOUNT_SETTINGS" << SETTINGS_EOF
|
|
63
70
|
{
|
|
71
|
+
"permissions": {
|
|
72
|
+
"defaultMode": "bypassPermissions",
|
|
73
|
+
"allow": ["*"],
|
|
74
|
+
"deny": []
|
|
75
|
+
},
|
|
64
76
|
"hooks": {
|
|
65
77
|
"PreToolUse": [
|
|
66
78
|
{
|
|
@@ -99,7 +99,6 @@ export type AppendBlockResult = {
|
|
|
99
99
|
reason: 'host-unresolved' | 'identity-unresolved';
|
|
100
100
|
detail: string;
|
|
101
101
|
};
|
|
102
|
-
export declare const ATTACHMENT_CEILING_BYTES = 31457280;
|
|
103
102
|
export declare function composeAppendSystemPrompt(host: HostContext, sources: IdentitySources, extras?: ComposeExtras): AppendBlockResult;
|
|
104
103
|
export type IdentityForbiddenViolation = {
|
|
105
104
|
class: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../src/system-prompt.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"system-prompt.d.ts","sourceRoot":"","sources":["../src/system-prompt.ts"],"names":[],"mappings":"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"}
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
//
|
|
5
5
|
// <host> ... </host> — hostname, LAN IPv4, admin URL,
|
|
6
6
|
// tunnel URL (or "none")
|
|
7
|
-
// <
|
|
8
|
-
// —
|
|
9
|
-
//
|
|
10
|
-
//
|
|
7
|
+
// <file-delivery> ... </file-delivery>
|
|
8
|
+
// — how a produced file reaches the
|
|
9
|
+
// operator: write under output/,
|
|
10
|
+
// then state its location (Artefacts
|
|
11
|
+
// panel / operator file share). There
|
|
12
|
+
// is no send-to-chat tool.
|
|
11
13
|
// <identity> ... </identity> — verbatim contents of
|
|
12
14
|
// <accountDir>/agents/<role>/IDENTITY.md
|
|
13
15
|
// <soul> ... </soul> — verbatim contents of
|
|
@@ -57,7 +59,6 @@
|
|
|
57
59
|
// inline-coded so an operator can grep for it.
|
|
58
60
|
import { readFileSync } from 'node:fs';
|
|
59
61
|
import { join } from 'node:path';
|
|
60
|
-
export const ATTACHMENT_CEILING_BYTES = 31_457_280; // 30 MiB, mirrors the SDK uploader
|
|
61
62
|
function readIdentityFile(path) {
|
|
62
63
|
let content;
|
|
63
64
|
try {
|
|
@@ -152,9 +153,9 @@ function renderAppendBlock(host, identityContent, soulContent, extras) {
|
|
|
152
153
|
`admin-url: ${host.adminUrl}`,
|
|
153
154
|
`tunnel-url: ${tunnelLine}`,
|
|
154
155
|
'</host>',
|
|
155
|
-
'<
|
|
156
|
-
'
|
|
157
|
-
'</
|
|
156
|
+
'<file-delivery>',
|
|
157
|
+
'Files you produce are not auto-delivered into the conversation — there is no working send-to-chat tool on this surface, and a bare on-disk path is unreachable from the chat. To deliver a file: write it under the output/ directory in your working directory (any depth), then in your reply tell the operator its exact location — it appears in the Artefacts panel (open the maxy sidebar to download it) and lives at the full output/ path, reachable on the operator file share (e.g. Samba) if one is set up. Always state the complete path, never a vague "in the output directory".',
|
|
158
|
+
'</file-delivery>',
|
|
158
159
|
'<identity>',
|
|
159
160
|
identityContent,
|
|
160
161
|
'</identity>',
|
|
@@ -382,8 +383,8 @@ export function runSystemPromptSelfTest() {
|
|
|
382
383
|
const required = [
|
|
383
384
|
'<host>',
|
|
384
385
|
'</host>',
|
|
385
|
-
'<
|
|
386
|
-
'</
|
|
386
|
+
'<file-delivery>',
|
|
387
|
+
'</file-delivery>',
|
|
387
388
|
'<identity>',
|
|
388
389
|
'</identity>',
|
|
389
390
|
'<soul>',
|