@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.
Files changed (28) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/PLUGIN.md +3 -3
  3. package/payload/platform/plugins/admin/hooks/__tests__/pdf-text-layer-inject.test.sh +177 -0
  4. package/payload/platform/plugins/admin/hooks/pdf-text-layer-inject.sh +137 -0
  5. package/payload/platform/plugins/admin/skills/a4-print-documents/SKILL.md +12 -1
  6. package/payload/platform/plugins/admin/skills/a4-print-documents/pdf-inspect.mjs +113 -0
  7. package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +6 -0
  8. package/payload/platform/plugins/docs/references/admin-session.md +15 -0
  9. package/payload/platform/scripts/__tests__/mailbox-inject-registered.test.sh +15 -39
  10. package/payload/platform/scripts/__tests__/public-prompt-gate-registered.test.sh +22 -37
  11. package/payload/platform/scripts/__tests__/public-surface-registered.test.sh +8 -31
  12. package/payload/platform/scripts/setup-account.sh +7 -62
  13. package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.d.ts +129 -0
  14. package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.d.ts.map +1 -0
  15. package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.js +344 -0
  16. package/payload/platform/services/claude-session-manager/dist/blocked-tool-census.js.map +1 -0
  17. package/payload/platform/services/claude-session-manager/dist/index.js +20 -0
  18. package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
  19. package/payload/platform/templates/account-settings.json +6 -0
  20. package/payload/server/server.js +750 -656
  21. package/payload/platform/scripts/lib/__tests__/account-settings-askgate.test.sh +0 -144
  22. package/payload/platform/scripts/lib/__tests__/account-settings-mailbox-inject.test.sh +0 -166
  23. package/payload/platform/scripts/lib/__tests__/account-settings-pdf-conformance.test.sh +0 -151
  24. package/payload/platform/scripts/lib/__tests__/account-settings-public-surface.test.sh +0 -109
  25. package/payload/platform/scripts/lib/account-settings-askgate.sh +0 -98
  26. package/payload/platform/scripts/lib/account-settings-mailbox-inject.sh +0 -91
  27. package/payload/platform/scripts/lib/account-settings-pdf-conformance.sh +0 -98
  28. 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 BOTH writers, with the SAME resolved
3
- # command string. The provision-time heredoc is a frequent merge-conflict site,
4
- # and the backfill lib is the only thing that reaches client sub-accounts, so
5
- # losing either is silent. Drift between the two is worse than losing one: the
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: the lib's own
10
- # filename contains "mailbox-inject.sh", so a bare grep for that is satisfied by
11
- # a comment and proves nothing about what the lib would write.
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
- # What provision-account-dir.sh writes into the settings heredoc. HOOKS_PATH is
23
- # assigned there as the literal string $PLATFORM_ROOT/plugins/admin/hooks, so the
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 "provision-account-dir.sh writes the hook into a UserPromptSubmit entry"
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 backfill lib must hold the RESOLVED form of that same command.
48
- if /usr/bin/grep -qF "_MBXINJECT_CMD='$RESOLVED'" "$BACKFILL"; then
49
- ok "the backfill lib writes the same resolved command string"
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 two writers can drift"
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 6 ]; then
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
- # Both writers are scanned. provision-account-dir.sh writes the array at account
8
- # creation; the account-settings-*.sh libs patch it onto accounts that already
9
- # exist, which is the only path that reaches client sub-accounts. Of the four
10
- # libs only mailbox-inject and public-surface touch this event askgate patches
11
- # PreToolUse and pdf-conformance patches PostToolUse, so the filter skipping
12
- # them is correct rather than a broken parser.
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 provision heredoc's UserPromptSubmit array.
29
- # The script name is taken from the END of each `"command"` string, not from a
30
- # `hooks/` prefix: provision writes `bash $HOOKS_PATH/<name>.sh`, so the literal
31
- # directory never appears there and a prefix anchor silently matches nothing.
32
- PROV_HOOKS=$(python3 - "$PROV" <<'PY'
33
- import re, sys
34
- src = open(sys.argv[1], encoding="utf-8").read()
35
- m = re.search(r'"UserPromptSubmit"\s*:\s*\[(.*?)\n \]', src, re.S)
36
- if not m:
37
- print("")
38
- sys.exit(0)
39
- cmds = re.findall(r'"command"\s*:\s*"([^"]*)"', m.group(1))
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
- # Every hook script named by a backfill lib that patches the same array. Read
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 either writer — the parser is broken, not the tree"
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 across both writers"
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 BOTH writers, on BOTH events, with
3
- # the SAME resolved command string. The provision-time heredoc is a frequent
4
- # merge-conflict site and the backfill lib is the only thing that reaches client
5
- # sub-accounts, so losing either is silent. Drift between them is worse than
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 "provision-account-dir.sh writes the hook exactly three times"
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 two writers can drift"
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 8 ]; then
99
- echo "FAIL: expected 8 checks, ran $((PASS+FAIL))" >&2
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. Retrofit the AskUserQuestion carrier gate onto every existing account
68
+ # 4. Converge the whole hook set on every account
77
69
  # ------------------------------------------------------------------
