@rubytech/create-maxy-code 0.1.569 → 0.1.570
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/plugins/admin/PLUGIN.md +3 -3
- package/payload/platform/plugins/admin/hooks/__tests__/pdf-text-layer-inject.test.sh +177 -0
- package/payload/platform/plugins/admin/hooks/pdf-text-layer-inject.sh +137 -0
- package/payload/platform/plugins/admin/skills/a4-print-documents/SKILL.md +12 -1
- package/payload/platform/plugins/admin/skills/a4-print-documents/pdf-inspect.mjs +113 -0
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
- package/payload/platform/plugins/docs/references/admin-session.md +15 -0
- package/payload/platform/scripts/__tests__/mailbox-inject-registered.test.sh +15 -39
- package/payload/platform/scripts/__tests__/public-prompt-gate-registered.test.sh +22 -37
- package/payload/platform/scripts/__tests__/public-surface-registered.test.sh +8 -31
- package/payload/platform/scripts/setup-account.sh +7 -62
- package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.d.ts +129 -0
- package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.js +344 -0
- package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.js +20 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/templates/account-settings.json +6 -0
- package/payload/server/server.js +750 -656
- package/payload/platform/scripts/lib/__tests__/account-settings-askgate.test.sh +0 -144
- package/payload/platform/scripts/lib/__tests__/account-settings-mailbox-inject.test.sh +0 -166
- package/payload/platform/scripts/lib/__tests__/account-settings-pdf-conformance.test.sh +0 -151
- package/payload/platform/scripts/lib/__tests__/account-settings-public-surface.test.sh +0 -109
- package/payload/platform/scripts/lib/account-settings-askgate.sh +0 -98
- package/payload/platform/scripts/lib/account-settings-mailbox-inject.sh +0 -91
- package/payload/platform/scripts/lib/account-settings-pdf-conformance.sh +0 -98
- package/payload/platform/scripts/lib/account-settings-public-surface.sh +0 -109
|
@@ -1,32 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Task 2157 — the hook must be registered in
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# backfill would not recognise what provision wrote, and would append a second
|
|
7
|
-
# copy to every house account on every install.
|
|
2
|
+
# Task 2157 — the hook must be registered in the account-settings template, in
|
|
3
|
+
# the UserPromptSubmit event, with a fully resolved command path. The template
|
|
4
|
+
# is the single writer since Task 2467, and Task 2475 retired the backfill lib
|
|
5
|
+
# that used to be the second one, so losing the entry here loses it everywhere.
|
|
8
6
|
#
|
|
9
|
-
# Substring greps are deliberately avoided for the command itself:
|
|
10
|
-
#
|
|
11
|
-
#
|
|
7
|
+
# Substring greps are deliberately avoided for the command itself: a bare grep
|
|
8
|
+
# for "mailbox-inject.sh" is satisfied by a comment and proves nothing about
|
|
9
|
+
# what would be written.
|
|
12
10
|
set -uo pipefail
|
|
13
11
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
14
12
|
PROV="$DIR/../../templates/account-settings.json"
|
|
15
|
-
BACKFILL="$DIR/../lib/account-settings-mailbox-inject.sh"
|
|
16
|
-
SETUP="$DIR/../setup-account.sh"
|
|
17
13
|
PASS=0; FAIL=0
|
|
18
14
|
|
|
19
15
|
ok() { echo "PASS: $1"; PASS=$((PASS+1)); }
|
|
20
16
|
bad() { echo "FAIL: $1" >&2; FAIL=$((FAIL+1)); }
|
|
21
17
|
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
# resolved command is the same string the backfill lib holds.
|
|
18
|
+
# The exact line the template must carry. $PLATFORM_ROOT is a literal in the
|
|
19
|
+
# settings file, resolved by Claude Code at hook-run time.
|
|
25
20
|
PROV_LINE='{ "type": "command", "command": "bash $PLATFORM_ROOT/plugins/admin/hooks/mailbox-inject.sh" }'
|
|
26
|
-
RESOLVED='bash $PLATFORM_ROOT/plugins/admin/hooks/mailbox-inject.sh'
|
|
27
21
|
|
|
28
22
|
if /usr/bin/grep -qF "$PROV_LINE" "$PROV"; then
|
|
29
|
-
ok "
|
|
23
|
+
ok "the template writes the hook into a UserPromptSubmit entry"
|
|
30
24
|
else
|
|
31
25
|
bad "exact provision command line absent from $PROV"
|
|
32
26
|
fi
|
|
@@ -44,36 +38,18 @@ else
|
|
|
44
38
|
bad "mailbox-inject.sh is not inside the UserPromptSubmit array in $PROV"
|
|
45
39
|
fi
|
|
46
40
|
|
|
47
|
-
# The
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
else
|
|
51
|
-
bad "backfill _MBXINJECT_CMD is not '$RESOLVED' in $BACKFILL"
|
|
52
|
-
fi
|
|
53
|
-
|
|
54
|
-
# HOOKS_PATH must still resolve to the path the backfill lib assumes; if someone
|
|
55
|
-
# repoints it, the two writers silently disagree and every account gets two hooks.
|
|
41
|
+
# The command must be fully resolved in the template. An unresolved HOOKS_PATH
|
|
42
|
+
# indirection would leave the path to be expanded by whoever copies the file,
|
|
43
|
+
# and the account would carry a hook command that resolves to nothing.
|
|
56
44
|
if ! /usr/bin/grep -q 'HOOKS_PATH' "$PROV"; then
|
|
57
45
|
ok "the template carries no unresolved hook-path indirection"
|
|
58
46
|
else
|
|
59
|
-
bad "$PROV still carries a HOOKS_PATH indirection, so the
|
|
60
|
-
fi
|
|
61
|
-
|
|
62
|
-
if /usr/bin/grep -qF 'account-settings-mailbox-inject.sh' "$SETUP"; then
|
|
63
|
-
ok "backfill lib sourced by setup-account.sh"
|
|
64
|
-
else
|
|
65
|
-
bad "backfill lib not sourced by $SETUP"
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
if /usr/bin/grep -qE '^reconcile_all_accounts_mailbox_inject "\$ACCOUNTS_DIR"' "$SETUP"; then
|
|
69
|
-
ok "backfill invoked over every account by setup-account.sh"
|
|
70
|
-
else
|
|
71
|
-
bad "reconcile_all_accounts_mailbox_inject not invoked in $SETUP"
|
|
47
|
+
bad "$PROV still carries a HOOKS_PATH indirection, so the written command would not resolve"
|
|
72
48
|
fi
|
|
73
49
|
|
|
74
50
|
echo "----"
|
|
75
51
|
echo "PASS=$PASS FAIL=$FAIL"
|
|
76
|
-
if [ $((PASS+FAIL)) -ne
|
|
52
|
+
if [ $((PASS+FAIL)) -ne 3 ]; then
|
|
77
53
|
echo "FAIL: expected 6 checks, ran $((PASS+FAIL))" >&2
|
|
78
54
|
exit 1
|
|
79
55
|
fi
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
# event and does not reproduce on demand: the only symptom is a stranger being
|
|
5
5
|
# told something about the operator.
|
|
6
6
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
7
|
+
# The template is the single writer: provision-account-dir.sh copies
|
|
8
|
+
# platform/templates/account-settings.json at account-creation time, and
|
|
9
|
+
# setup-account.sh's reconcile_all_accounts_settings converges every existing
|
|
10
|
+
# account against the same file on every install. Task 2475 retired the four
|
|
11
|
+
# one-hook backfill libs that used to patch this array, so the template is the
|
|
12
|
+
# only place a UserPromptSubmit hook can be registered.
|
|
13
13
|
#
|
|
14
14
|
# The guard is matched as a fixed string, never as a mention of the variable: a
|
|
15
15
|
# hook that merely READS MAXY_PUBLIC_ALLOWED_TOOLS is not gated by it, and
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
set -uo pipefail
|
|
18
18
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
19
|
PROV="$DIR/../lib/provision-account-dir.sh"
|
|
20
|
+
TPL="$DIR/../../templates/account-settings.json"
|
|
20
21
|
HOOKS_DIR="$DIR/../../plugins/admin/hooks"
|
|
21
22
|
GUARD='[ -n "${MAXY_PUBLIC_ALLOWED_TOOLS:-}" ]'
|
|
22
23
|
INERT="archive-ingest-surface-gate.sh public-tool-surface.sh"
|
|
@@ -25,44 +26,28 @@ PASS=0; FAIL=0
|
|
|
25
26
|
ok() { echo "PASS: $1"; PASS=$((PASS+1)); }
|
|
26
27
|
bad() { echo "FAIL: $1" >&2; FAIL=$((FAIL+1)); }
|
|
27
28
|
|
|
28
|
-
# Every hook script named inside the
|
|
29
|
-
#
|
|
30
|
-
# `hooks/` prefix:
|
|
31
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
names = [n.group(1) for n in (re.search(r'([A-Za-z0-9_.-]+\.sh)\s*$', c.strip()) for c in cmds) if n]
|
|
29
|
+
# Every hook script named inside the template's UserPromptSubmit array. The
|
|
30
|
+
# script name is taken from the END of each `"command"` string, not from a
|
|
31
|
+
# `hooks/` prefix: the template writes `bash $PLATFORM_ROOT/plugins/admin/hooks/
|
|
32
|
+
# <name>.sh`, and anchoring on a literal directory would break the first time
|
|
33
|
+
# that prefix is spelled differently.
|
|
34
|
+
TPL_HOOKS=$(python3 - "$TPL" <<'PY'
|
|
35
|
+
import json, re, sys
|
|
36
|
+
doc = json.load(open(sys.argv[1], encoding="utf-8"))
|
|
37
|
+
cmds = [h.get("command", "")
|
|
38
|
+
for entry in doc.get("hooks", {}).get("UserPromptSubmit", [])
|
|
39
|
+
for h in entry.get("hooks", [])]
|
|
40
|
+
names = [m.group(1) for m in (re.search(r'([A-Za-z0-9_.-]+\.sh)\s*$', c.strip()) for c in cmds) if m]
|
|
41
41
|
print("\n".join(sorted(set(names))))
|
|
42
42
|
PY
|
|
43
43
|
)
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
# only from the `_<NAME>_CMD=` constant each lib registers, not from every `.sh`
|
|
47
|
-
# in the file: these libs name their siblings in prose, and a bare `.sh` sweep
|
|
48
|
-
# turns those comments into phantom registrations.
|
|
49
|
-
BACKFILL_HOOKS=""
|
|
50
|
-
for lib in "$DIR"/../lib/account-settings-*.sh; do
|
|
51
|
-
grep -q 'hooks\.UserPromptSubmit' "$lib" || continue
|
|
52
|
-
cmd_names=$(grep -oE "^_[A-Z0-9_]+_CMD='[^']*'" "$lib" | grep -oE "[A-Za-z0-9_.-]+\.sh'$" | tr -d "'")
|
|
53
|
-
if [ -z "$cmd_names" ]; then
|
|
54
|
-
bad "$(basename "$lib") patches UserPromptSubmit but names no _<NAME>_CMD script — the parser cannot see what it registers"
|
|
55
|
-
fi
|
|
56
|
-
BACKFILL_HOOKS="$BACKFILL_HOOKS
|
|
57
|
-
$cmd_names"
|
|
58
|
-
done
|
|
59
|
-
|
|
60
|
-
ALL=$(printf '%s\n%s\n' "$PROV_HOOKS" "$BACKFILL_HOOKS" | grep -v '^$' | sort -u)
|
|
45
|
+
ALL=$(printf '%s\n' "$TPL_HOOKS" | grep -v '^$' | sort -u)
|
|
61
46
|
|
|
62
47
|
if [ -z "$ALL" ]; then
|
|
63
|
-
bad "no UserPromptSubmit hooks found in
|
|
48
|
+
bad "no UserPromptSubmit hooks found in the template — the parser is broken, not the tree"
|
|
64
49
|
else
|
|
65
|
-
ok "found $(printf '%s\n' "$ALL" | wc -l | tr -d ' ') registered UserPromptSubmit hooks
|
|
50
|
+
ok "found $(printf '%s\n' "$ALL" | wc -l | tr -d ' ') registered UserPromptSubmit hooks in the template"
|
|
66
51
|
fi
|
|
67
52
|
|
|
68
53
|
for h in $ALL; do
|
|
@@ -1,25 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Task 2339 — the hook must be registered in
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
# losing one: the backfill would not recognise what provision wrote and would
|
|
7
|
-
# append a second copy to every house account on every install.
|
|
2
|
+
# Task 2339 — the hook must be registered in the account-settings template, on
|
|
3
|
+
# all three events, with a fully resolved command path. The template is the
|
|
4
|
+
# single writer since Task 2467, and Task 2475 retired the backfill lib that
|
|
5
|
+
# used to be the second one, so losing an entry here loses it everywhere.
|
|
8
6
|
set -uo pipefail
|
|
9
7
|
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
10
8
|
PROV="$DIR/../../templates/account-settings.json"
|
|
11
|
-
BACKFILL="$DIR/../lib/account-settings-public-surface.sh"
|
|
12
|
-
SETUP="$DIR/../setup-account.sh"
|
|
13
9
|
PASS=0; FAIL=0
|
|
14
10
|
|
|
15
11
|
ok() { echo "PASS: $1"; PASS=$((PASS+1)); }
|
|
16
12
|
bad() { echo "FAIL: $1" >&2; FAIL=$((FAIL+1)); }
|
|
17
13
|
|
|
18
14
|
PROV_LINE='{ "type": "command", "command": "bash $PLATFORM_ROOT/plugins/admin/hooks/public-tool-surface.sh" }'
|
|
19
|
-
RESOLVED='bash $PLATFORM_ROOT/plugins/admin/hooks/public-tool-surface.sh'
|
|
20
15
|
|
|
21
16
|
if [ "$(/usr/bin/grep -cF "$PROV_LINE" "$PROV")" = "3" ]; then
|
|
22
|
-
ok "
|
|
17
|
+
ok "the template writes the hook exactly three times"
|
|
23
18
|
else
|
|
24
19
|
bad "expected 3 provision command lines in $PROV"
|
|
25
20
|
fi
|
|
@@ -69,34 +64,16 @@ else
|
|
|
69
64
|
bad "public-tool-surface.sh is not inside the UserPromptSubmit array in $PROV"
|
|
70
65
|
fi
|
|
71
66
|
|
|
72
|
-
if /usr/bin/grep -qF "_PUBSURFACE_CMD='$RESOLVED'" "$BACKFILL"; then
|
|
73
|
-
ok "the backfill lib writes the same resolved command string"
|
|
74
|
-
else
|
|
75
|
-
bad "backfill _PUBSURFACE_CMD is not '$RESOLVED' in $BACKFILL"
|
|
76
|
-
fi
|
|
77
|
-
|
|
78
67
|
if ! /usr/bin/grep -q 'HOOKS_PATH' "$PROV"; then
|
|
79
68
|
ok "the template carries no unresolved hook-path indirection"
|
|
80
69
|
else
|
|
81
|
-
bad "$PROV still carries a HOOKS_PATH indirection, so the
|
|
82
|
-
fi
|
|
83
|
-
|
|
84
|
-
if /usr/bin/grep -qF 'account-settings-public-surface.sh' "$SETUP"; then
|
|
85
|
-
ok "backfill lib sourced by setup-account.sh"
|
|
86
|
-
else
|
|
87
|
-
bad "backfill lib not sourced by $SETUP"
|
|
88
|
-
fi
|
|
89
|
-
|
|
90
|
-
if /usr/bin/grep -qE '^reconcile_all_accounts_public_surface "\$ACCOUNTS_DIR"' "$SETUP"; then
|
|
91
|
-
ok "backfill invoked over every account by setup-account.sh"
|
|
92
|
-
else
|
|
93
|
-
bad "reconcile_all_accounts_public_surface not invoked in $SETUP"
|
|
70
|
+
bad "$PROV still carries a HOOKS_PATH indirection, so the written command would not resolve"
|
|
94
71
|
fi
|
|
95
72
|
|
|
96
73
|
echo "----"
|
|
97
74
|
echo "PASS=$PASS FAIL=$FAIL"
|
|
98
|
-
if [ $((PASS+FAIL)) -ne
|
|
99
|
-
echo "FAIL: expected
|
|
75
|
+
if [ $((PASS+FAIL)) -ne 5 ]; then
|
|
76
|
+
echo "FAIL: expected 5 checks, ran $((PASS+FAIL))" >&2
|
|
100
77
|
exit 1
|
|
101
78
|
fi
|
|
102
79
|
[ "$FAIL" -eq 0 ]
|
|
@@ -34,14 +34,6 @@ ACCOUNTS_DIR="$INSTALL_DIR/data/accounts"
|
|
|
34
34
|
. "$SCRIPT_DIR/lib/resolve-account-dir.sh"
|
|
35
35
|
# shellcheck source=lib/provision-account-dir.sh
|
|
36
36
|
. "$SCRIPT_DIR/lib/provision-account-dir.sh"
|
|
37
|
-
# shellcheck source=lib/account-settings-askgate.sh
|
|
38
|
-
. "$SCRIPT_DIR/lib/account-settings-askgate.sh"
|
|
39
|
-
# shellcheck source=lib/account-settings-pdf-conformance.sh
|
|
40
|
-
. "$SCRIPT_DIR/lib/account-settings-pdf-conformance.sh"
|
|
41
|
-
# shellcheck source=lib/account-settings-mailbox-inject.sh
|
|
42
|
-
. "$SCRIPT_DIR/lib/account-settings-mailbox-inject.sh"
|
|
43
|
-
# shellcheck source=lib/account-settings-public-surface.sh
|
|
44
|
-
. "$SCRIPT_DIR/lib/account-settings-public-surface.sh"
|
|
45
37
|
# shellcheck source=lib/read-brand-json.sh
|
|
46
38
|
. "$SCRIPT_DIR/lib/read-brand-json.sh"
|
|
47
39
|
# Resolve brand-aware persistent users.json path before the resolver runs.
|
|
@@ -73,62 +65,15 @@ provision_account_dir "$ACCOUNT_DIR" "$ACCOUNT_ID" "house"
|
|
|
73
65
|
reconcile_all_accounts_owned_dirs "$ACCOUNTS_DIR"
|
|
74
66
|
|
|
75
67
|
# ------------------------------------------------------------------
|
|
76
|
-
# 4.
|
|
68
|
+
# 4. Converge the whole hook set on every account
|
|
77
69
|
# ------------------------------------------------------------------
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
# before Task 1552 keeps a stale matcher with no channel-carrier gate and
|
|
82
|
-
# re-wedges on a channel AskUserQuestion. Reconcile every account on every install
|
|
83
|
-
# so the gate reaches pre-1552 accounts. Idempotent; logs one [backfill-1683] line
|
|
84
|
-
# per account.
|
|
85
|
-
reconcile_all_accounts_askgate "$ACCOUNTS_DIR"
|
|
86
|
-
|
|
87
|
-
# ------------------------------------------------------------------
|
|
88
|
-
# 5. Retrofit the quote-render PDF-conformance matcher onto every account
|
|
89
|
-
# ------------------------------------------------------------------
|
|
90
|
-
# Standing check + durable backfill (Task 1929): provision_account_dir writes the
|
|
91
|
-
# PostToolUse browser-pdf-save matcher (quote-render-pdf-conformance.sh) only at
|
|
92
|
-
# provision time, and this script re-provisions only the house account. An
|
|
93
|
-
# account created before Task 1922 keeps a settings.json without the matcher and
|
|
94
|
-
# never runs the PDF gate. Reconcile every account on every install so the gate
|
|
95
|
-
# reaches pre-1922 accounts. Idempotent; logs one [backfill-1929] line per account.
|
|
96
|
-
reconcile_all_accounts_pdf_conformance "$ACCOUNTS_DIR"
|
|
97
|
-
|
|
98
|
-
# ------------------------------------------------------------------
|
|
99
|
-
# 6. Retrofit the per-turn <mailboxes> injection hook onto every account
|
|
100
|
-
# ------------------------------------------------------------------
|
|
101
|
-
# Standing check + durable backfill (Task 2157): provision_account_dir writes the
|
|
102
|
-
# UserPromptSubmit array only at provision time, and this script re-provisions
|
|
103
|
-
# only the house account. A client sub-account created by the account-lifecycle
|
|
104
|
-
# tool keeps a settings.json without mailbox-inject.sh and never gets the
|
|
105
|
-
# standing mailbox list, so its sessions go on learning attachment state by
|
|
106
|
-
# polling one mail plugin and reading that plugin's miss as an account-wide fact.
|
|
107
|
-
# Reconcile every account on every install. Idempotent; logs one [backfill-2157]
|
|
108
|
-
# line per account.
|
|
109
|
-
reconcile_all_accounts_mailbox_inject "$ACCOUNTS_DIR"
|
|
110
|
-
|
|
111
|
-
# ------------------------------------------------------------------
|
|
112
|
-
# 7. Retrofit the realised-public-tool-surface hook onto every account
|
|
113
|
-
# ------------------------------------------------------------------
|
|
114
|
-
# Standing check + durable backfill (Task 2339): provision_account_dir writes the
|
|
115
|
-
# matcher-less PreToolUse entry and the Stop entry only at provision time, and
|
|
116
|
-
# this script re-provisions only the house account. Without this, the hook that
|
|
117
|
-
# records what a public spawn's tools actually did would reach house accounts
|
|
118
|
-
# only — including not reaching the account where five tools were seen running
|
|
119
|
-
# under a one-tool allowlist. Reconcile every account on every install.
|
|
120
|
-
# Idempotent; logs one [backfill-2339] line per account.
|
|
121
|
-
reconcile_all_accounts_public_surface "$ACCOUNTS_DIR"
|
|
122
|
-
|
|
123
|
-
# ------------------------------------------------------------------
|
|
124
|
-
# 8. Converge the whole hook set on every account
|
|
125
|
-
# ------------------------------------------------------------------
|
|
126
|
-
# Steps 3 to 7 each retrofit one hook, and each was written after that hook was
|
|
127
|
-
# found missing on a live account. provision_account_dir writes the full set,
|
|
128
|
-
# but only at provision time, and this script re-provisions the house account
|
|
129
|
-
# and no other — so every future hook repeats the same fault. This step
|
|
70
|
+
# provision_account_dir writes the full hook set, but only at provision time,
|
|
71
|
+
# and this script re-provisions the house account and no other — so an account
|
|
72
|
+
# created before a hook landed would run a stale set indefinitely. This step
|
|
130
73
|
# converges each account's settings against the shipped template instead, and
|
|
131
|
-
# states the divergence count on every run including zero.
|
|
74
|
+
# states the divergence count on every run including zero. It is the single
|
|
75
|
+
# writer of account settings: a hook added to
|
|
132
76
|
# platform/templates/account-settings.json needs no backfill of its own.
|
|
77
|
+
# (Task 2475 retired the four one-hook backfill libs this replaced.)
|
|
133
78
|
. "$SCRIPT_DIR/lib/account-settings-reconcile.sh"
|
|
134
79
|
reconcile_all_accounts_settings "$ACCOUNTS_DIR"
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import type { Logger } from './types.js';
|
|
2
|
+
export declare const TAG = "[blocked-tool]";
|
|
3
|
+
export interface ToolUseRecord {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
}
|
|
7
|
+
/** One transcript's scan result. `agentId` is `—` for the session's own
|
|
8
|
+
* transcript and `agent-<id>` for a subagent's — the field that distinguishes
|
|
9
|
+
* a card the operator CAN see in the session UI from one they cannot. */
|
|
10
|
+
export interface TranscriptScan {
|
|
11
|
+
agentId: string;
|
|
12
|
+
path: string;
|
|
13
|
+
mtimeMs: number;
|
|
14
|
+
unmatched: ToolUseRecord[];
|
|
15
|
+
}
|
|
16
|
+
export interface BlockedCall {
|
|
17
|
+
sessionId: string;
|
|
18
|
+
agentId: string;
|
|
19
|
+
toolUseId: string;
|
|
20
|
+
tool: string;
|
|
21
|
+
blockedForMs: number;
|
|
22
|
+
}
|
|
23
|
+
export interface BlockedCensusSnapshot {
|
|
24
|
+
/** Transcripts read this tick, across every live session. */
|
|
25
|
+
scanned: number;
|
|
26
|
+
blocked: BlockedCall[];
|
|
27
|
+
/** The session ids that were live this tick. A previously-blocked call whose
|
|
28
|
+
* session is no longer here did not resolve — the session went away — and
|
|
29
|
+
* reporting that as `op=cleared` would tell the operator the block lifted. */
|
|
30
|
+
liveSessionIds: string[];
|
|
31
|
+
}
|
|
32
|
+
/** PURE. Every `tool_use` id in these lines with no matching `tool_result`.
|
|
33
|
+
* Order is first-sighting order. Malformed lines are skipped, not thrown on:
|
|
34
|
+
* a JSONL read can land mid-append. A `tool_use` carrying no `name` is
|
|
35
|
+
* reported as `unknown` rather than dropped — an unnamed blocked call is
|
|
36
|
+
* still a blocked call. */
|
|
37
|
+
export declare function unmatchedToolUses(lines: readonly string[]): ToolUseRecord[];
|
|
38
|
+
/** PURE. Which unmatched calls are blocked. Blocked means unmatched AND the
|
|
39
|
+
* transcript has not advanced for `staleMs`. Without the staleness term every
|
|
40
|
+
* in-flight call would be reported, which would make the signal useless. */
|
|
41
|
+
export declare function blockedFromScans(sessionId: string, scans: readonly TranscriptScan[], nowMs: number, staleMs: number): BlockedCall[];
|
|
42
|
+
/** PURE. Emitted every tick INCLUDING zero, so a census that stopped ticking is
|
|
43
|
+
* distinguishable from one that found nothing. `sessions` is separate from
|
|
44
|
+
* `scanned` on purpose: `sessions=3 scanned=0` is a census that could not
|
|
45
|
+
* resolve a transcript for any live session, which without the first field
|
|
46
|
+
* reads exactly like a quiet box. */
|
|
47
|
+
export declare function formatBlockedCensus(s: BlockedCensusSnapshot): string;
|
|
48
|
+
/** PURE. */
|
|
49
|
+
export declare function formatBlocked(c: BlockedCall): string;
|
|
50
|
+
/** PURE. Same ids as its `op=blocked` twin, so the pair is greppable. Emitted
|
|
51
|
+
* ONLY when the session is still live and the result landed. */
|
|
52
|
+
export declare function formatCleared(c: BlockedCall): string;
|
|
53
|
+
/** PURE. The other way a blocked call leaves the reported set: its session
|
|
54
|
+
* stopped being live. The call did NOT resolve, and saying `op=cleared` here
|
|
55
|
+
* would tell the operator the block lifted when the session actually went
|
|
56
|
+
* away with the card still unanswered. */
|
|
57
|
+
export declare function formatSessionGone(c: BlockedCall): string;
|
|
58
|
+
export declare const DEFAULT_STALE_MS = 120000;
|
|
59
|
+
export declare const DEFAULT_NOTIFY_AFTER_MS = 180000;
|
|
60
|
+
/** A `tool_result` always follows its `tool_use` in the same file, so a
|
|
61
|
+
* contiguous tail containing the use contains its result too — unless one
|
|
62
|
+
* result exceeds this window, in which case that one call reads as unmatched.
|
|
63
|
+
* 1 MB is a chosen bound, not a measured one; the failure mode it admits is a
|
|
64
|
+
* false `op=blocked` on a call whose result was larger than the window. */
|
|
65
|
+
export declare const TAIL_BYTES: number;
|
|
66
|
+
export interface BlockedCensusIo {
|
|
67
|
+
listSessionIds(): string[];
|
|
68
|
+
/** The session's own transcript (`agentId: '—'`) followed by every
|
|
69
|
+
* `<sessionId>/subagents/agent-*.jsonl`. */
|
|
70
|
+
listTranscripts(sessionId: string): Array<{
|
|
71
|
+
agentId: string;
|
|
72
|
+
path: string;
|
|
73
|
+
}>;
|
|
74
|
+
/** Last `maxBytes` of the file split into lines, plus its mtime. Null when
|
|
75
|
+
* the file cannot be read. The first line is dropped when the read started
|
|
76
|
+
* mid-record. */
|
|
77
|
+
readTail(path: string, maxBytes: number): {
|
|
78
|
+
lines: string[];
|
|
79
|
+
mtimeMs: number;
|
|
80
|
+
} | null;
|
|
81
|
+
now(): number;
|
|
82
|
+
}
|
|
83
|
+
/** IMPURE. `scanned` counts transcripts actually READ, so a session whose
|
|
84
|
+
* files vanished does not inflate the count into looking like coverage. */
|
|
85
|
+
export declare function collectBlockedCensus(io: BlockedCensusIo, staleMs: number, tailBytes: number): BlockedCensusSnapshot;
|
|
86
|
+
/** `claudeConfigDir` is the state root (`CLAUDE_CONFIG_DIR`), NOT a spawn cwd.
|
|
87
|
+
* Task 1490 re-points the manager's `spawnCwd` per sub-account spawn, so a
|
|
88
|
+
* client session's transcript is filed under a slug derived from the
|
|
89
|
+
* sub-account directory rather than the house cwd (http-server.ts:815 states
|
|
90
|
+
* this: reading `deps.spawnCwd` "would always miss the file"). Keying this
|
|
91
|
+
* census on one cwd would make it blind to exactly the sub-account sessions it
|
|
92
|
+
* was written for, so it resolves each session through the all-slug scan
|
|
93
|
+
* instead. */
|
|
94
|
+
export declare function defaultBlockedCensusIo(claudeConfigDir: string): BlockedCensusIo;
|
|
95
|
+
export interface BlockedToolCensusDeps {
|
|
96
|
+
/** State root (`CLAUDE_CONFIG_DIR`), not a spawn cwd. See
|
|
97
|
+
* defaultBlockedCensusIo for why a cwd cannot be used here. */
|
|
98
|
+
claudeConfigDir: string;
|
|
99
|
+
intervalMs: number;
|
|
100
|
+
staleMs?: number;
|
|
101
|
+
notifyAfterMs?: number;
|
|
102
|
+
logger: Logger;
|
|
103
|
+
/** Injected in tests; production uses defaultBlockedCensusIo(claudeConfigDir). */
|
|
104
|
+
io?: BlockedCensusIo;
|
|
105
|
+
/** The loopback POST to the UI's notification route. Absent means log-only. */
|
|
106
|
+
notify?: (c: BlockedCall) => Promise<{
|
|
107
|
+
delivered: boolean;
|
|
108
|
+
channel: string;
|
|
109
|
+
}>;
|
|
110
|
+
}
|
|
111
|
+
export interface BlockedToolCensus {
|
|
112
|
+
tickOnce(): BlockedCensusSnapshot;
|
|
113
|
+
latest(): BlockedCensusSnapshot | null;
|
|
114
|
+
start(): void;
|
|
115
|
+
stop(): void;
|
|
116
|
+
}
|
|
117
|
+
export declare function createBlockedToolCensus(deps: BlockedToolCensusDeps): BlockedToolCensus;
|
|
118
|
+
/** Production notifier. POSTs to the brand UI's loopback route, the same
|
|
119
|
+
* transport `postLogLineViaFetch` uses. A throw or a non-200 resolves to
|
|
120
|
+
* `delivered:false` rather than rejecting: the census logs the outcome either
|
|
121
|
+
* way, and a send that failed silently is the blind spot this task closes. */
|
|
122
|
+
export declare function postBlockedNotifyViaFetch(opts: {
|
|
123
|
+
uiPort: string;
|
|
124
|
+
logger: Logger;
|
|
125
|
+
}): (c: BlockedCall) => Promise<{
|
|
126
|
+
delivered: boolean;
|
|
127
|
+
channel: string;
|
|
128
|
+
}>;
|
|
129
|
+
//# sourceMappingURL=blocked-tool-census.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blocked-tool-census.d.ts","sourceRoot":"","sources":["../src/blocked-tool-census.ts"],"names":[],"mappings":"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"}
|