@rubytech/create-realagent-code 0.1.216 → 0.1.221
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/lib/mcp-eager/dist/index.d.ts +0 -55
- package/payload/platform/lib/mcp-eager/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/mcp-eager/dist/index.js +0 -76
- package/payload/platform/lib/mcp-eager/dist/index.js.map +1 -1
- package/payload/platform/lib/mcp-eager/src/index.ts +0 -101
- package/payload/platform/plugins/admin/PLUGIN.md +2 -3
- package/payload/platform/plugins/admin/hooks/__tests__/hook-emit-stale-lock-ttl.test.sh +102 -0
- package/payload/platform/plugins/admin/hooks/archive-ingest-surface-gate.sh +1 -2
- package/payload/platform/plugins/admin/hooks/lib/hook-emit.sh +32 -3
- package/payload/platform/plugins/admin/hooks/post-tool-use-agent.sh +20 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +2 -2
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +18 -18
- package/payload/platform/plugins/admin/skills/plugin-management/SKILL.md +2 -2
- package/payload/platform/plugins/browser/mcp/dist/index.js +19 -19
- package/payload/platform/plugins/browser/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/docs/references/platform.md +10 -17
- package/payload/platform/plugins/docs/references/troubleshooting.md +7 -0
- package/payload/platform/plugins/email/mcp/dist/index.js +12 -12
- package/payload/platform/plugins/email/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/index.js +8 -8
- package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/memory/references/graph-primitives.md +2 -2
- package/payload/platform/plugins/url-get/mcp/dist/index.js +2 -2
- package/payload/platform/plugins/url-get/mcp/dist/index.js.map +1 -1
- package/payload/platform/scripts/check-no-esm-require.mjs +3 -0
- package/payload/platform/scripts/setup-account.sh +0 -15
- package/payload/platform/services/claude-session-manager/dist/auth-snapshot.d.ts +4 -0
- package/payload/platform/services/claude-session-manager/dist/auth-snapshot.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/auth-snapshot.js +50 -0
- package/payload/platform/services/claude-session-manager/dist/auth-snapshot.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts +0 -14
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +44 -71
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +12 -31
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.d.ts +3 -0
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.js +55 -1
- package/payload/platform/services/claude-session-manager/dist/jsonl-tail.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +27 -20
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts +23 -17
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +94 -73
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-life.d.ts +4 -0
- package/payload/platform/services/claude-session-manager/dist/rc-life.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/rc-life.js +16 -0
- package/payload/platform/services/claude-session-manager/dist/rc-life.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts +12 -46
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.js +24 -73
- package/payload/platform/services/claude-session-manager/dist/systemd-scope.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +3 -3
- package/payload/server/public/assets/AdminShell-BcxEZnpu.js +1 -0
- package/payload/server/public/assets/{admin-DjGGOx2K.js → admin-DMo5DWKH.js} +1 -1
- package/payload/server/public/assets/{data-WBEvtRV-.js → data-D6IQdm0R.js} +1 -1
- package/payload/server/public/assets/{graph-DsOLsIPf.js → graph-PkZKVkWD.js} +1 -1
- package/payload/server/public/data.html +2 -2
- package/payload/server/public/graph.html +2 -2
- package/payload/server/public/index.html +2 -2
- package/payload/server/server.js +49 -12
- package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-admin-tool-gate.test.sh +0 -169
- package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-base64-guard.test.sh +0 -204
- package/payload/platform/plugins/admin/hooks/__tests__/pre-tool-use-memory-write-passthrough.test.sh +0 -118
- package/payload/platform/plugins/admin/hooks/pre-tool-use.sh +0 -409
- package/payload/server/public/assets/AdminShell-CPWbJ_I8.js +0 -1
|
@@ -1,409 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# PreToolUse hook — enforces agent tool boundaries and approval gating.
|
|
3
|
-
# Usage: pre-tool-use.sh <agent-type>
|
|
4
|
-
# Receives tool call JSON on stdin (from Claude Code hook protocol).
|
|
5
|
-
# Exit 0 = allow, exit 2 = block.
|
|
6
|
-
|
|
7
|
-
set -uo pipefail
|
|
8
|
-
|
|
9
|
-
AGENT_TYPE="${1:-public}"
|
|
10
|
-
|
|
11
|
-
# Source the propagation emitter (Task 560). The library is fail-open;
|
|
12
|
-
# any error path inside it logs to server.log and returns 0, so sourcing
|
|
13
|
-
# never blocks the hook's primary allow/block contract.
|
|
14
|
-
HOOKS_LIB_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)/lib"
|
|
15
|
-
# shellcheck disable=SC1091
|
|
16
|
-
[ -f "$HOOKS_LIB_DIR/hook-emit.sh" ] && source "$HOOKS_LIB_DIR/hook-emit.sh"
|
|
17
|
-
|
|
18
|
-
# Read stdin — fail closed if unavailable
|
|
19
|
-
if [ -t 0 ]; then
|
|
20
|
-
# No INPUT yet; propagate with agentId=unknown so the regression is visible.
|
|
21
|
-
if declare -F hook_emit_decision >/dev/null 2>&1; then
|
|
22
|
-
hook_emit_decision "unknown" "admin-tool-gate" "?" "block" \
|
|
23
|
-
"stdin-missing" "Blocked: Cannot determine tool call (no stdin). Failing closed." 2 0
|
|
24
|
-
fi
|
|
25
|
-
echo "Blocked: Cannot determine tool call (no stdin). Failing closed." >&2
|
|
26
|
-
exit 2
|
|
27
|
-
fi
|
|
28
|
-
INPUT=$(cat)
|
|
29
|
-
TOOL_NAME=$(echo "$INPUT" | grep -o '"tool_name":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
30
|
-
|
|
31
|
-
# Session id for propagation. Empty / parse-failure routes to "unknown" —
|
|
32
|
-
# the propagator emits the record under agentId=unknown rather than dropping.
|
|
33
|
-
SESSION_ID=$(printf '%s' "$INPUT" | python3 -c '
|
|
34
|
-
import sys, json
|
|
35
|
-
try:
|
|
36
|
-
print(json.load(sys.stdin).get("session_id", "") or "unknown")
|
|
37
|
-
except Exception:
|
|
38
|
-
print("unknown")
|
|
39
|
-
' 2>/dev/null || echo "unknown")
|
|
40
|
-
|
|
41
|
-
# ---------------------------------------------------------------------------
|
|
42
|
-
# Admin agent — code directory protection + approval gating
|
|
43
|
-
# ---------------------------------------------------------------------------
|
|
44
|
-
if [ "$AGENT_TYPE" = "admin" ]; then
|
|
45
|
-
|
|
46
|
-
# ── Orchestration-only authoring + research block (Task 529, 559, 566) ───
|
|
47
|
-
# The admin seat is orchestration + clarification + delivery only.
|
|
48
|
-
# Write/Edit/MultiEdit/Bash/WebFetch/WebSearch are specialist-owned —
|
|
49
|
-
# content-producer for authoring, research-assistant for research, the
|
|
50
|
-
# shell tools for whichever specialist owns the workflow that needs them.
|
|
51
|
-
# In-window Task subagents must keep these tools (Task 222 regression
|
|
52
|
-
# class). Maxy MCP `Agent`-spawned specialists are separate top-level
|
|
53
|
-
# `claude rc` processes — they have no `parent_tool_use_id` but pty-spawner
|
|
54
|
-
# stamps `MAXY_SPECIALIST=<name>` on the child env (pty-spawner.ts:1601).
|
|
55
|
-
#
|
|
56
|
-
# Discriminator (union of positive markers, never path-shape inference):
|
|
57
|
-
# parent_tool_use_id non-empty → in-window Task subagent (allow)
|
|
58
|
-
# $MAXY_SPECIALIST non-empty → maxy MCP Agent specialist (allow)
|
|
59
|
-
# $MAXY_SESSION_ROLE = admin → admin-direct (block, orchestration-only)
|
|
60
|
-
# none of the above → fail closed (missing-marker)
|
|
61
|
-
#
|
|
62
|
-
# Adding a future spawn shape that lands neither marker surfaces as a loud
|
|
63
|
-
# `reason=missing-marker` block, not a silent passthrough or a silent
|
|
64
|
-
# admin-direct block on legitimate specialist work.
|
|
65
|
-
case "$TOOL_NAME" in
|
|
66
|
-
Write|Edit|MultiEdit|Bash|WebFetch|WebSearch)
|
|
67
|
-
PARENT_TOOL_USE_ID=$(echo "$INPUT" | python3 -c '
|
|
68
|
-
import sys, json
|
|
69
|
-
try:
|
|
70
|
-
v = json.load(sys.stdin).get("parent_tool_use_id", "")
|
|
71
|
-
print(v if isinstance(v, str) else "")
|
|
72
|
-
except Exception:
|
|
73
|
-
print("")
|
|
74
|
-
' 2>/dev/null)
|
|
75
|
-
SPECIALIST="${MAXY_SPECIALIST:-}"
|
|
76
|
-
SESSION_ROLE="${MAXY_SESSION_ROLE:-}"
|
|
77
|
-
if [ -n "$PARENT_TOOL_USE_ID" ] || [ -n "$SPECIALIST" ]; then
|
|
78
|
-
# Subagent — passthrough. Tool name resolves on the subagent's
|
|
79
|
-
# own frontmatter `tools:` allowlist downstream.
|
|
80
|
-
SOURCE="task-subagent"
|
|
81
|
-
if [ -n "$SPECIALIST" ] && [ -n "$PARENT_TOOL_USE_ID" ]; then
|
|
82
|
-
SOURCE="task-subagent+mcp-specialist"
|
|
83
|
-
elif [ -n "$SPECIALIST" ]; then
|
|
84
|
-
SOURCE="mcp-specialist"
|
|
85
|
-
fi
|
|
86
|
-
echo "[admin-tool-gate] role=subagent parent_tool_use_id=${PARENT_TOOL_USE_ID:--} specialist=${SPECIALIST:--} session_role=${SESSION_ROLE:--} tool=${TOOL_NAME} decision=allow source=${SOURCE}" >&2
|
|
87
|
-
elif [ "$SESSION_ROLE" = "admin" ]; then
|
|
88
|
-
# Admin direct invocation — refuse with dispatch hint.
|
|
89
|
-
case "$TOOL_NAME" in
|
|
90
|
-
Write|Edit|MultiEdit)
|
|
91
|
-
OWNER="content-producer"
|
|
92
|
-
;;
|
|
93
|
-
Bash)
|
|
94
|
-
OWNER="the specialist that owns the workflow needing shell access"
|
|
95
|
-
;;
|
|
96
|
-
WebFetch|WebSearch)
|
|
97
|
-
OWNER="research-assistant"
|
|
98
|
-
;;
|
|
99
|
-
esac
|
|
100
|
-
echo "[admin-tool-gate] role=admin parent_tool_use_id=- specialist=- session_role=admin tool=${TOOL_NAME} decision=block reason=orchestration-only owner=${OWNER}" >&2
|
|
101
|
-
hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
|
|
102
|
-
"orchestration-only" \
|
|
103
|
-
"Blocked: \`${TOOL_NAME}\` is not callable from the admin seat. The admin seat is orchestration + clarification + delivery only — authoring, shell work, and web research are owned by specialists.
|
|
104
|
-
Dispatch the work to ${OWNER} via the Agent tool."
|
|
105
|
-
else
|
|
106
|
-
# No positive marker — fail closed. A new spawn shape that lands
|
|
107
|
-
# neither parent_tool_use_id nor MAXY_SPECIALIST and is not the
|
|
108
|
-
# admin seat should surface visibly, not silently pass or silently
|
|
109
|
-
# block under the wrong reason.
|
|
110
|
-
echo "[admin-tool-gate] role=unknown parent_tool_use_id=- specialist=- session_role=${SESSION_ROLE:--} tool=${TOOL_NAME} decision=block reason=missing-marker" >&2
|
|
111
|
-
hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
|
|
112
|
-
"missing-marker" \
|
|
113
|
-
"Blocked: \`${TOOL_NAME}\` call carries no positive role marker (no parent_tool_use_id, no MAXY_SPECIALIST, MAXY_SESSION_ROLE is not 'admin'). Failing closed so a new spawn shape cannot silently bypass orchestration gating."
|
|
114
|
-
fi
|
|
115
|
-
;;
|
|
116
|
-
esac
|
|
117
|
-
|
|
118
|
-
# ── Code directory protection ────────────────────────────────────────────
|
|
119
|
-
case "$TOOL_NAME" in
|
|
120
|
-
Write|Edit)
|
|
121
|
-
FILE_PATH=$(echo "$INPUT" | grep -o '"file_path":"[^"]*"' | head -1 | cut -d'"' -f4)
|
|
122
|
-
case "$FILE_PATH" in
|
|
123
|
-
# Platform UI source (platform/ui/) — source lives here, not on device
|
|
124
|
-
*/platform/ui/app/*|*/platform/ui/lib/*|*/platform/ui/*.ts|*/platform/ui/*.tsx|*/platform/ui/*.mjs)
|
|
125
|
-
hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
|
|
126
|
-
"code-dir-protection-ui" \
|
|
127
|
-
"Blocked: Admin agent cannot modify platform UI source at $FILE_PATH"
|
|
128
|
-
;;
|
|
129
|
-
# Compiled MCP servers, hooks, and scripts — what actually ships to device
|
|
130
|
-
*/platform/plugins/*/mcp/dist/*|*/platform/plugins/*/hooks/*|*/platform/scripts/*)
|
|
131
|
-
hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
|
|
132
|
-
"code-dir-protection-platform" \
|
|
133
|
-
"Blocked: Admin agent cannot modify platform code at $FILE_PATH"
|
|
134
|
-
;;
|
|
135
|
-
# Entitlement library — verifier source, compiled output, vendored
|
|
136
|
-
# pubkey, hash file, and any signed entitlement.json. Tampering here is the
|
|
137
|
-
# agent path to revenue bypass; the legitimate write path is the Rubytech
|
|
138
|
-
# issuance endpoint, never the agent's filesystem.
|
|
139
|
-
# Patterns intentionally cover both absolute (*/...) and relative
|
|
140
|
-
# (no leading slash) paths so an agent can't bypass via cwd-relative writes.
|
|
141
|
-
*/platform/lib/entitlement/*|platform/lib/entitlement/*|*/entitlement.json|entitlement.json)
|
|
142
|
-
echo "[entitlement] tool-deny: tool=${TOOL_NAME} path=${FILE_PATH} field=entitlement-file" >&2
|
|
143
|
-
hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
|
|
144
|
-
"entitlement-file" \
|
|
145
|
-
"Blocked: Admin agent cannot modify entitlement files at $FILE_PATH. Effective tier and purchasedPlugins derive from a Rubytech-signed payload."
|
|
146
|
-
;;
|
|
147
|
-
# account.json — agent must use the account-update
|
|
148
|
-
# MCP tool (or plugin-toggle-enabled for enabledPlugins changes), which
|
|
149
|
-
# whitelists editable fields server-side. Direct Edit/Write bypasses
|
|
150
|
-
# that whitelist; deny unconditionally including cwd-relative paths.
|
|
151
|
-
# adds */account.json and *account.json as a backstop so any
|
|
152
|
-
# layout the named patterns miss is still caught — the Adam Mackay
|
|
153
|
-
# incident showed a tier upgrade via raw Edit on account.json reach
|
|
154
|
-
# the disk, exactly the failure mode this hook exists to prevent.
|
|
155
|
-
*/data/accounts/*/account.json|*/config/accounts/*/account.json|data/accounts/*/account.json|config/accounts/*/account.json|*/account.json|*account.json|account.json)
|
|
156
|
-
echo "[entitlement] tool-deny: tool=${TOOL_NAME} path=${FILE_PATH} field=account-json" >&2
|
|
157
|
-
hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
|
|
158
|
-
"account-json" \
|
|
159
|
-
"Blocked: Admin agent cannot edit account.json directly. Use the account-update or plugin-toggle-enabled MCP tools — they whitelist editable fields server-side and exclude tier and purchasedPlugins by design."
|
|
160
|
-
;;
|
|
161
|
-
# Pending action queue — only the hook and MCP tools may write here
|
|
162
|
-
*/pending-actions/*)
|
|
163
|
-
hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "${TOOL_NAME}" \
|
|
164
|
-
"pending-actions" \
|
|
165
|
-
"Blocked: Admin agent cannot modify pending action files directly"
|
|
166
|
-
;;
|
|
167
|
-
esac
|
|
168
|
-
;;
|
|
169
|
-
Bash)
|
|
170
|
-
# Block shell commands referencing protected code directories or pending actions
|
|
171
|
-
case "$INPUT" in
|
|
172
|
-
*"platform/ui/app/"*|*"platform/ui/lib/"*|*"platform/plugins/"*"hooks/"*|*"platform/scripts/"*)
|
|
173
|
-
hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "Bash" \
|
|
174
|
-
"code-dir-protection-bash" \
|
|
175
|
-
"Blocked: Admin agent cannot run shell commands against code directories"
|
|
176
|
-
;;
|
|
177
|
-
# Entitlement files via shell — same surface, blocked symmetrically
|
|
178
|
-
*"platform/lib/entitlement/"*|*"entitlement.json"*)
|
|
179
|
-
echo "[entitlement] tool-deny: tool=Bash path=entitlement-file field=entitlement-file" >&2
|
|
180
|
-
hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "Bash" \
|
|
181
|
-
"entitlement-shell" \
|
|
182
|
-
"Blocked: Admin agent cannot reference entitlement files via shell."
|
|
183
|
-
;;
|
|
184
|
-
# account.json via shell — same rationale as Edit/Write block
|
|
185
|
-
*"data/accounts/"*"account.json"*|*"config/accounts/"*"account.json"*)
|
|
186
|
-
echo "[entitlement] tool-deny: tool=Bash path=account-json field=account-json" >&2
|
|
187
|
-
hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "Bash" \
|
|
188
|
-
"account-json-shell" \
|
|
189
|
-
"Blocked: Admin agent cannot edit account.json via shell. Use the account-update MCP tool."
|
|
190
|
-
;;
|
|
191
|
-
*"pending-actions/"*)
|
|
192
|
-
hook_block_with_emit "$SESSION_ID" "admin-tool-gate" "Bash" \
|
|
193
|
-
"pending-actions-shell" \
|
|
194
|
-
"Blocked: Admin agent cannot modify pending action files via shell"
|
|
195
|
-
;;
|
|
196
|
-
esac
|
|
197
|
-
;;
|
|
198
|
-
esac
|
|
199
|
-
|
|
200
|
-
# ── Base64 context-overflow guard ─────────────────────────────
|
|
201
|
-
# Block inline base64 payloads from reaching the model context. Two paths:
|
|
202
|
-
#
|
|
203
|
-
# 1. Bash command that ENCODES a binary file to base64/hex (the producer).
|
|
204
|
-
# 2. Write/Edit content carrying an inline `data:<mime>;base64,…` blob
|
|
205
|
-
# (the consumer — agent quoting bytes from a prior tool_result into a
|
|
206
|
-
# HTML/markdown Write).
|
|
207
|
-
#
|
|
208
|
-
# Either path landed ~33 KB of base64 in the SDK request and the next turn
|
|
209
|
-
# tripped `main_stream_stalled` at 180 s. The
|
|
210
|
-
# remediation is symmetric: the agent saves bytes to `$ACCOUNT_DIR/tmp/<sha1>.<ext>`
|
|
211
|
-
# and references the path (`<img src="./file">` or Read-by-path) instead of
|
|
212
|
-
# carrying bytes through the assistant turn.
|
|
213
|
-
#
|
|
214
|
-
# Parsing uses python3 (already a hook dependency at the action-id site
|
|
215
|
-
# below); grep on JSON is unsafe for content with escaped quotes or
|
|
216
|
-
# embedded newlines. Parse failure is fail-open (empty extracted string,
|
|
217
|
-
# no match, allow) — matches the playwright-file-guard fail-open contract.
|
|
218
|
-
# Single python3 invocation parses the JSON, runs the tool-specific
|
|
219
|
-
# regex match (avoiding BSD-vs-GNU grep interval-count incompatibilities —
|
|
220
|
-
# `grep -E '{4096,}'` errors with "invalid repetition count(s)" on macOS
|
|
221
|
-
# BSD grep under some pattern combinations), and prints the rejection
|
|
222
|
-
# outcome to stdout as `REJECT:<reason>:<bytes>` or `ALLOW`. The wrapping
|
|
223
|
-
# bash logic reads the verdict and emits the rejection log/stderr/exit-2.
|
|
224
|
-
# Parse failure prints `ALLOW` (fail-open, matching the playwright-file-
|
|
225
|
-
# guard contract for malformed stdin).
|
|
226
|
-
GUARD_VERDICT=$(echo "$INPUT" | python3 -c '
|
|
227
|
-
import sys, json, re
|
|
228
|
-
try:
|
|
229
|
-
d = json.load(sys.stdin)
|
|
230
|
-
tool = d.get("tool_name", "")
|
|
231
|
-
ti = d.get("tool_input", {}) or {}
|
|
232
|
-
if tool in ("Write", "Edit"):
|
|
233
|
-
# Write.content OR Edit.new_string can carry inline base64.
|
|
234
|
-
content = ti.get("content") or ti.get("new_string") or ""
|
|
235
|
-
if not isinstance(content, str):
|
|
236
|
-
print("ALLOW"); sys.exit(0)
|
|
237
|
-
# data:<mime>;base64,<≥4096 base64 chars> — threshold matches the
|
|
238
|
-
# doctrine paragraph in .docs/agents.md. The 4096-char body is
|
|
239
|
-
# ~3 KB binary, far above any legitimate inline icon.
|
|
240
|
-
m = re.search(r"data:[^;]+;base64,[A-Za-z0-9+/]{4096,}={0,2}", content)
|
|
241
|
-
if m:
|
|
242
|
-
print(f"REJECT:base64-write-content:{len(content)}")
|
|
243
|
-
else:
|
|
244
|
-
print("ALLOW")
|
|
245
|
-
elif tool == "Bash":
|
|
246
|
-
command = ti.get("command", "")
|
|
247
|
-
if not isinstance(command, str):
|
|
248
|
-
print("ALLOW"); sys.exit(0)
|
|
249
|
-
# Per-segment scan. A compound command like
|
|
250
|
-
# cat in.b64 | base64 -d > out.bin; cat photo.png | base64
|
|
251
|
-
# contains both a legitimate decode AND a malicious encoder. A whole-
|
|
252
|
-
# command decode-flag check is fooled into allowing the encoder. Split
|
|
253
|
-
# on shell separators (;, &&, ||, &) and scan each segment as its own
|
|
254
|
-
# command — the encoder rejection fires when ANY segment is a bare
|
|
255
|
-
# base64 invocation without a paired decode flag in the SAME segment.
|
|
256
|
-
# Pipelines (|) keep the segment together because the encoder direction
|
|
257
|
-
# of `cat file | base64` lives across the pipe.
|
|
258
|
-
segments = re.split(r";|&&|\|\||(?<![|&])&(?![|&])", command)
|
|
259
|
-
rejected = None
|
|
260
|
-
for seg in segments:
|
|
261
|
-
if re.search(r"(?:^|[\s|;&])xxd[\t ]+-p(?![A-Za-z0-9_-])", seg):
|
|
262
|
-
rejected = "xxd-plain-hex"; break
|
|
263
|
-
if re.search(r"(?:^|[\s|;&])base64(?![A-Za-z0-9_-])", seg):
|
|
264
|
-
if not re.search(r"base64[\t ]+[^|]*(?:-d|-D|--decode)(?![A-Za-z0-9_])", seg):
|
|
265
|
-
rejected = "base64-encoder"; break
|
|
266
|
-
if rejected:
|
|
267
|
-
print(f"REJECT:{rejected}:{len(command)}")
|
|
268
|
-
else:
|
|
269
|
-
print("ALLOW")
|
|
270
|
-
else:
|
|
271
|
-
print("ALLOW")
|
|
272
|
-
except Exception:
|
|
273
|
-
print("ALLOW")
|
|
274
|
-
' 2>/dev/null || echo "ALLOW")
|
|
275
|
-
case "$GUARD_VERDICT" in
|
|
276
|
-
REJECT:base64-write-content:*)
|
|
277
|
-
BYTES="${GUARD_VERDICT##*:}"
|
|
278
|
-
echo "[pre-tool-use] guard=base64-write-content bytes=${BYTES} action=reject" >&2
|
|
279
|
-
hook_block_with_emit "$SESSION_ID" "base64-guard" "${TOOL_NAME}" \
|
|
280
|
-
"base64-write-content" \
|
|
281
|
-
"Blocked: ${TOOL_NAME} content carries an inline base64 payload (>4 KB encoded). Inline binary in Write.content overloads the model context — the same path produced a main_stream_stalled at ~33 KB on 2026-05-09.
|
|
282
|
-
Save the bytes to \$ACCOUNT_DIR/tmp/<sha1>.<ext> via Bash (e.g. 'base64 -d > out.png'), then reference the file from the document: <img src=\"./<file>\"> or Read-by-path. Do not carry binary bytes through the assistant turn."
|
|
283
|
-
;;
|
|
284
|
-
REJECT:base64-encoder:*|REJECT:xxd-plain-hex:*)
|
|
285
|
-
REASON="${GUARD_VERDICT#REJECT:}"; REASON="${REASON%:*}"
|
|
286
|
-
BYTES="${GUARD_VERDICT##*:}"
|
|
287
|
-
echo "[pre-tool-use] guard=base64-tool-result bytes=${BYTES} tool=Bash reason=${REASON} action=reject" >&2
|
|
288
|
-
hook_block_with_emit "$SESSION_ID" "base64-guard" "Bash" \
|
|
289
|
-
"$REASON" \
|
|
290
|
-
"Blocked: Bash command would emit binary as inline base64/hex to stdout, which lands in the assistant turn and overloads the model context (the 2026-05-09 Rubytech-invoice path hit 66% context after a single ~33 KB tool_result).
|
|
291
|
-
Instead: save the bytes directly to \$ACCOUNT_DIR/tmp/<sha1>.<ext> and operate on the file via path — Read for inspection, <img src=\"./<file>\"> for HTML embedding, the \`file-presentation\` skill for delivery. Decoding base64 (e.g. 'base64 -d in.b64 > out.bin') is allowed."
|
|
292
|
-
;;
|
|
293
|
-
*)
|
|
294
|
-
: # ALLOW — fall through to approval gating below
|
|
295
|
-
;;
|
|
296
|
-
esac
|
|
297
|
-
|
|
298
|
-
# ── Approval gating (EU AI Act Article 14 — human oversight) ─────────────
|
|
299
|
-
# Strip the mcp__<plugin>__ prefix to get the short tool name.
|
|
300
|
-
# Built-in tools (no prefix) pass through unchanged.
|
|
301
|
-
SHORT_NAME=$(echo "$TOOL_NAME" | sed 's/^mcp__[^_]*__//')
|
|
302
|
-
|
|
303
|
-
# Derive platform root from this hook's location:
|
|
304
|
-
# platform/plugins/admin/hooks/pre-tool-use.sh → platform/
|
|
305
|
-
HOOK_DIR="$(cd "$(dirname "$0")" 2>/dev/null && pwd)"
|
|
306
|
-
PLATFORM_ROOT="${HOOK_DIR}/../../.."
|
|
307
|
-
|
|
308
|
-
# Resolve the single account directory (Phase 0).
|
|
309
|
-
ACCOUNTS_DIR="${PLATFORM_ROOT}/../data/accounts"
|
|
310
|
-
ACCOUNT_DIR=""
|
|
311
|
-
if [ -d "$ACCOUNTS_DIR" ]; then
|
|
312
|
-
ACCOUNT_DIR=$(find "$ACCOUNTS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | head -1)
|
|
313
|
-
fi
|
|
314
|
-
|
|
315
|
-
# Default require-review tools — matches DEFAULT_REQUIRE_REVIEW in claude-agent.ts
|
|
316
|
-
DEFAULT_REQUIRE_REVIEW="email-send email-reply whatsapp-send whatsapp-send-document message contact-erase"
|
|
317
|
-
|
|
318
|
-
# Check if this tool requires approval review.
|
|
319
|
-
# 1. Read per-account override from account.json approvalPolicy (if it exists)
|
|
320
|
-
# 2. Fall back to the default require-review list
|
|
321
|
-
POLICY=""
|
|
322
|
-
if [ -n "$ACCOUNT_DIR" ] && [ -f "${ACCOUNT_DIR}/account.json" ]; then
|
|
323
|
-
# Check per-account override first (grep for the tool name in the approvalPolicy block)
|
|
324
|
-
OVERRIDE=$(grep -o "\"${SHORT_NAME}\"[[:space:]]*:[[:space:]]*\"[^\"]*\"" "${ACCOUNT_DIR}/account.json" 2>/dev/null | head -1 | grep -o '"[^"]*"$' | tr -d '"')
|
|
325
|
-
if [ -n "$OVERRIDE" ]; then
|
|
326
|
-
POLICY="$OVERRIDE"
|
|
327
|
-
fi
|
|
328
|
-
fi
|
|
329
|
-
|
|
330
|
-
# If no per-account override, check the default list
|
|
331
|
-
if [ -z "$POLICY" ]; then
|
|
332
|
-
for TOOL in $DEFAULT_REQUIRE_REVIEW; do
|
|
333
|
-
if [ "$SHORT_NAME" = "$TOOL" ]; then
|
|
334
|
-
POLICY="require-review"
|
|
335
|
-
break
|
|
336
|
-
fi
|
|
337
|
-
done
|
|
338
|
-
fi
|
|
339
|
-
|
|
340
|
-
# If not in any policy, auto-execute (allow)
|
|
341
|
-
if [ "$POLICY" != "require-review" ]; then
|
|
342
|
-
exit 0
|
|
343
|
-
fi
|
|
344
|
-
|
|
345
|
-
# ── Special case: contact-erase preview calls pass through ───────────────
|
|
346
|
-
# The contact-erase tool has a two-step flow: first call with confirm:false
|
|
347
|
-
# returns a preview (data counts), second call with confirm:true executes.
|
|
348
|
-
# Only gate the destructive confirm:true call.
|
|
349
|
-
if [ "$SHORT_NAME" = "contact-erase" ]; then
|
|
350
|
-
if ! echo "$INPUT" | grep -q '"confirm"[[:space:]]*:[[:space:]]*true'; then
|
|
351
|
-
exit 0
|
|
352
|
-
fi
|
|
353
|
-
fi
|
|
354
|
-
|
|
355
|
-
# ── Queue the action for approval ───────────────────────────────────────
|
|
356
|
-
PENDING_DIR="${ACCOUNT_DIR}/pending-actions"
|
|
357
|
-
mkdir -p "$PENDING_DIR" 2>/dev/null
|
|
358
|
-
|
|
359
|
-
ACTION_ID="$(cat /proc/sys/kernel/random/uuid 2>/dev/null || python3 -c 'import uuid; print(uuid.uuid4())' 2>/dev/null || date +%s%N)"
|
|
360
|
-
CREATED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
361
|
-
|
|
362
|
-
# Write atomically: temp file + mv
|
|
363
|
-
TEMP=$(mktemp "${PENDING_DIR}/.tmp.XXXXXX" 2>/dev/null)
|
|
364
|
-
if [ -z "$TEMP" ]; then
|
|
365
|
-
hook_block_with_emit "$SESSION_ID" "approval-gate" "${TOOL_NAME}" \
|
|
366
|
-
"approval-queue-mktemp" \
|
|
367
|
-
"Blocked: Action requires approval but failed to create queue file (disk error). Failing closed."
|
|
368
|
-
fi
|
|
369
|
-
|
|
370
|
-
# The pending action file contains the full hook JSON (tool_name + tool_input)
|
|
371
|
-
# plus metadata. The MCP approval tools parse it with Node.js JSON.parse.
|
|
372
|
-
cat > "$TEMP" <<ENDJSON
|
|
373
|
-
{
|
|
374
|
-
"actionId": "${ACTION_ID}",
|
|
375
|
-
"toolName": "${SHORT_NAME}",
|
|
376
|
-
"pluginName": "$(echo "$TOOL_NAME" | sed -n 's/^mcp__\([^_]*\)__.*/\1/p')",
|
|
377
|
-
"hookPayload": ${INPUT},
|
|
378
|
-
"state": "pending",
|
|
379
|
-
"createdAt": "${CREATED_AT}"
|
|
380
|
-
}
|
|
381
|
-
ENDJSON
|
|
382
|
-
|
|
383
|
-
if ! mv "$TEMP" "${PENDING_DIR}/${ACTION_ID}.json" 2>/dev/null; then
|
|
384
|
-
rm -f "$TEMP" 2>/dev/null
|
|
385
|
-
hook_block_with_emit "$SESSION_ID" "approval-gate" "${TOOL_NAME}" \
|
|
386
|
-
"approval-queue-mv" \
|
|
387
|
-
"Blocked: Action requires approval but failed to queue (filesystem error). Failing closed."
|
|
388
|
-
fi
|
|
389
|
-
|
|
390
|
-
# Record the queued-for-approval block in the propagation buffer; the
|
|
391
|
-
# readable summary still goes to stdout (CC attaches it as the agent's
|
|
392
|
-
# tool_result). hook_emit_decision is the no-exit variant — the
|
|
393
|
-
# subsequent echoes + exit 2 below remain the operator-facing surface.
|
|
394
|
-
hook_emit_decision "$SESSION_ID" "approval-gate" "${TOOL_NAME}" \
|
|
395
|
-
"block" "approval-queued" \
|
|
396
|
-
"Action ${ACTION_ID} queued for review" 2 0
|
|
397
|
-
|
|
398
|
-
# Extract a readable summary of the action for the agent to present
|
|
399
|
-
# tool_input is the second JSON value in the hook payload
|
|
400
|
-
echo "Action requires approval before execution."
|
|
401
|
-
echo ""
|
|
402
|
-
echo "Action ID: ${ACTION_ID}"
|
|
403
|
-
echo "Tool: ${SHORT_NAME}"
|
|
404
|
-
echo "Status: Queued for review"
|
|
405
|
-
echo ""
|
|
406
|
-
echo "The admin must approve this action before it can execute."
|
|
407
|
-
echo "Use action-approve to send, action-reject to cancel, or action-edit to modify."
|
|
408
|
-
exit 2
|
|
409
|
-
fi
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{o as e}from"./chunk-DD-I1_y5.js";import{c as t,d as n,i as r,l as i,o as a,s as o,t as s}from"./brand-SzZmKKjz.js";var c=i(`bot`,[[`path`,{d:`M12 8V4H8`,key:`hb8ula`}],[`rect`,{width:`16`,height:`12`,x:`4`,y:`8`,rx:`2`,key:`enze0r`}],[`path`,{d:`M2 14h2`,key:`vft8re`}],[`path`,{d:`M20 14h2`,key:`4cs60a`}],[`path`,{d:`M15 13v2`,key:`1xurst`}],[`path`,{d:`M9 13v2`,key:`rq6x2g`}]]),l=i(`box`,[[`path`,{d:`M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z`,key:`hh9hay`}],[`path`,{d:`m3.3 7 8.7 5 8.7-5`,key:`g66t2b`}],[`path`,{d:`M12 22V12`,key:`d0xqtd`}]]),u=i(`check`,[[`path`,{d:`M20 6 9 17l-5-5`,key:`1gmf2c`}]]),d=i(`database`,[[`ellipse`,{cx:`12`,cy:`5`,rx:`9`,ry:`3`,key:`msslwz`}],[`path`,{d:`M3 5V19A9 3 0 0 0 21 19V5`,key:`1wlel7`}],[`path`,{d:`M3 12A9 3 0 0 0 21 12`,key:`mv7ke4`}]]),f=i(`download`,[[`path`,{d:`M12 15V3`,key:`m9g1x1`}],[`path`,{d:`M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4`,key:`ih7n3h`}],[`path`,{d:`m7 10 5 5 5-5`,key:`brsn70`}]]),p=i(`external-link`,[[`path`,{d:`M15 3h6v6`,key:`1q9fwt`}],[`path`,{d:`M10 14 21 3`,key:`gplh6r`}],[`path`,{d:`M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6`,key:`a6xqqp`}]]),m=i(`history`,[[`path`,{d:`M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8`,key:`1357e3`}],[`path`,{d:`M3 3v5h5`,key:`1xhq8a`}],[`path`,{d:`M12 7v5l4 2`,key:`1fdv2h`}]]),h=i(`info`,[[`circle`,{cx:`12`,cy:`12`,r:`10`,key:`1mglay`}],[`path`,{d:`M12 16v-4`,key:`1dtifu`}],[`path`,{d:`M12 8h.01`,key:`e9boi3`}]]),g=i(`list-todo`,[[`path`,{d:`M13 5h8`,key:`a7qcls`}],[`path`,{d:`M13 12h8`,key:`h98zly`}],[`path`,{d:`M13 19h8`,key:`c3s6r1`}],[`path`,{d:`m3 17 2 2 4-4`,key:`1jhpwq`}],[`rect`,{x:`3`,y:`4`,width:`6`,height:`6`,rx:`1`,key:`cif1o7`}]]),_=i(`loader-circle`,[[`path`,{d:`M21 12a9 9 0 1 1-6.219-8.56`,key:`13zald`}]]),v=i(`log-out`,[[`path`,{d:`m16 17 5-5-5-5`,key:`1bji2h`}],[`path`,{d:`M21 12H9`,key:`dn1m92`}],[`path`,{d:`M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4`,key:`1uf3rs`}]]),y=i(`menu`,[[`path`,{d:`M4 5h16`,key:`1tepv9`}],[`path`,{d:`M4 12h16`,key:`1lakjw`}],[`path`,{d:`M4 19h16`,key:`1djgab`}]]),b=i(`panel-right-open`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M15 3v18`,key:`14nvp0`}],[`path`,{d:`m10 15-3-3 3-3`,key:`1pgupc`}]]),x=i(`panel-right`,[[`rect`,{width:`18`,height:`18`,x:`3`,y:`3`,rx:`2`,key:`afitv7`}],[`path`,{d:`M15 3v18`,key:`14nvp0`}]]),S=i(`plus`,[[`path`,{d:`M5 12h14`,key:`1ays0h`}],[`path`,{d:`M12 5v14`,key:`s699le`}]]),C=i(`refresh-cw`,[[`path`,{d:`M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8`,key:`v9h5vc`}],[`path`,{d:`M21 3v5h-5`,key:`1q7to0`}],[`path`,{d:`M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16`,key:`3uifl3`}],[`path`,{d:`M8 16H3v5`,key:`1cv678`}]]),ee=i(`share-2`,[[`circle`,{cx:`18`,cy:`5`,r:`3`,key:`gq8acd`}],[`circle`,{cx:`6`,cy:`12`,r:`3`,key:`w7nqdw`}],[`circle`,{cx:`18`,cy:`19`,r:`3`,key:`1xt0gg`}],[`line`,{x1:`8.59`,x2:`15.42`,y1:`13.51`,y2:`17.49`,key:`47mynk`}],[`line`,{x1:`15.41`,x2:`8.59`,y1:`6.51`,y2:`10.49`,key:`1n3mei`}]]),w=i(`users`,[[`path`,{d:`M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2`,key:`1yyitq`}],[`path`,{d:`M16 3.128a4 4 0 0 1 0 7.744`,key:`16gr8j`}],[`path`,{d:`M22 21v-2a4 4 0 0 0-3-3.87`,key:`kshegd`}],[`circle`,{cx:`9`,cy:`7`,r:`4`,key:`nufk8`}]]),T=i(`x`,[[`path`,{d:`M18 6 6 18`,key:`1bl5f8`}],[`path`,{d:`m6 6 12 12`,key:`d8bk6v`}]]),E=e(n(),1),D=a(),O=`maxy-shell-side-px`;function k(){if(typeof window>`u`)return 0;let e=document.querySelector(`.platform > .artefact`)?.getBoundingClientRect();return e&&e.width>0?Math.round(e.width):0}function A(e){if(typeof window>`u`)return e;let t=window.innerWidth,n=Math.max(248,t-480-k());return Math.min(Math.max(e,248),n)}function j(){if(typeof window>`u`)return 264;try{let e=window.localStorage.getItem(O);if(!e)return 264;let t=parseInt(e,10);if(Number.isFinite(t)&&t>=248)return A(t)}catch{}return 264}function te({targetSelector:e=`.platform`}){let t=(0,E.useRef)(null),[n,r]=(0,E.useState)(()=>j()),i=(0,E.useRef)(n);(0,E.useEffect)(()=>{let t=document.querySelector(e);!t||!(t instanceof HTMLElement)||(t.style.setProperty(`--side-px`,`${n}px`),i.current=n)},[n,e]);let a=(0,E.useCallback)(()=>{r(e=>{let t=A(e);return t===e?e:t})},[]);(0,E.useEffect)(()=>{a(),window.addEventListener(`resize`,a);let t=document.querySelector(e),n=null;return t instanceof HTMLElement&&(n=new MutationObserver(a),n.observe(t,{attributes:!0,attributeFilter:[`data-artefact`,`class`]})),()=>{window.removeEventListener(`resize`,a),n?.disconnect()}},[a,e]);let o=e=>{e.preventDefault();let n=t.current;if(!n)return;n.setPointerCapture(e.pointerId),n.classList.add(`dragging`);let a=!1,o=e=>{a=!0,r(A(Math.round(e.clientX)))},s=()=>{if(n.releasePointerCapture(e.pointerId),n.classList.remove(`dragging`),window.removeEventListener(`pointermove`,o),window.removeEventListener(`pointerup`,s),!a)return;let t=i.current;try{window.localStorage.setItem(O,String(t))}catch{}console.info(`[admin-ui] sidebar-resize px=${t}`)};window.addEventListener(`pointermove`,o),window.addEventListener(`pointerup`,s)},s=()=>{let e=A(264);r(e);try{window.localStorage.removeItem(O)}catch{}console.info(`[admin-ui] sidebar-resize px=${e}`)};return(0,D.jsx)(`div`,{ref:t,className:`side-resize-handle`,role:`separator`,"aria-orientation":`vertical`,"aria-label":`Resize sidebar`,onPointerDown:o,onDoubleClick:s})}var M=`maxy-admin-session`,N=typeof BroadcastChannel<`u`;function P(e){if(typeof e!=`object`||!e)return!1;let t=e;return t.type===`rotation`&&typeof t.newKey==`string`&&t.newKey.length>0&&(t.surface===`chat`||t.surface===`graph`||t.surface===`data`||t.surface===`sessions`)&&typeof t.ts==`number`}function F(e){if(!N)return()=>{};let t=new BroadcastChannel(M),n=t=>{if(!P(t.data)){console.warn(`[admin-session-broadcast] outcome=received-malformed payload=${JSON.stringify(t.data)}`);return}e(t.data)};return t.addEventListener(`message`,n),()=>{t.removeEventListener(`message`,n),t.close()}}var I=`maxy-admin-session-key`;function L(e,t){return`${e}${e.includes(`?`)?`&`:`?`}session_key=${encodeURIComponent(t)}`}async function R(e){let t;try{t=await e.clone().json()}catch{return`parse-failed`}if(typeof t!=`object`||!t)return`unknown-401`;let n=t.code;return n===`session-missing`||n===`session-not-registered`||n===`session-expired-age`||n===`grant-expired`?n:`unknown-401`}function z(){try{return sessionStorage.getItem(I)}catch{return null}}function B(e){let{initialCacheKey:t,surface:n}=e,[r,i]=(0,E.useState)(t),a=(0,E.useRef)(t);(0,E.useEffect)(()=>{a.current=r},[r]),(0,E.useEffect)(()=>{t!==a.current&&(i(t),a.current=t,s(e=>e+1))},[t]);let[o,s]=(0,E.useState)(0),[c,l]=(0,E.useState)(null);(0,E.useEffect)(()=>F(e=>{let t=a.current.slice(0,8),r=e.newKey.slice(0,8);e.newKey!==a.current&&(console.log(`[admin-session-broadcast] outcome=received oldKey=${t} newKey=${r} surface=${n} senderSurface=${e.surface}`),i(e.newKey),s(e=>e+1),l(null))}),[n]);let u=(0,E.useCallback)(()=>{try{sessionStorage.removeItem(I)}catch{}window.location.href=`/`},[]);return{adminFetch:(0,E.useCallback)(async(e,t)=>{let r=a.current,o=L(e,r),c=await fetch(o,t);if(c.status!==401)return c;let d=await R(c);if(d===`session-missing`)return console.warn(`[useAdminFetch] outcome=redirect-to-login reason=${d} surface=${n} path=${e}`),u(),c;if(d!==`session-not-registered`)return console.warn(`[useAdminFetch] outcome=redirect-to-login reason=${d} surface=${n} path=${e}`),l({message:`Your admin session has expired. Click to reload and sign in.`,reason:d}),c;let f=z();if(!f||f===r)return console.warn(`[useAdminFetch] outcome=redirect-to-login reason=session-not-registered-no-fresh-key surface=${n} path=${e}`),l({message:`Your admin session was renewed in another tab. Click to reload.`,reason:`session-not-registered`}),c;i(f),a.current=f,s(e=>e+1),console.log(`[useAdminFetch] outcome=retry-after-rotation oldKey=${r.slice(0,8)} newKey=${f.slice(0,8)} surface=${n} path=${e}`);let p=L(e,f),m=await fetch(p,t);return m.status===401&&(console.warn(`[useAdminFetch] outcome=redirect-to-login reason=second-401-after-retry surface=${n} path=${e}`),l({message:`Your admin session has expired. Click to reload and sign in.`,reason:`session-not-registered`})),m},[u,n]),cacheKey:r,sessionRefetchNonce:o,banner:c,reloadToLogin:u}}var V=(0,E.createContext)(null);function H({cacheKey:e,onSessionExpired:t,surface:n,children:r}){let i=(0,E.useCallback)(async(r,i)=>{if(!e)return fetch(r,i);let a=L(r,e),o=await fetch(a,i);if(o.status!==401)return o;let s=await R(o),c=e.slice(-8);if(s===`session-not-registered`){let a=z();if(a&&a!==e){let e=L(r,a),o=await fetch(e,i);return o.status===401?(console.warn(`[admin-auth] outcome=session-expired-redirect code=second-401-after-retry path=${r} cacheKey=${c} surface=${n}`),t({code:`second-401-after-retry`,path:r}),o):o}}return console.warn(`[admin-auth] outcome=session-expired-redirect code=${s} path=${r} cacheKey=${c} surface=${n}`),t({code:s,path:r}),o},[e,t,n]);return(0,D.jsx)(V.Provider,{value:i,children:r})}function ne(){return(0,E.useContext)(V)}function re(e){let t=ne();return(0,E.useMemo)(()=>t||(async(t,n)=>fetch(e?L(t,e):t,n)),[t,e])}function U(e){let{businessName:t,onNavigate:n,onToggleSidebar:i,sidebarOpen:a,onLogout:c}=e,[l,f]=(0,E.useState)(!1),m=(0,E.useRef)(null),g=(0,E.useRef)(null),S=re(null),[C,w]=(0,E.useState)(null),[O,k]=(0,E.useState)(!1),[A,j]=(0,E.useState)(null),[te,M]=(0,E.useState)(null),[N,P]=(0,E.useState)(null),[F,I]=(0,E.useState)(``);(0,E.useEffect)(()=>{I(window.location.hostname.startsWith(`admin.`)?window.location.origin.replace(`admin.`,`public.`):window.location.origin)},[]),(0,E.useEffect)(()=>{if(!l)return;let e=e=>{m.current&&!m.current.contains(e.target)&&f(!1)};return document.addEventListener(`mousedown`,e),()=>document.removeEventListener(`mousedown`,e)},[l]),(0,E.useEffect)(()=>{if(!l)return;let e=e=>{e.key===`Escape`&&f(!1)};return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[l]),(0,E.useEffect)(()=>{l&&g.current?.focus()},[l]),(0,E.useEffect)(()=>{if(!l)return;let e=!1;return S(`/api/admin/version`).then(e=>e.json()).then(t=>{e||P(t)}).catch(e=>{console.error(`[admin/version] menu-open client fetch failed:`,e)}),()=>{e=!0}},[l,S]);let L=(0,E.useCallback)(()=>{f(e=>{let t=!e;return t&&console.info(`[admin-ui] header-menu-open`),t})},[]),R=(0,E.useCallback)(e=>{f(!1),n(e)},[n]),z=(0,E.useCallback)(async()=>{if(C!==null){w(null),M(null);return}k(!0),j(null);try{let e=await S(`/api/admin/agents`);if(!e.ok)throw Error(`Failed to load agents`);w((await e.json()).agents??[])}catch(e){console.error(`[admin/agents] list failed:`,e),j(e instanceof Error?e.message:String(e)),w([])}finally{k(!1)}},[C,S]),B=(0,E.useCallback)(e=>{M(null),w(t=>t?.filter(t=>t.slug!==e)??null),S(`/api/admin/agents/${encodeURIComponent(e)}`,{method:`DELETE`}).catch(e=>console.error(`[admin/agents] delete failed:`,e))},[S]),V=(0,E.useCallback)(()=>{f(!1),c()},[c]),H=t||s.productName;return(0,D.jsxs)(`header`,{className:`admin-header`,children:[(0,D.jsx)(`button`,{type:`button`,className:`admin-sidebar-toggle`,"aria-label":a?`Hide sidebar`:`Show sidebar`,title:a?`Hide sidebar`:`Show sidebar`,"aria-expanded":a,onClick:i,children:a?(0,D.jsx)(b,{size:20,strokeWidth:1.5}):(0,D.jsx)(x,{size:20,strokeWidth:1.5})}),(0,D.jsxs)(`div`,{className:`chat-header-label`,children:[(0,D.jsx)(`span`,{className:`chat-logo-btn`,"aria-hidden":`true`,children:(0,D.jsx)(`img`,{src:r,alt:``,className:`chat-logo`})}),(0,D.jsx)(`h1`,{className:`chat-tagline`,children:H})]}),(0,D.jsxs)(`div`,{className:`chat-burger-wrap`,ref:m,children:[(0,D.jsx)(`button`,{type:`button`,className:`admin-burger`,onClick:L,"aria-label":`Menu`,"aria-haspopup":`true`,"aria-expanded":l,children:(0,D.jsx)(y,{size:20})}),l&&(0,D.jsxs)(`div`,{className:`admin-menu`,role:`menu`,children:[(0,D.jsxs)(`button`,{ref:g,type:`button`,className:`chat-menu-item`,role:`menuitem`,onClick:()=>R(`data`),children:[(0,D.jsx)(d,{size:14}),` Data`]}),(0,D.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,role:`menuitem`,onClick:()=>R(`graph`),children:[(0,D.jsx)(ee,{size:14}),` Graph`]}),(0,D.jsx)(`div`,{className:`chat-menu-divider`}),(0,D.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,role:`menuitem`,onClick:z,children:[(0,D.jsx)(p,{size:14}),` Public`,O&&(0,D.jsx)(_,{size:12,className:`spin`})]}),C!==null&&(0,D.jsxs)(`div`,{className:`chat-menu-agents`,children:[A&&(0,D.jsx)(`span`,{className:`chat-menu-agent-error`,children:A}),C.length===0&&!A&&(0,D.jsx)(`span`,{className:`chat-menu-agent-empty`,children:`No public agents configured`}),C.map(e=>(0,D.jsxs)(`div`,{className:`chat-menu-item chat-menu-agent-item`,children:[(0,D.jsx)(`span`,{className:`agent-status-dot ${e.status}`}),(0,D.jsxs)(`span`,{className:`agent-text`,children:[(0,D.jsx)(`span`,{className:`agent-display-name`,children:e.displayName}),(0,D.jsxs)(`span`,{className:`agent-slug`,children:[`/`,e.slug]})]}),te===e.slug?(0,D.jsxs)(`span`,{className:`agent-actions agent-confirm`,children:[(0,D.jsx)(`button`,{type:`button`,className:`agent-action-btn agent-confirm-yes`,title:`Confirm delete`,onClick:()=>B(e.slug),children:(0,D.jsx)(u,{size:12})}),(0,D.jsx)(`button`,{type:`button`,className:`agent-action-btn agent-confirm-no`,title:`Cancel`,onClick:()=>M(null),children:(0,D.jsx)(T,{size:12})})]}):(0,D.jsxs)(`span`,{className:`agent-actions`,children:[(0,D.jsx)(`a`,{href:`${F}/${e.slug}`,target:`_blank`,rel:`noopener noreferrer`,className:`agent-action-btn`,title:`Open agent`,onClick:()=>f(!1),children:(0,D.jsx)(p,{size:12})}),(0,D.jsx)(`button`,{type:`button`,className:`agent-action-btn agent-delete-btn`,title:`Delete agent`,onClick:()=>M(e.slug),children:(0,D.jsx)(o,{size:12})})]})]},e.slug))]}),(0,D.jsx)(`div`,{className:`chat-menu-divider`}),(0,D.jsxs)(`div`,{className:`chat-menu-version chat-menu-version-passive`,children:[(0,D.jsx)(h,{size:14}),(0,D.jsxs)(`span`,{className:`version-installed`,children:[N?`v${N.installed}`:`…`,N&&!N.updateAvailable&&(0,D.jsx)(`span`,{className:`version-uptodate-dot`}),N?.updateAvailable&&(0,D.jsx)(`span`,{className:`version-update-dot`})]})]}),(0,D.jsxs)(`button`,{type:`button`,className:`chat-menu-item`,role:`menuitem`,onClick:V,children:[(0,D.jsx)(v,{size:14}),` Log out`]})]})]})]})}function ie(e,t,n=`data`){let r=`/api/admin/files/download?session_key=${encodeURIComponent(e)}&path=${encodeURIComponent(t)}`+(n===`data`?``:`&root=${encodeURIComponent(n)}`),i=document.createElement(`a`);i.href=r,i.rel=`noopener noreferrer`,document.body.appendChild(i),i.click(),i.remove()}var ae=`https://claude.ai/code`;function oe(e){let{businessName:n,cacheKey:r,role:i,userName:a,userAvatar:u,onSelectProjects:d,onSelectPeople:h,onSelectTasks:_,onSelectAgents:v,onCloseMobileDrawer:y,collapsed:b}=e,x=re(r),ee=s.productName,T=typeof a==`string`?a:a===null?`name unavailable`:n||ee,O=(T.trim().charAt(0)||`?`).toUpperCase(),[k,A]=(0,E.useState)(`artefacts`),[j,te]=(0,E.useState)([]),[M,N]=(0,E.useState)(!1),[P,F]=(0,E.useState)(null),[I,L]=(0,E.useState)(!1),[R,z]=(0,E.useState)(`all`),[B,V]=(0,E.useState)(null),[H,ne]=(0,E.useState)([]),[U,oe]=(0,E.useState)(!1),[W,G]=(0,E.useState)(null),[K,ce]=(0,E.useState)(!1),[q,le]=(0,E.useState)(!1),[ue,J]=(0,E.useState)(null),[Y,de]=(0,E.useState)(new Set),[fe,pe]=(0,E.useState)(new Set),[X,me]=(0,E.useState)(!1),[Z,he]=(0,E.useState)(`node-pty`),ge=(0,E.useCallback)(e=>{if(y(),!r){console.error(`[admin-ui] artefact-download-blocked id=${e.id} reason=no-cache-key`),V({message:`Session not ready — try again`,failed:!0});return}if(!e.downloadPath){console.error(`[admin-ui] artefact-download-blocked id=${e.id} reason=not-downloadable`),V({message:`${e.name} can’t be downloaded`,failed:!0});return}console.info(`[admin-ui] artefact-download id=${e.id} root=${e.downloadRoot??`data`} path=${e.downloadPath}`),ie(r,e.downloadPath,e.downloadRoot??`data`),V({message:`Downloading ${e.name}`,failed:!1})},[r,y]);(0,E.useEffect)(()=>{if(!B)return;let e=setTimeout(()=>V(null),2500);return()=>clearTimeout(e)},[B]);let Q=(0,E.useCallback)(async()=>{if(r){L(!0),F(null);try{let e=await x(`/api/admin/sidebar-artefacts`);if(!e.ok)throw Error(`status ${e.status}`);te((await e.json()).artefacts??[]),N(!0)}catch(e){let t=e instanceof Error?e.message:String(e);F(`Failed to load artefacts: ${t}`),console.error(`[admin-ui] sidebar-artefacts fetch failed: ${t}`)}finally{L(!1)}}},[r,x]);(0,E.useEffect)(()=>{!r||M||Q()},[r,M,Q]);let _e=()=>{A(`artefacts`),console.info(`[admin-ui] sidebar-nav surface=artefacts count=${M?j.length:0} collapsed=${b}`),M||Q()},$=1.5,ve=()=>{console.info(`[admin-ui] sidebar-refresh surface=artefacts`),Q()},ye=(0,E.useCallback)(async()=>{if(r){ce(!0),G(null);try{let e=await x(`/api/admin/sidebar-sessions`);if(!e.ok)throw Error(`status ${e.status}`);let t=await e.json();ne(t.sessions??[]),J(t.accountId??null),oe(!0)}catch(e){let t=e instanceof Error?e.message:String(e);G(`Failed to load sessions: ${t}`),console.error(`[admin-ui] sidebar-sessions fetch failed: ${t}`)}finally{ce(!1)}}},[r,x]),be=()=>{A(`sessions`),console.info(`[admin-ui] sidebar-nav surface=sessions count=${U?H.length:0} collapsed=${b}`),U||ye()},xe=()=>{console.info(`[admin-ui] sidebar-refresh surface=sessions`),ye()},Se=(0,E.useCallback)(async e=>{if(!fe.has(e.sessionId)){pe(t=>{let n=new Set(t);return n.add(e.sessionId),n});try{let t=await x(`/api/admin/session-rc-spawn`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sessionId:e.sessionId,name:e.title||e.sessionId,spawnPath:Z})});if(!t.ok){let n=await t.json().catch(()=>({}));console.error(`[admin-ui] sidebar-session-resume-failed sessionId=${e.sessionId} status=${t.status} error=${n.error??`unknown`}`),V({message:`Resume failed: ${n.error??`status ${t.status}`}`,failed:!0});return}let n=await t.json();console.info(`[admin-ui] sidebar-session-resume sessionId=${e.sessionId} spawnedPid=${n.spawnedPid??`?`}`),y(),window.open(ae,`_blank`,`noopener,noreferrer`)}catch(t){let n=t instanceof Error?t.message:String(t);console.error(`[admin-ui] sidebar-session-resume-failed sessionId=${e.sessionId} error=${n}`),V({message:`Resume failed: ${n}`,failed:!0})}finally{pe(t=>{let n=new Set(t);return n.delete(e.sessionId),n})}}},[x,y,fe,Z]),Ce=(0,E.useCallback)(async()=>{if(!X){me(!0);try{let e=await x(`/api/admin/session-rc-spawn`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({spawnPath:Z})});if(!e.ok){let t=await e.json().catch(()=>({}));console.error(`[admin-ui] sidebar-new-session-failed status=${e.status} error=${t.error??`unknown`}`),V({message:`New session failed: ${t.error??`status ${e.status}`}`,failed:!0});return}let t=await e.json();console.info(`[admin-ui] sidebar-new-session-spawned spawnedPid=${t.spawnedPid??`?`}`),window.open(ae,`_blank`,`noopener,noreferrer`)}catch(e){let t=e instanceof Error?e.message:String(e);console.error(`[admin-ui] sidebar-new-session-failed error=${t}`),V({message:`New session failed: ${t}`,failed:!0})}finally{me(!1)}}},[x,X,Z]),we=(0,E.useCallback)(async(e,t)=>{if(e.stopPropagation(),!Y.has(t.sessionId)){de(e=>{let n=new Set(e);return n.add(t.sessionId),n});try{let e=await x(`/api/admin/session-delete`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({sessionId:t.sessionId,projectDir:t.projectDir})});if(!e.ok){let n=await e.json().catch(()=>({}));console.error(`[admin-ui] sidebar-session-delete-failed sessionId=${t.sessionId} status=${e.status} error=${n.error??`unknown`}`),V({message:`Delete failed: ${n.error??`status ${e.status}`}`,failed:!0});return}let n=await e.json();console.info(`[admin-ui] sidebar-session-delete sessionId=${t.sessionId} pidKilled=${n.pidKilled??`?`} deleted=${n.deleted??`?`}`),ne(e=>e.filter(e=>e.sessionId!==t.sessionId)),ye()}catch(e){let n=e instanceof Error?e.message:String(e);console.error(`[admin-ui] sidebar-session-delete-failed sessionId=${t.sessionId} error=${n}`),V({message:`Delete failed: ${n}`,failed:!0})}finally{de(e=>{let n=new Set(e);return n.delete(t.sessionId),n})}}},[x,Y,ye]);return(0,D.jsxs)(`aside`,{className:`side`,children:[(0,D.jsxs)(`div`,{className:`side-new-session-row`,children:[(0,D.jsxs)(`button`,{type:`button`,className:`side-new-session`,onClick:Ce,disabled:X,"aria-busy":X,children:[(0,D.jsx)(S,{size:14}),(0,D.jsx)(`span`,{children:X?`Spawning…`:`New session`})]}),(0,D.jsxs)(`label`,{className:`side-spawn-path-toggle`,title:`Task 562 rc-spawn path (temporary diagnostic)`,style:{display:`flex`,alignItems:`center`,gap:4,fontSize:10,opacity:.7,paddingLeft:8},children:[(0,D.jsx)(`input`,{type:`checkbox`,checked:Z===`script-scope`,onChange:e=>he(e.target.checked?`script-scope`:`node-pty`)}),(0,D.jsx)(`span`,{children:`script-scope`})]})]}),(0,D.jsxs)(`nav`,{className:`side-nav`,children:[(0,D.jsxs)(`button`,{type:`button`,className:`nav-row`,onClick:()=>{console.info(`[admin-ui] sidebar-nav surface=people`),h(),y()},children:[(0,D.jsx)(w,{size:20,strokeWidth:$}),(0,D.jsx)(`span`,{className:`label`,children:`People`}),(0,D.jsx)(`span`,{className:`kbd`})]}),(0,D.jsxs)(`button`,{type:`button`,className:`nav-row`,onClick:()=>{console.info(`[admin-ui] sidebar-nav surface=agents`),v(),y()},children:[(0,D.jsx)(c,{size:20,strokeWidth:$}),(0,D.jsx)(`span`,{className:`label`,children:`Agents`}),(0,D.jsx)(`span`,{className:`kbd`})]}),(0,D.jsxs)(`button`,{type:`button`,className:`nav-row`,onClick:()=>{console.info(`[admin-ui] sidebar-nav surface=projects`),d(),y()},children:[(0,D.jsx)(l,{size:20,strokeWidth:$}),(0,D.jsx)(`span`,{className:`label`,children:`Projects`}),(0,D.jsx)(`span`,{className:`kbd`})]}),(0,D.jsxs)(`button`,{type:`button`,className:`nav-row`,onClick:()=>{console.info(`[admin-ui] sidebar-nav surface=tasks`),_(),y()},children:[(0,D.jsx)(g,{size:20,strokeWidth:$}),(0,D.jsx)(`span`,{className:`label`,children:`Tasks`}),(0,D.jsx)(`span`,{className:`kbd`})]}),(0,D.jsxs)(`button`,{type:`button`,className:`nav-row${k===`artefacts`?` active`:``}`,onClick:_e,children:[(0,D.jsx)(t,{size:20,strokeWidth:$}),(0,D.jsx)(`span`,{className:`label`,children:`Artefacts`}),(0,D.jsx)(`span`,{className:`kbd`})]}),(0,D.jsxs)(`button`,{type:`button`,className:`nav-row${k===`sessions`?` active`:``}`,onClick:be,children:[(0,D.jsx)(m,{size:20,strokeWidth:$}),(0,D.jsx)(`span`,{className:`label`,children:`Sessions`}),(0,D.jsx)(`span`,{className:`kbd`})]})]}),k===`artefacts`&&(0,D.jsxs)(`div`,{className:`side-list`,children:[(0,D.jsxs)(`div`,{className:`group-head`,children:[(0,D.jsx)(`span`,{children:`Artefacts`}),(0,D.jsxs)(`span`,{className:`group-head-meta`,children:[(0,D.jsx)(`button`,{type:`button`,className:`group-head-refresh`,title:`Refresh artefacts`,"aria-label":`Refresh artefacts`,onClick:ve,disabled:I,children:(0,D.jsx)(C,{size:12,className:I?`spinning`:void 0})}),(0,D.jsx)(`span`,{children:I?`…`:String(j.length)})]})]}),P&&(0,D.jsx)(`div`,{className:`conv`,style:{color:`var(--text-tertiary)`,cursor:`default`},children:P}),M&&!P&&j.length>0&&(()=>{let e=j.filter(e=>e.kind===`agent-template`).length,t=j.length-e;return(0,D.jsx)(`div`,{className:`artefact-filter-chips`,children:[{key:`all`,label:`All`,count:j.length},{key:`agent`,label:`Agents`,count:e},{key:`file`,label:`Files`,count:t}].map(e=>(0,D.jsxs)(`button`,{type:`button`,className:`artefact-filter-chip${R===e.key?` active`:``}`,onClick:()=>z(e.key),disabled:e.count===0&&e.key!==`all`,children:[e.label,(0,D.jsx)(`span`,{className:`artefact-filter-chip-count`,children:e.count})]},e.key))})})(),M&&!P&&j.length===0&&(0,D.jsx)(`div`,{className:`conv`,style:{color:`var(--text-tertiary)`,cursor:`default`},children:`No artefacts yet`}),j.filter(e=>R===`all`?!0:R===`agent`?e.kind===`agent-template`:e.kind!==`agent-template`).map(e=>{let n=e.kind===`agent-template`,r=n?c:t,i=Date.parse(e.updatedAt),a=Number.isFinite(i)?new Date(i).toLocaleString(void 0,{dateStyle:`medium`,timeStyle:`short`}):``,o=e.downloadPath!==null;return(0,D.jsxs)(`button`,{type:`button`,className:`conv`,onClick:()=>ge(e),disabled:!o,style:o?void 0:{cursor:`default`},title:o?`Download ${e.name}`:`${e.name} can’t be downloaded`,children:[(0,D.jsx)(r,{size:14,className:`conv-icon`,"data-kind":n?`agent`:`file`,"aria-label":n?`agent template`:`file`}),(0,D.jsxs)(`span`,{className:`conv-stack`,children:[(0,D.jsx)(`span`,{className:`conv-name-line`,children:(0,D.jsx)(`span`,{className:`conv-name`,children:e.name})}),a&&(0,D.jsx)(`span`,{className:`conv-timestamp`,children:a})]}),o&&(0,D.jsx)(f,{size:12,className:`conv-rc-icon`,"aria-hidden":`true`})]},e.id)})]}),k===`sessions`&&(()=>{let e=q?H:H.filter(e=>!e.isSubagent),t=H.some(e=>e.isSubagent);return(0,D.jsxs)(`div`,{className:`side-list`,children:[(0,D.jsxs)(`div`,{className:`group-head`,children:[(0,D.jsx)(`span`,{children:`Sessions`}),(0,D.jsxs)(`span`,{className:`group-head-meta`,children:[(0,D.jsx)(`button`,{type:`button`,className:`group-head-refresh`,title:`Refresh sessions`,"aria-label":`Refresh sessions`,onClick:xe,disabled:K,children:(0,D.jsx)(C,{size:12,className:K?`spinning`:void 0})}),(0,D.jsx)(`span`,{children:K?`…`:String(e.length)})]})]}),ue&&(0,D.jsx)(`div`,{className:`side-account-id`,title:`This install's accountId. The first 8 characters match the truncated UUID label on the live Remote Control daemon entry in claude.ai/code.`,children:(0,D.jsx)(`code`,{children:ue})}),t&&(0,D.jsx)(`div`,{className:`artefact-filter-chips`,children:(0,D.jsx)(`button`,{type:`button`,className:`artefact-filter-chip${q?` active`:``}`,onClick:()=>le(e=>!e),title:q?`Hide subagent sessions`:`Show subagent sessions`,children:`Subagents`})}),W&&(0,D.jsx)(`div`,{className:`conv`,style:{color:`var(--text-tertiary)`,cursor:`default`},children:W}),U&&!W&&e.length===0&&(0,D.jsx)(`div`,{className:`conv`,style:{color:`var(--text-tertiary)`,cursor:`default`},children:`No sessions yet`}),e.map(e=>{let t=Date.parse(e.startedAt),n=Number.isFinite(t)?new Date(t).toLocaleString(void 0,{dateStyle:`medium`,timeStyle:`short`}):``,r=fe.has(e.sessionId),i=Y.has(e.sessionId);return(0,D.jsxs)(`div`,{className:`conv conv-with-actions`,children:[(0,D.jsxs)(`div`,{className:`conv-main-static`,children:[(0,D.jsx)(`span`,{className:`conv-live-dot`,"data-live":e.live?`1`:`0`,"aria-label":e.live?`live session`:`ended session`}),(0,D.jsxs)(`span`,{className:`conv-stack`,children:[(0,D.jsx)(`span`,{className:`conv-name-line`,children:(0,D.jsx)(`span`,{className:`conv-name`,children:e.title})}),n&&(0,D.jsx)(`span`,{className:`conv-timestamp`,children:n})]})]}),(0,D.jsxs)(`div`,{className:`conv-actions`,children:[(0,D.jsx)(`button`,{type:`button`,className:`conv-action`,onClick:()=>{Se(e)},disabled:r||i,"aria-busy":r,"aria-label":`Resume session ${e.title} in claude.ai/code`,title:`Resume in a fresh local Remote Control PTY`,children:(0,D.jsx)(p,{size:12})}),(0,D.jsx)(`button`,{type:`button`,className:`conv-action conv-action-danger`,onClick:t=>{we(t,e)},disabled:r||i,"aria-busy":i,"aria-label":`Delete session ${e.title}`,title:`Delete session (kills PID if live, removes JSONL)`,children:(0,D.jsx)(o,{size:12})})]})]},e.sessionId)})]})})(),(0,D.jsx)(se,{}),(0,D.jsxs)(`div`,{className:`side-foot`,children:[(0,D.jsx)(`div`,{className:`avatar`,children:u?(0,D.jsx)(`img`,{src:u,alt:T}):O}),(0,D.jsxs)(`div`,{className:`who`,children:[(0,D.jsx)(`span`,{className:`name`,children:T}),(0,D.jsx)(`span`,{className:`role`,children:i??`operator`})]})]}),B&&(0,D.jsx)(`div`,{className:`copy-toast${B.failed?` copy-toast-failed`:``}`,role:`status`,children:B.message})]})}var W=5e3;function se(){let[e,t]=(0,E.useState)(null);if((0,E.useEffect)(()=>{let e=!1,n=null;async function r(){try{let n=await fetch(`/api/admin/system-stats`);if(!n.ok){console.error(`[admin-ui] system-stats-fetch-failed status=${n.status}`);return}let r=await n.json();e||t(r)}catch(e){console.error(`[admin-ui] system-stats-fetch-failed reason=${e instanceof Error?e.message:String(e)}`)}}function i(){n===null&&(r(),n=setInterval(()=>{r()},W))}function a(){n!==null&&(clearInterval(n),n=null)}function o(){document.hidden?a():i()}return document.hidden||i(),document.addEventListener(`visibilitychange`,o),()=>{e=!0,a(),document.removeEventListener(`visibilitychange`,o)}},[]),!e||e.platform===`darwin`)return null;let n=e.cpuPct,r=e.memUsedPct,i=n!==null&&n>=.9,a=n!==null&&n>=.98,o=r!==null&&r>=.9,s=r!==null&&r>=.98,c=i||o,l=a||s,u=e=>e===null?`—`:`${Math.round(e*100)}%`,d=e=>{if(e===null)return{width:`0%`,background:`var(--text-tertiary)`};let t=Math.min(1,Math.max(0,e)),n=Math.round(140*(1-t));return{width:`${Math.round(t*100)}%`,background:`hsl(${n}, 65%, 45%)`}},f=[`system-stats`];return c&&f.push(`system-stats--warn`),l&&f.push(`system-stats--crit`),(0,D.jsxs)(`div`,{className:f.join(` `),role:`status`,"aria-label":`host CPU and RAM`,children:[(0,D.jsxs)(`div`,{className:`system-stats__metric`,children:[(0,D.jsxs)(`span`,{className:i?`system-stats__fig system-stats__fig--warn`:`system-stats__fig`,children:[`CPU `,u(n)]}),(0,D.jsx)(`div`,{className:`system-stats__bar`,children:(0,D.jsx)(`div`,{className:`system-stats__bar-fill`,style:d(n)})})]}),(0,D.jsxs)(`div`,{className:`system-stats__metric`,children:[(0,D.jsxs)(`span`,{className:o?`system-stats__fig system-stats__fig--warn`:`system-stats__fig`,children:[`RAM `,u(r)]}),(0,D.jsx)(`div`,{className:`system-stats__bar`,children:(0,D.jsx)(`div`,{className:`system-stats__bar-fill`,style:d(r)})})]})]})}var G=`admin-sidebar-collapsed`,K=`admin-sidebar-drawer-open`;function ce(){if(typeof window>`u`)return!1;try{return window.sessionStorage.getItem(G)===`1`}catch{return!1}}function q(e){if(!(typeof window>`u`))try{e?window.sessionStorage.setItem(G,`1`):window.sessionStorage.removeItem(G)}catch{}}function le(){if(typeof window>`u`)return!1;try{return window.sessionStorage.getItem(K)===`1`}catch{return!1}}function ue(e){if(!(typeof window>`u`))try{e?window.sessionStorage.setItem(K,`1`):window.sessionStorage.removeItem(K)}catch{}}var J=720;function Y(e){let{cacheKey:t,businessName:n,onLogout:r,userName:i,userAvatar:a,role:o,children:s,footer:c}=e,[l,u]=(0,E.useState)(()=>ce()),[d,f]=(0,E.useState)(()=>le()),[p,m]=(0,E.useState)(()=>typeof window<`u`&&window.matchMedia(`(max-width: ${J}px)`).matches);(0,E.useEffect)(()=>{if(typeof window>`u`)return;let e=window.matchMedia(`(max-width: ${J}px)`),t=e=>m(e.matches);return e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[]);let h=(0,E.useCallback)(e=>{q(e),u(e)},[]),g=(0,E.useCallback)(e=>{ue(e),f(e)},[]);(0,E.useEffect)(()=>{if(typeof window>`u`)return;let e=window.location?.pathname??``;console.info(`[admin-ui] shell-mount route=${e} collapsed=${l} drawer=${d}`)},[]);let _=p?d:!l,v=(0,E.useCallback)(()=>{if(!(typeof window>`u`))if(window.matchMedia(`(max-width: ${J}px)`).matches){let e=d;console.info(`[admin-ui] header-sidebar-toggle from=${e?`open`:`closed`} mode=drawer`),g(!e)}else{let e=l;console.info(`[admin-ui] header-sidebar-toggle from=${e?`closed`:`open`} mode=collapse`),h(!e)}},[l,d,h,g]),y=(0,E.useCallback)(e=>{console.info(`[admin-ui] header-menu-nav target=${e}`),window.location.href=e===`data`?`/data`:`/graph`},[]),b={collapsed:l,mobileDrawerOpen:d,sidebarOpen:_,onToggleSidebar:v,setMobileDrawerOpen:g};return(0,D.jsxs)(`div`,{className:`admin-shell admin-page admin-shell-root`,children:[(0,D.jsx)(U,{businessName:n,onNavigate:y,onToggleSidebar:v,sidebarOpen:_,onLogout:r}),(0,D.jsxs)(`div`,{className:`platform${d?` menu-open`:``}${l?` sidebar-collapsed`:``}`,"data-artefact":`closed`,children:[(0,D.jsx)(oe,{businessName:n,cacheKey:t,role:o??null,userName:i,userAvatar:a??null,onSelectProjects:()=>{window.location.href=`/graph?label=Project`},onSelectPeople:()=>{window.location.href=`/graph?label=Person`},onSelectTasks:()=>{window.location.href=`/graph?label=Task`},onSelectAgents:()=>{window.location.href=`/graph?label=Agent`},onCloseMobileDrawer:()=>g(!1),collapsed:l,mobileDrawerOpen:d}),typeof s==`function`?s(b):s]}),d&&(0,D.jsx)(`div`,{className:`sidebar-backdrop menu-open`,"aria-hidden":`true`,onClick:()=>g(!1)}),c]})}export{te as a,C as c,f as d,d as f,B as i,S as l,ie as n,T as o,H as r,ee as s,Y as t,_ as u};
|