78
- # Standing check + durable backfill (Task 1683): provision_account_dir writes the
79
- # AskUserQuestion PreToolUse matcher (both gates) only at provision time, and this
80
- # script re-provisions only the house account so a client sub-account created
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. A hook added to
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":"AAiCA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AAIxC,eAAO,MAAM,GAAG,mBAAmB,CAAA;AAEnC,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;0EAE0E;AAC1E,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,aAAa,EAAE,CAAA;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,qBAAqB;IACpC,6DAA6D;IAC7D,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,WAAW,EAAE,CAAA;IACtB;;mFAE+E;IAC/E,cAAc,EAAE,MAAM,EAAE,CAAA;CACzB;AAqBD;;;;4BAI4B;AAC5B,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,EAAE,CAkB3E;AAED;;6EAE6E;AAC7E,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,SAAS,cAAc,EAAE,EAChC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,WAAW,EAAE,CAUf;AAMD;;;;sCAIsC;AACtC,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,qBAAqB,GAAG,MAAM,CAEpE;AAED,YAAY;AACZ,wBAAgB,aAAa,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAEpD;AAED;iEACiE;AACjE,wBAAgB,aAAa,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAEpD;AAED;;;2CAG2C;AAC3C,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,CAExD;AAED,eAAO,MAAM,gBAAgB,SAAU,CAAA;AACvC,eAAO,MAAM,uBAAuB,SAAU,CAAA;AAC9C;;;;4EAI4E;AAC5E,eAAO,MAAM,UAAU,QAAc,CAAA;AAErC,MAAM,WAAW,eAAe;IAC9B,cAAc,IAAI,MAAM,EAAE,CAAA;IAC1B;iDAC6C;IAC7C,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAC5E;;sBAEkB;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IACrF,GAAG,IAAI,MAAM,CAAA;CACd;AAED;4EAC4E;AAC5E,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,eAAe,EACnB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,qBAAqB,CAoBvB;AAED;;;;;;;eAOe;AACf,wBAAgB,sBAAsB,CAAC,eAAe,EAAE,MAAM,GAAG,eAAe,CAyD/E;AAED,MAAM,WAAW,qBAAqB;IACpC;oEACgE;IAChE,eAAe,EAAE,MAAM,CAAA;IACvB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,kFAAkF;IAClF,EAAE,CAAC,EAAE,eAAe,CAAA;IACpB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC9E;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,IAAI,qBAAqB,CAAA;IACjC,MAAM,IAAI,qBAAqB,GAAG,IAAI,CAAA;IACtC,KAAK,IAAI,IAAI,CAAA;IACb,IAAI,IAAI,IAAI,CAAA;CACb;AAOD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,qBAAqB,GAAG,iBAAiB,CA4EtF;AAED;;;+EAG+E;AAC/E,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,MAAM,EAAE,MAAM,CAAA;IACd,MAAM,EAAE,MAAM,CAAA;CACf,GAAG,CAAC,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC;IAAE,SAAS,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CA+BvE"}