@naswerks/periscope 1.0.0
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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +146 -0
- package/SECURITY.md +315 -0
- package/dist/bin/command.d.ts +97 -0
- package/dist/bin/command.js +141 -0
- package/dist/bin/config.d.ts +7 -0
- package/dist/bin/config.js +90 -0
- package/dist/bin/login.d.ts +48 -0
- package/dist/bin/login.js +82 -0
- package/dist/bin/main.d.ts +33 -0
- package/dist/bin/main.js +173 -0
- package/dist/bin/pair.d.ts +28 -0
- package/dist/bin/pair.js +198 -0
- package/dist/bin/periscope.d.ts +3 -0
- package/dist/bin/periscope.js +4 -0
- package/dist/bin/posture.d.ts +64 -0
- package/dist/bin/posture.js +157 -0
- package/dist/bin/reconfigure.d.ts +64 -0
- package/dist/bin/reconfigure.js +195 -0
- package/dist/bin/serve.d.ts +103 -0
- package/dist/bin/serve.js +441 -0
- package/dist/bin/status.d.ts +11 -0
- package/dist/bin/status.js +49 -0
- package/dist/bin/workspaces.d.ts +109 -0
- package/dist/bin/workspaces.js +215 -0
- package/dist/control/backoff.d.ts +25 -0
- package/dist/control/backoff.js +20 -0
- package/dist/control/codec.d.ts +15 -0
- package/dist/control/codec.js +501 -0
- package/dist/control/credential.d.ts +23 -0
- package/dist/control/credential.js +8 -0
- package/dist/control/frames.d.ts +1180 -0
- package/dist/control/frames.js +441 -0
- package/dist/control/link-state.d.ts +32 -0
- package/dist/control/link-state.js +39 -0
- package/dist/control/link.d.ts +109 -0
- package/dist/control/link.js +736 -0
- package/dist/control/queue.d.ts +110 -0
- package/dist/control/queue.js +174 -0
- package/dist/control/seq.d.ts +49 -0
- package/dist/control/seq.js +52 -0
- package/dist/control/stream-routing.d.ts +200 -0
- package/dist/control/stream-routing.js +167 -0
- package/dist/control/stream.d.ts +43 -0
- package/dist/control/stream.js +64 -0
- package/dist/core/async-queue.d.ts +26 -0
- package/dist/core/async-queue.js +63 -0
- package/dist/core/failure.d.ts +22 -0
- package/dist/core/failure.js +53 -0
- package/dist/core/index.d.ts +18 -0
- package/dist/core/index.js +7 -0
- package/dist/core/keyed-turns.d.ts +28 -0
- package/dist/core/keyed-turns.js +44 -0
- package/dist/core/paths.d.ts +25 -0
- package/dist/core/paths.js +77 -0
- package/dist/core/refusal.d.ts +43 -0
- package/dist/core/refusal.js +399 -0
- package/dist/core/result.d.ts +22 -0
- package/dist/core/result.js +16 -0
- package/dist/core/time.d.ts +19 -0
- package/dist/core/time.js +25 -0
- package/dist/core/vocab.d.ts +23 -0
- package/dist/core/vocab.js +37 -0
- package/dist/core/workspace-id.d.ts +12 -0
- package/dist/core/workspace-id.js +12 -0
- package/dist/gate/command.d.ts +136 -0
- package/dist/gate/command.js +551 -0
- package/dist/gate/decision.d.ts +111 -0
- package/dist/gate/decision.js +97 -0
- package/dist/gate/escalate.d.ts +82 -0
- package/dist/gate/escalate.js +92 -0
- package/dist/gate/gate.d.ts +173 -0
- package/dist/gate/gate.js +417 -0
- package/dist/gate/index.d.ts +39 -0
- package/dist/gate/index.js +31 -0
- package/dist/gate/jail.d.ts +92 -0
- package/dist/gate/jail.js +131 -0
- package/dist/gate/local.d.ts +69 -0
- package/dist/gate/local.js +51 -0
- package/dist/gate/outcome.d.ts +72 -0
- package/dist/gate/outcome.js +91 -0
- package/dist/gate/shell.d.ts +36 -0
- package/dist/gate/shell.js +226 -0
- package/dist/host/agent-process.d.ts +452 -0
- package/dist/host/agent-process.js +382 -0
- package/dist/host/bulk-post.d.ts +44 -0
- package/dist/host/bulk-post.js +114 -0
- package/dist/host/claude-transcripts.d.ts +84 -0
- package/dist/host/claude-transcripts.js +339 -0
- package/dist/host/config-file.d.ts +60 -0
- package/dist/host/config-file.js +216 -0
- package/dist/host/git-facts.d.ts +7 -0
- package/dist/host/git-facts.js +106 -0
- package/dist/host/hooks.d.ts +56 -0
- package/dist/host/hooks.js +75 -0
- package/dist/host/host.d.ts +356 -0
- package/dist/host/host.js +1294 -0
- package/dist/host/index.d.ts +35 -0
- package/dist/host/index.js +22 -0
- package/dist/host/link-state-file.d.ts +18 -0
- package/dist/host/link-state-file.js +66 -0
- package/dist/host/loopback.d.ts +38 -0
- package/dist/host/loopback.js +122 -0
- package/dist/host/machine.d.ts +9 -0
- package/dist/host/machine.js +19 -0
- package/dist/host/mcp-server.d.ts +11 -0
- package/dist/host/mcp-server.js +48 -0
- package/dist/host/package-facts.d.ts +3 -0
- package/dist/host/package-facts.js +26 -0
- package/dist/host/paired-credential-store.d.ts +32 -0
- package/dist/host/paired-credential-store.js +112 -0
- package/dist/host/paths.d.ts +80 -0
- package/dist/host/paths.js +165 -0
- package/dist/host/repository-read.d.ts +33 -0
- package/dist/host/repository-read.js +201 -0
- package/dist/host/session-store.d.ts +53 -0
- package/dist/host/session-store.js +100 -0
- package/dist/host/sign-in.d.ts +90 -0
- package/dist/host/sign-in.js +239 -0
- package/dist/host/telemetry.d.ts +42 -0
- package/dist/host/telemetry.js +74 -0
- package/dist/host/token-cache.d.ts +62 -0
- package/dist/host/token-cache.js +185 -0
- package/dist/host/transcript-fs.d.ts +4 -0
- package/dist/host/transcript-fs.js +104 -0
- package/dist/host/wire-request.d.ts +80 -0
- package/dist/host/wire-request.js +196 -0
- package/dist/host/workspace-fs.d.ts +14 -0
- package/dist/host/workspace-fs.js +84 -0
- package/dist/host/workspace-trust.d.ts +23 -0
- package/dist/host/workspace-trust.js +80 -0
- package/dist/identity/authorize.d.ts +59 -0
- package/dist/identity/authorize.js +72 -0
- package/dist/identity/config.d.ts +98 -0
- package/dist/identity/config.js +130 -0
- package/dist/identity/credential.d.ts +91 -0
- package/dist/identity/credential.js +76 -0
- package/dist/identity/device-code.d.ts +115 -0
- package/dist/identity/device-code.js +134 -0
- package/dist/identity/index.d.ts +33 -0
- package/dist/identity/index.js +10 -0
- package/dist/identity/mode.d.ts +106 -0
- package/dist/identity/mode.js +69 -0
- package/dist/identity/paired-credential.d.ts +52 -0
- package/dist/identity/paired-credential.js +43 -0
- package/dist/identity/pkce.d.ts +50 -0
- package/dist/identity/pkce.js +94 -0
- package/dist/identity/store.d.ts +43 -0
- package/dist/identity/store.js +43 -0
- package/dist/identity/token.d.ts +58 -0
- package/dist/identity/token.js +149 -0
- package/dist/index.d.ts +45 -0
- package/dist/index.js +53 -0
- package/dist/mcp/descriptor.d.ts +59 -0
- package/dist/mcp/descriptor.js +14 -0
- package/dist/mcp/index.d.ts +14 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/schema.d.ts +37 -0
- package/dist/mcp/schema.js +175 -0
- package/dist/mcp/server.d.ts +107 -0
- package/dist/mcp/server.js +66 -0
- package/dist/persistence/entry.d.ts +74 -0
- package/dist/persistence/entry.js +105 -0
- package/dist/persistence/index.d.ts +22 -0
- package/dist/persistence/index.js +8 -0
- package/dist/persistence/key.d.ts +46 -0
- package/dist/persistence/key.js +33 -0
- package/dist/persistence/mirror.d.ts +71 -0
- package/dist/persistence/mirror.js +57 -0
- package/dist/persistence/receipt.d.ts +111 -0
- package/dist/persistence/receipt.js +85 -0
- package/dist/persistence/retention.d.ts +68 -0
- package/dist/persistence/retention.js +68 -0
- package/dist/persistence/store.d.ts +106 -0
- package/dist/persistence/store.js +86 -0
- package/dist/persistence/transition-log.d.ts +73 -0
- package/dist/persistence/transition-log.js +133 -0
- package/dist/protocol.d.ts +27 -0
- package/dist/protocol.js +12 -0
- package/dist/sessions/index.d.ts +8 -0
- package/dist/sessions/index.js +4 -0
- package/dist/sessions/registry.d.ts +186 -0
- package/dist/sessions/registry.js +190 -0
- package/dist/sessions/session.d.ts +178 -0
- package/dist/sessions/session.js +288 -0
- package/dist/sessions/spawn-env.d.ts +88 -0
- package/dist/sessions/spawn-env.js +277 -0
- package/dist/state/coverage.d.ts +308 -0
- package/dist/state/coverage.js +315 -0
- package/dist/state/index.d.ts +12 -0
- package/dist/state/index.js +7 -0
- package/dist/state/machine.d.ts +112 -0
- package/dist/state/machine.js +237 -0
- package/dist/state/model.d.ts +233 -0
- package/dist/state/model.js +214 -0
- package/dist/state/observer.d.ts +55 -0
- package/dist/state/observer.js +413 -0
- package/dist/state/reporter.d.ts +45 -0
- package/dist/state/reporter.js +35 -0
- package/dist/state/store.d.ts +56 -0
- package/dist/state/store.js +120 -0
- package/dist/telemetry/index.d.ts +11 -0
- package/dist/telemetry/index.js +2 -0
- package/dist/telemetry/usage.d.ts +78 -0
- package/dist/telemetry/usage.js +69 -0
- package/dist/workspace/git-worktree.d.ts +150 -0
- package/dist/workspace/git-worktree.js +417 -0
- package/dist/workspace/index.d.ts +9 -0
- package/dist/workspace/index.js +4 -0
- package/dist/workspace/plain-dir.d.ts +34 -0
- package/dist/workspace/plain-dir.js +90 -0
- package/dist/workspace/provider.d.ts +152 -0
- package/dist/workspace/provider.js +2 -0
- package/dist/workspace/worktree-porcelain.d.ts +29 -0
- package/dist/workspace/worktree-porcelain.js +100 -0
- package/docs/architecture.md +277 -0
- package/docs/configuration.md +187 -0
- package/docs/gate.md +219 -0
- package/docs/identity.md +107 -0
- package/docs/protocol.md +501 -0
- package/docs/state-machine.md +160 -0
- package/examples/README.md +141 -0
- package/examples/minimal-controller/controller.ts +38 -0
- package/examples/parallel-run-proof/permission-mode-probe.ts +129 -0
- package/examples/parallel-run-proof/run.ts +850 -0
- package/examples/test-controller/controller.ts +655 -0
- package/examples/test-controller/serve.ts +29 -0
- package/examples/tsconfig.json +30 -0
- package/package.json +98 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The environment a spawned agent process receives — an allow-list, not a strip-list.
|
|
3
|
+
*
|
|
4
|
+
* The Agent SDK's `env` option replaces the subprocess environment rather than merging it, and when
|
|
5
|
+
* omitted the subprocess inherits `process.env` whole. So the default posture is full inheritance,
|
|
6
|
+
* and the only way to hold a boundary is to state what crosses it. A strip-list can only ever remove
|
|
7
|
+
* what someone thought of; this one is "absent unless declared".
|
|
8
|
+
*
|
|
9
|
+
* The failures this prevents are all silent, which is why the set is pinned key-by-key rather than
|
|
10
|
+
* eyeballed. Three are recorded, and two have been observed in a live host:
|
|
11
|
+
* - `CLAUDE_CODE_CHILD_SESSION` inherited: the CLI treats the spawn as a nested session and never
|
|
12
|
+
* persists its transcript, while the Stop path still reports the path it never wrote.
|
|
13
|
+
* - `CLAUDE_EFFORT` inherited: the host's own effort silently becomes every spawn's effort.
|
|
14
|
+
* - a host secret inherited: every subprocess the agent runs authenticates as the host.
|
|
15
|
+
*
|
|
16
|
+
* How the declared set was derived — read this before adding or removing a key.
|
|
17
|
+
* Not copied from any single product's list. A list derived from a capture of one product's
|
|
18
|
+
* machines declares that product's own service-discovery variables; carrying those here is how a
|
|
19
|
+
* general-purpose package quietly becomes a single-product one. The test that gates this file runs
|
|
20
|
+
* a closed diff over two corpora — a live host capture and a production capture from a different
|
|
21
|
+
* machine — and every key in either is declared or carries a written reason for being dropped. Two
|
|
22
|
+
* corpora rather than one because a single capture under-includes whatever that box happens to
|
|
23
|
+
* lack: proxy variables, `XDG_`, `LC_`. Do not tidy them into one.
|
|
24
|
+
*
|
|
25
|
+
* Product-specific names are not declared here. They come back through `extraAllowedKeys`, which the
|
|
26
|
+
* embedder states for its own deployment — `USERPROFILE` is a primitive, a dashboard endpoint URL
|
|
27
|
+
* is somebody's product.
|
|
28
|
+
*
|
|
29
|
+
* The standing posture, because it decides every close call: over-inclusion is recoverable and
|
|
30
|
+
* visible; under-inclusion breaks agents in ways that read as model failures many sessions later.
|
|
31
|
+
* So a key is declared unless there is a positive reason to drop it — it carries a credential, it is
|
|
32
|
+
* another process's handle, it is one product's configuration, or it actively lies to the child
|
|
33
|
+
* about what it is.
|
|
34
|
+
*/
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
// The declared set.
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
const ALLOWED_EXACT = [
|
|
39
|
+
// -- Home resolution. Load-bearing, and the group travels together or not at all.
|
|
40
|
+
// On Windows the key is USERPROFILE, not HOME: `os.homedir()` reads the USERPROFILE family,
|
|
41
|
+
// and `HOME` in a Windows child is a Git-Bash invention. Declaring HOME while omitting
|
|
42
|
+
// USERPROFILE leaves the CLI unable to find its ambient credentials file — every agent
|
|
43
|
+
// silently unauthenticated, presenting as a confused model rather than an error.
|
|
44
|
+
'USERPROFILE',
|
|
45
|
+
'HOMEDRIVE',
|
|
46
|
+
'HOMEPATH',
|
|
47
|
+
'HOME',
|
|
48
|
+
'APPDATA',
|
|
49
|
+
'LOCALAPPDATA',
|
|
50
|
+
'ALLUSERSPROFILE',
|
|
51
|
+
'OneDrive',
|
|
52
|
+
// Belongs to the group above, not to the CLI knobs below: it is another spelling of "where are
|
|
53
|
+
// my credentials", so dropping it breaks ambient auth in exactly the same silent way.
|
|
54
|
+
'CLAUDE_CONFIG_DIR',
|
|
55
|
+
// -- Identity
|
|
56
|
+
'USERNAME',
|
|
57
|
+
'USERDOMAIN',
|
|
58
|
+
'USERDOMAIN_ROAMINGPROFILE',
|
|
59
|
+
'COMPUTERNAME',
|
|
60
|
+
'USER',
|
|
61
|
+
'LOGNAME',
|
|
62
|
+
// -- Windows OS substrate
|
|
63
|
+
'PATH',
|
|
64
|
+
'PATHEXT',
|
|
65
|
+
'COMSPEC',
|
|
66
|
+
'SYSTEMDRIVE',
|
|
67
|
+
'SYSTEMROOT',
|
|
68
|
+
'WINDIR',
|
|
69
|
+
'TEMP',
|
|
70
|
+
'TMP',
|
|
71
|
+
'ProgramData',
|
|
72
|
+
'PROGRAMFILES',
|
|
73
|
+
'ProgramFiles(x86)',
|
|
74
|
+
'ProgramW6432',
|
|
75
|
+
'COMMONPROGRAMFILES',
|
|
76
|
+
'CommonProgramFiles(x86)',
|
|
77
|
+
'CommonProgramW6432',
|
|
78
|
+
'PUBLIC',
|
|
79
|
+
'DriverData',
|
|
80
|
+
'OS',
|
|
81
|
+
'NoDefaultCurrentDirectoryInExePath',
|
|
82
|
+
'PROMPT',
|
|
83
|
+
'PSModulePath',
|
|
84
|
+
'SESSIONNAME',
|
|
85
|
+
'LOGONSERVER',
|
|
86
|
+
'NUMBER_OF_PROCESSORS',
|
|
87
|
+
'ZES_ENABLE_SYSMAN',
|
|
88
|
+
// -- POSIX substrate. Neither corpus is a POSIX capture, so these are declared from the
|
|
89
|
+
// Windows keys' POSIX twins rather than observed. Stated openly because it is the one place
|
|
90
|
+
// this set is designed rather than derived — and under-inclusion on a platform no corpus
|
|
91
|
+
// covers is the invisible direction.
|
|
92
|
+
'TMPDIR',
|
|
93
|
+
// -- Terminal + locale
|
|
94
|
+
'TERM',
|
|
95
|
+
'TERM_PROGRAM',
|
|
96
|
+
'TERM_PROGRAM_VERSION',
|
|
97
|
+
'COLORTERM',
|
|
98
|
+
'COLOR',
|
|
99
|
+
'LANG',
|
|
100
|
+
'TZ',
|
|
101
|
+
// -- Node / npm toolchain. `npm_config_*` rides the prefix list; `npm_package_*`,
|
|
102
|
+
// `npm_lifecycle_*` and `INIT_CWD` deliberately do not — they describe the host's own npm
|
|
103
|
+
// invocation and would tell a child it is the host package.
|
|
104
|
+
'NODE',
|
|
105
|
+
'npm_command',
|
|
106
|
+
'npm_execpath',
|
|
107
|
+
'npm_node_execpath',
|
|
108
|
+
'NVM_HOME',
|
|
109
|
+
'NVM_SYMLINK',
|
|
110
|
+
'COREPACK_ENABLE_AUTO_PIN',
|
|
111
|
+
// -- Shell, git and dev toolchain. EDITOR/GIT_EDITOR are declared because an agent running a git
|
|
112
|
+
// command that opens an editor with none configured hangs instead of failing.
|
|
113
|
+
'SHELL',
|
|
114
|
+
'MSYSTEM',
|
|
115
|
+
'EXEPATH',
|
|
116
|
+
'PLINK_PROTOCOL',
|
|
117
|
+
'SSL_CERT_DIR',
|
|
118
|
+
'PWD',
|
|
119
|
+
'SHLVL',
|
|
120
|
+
'EDITOR',
|
|
121
|
+
'GIT_EDITOR',
|
|
122
|
+
'DOTNET_NOLOGO',
|
|
123
|
+
'MSBUILDTERMINALLOGGER',
|
|
124
|
+
'ChocolateyInstall',
|
|
125
|
+
'ChocolateyLastPathUpdate',
|
|
126
|
+
// -- Agent CLI knobs, exact names only. A `CLAUDE_` prefix would re-admit the stripped set below,
|
|
127
|
+
// above all the effort variable, and undo the whole point of this file.
|
|
128
|
+
'CLAUDE_CODE_ENABLE_TELEMETRY',
|
|
129
|
+
];
|
|
130
|
+
const ALLOWED_PREFIXES = [
|
|
131
|
+
'NODE_', // NODE_ENV / NODE_NO_WARNINGS / NODE_OPTIONS / NODE_EXTRA_CA_CERTS
|
|
132
|
+
'npm_config_', // a child running npm needs the host's cache and prefix resolution
|
|
133
|
+
'PROCESSOR_', // PROCESSOR_ARCHITECTURE / _IDENTIFIER / _LEVEL / _REVISION
|
|
134
|
+
'XDG_', // POSIX base directories
|
|
135
|
+
'LC_', // locale category overrides
|
|
136
|
+
];
|
|
137
|
+
// Matched as a suffix so one entry covers HTTP_PROXY / http_proxy / HTTPS_PROXY / ALL_PROXY /
|
|
138
|
+
// NO_PROXY, case-insensitively. A proxied host is exactly where under-inclusion is invisible on the
|
|
139
|
+
// machine that wrote the list and fatal on the machine that runs it.
|
|
140
|
+
const ALLOWED_SUFFIXES = ['_PROXY'];
|
|
141
|
+
/**
|
|
142
|
+
* Denied whatever else matches. Runs before every allow, including `extraAllowedKeys`, because a
|
|
143
|
+
* key that is a credential does not stop being one because someone widened a prefix.
|
|
144
|
+
*/
|
|
145
|
+
const DENIED_PATTERNS = [
|
|
146
|
+
// npm materialises every .npmrc key as an environment variable, so `npm_config_` genuinely
|
|
147
|
+
// matches shapes like `npm_config_//registry.example.com/:_authToken`. A targeted deny rather
|
|
148
|
+
// than a narrower prefix: a child running npm still needs the rest of its config resolution.
|
|
149
|
+
/^npm_config_.*(:_authtoken|:_auth|:_password|:username|:email|_auth)$/i,
|
|
150
|
+
// Disables certificate validation for every outbound request the child makes. A host may have a
|
|
151
|
+
// local reason for it; a spawned agent inherits the reason's absence along with the value, and
|
|
152
|
+
// the failure is a confidentiality one it cannot see. An embedder that means it states it as a
|
|
153
|
+
// literal through `extraEnv`, where it has an author.
|
|
154
|
+
/^NODE_TLS_REJECT_UNAUTHORIZED$/i,
|
|
155
|
+
];
|
|
156
|
+
/**
|
|
157
|
+
* Stripped last and unconditionally — after the allow-list, after `extraEnv`, after everything.
|
|
158
|
+
*
|
|
159
|
+
* Deliberately redundant with "absent unless declared": this survives a widened declared set, a
|
|
160
|
+
* `CLAUDE_`-shaped prefix added in haste, and an embedder that sets one of these by hand. The class
|
|
161
|
+
* is the running host's own session fingerprint, and a spawned agent is a top-level session, not a
|
|
162
|
+
* continuation of whoever launched the host.
|
|
163
|
+
*/
|
|
164
|
+
const HOST_SESSION_MARKERS = [
|
|
165
|
+
'CLAUDECODE',
|
|
166
|
+
'CLAUDE_CODE_CHILD_SESSION',
|
|
167
|
+
'CLAUDE_CODE_ENTRYPOINT',
|
|
168
|
+
'CLAUDE_CODE_SESSION_ID',
|
|
169
|
+
'CLAUDE_CODE_EXECPATH',
|
|
170
|
+
'CLAUDE_CODE_SSE_PORT',
|
|
171
|
+
'CLAUDE_PID',
|
|
172
|
+
'CLAUDE_EFFORT',
|
|
173
|
+
'CLAUDE_AGENT_SDK_VERSION',
|
|
174
|
+
'CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING',
|
|
175
|
+
'CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING',
|
|
176
|
+
'CLAUDE_CODE_ENABLE_TASKS',
|
|
177
|
+
'MCP_CONNECTION_NONBLOCKING',
|
|
178
|
+
'AI_AGENT',
|
|
179
|
+
// The host's own thinking budget, same class as the effort variable: an inherited value silently
|
|
180
|
+
// becomes every spawn's budget. A deliberate one arrives through the SDK options, which have an
|
|
181
|
+
// author and a type.
|
|
182
|
+
'MAX_THINKING_TOKENS',
|
|
183
|
+
];
|
|
184
|
+
// ---------------------------------------------------------------------------
|
|
185
|
+
// Matching.
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Matching is case-insensitive. Windows environment names are case-insensitive and Node preserves
|
|
188
|
+
// the OS's own casing verbatim — `ProgramData`, `CommonProgramFiles(x86)`, `ChocolateyInstall` — so
|
|
189
|
+
// a case-sensitive set silently drops real keys. On POSIX this only over-includes (a lowercase twin
|
|
190
|
+
// of a declared name), which is the recoverable direction, and it is how `http_proxy` is covered by
|
|
191
|
+
// the same entry as `HTTPS_PROXY`.
|
|
192
|
+
const ALLOWED_EXACT_UPPER = new Set(ALLOWED_EXACT.map((key) => key.toUpperCase()));
|
|
193
|
+
const ALLOWED_PREFIXES_UPPER = ALLOWED_PREFIXES.map((prefix) => prefix.toUpperCase());
|
|
194
|
+
const ALLOWED_SUFFIXES_UPPER = ALLOWED_SUFFIXES.map((suffix) => suffix.toUpperCase());
|
|
195
|
+
const HOST_SESSION_MARKERS_UPPER = new Set(HOST_SESSION_MARKERS.map((key) => key.toUpperCase()));
|
|
196
|
+
/** Every key this package declares for a spawned agent, in declaration order. For the pin. */
|
|
197
|
+
export const DECLARED_EXACT_KEYS = ALLOWED_EXACT;
|
|
198
|
+
export const DECLARED_PREFIXES = ALLOWED_PREFIXES;
|
|
199
|
+
export const DECLARED_SUFFIXES = ALLOWED_SUFFIXES;
|
|
200
|
+
export const STRIPPED_HOST_SESSION_KEYS = HOST_SESSION_MARKERS;
|
|
201
|
+
function isDenied(key, policy) {
|
|
202
|
+
if (DENIED_PATTERNS.some((pattern) => pattern.test(key)))
|
|
203
|
+
return true;
|
|
204
|
+
const upper = key.toUpperCase();
|
|
205
|
+
return (policy.extraDeniedKeys ?? []).some((denied) => denied.toUpperCase() === upper);
|
|
206
|
+
}
|
|
207
|
+
/** Is this key declared for spawned agents? Exported so the gate test can drive it directly. */
|
|
208
|
+
export function isDeclaredSpawnEnvKey(key, policy = {}) {
|
|
209
|
+
if (isDenied(key, policy))
|
|
210
|
+
return false;
|
|
211
|
+
const upper = key.toUpperCase();
|
|
212
|
+
if ((policy.extraAllowedKeys ?? []).some((extra) => extra.toUpperCase() === upper))
|
|
213
|
+
return true;
|
|
214
|
+
if (ALLOWED_EXACT_UPPER.has(upper))
|
|
215
|
+
return true;
|
|
216
|
+
if (ALLOWED_PREFIXES_UPPER.some((prefix) => upper.startsWith(prefix)))
|
|
217
|
+
return true;
|
|
218
|
+
return ALLOWED_SUFFIXES_UPPER.some((suffix) => upper.endsWith(suffix));
|
|
219
|
+
}
|
|
220
|
+
const isProxyKey = (key) => ALLOWED_SUFFIXES_UPPER.some((suffix) => key.toUpperCase().endsWith(suffix));
|
|
221
|
+
/**
|
|
222
|
+
* Strip `user:pass@` from a proxy URL, keeping the proxy reachable.
|
|
223
|
+
*
|
|
224
|
+
* The key stays declared and the credential leaves the value: proxy URLs routinely embed
|
|
225
|
+
* credentials, so declaring the key was declaring a secret — but dropping the key breaks every
|
|
226
|
+
* spawn behind a corporate proxy, which trades an availability failure for a confidentiality one.
|
|
227
|
+
* A value that does not parse as a URL (`NO_PROXY` is a comma-separated host list) is returned
|
|
228
|
+
* verbatim: a redaction must never corrupt a value it does not understand, because a mangled
|
|
229
|
+
* no-proxy list silently changes which hosts bypass the proxy.
|
|
230
|
+
*/
|
|
231
|
+
export function redactProxyCredential(value) {
|
|
232
|
+
if (!value.includes('@'))
|
|
233
|
+
return value;
|
|
234
|
+
try {
|
|
235
|
+
const url = new URL(value);
|
|
236
|
+
if (url.username === '' && url.password === '')
|
|
237
|
+
return value;
|
|
238
|
+
url.username = '';
|
|
239
|
+
url.password = '';
|
|
240
|
+
return url.toString();
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return value;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* The environment for a spawned agent: the declared subset of `baseEnv`, then the embedder's
|
|
248
|
+
* literals, then the host-session strip.
|
|
249
|
+
*
|
|
250
|
+
* The strip runs last on purpose. It is the one rule that cannot be widened away, so the property
|
|
251
|
+
* "a spawned agent is never told it is a continuation of this process" holds no matter what a later
|
|
252
|
+
* change does to the declared set or what an embedder passes.
|
|
253
|
+
*
|
|
254
|
+
* Returns a fresh object every call and never mutates `baseEnv`.
|
|
255
|
+
*/
|
|
256
|
+
export function composeSpawnEnv(baseEnv, policy = {}) {
|
|
257
|
+
const env = {};
|
|
258
|
+
for (const key of Object.keys(baseEnv)) {
|
|
259
|
+
if (!isDeclaredSpawnEnvKey(key, policy))
|
|
260
|
+
continue;
|
|
261
|
+
const value = baseEnv[key];
|
|
262
|
+
// An undeclared value and an empty one are different things, but neither is worth passing: the
|
|
263
|
+
// SDK's env map allows `undefined` and the CLI would see the name with nothing behind it.
|
|
264
|
+
if (value === undefined)
|
|
265
|
+
continue;
|
|
266
|
+
env[key] = isProxyKey(key) ? redactProxyCredential(value) : value;
|
|
267
|
+
}
|
|
268
|
+
for (const [key, value] of Object.entries(policy.extraEnv ?? {})) {
|
|
269
|
+
env[key] = value;
|
|
270
|
+
}
|
|
271
|
+
for (const key of Object.keys(env)) {
|
|
272
|
+
if (HOST_SESSION_MARKERS_UPPER.has(key.toUpperCase()))
|
|
273
|
+
delete env[key];
|
|
274
|
+
}
|
|
275
|
+
return env;
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=spawn-env.js.map
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
export type CoverageHandling = 'wired' | 'declined';
|
|
2
|
+
export interface CoverageRow {
|
|
3
|
+
readonly handling: CoverageHandling;
|
|
4
|
+
/** What it produces when wired; why it is not consumed when declined. Required either way. */
|
|
5
|
+
readonly note: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* All 31 hook events. 18 wired, 13 declined.
|
|
9
|
+
*
|
|
10
|
+
* `PreToolUse` appears here as an observation only. The permission decision is a separate
|
|
11
|
+
* concern with a separate handler; `HookCallbackMatcher.hooks` is an array and the SDK runs every
|
|
12
|
+
* entry, so a decision handler registers alongside this one without either editing the other.
|
|
13
|
+
*/
|
|
14
|
+
export declare const HOOK_COVERAGE: {
|
|
15
|
+
readonly PreToolUse: {
|
|
16
|
+
readonly handling: "wired";
|
|
17
|
+
readonly note: "opens a `tool` entry keyed by tool_use_id; state -> working. Observation only — the permission decision is a separate handler on the same event.";
|
|
18
|
+
};
|
|
19
|
+
readonly PostToolUse: {
|
|
20
|
+
readonly handling: "wired";
|
|
21
|
+
readonly note: "closes the `tool` entry for its tool_use_id. The ordinary exit.";
|
|
22
|
+
};
|
|
23
|
+
readonly PostToolUseFailure: {
|
|
24
|
+
readonly handling: "wired";
|
|
25
|
+
readonly note: "closes the `tool` entry naming the failure — the failure exit, distinct from the success one because \"it stopped\" and \"it broke\" are different answers.";
|
|
26
|
+
};
|
|
27
|
+
readonly PostToolBatch: {
|
|
28
|
+
readonly handling: "wired";
|
|
29
|
+
readonly note: "fires exactly once after every call in a batch resolves, so it closes any tool entry still open. The backstop for a PostToolUse that never arrived — measured on two live sessions, including an all-denied batch: it fires on a hook-authored deny and its tool_calls includes the denied tool_use_id, so a denied call's entry does not leak. It closes the entry at end of batch and says only that; the denial itself is a separate transition the gate emits.";
|
|
30
|
+
};
|
|
31
|
+
readonly UserPromptSubmit: {
|
|
32
|
+
readonly handling: "wired";
|
|
33
|
+
readonly note: "state -> working. The turn boundary opening; the counterpart to Stop.";
|
|
34
|
+
};
|
|
35
|
+
readonly Stop: {
|
|
36
|
+
readonly handling: "wired";
|
|
37
|
+
readonly note: "state -> idle. The clean turn end, recorded for every session with no exemption — a turn boundary that is missing for any class of session leaves every reader inferring.";
|
|
38
|
+
};
|
|
39
|
+
readonly StopFailure: {
|
|
40
|
+
readonly handling: "wired";
|
|
41
|
+
readonly note: "state -> errored, carrying the SDKAssistantMessageError. Never collapsed into Stop: a turn that broke is not a turn that finished.";
|
|
42
|
+
};
|
|
43
|
+
readonly SessionStart: {
|
|
44
|
+
readonly handling: "wired";
|
|
45
|
+
readonly note: "records how the session began (startup | resume | clear | compact | fork). No state change — it names a why the trace would otherwise have to infer. Measured not to fire on an SDK-hosted start: a real session running every wired hook saw PreToolUse, PostToolUse, PostToolBatch, UserPromptSubmit and Stop, and no SessionStart. The likely reason is ordering — `options.hooks` reach the CLI after it has already started — but that was not isolated. Wired and not observed, which is a different row from declined.";
|
|
46
|
+
};
|
|
47
|
+
readonly SessionEnd: {
|
|
48
|
+
readonly handling: "wired";
|
|
49
|
+
readonly note: "state -> ended, carrying the ExitReason. Never inferred from silence.";
|
|
50
|
+
};
|
|
51
|
+
readonly CwdChanged: {
|
|
52
|
+
readonly handling: "wired";
|
|
53
|
+
readonly note: "updates `where.cwd` for every later transition. Declining it would leave every subsequent record carrying a stale directory — a trace that lies quietly.";
|
|
54
|
+
};
|
|
55
|
+
readonly SubagentStart: {
|
|
56
|
+
readonly handling: "wired";
|
|
57
|
+
readonly note: "opens a `subagent` entry keyed by agent_id, carrying agent_type.";
|
|
58
|
+
};
|
|
59
|
+
readonly SubagentStop: {
|
|
60
|
+
readonly handling: "wired";
|
|
61
|
+
readonly note: "closes the `subagent` entry for its agent_id.";
|
|
62
|
+
};
|
|
63
|
+
readonly PreCompact: {
|
|
64
|
+
readonly handling: "wired";
|
|
65
|
+
readonly note: "opens a `compacting` entry, carrying trigger (manual | auto).";
|
|
66
|
+
};
|
|
67
|
+
readonly PostCompact: {
|
|
68
|
+
readonly handling: "wired";
|
|
69
|
+
readonly note: "closes the `compacting` entry.";
|
|
70
|
+
};
|
|
71
|
+
readonly PermissionRequest: {
|
|
72
|
+
readonly handling: "wired";
|
|
73
|
+
readonly note: "opens a `permission` entry for the tool. Measured not to fire for a hook-authored decision — `gate/outcome.ts` records the measurement — so the held-visibility this model buys is delivered by the gate's own hold entry (keyed by tool_use_id), not by this hook. Do not wait on this hook for entries the gate opens elsewhere; re-measure before relying on it for any other decision path.";
|
|
74
|
+
};
|
|
75
|
+
readonly PermissionDenied: {
|
|
76
|
+
readonly handling: "wired";
|
|
77
|
+
readonly note: "closes the `permission` entry, cause kind `hook`, carrying the deny reason. A denial and a permission-path outage must not read alike: an outage arrives as cause kind `refusal` with a refusal reason, so the two differ in kind and event. Measured not to fire for a hook-authored deny: a real session whose PreToolUse returned `permissionDecision: deny` blocked the tool (no PostToolUse) but emitted no PermissionDenied — so this hook appears to belong to a decision path other than the hook lane. Whoever builds the decision path must not rely on it as the deny receipt without re-measuring.";
|
|
78
|
+
};
|
|
79
|
+
readonly Elicitation: {
|
|
80
|
+
readonly handling: "wired";
|
|
81
|
+
readonly note: "opens an `elicitation` entry for the MCP server. The session is genuinely blocked on an answer.";
|
|
82
|
+
};
|
|
83
|
+
readonly ElicitationResult: {
|
|
84
|
+
readonly handling: "wired";
|
|
85
|
+
readonly note: "closes the `elicitation` entry, carrying the action (accept | decline | cancel).";
|
|
86
|
+
};
|
|
87
|
+
readonly Notification: {
|
|
88
|
+
readonly handling: "declined";
|
|
89
|
+
readonly note: "a display notification (message, title, notification_type). Nothing about it moves the session; it is content, and content belongs to the streaming layer.";
|
|
90
|
+
};
|
|
91
|
+
readonly UserPromptExpansion: {
|
|
92
|
+
readonly handling: "declined";
|
|
93
|
+
readonly note: "a slash-command or MCP-prompt expansion of a prompt already submitted. The turn boundary was UserPromptSubmit; recording this too would add a transition carrying no new fact.";
|
|
94
|
+
};
|
|
95
|
+
readonly Setup: {
|
|
96
|
+
readonly handling: "declined";
|
|
97
|
+
readonly note: "environment housekeeping (trigger: init | maintenance). It runs beside the session rather than in it, and produces no condition the session can be blocked on.";
|
|
98
|
+
};
|
|
99
|
+
readonly TeammateIdle: {
|
|
100
|
+
readonly handling: "declined";
|
|
101
|
+
readonly note: "reports that another session is idle. Recording it here would attribute a different session's state to this one — precisely the cross-session aggregation that belongs to the controller, not a host.";
|
|
102
|
+
};
|
|
103
|
+
readonly TaskCreated: {
|
|
104
|
+
readonly handling: "declined";
|
|
105
|
+
readonly note: "the task-list surface (task_subject, teammate_name). Its overlap with the task_started/task_updated message lane has not been resolved against a live session, and wiring both would double-count one entry. Declined on the unresolved overlap, not on irrelevance.";
|
|
106
|
+
};
|
|
107
|
+
readonly TaskCompleted: {
|
|
108
|
+
readonly handling: "declined";
|
|
109
|
+
readonly note: "the closing half of TaskCreated, declined for the same unresolved overlap. system/task_notification is the completion signal that is wired.";
|
|
110
|
+
};
|
|
111
|
+
readonly ConfigChange: {
|
|
112
|
+
readonly handling: "declined";
|
|
113
|
+
readonly note: "settings or skills changed on disk. It changes what the session can do, not what it is doing — a capability fact, and capability is the workspace layer's.";
|
|
114
|
+
};
|
|
115
|
+
readonly InstructionsLoaded: {
|
|
116
|
+
readonly handling: "declined";
|
|
117
|
+
readonly note: "memory/CLAUDE.md loading. Context composition, which is what a turn is made of rather than a condition it is in.";
|
|
118
|
+
};
|
|
119
|
+
readonly WorktreeCreate: {
|
|
120
|
+
readonly handling: "declined";
|
|
121
|
+
readonly note: "the agent created a worktree. It does not move the session — `where` still describes where the session runs. Relevant to whichever layer provisions workspaces.";
|
|
122
|
+
};
|
|
123
|
+
readonly WorktreeRemove: {
|
|
124
|
+
readonly handling: "declined";
|
|
125
|
+
readonly note: "as WorktreeCreate. Note that removing the worktree a session is in would be visible through CwdChanged, which is wired.";
|
|
126
|
+
};
|
|
127
|
+
readonly DirectoryAdded: {
|
|
128
|
+
readonly handling: "declined";
|
|
129
|
+
readonly note: "widens the set of directories the session may touch. A permission-surface fact, not a state one; it belongs with whatever enforces the path boundary.";
|
|
130
|
+
};
|
|
131
|
+
readonly FileChanged: {
|
|
132
|
+
readonly handling: "declined";
|
|
133
|
+
readonly note: "a watched file changed (change | add | unlink). High volume, driven by the filesystem rather than by the session, and it holds nothing.";
|
|
134
|
+
};
|
|
135
|
+
readonly MessageDisplay: {
|
|
136
|
+
readonly handling: "declined";
|
|
137
|
+
readonly note: "one flush of an assistant message, indexed per delta. The highest-volume event in the set and pure content — the streaming layer's lane, not the state model's.";
|
|
138
|
+
};
|
|
139
|
+
};
|
|
140
|
+
/**
|
|
141
|
+
* All 39 members of the SDKMessage union, keyed by discriminator.
|
|
142
|
+
*
|
|
143
|
+
* Fewer keys than members: several shapes share one discriminator (`user` covers the ordinary and
|
|
144
|
+
* the replayed user message; `result` covers success and every error subtype). The pin walks the
|
|
145
|
+
* union member by member and checks each one's discriminator has a row, so the many-to-one is
|
|
146
|
+
* accounted for rather than hidden by the shorter key list.
|
|
147
|
+
*/
|
|
148
|
+
export declare const MESSAGE_COVERAGE: {
|
|
149
|
+
readonly 'system/init': {
|
|
150
|
+
readonly handling: "wired";
|
|
151
|
+
readonly note: "state -> ready, and the only place the session id, CLI version receipt, model, tool/skill/plugin inventory and apiKeySource arrive. Everything downstream keys off the id it carries.";
|
|
152
|
+
};
|
|
153
|
+
readonly 'system/status': {
|
|
154
|
+
readonly handling: "wired";
|
|
155
|
+
readonly note: "SDKStatus — sets activity `requesting` or `compacting`, and closes it on null. The SDK's own word for what a session is doing, adopted verbatim.";
|
|
156
|
+
};
|
|
157
|
+
readonly 'system/session_state_changed': {
|
|
158
|
+
readonly handling: "wired";
|
|
159
|
+
readonly note: "documented as the authoritative turn-over signal — measured not to fire: never observed on any of the 3 real turns measured; `Stop` and `result` are the observed turn boundaries. When it does arrive: idle -> state idle, running -> state working. `requires_action` deliberately does not set state: what the session requires is already carried by the open permission or elicitation entry, and a second representation would be a second vocabulary.";
|
|
160
|
+
};
|
|
161
|
+
readonly 'system/compact_boundary': {
|
|
162
|
+
readonly handling: "wired";
|
|
163
|
+
readonly note: "compaction actually happened, with trigger and token counts. Closes the compacting entry as a backstop for a PostCompact that never arrived.";
|
|
164
|
+
};
|
|
165
|
+
readonly 'system/task_started': {
|
|
166
|
+
readonly handling: "wired";
|
|
167
|
+
readonly note: "no transition of its own — it supplies the task_id -> tool_use_id join, without which task_updated could not name the entry it backgrounds. Wired as correlation, and saying so is the point of this column.";
|
|
168
|
+
};
|
|
169
|
+
readonly 'system/task_updated': {
|
|
170
|
+
readonly handling: "wired";
|
|
171
|
+
readonly note: "`patch.is_backgrounded` is the caused moment a task stops holding the session — the entry moves to the background lane there, never on a timer and never by inference. `patch.status` in completed | failed | killed closes it.";
|
|
172
|
+
};
|
|
173
|
+
readonly 'system/task_notification': {
|
|
174
|
+
readonly handling: "wired";
|
|
175
|
+
readonly note: "a background task finished (completed | failed | stopped) carrying tool_use_id. The exit for backgrounded work: backgrounding is not completion, so the entry stays open and ages until this arrives.";
|
|
176
|
+
};
|
|
177
|
+
readonly 'system/worker_shutting_down': {
|
|
178
|
+
readonly handling: "wired";
|
|
179
|
+
readonly note: "a named reason for a teardown that would otherwise present as the process simply ending. Its own type warns that absence is not a dead-host signal — handoffs and fatal paths emit nothing — so it is a why when present, never a liveness check.";
|
|
180
|
+
};
|
|
181
|
+
readonly 'system/model_refusal_no_fallback': {
|
|
182
|
+
readonly handling: "wired";
|
|
183
|
+
readonly note: "the turn ends with no retry. Records the why; the state change itself rides result/StopFailure, so the turn end is not recorded twice.";
|
|
184
|
+
};
|
|
185
|
+
readonly result: {
|
|
186
|
+
readonly handling: "wired";
|
|
187
|
+
readonly note: "subtype success -> idle (the backstop for a missed Stop); every error subtype -> errored, carrying terminal_reason. This is where a turn that ended without any hook firing still gets a boundary.";
|
|
188
|
+
};
|
|
189
|
+
readonly assistant: {
|
|
190
|
+
readonly handling: "declined";
|
|
191
|
+
readonly note: "model output. Content, owned by the streaming layer; the state it implies is already carried by system/status and system/session_state_changed.";
|
|
192
|
+
};
|
|
193
|
+
readonly user: {
|
|
194
|
+
readonly handling: "declined";
|
|
195
|
+
readonly note: "the turn input, including the replayed echo. UserPromptSubmit is the wired boundary; this is the payload that crossed it.";
|
|
196
|
+
};
|
|
197
|
+
readonly stream_event: {
|
|
198
|
+
readonly handling: "declined";
|
|
199
|
+
readonly note: "partial assistant deltas. The highest-volume message in the union and pure content — the streaming layer's.";
|
|
200
|
+
};
|
|
201
|
+
readonly tool_progress: {
|
|
202
|
+
readonly handling: "declined";
|
|
203
|
+
readonly note: "carries elapsed_time_seconds for a running tool. Declined deliberately: the open entry's age is computed from its own openedAt, and a second elapsed-time source is a number that can disagree with the trace.";
|
|
204
|
+
};
|
|
205
|
+
readonly tool_use_summary: {
|
|
206
|
+
readonly handling: "declined";
|
|
207
|
+
readonly note: "a prose summary of preceding tool calls. Content.";
|
|
208
|
+
};
|
|
209
|
+
readonly auth_status: {
|
|
210
|
+
readonly handling: "declined";
|
|
211
|
+
readonly note: "isAuthenticating plus provider output. An identity-plane fact; the session is not blocked on it in a way this model can name.";
|
|
212
|
+
};
|
|
213
|
+
readonly rate_limit_event: {
|
|
214
|
+
readonly handling: "declined";
|
|
215
|
+
readonly note: "rate-limit windows. Usage and cost belong to the telemetry lane, which reads the same stream.";
|
|
216
|
+
};
|
|
217
|
+
readonly prompt_suggestion: {
|
|
218
|
+
readonly handling: "declined";
|
|
219
|
+
readonly note: "a predicted next prompt. A suggestion for a human; nothing has happened.";
|
|
220
|
+
};
|
|
221
|
+
readonly conversation_reset: {
|
|
222
|
+
readonly handling: "declined";
|
|
223
|
+
readonly note: "/clear, plan-mode exit and fresh-session flows mint a new conversation id. The session continues — what changed is which transcript later reads attach to, which is the persistence layer's question.";
|
|
224
|
+
};
|
|
225
|
+
readonly 'system/api_retry': {
|
|
226
|
+
readonly handling: "declined";
|
|
227
|
+
readonly note: "a retryable API failure being retried. The session stays in `requesting`, and a session stuck in retries is already visible as that entry's growing age rather than needing a state of its own.";
|
|
228
|
+
};
|
|
229
|
+
readonly 'system/control_request_progress': {
|
|
230
|
+
readonly handling: "declined";
|
|
231
|
+
readonly note: "progress for a client-originated control request. It belongs to whoever made that request, correlated by its own request_id.";
|
|
232
|
+
};
|
|
233
|
+
readonly 'system/model_refusal_fallback': {
|
|
234
|
+
readonly handling: "declined";
|
|
235
|
+
readonly note: "the turn was retried on a fallback model and continues. Nothing ended; the no-fallback counterpart is the one that is wired.";
|
|
236
|
+
};
|
|
237
|
+
readonly 'system/local_command_output': {
|
|
238
|
+
readonly handling: "declined";
|
|
239
|
+
readonly note: "output from a local slash command. Content.";
|
|
240
|
+
};
|
|
241
|
+
readonly 'system/hook_started': {
|
|
242
|
+
readonly handling: "declined";
|
|
243
|
+
readonly note: "the lifecycle of a command hook (hook_id, hook_name, stdout/stderr). This host installs in-process callbacks and runs no command hooks, so these describe a mechanism it does not use.";
|
|
244
|
+
};
|
|
245
|
+
readonly 'system/hook_progress': {
|
|
246
|
+
readonly handling: "declined";
|
|
247
|
+
readonly note: "as system/hook_started — the command-hook mechanism, unused here.";
|
|
248
|
+
};
|
|
249
|
+
readonly 'system/hook_response': {
|
|
250
|
+
readonly handling: "declined";
|
|
251
|
+
readonly note: "as system/hook_started. Worth revisiting by whoever builds the permission decision path if command hooks are ever installed alongside the in-process ones.";
|
|
252
|
+
};
|
|
253
|
+
readonly 'system/plugin_install': {
|
|
254
|
+
readonly handling: "declined";
|
|
255
|
+
readonly note: "headless plugin installation progress. A provisioning fact that precedes work rather than being work.";
|
|
256
|
+
};
|
|
257
|
+
readonly 'system/task_progress': {
|
|
258
|
+
readonly handling: "declined";
|
|
259
|
+
readonly note: "periodic progress for a running task. High volume, and the entry's own age is the number that matters.";
|
|
260
|
+
};
|
|
261
|
+
readonly 'system/background_tasks_changed': {
|
|
262
|
+
readonly handling: "declined";
|
|
263
|
+
readonly note: "declined on the type's own instruction. It is a level signal with replace semantics whose docs say the payload carries ids only and must not be correlated with the edge stream — so consuming it would mean rebuilding the entry set from ids, losing every entry's age and opening cause. The edges (task_started / task_updated / task_notification) are wired instead, and an edge that goes missing surfaces as an open entry with an age, which is the useful signal rather than the one a rebuild would erase.";
|
|
264
|
+
};
|
|
265
|
+
readonly 'system/thinking_tokens': {
|
|
266
|
+
readonly handling: "declined";
|
|
267
|
+
readonly note: "a live thinking-token estimate for spinners. Explicitly approximate in its own docs, and telemetry's if anyone wants it.";
|
|
268
|
+
};
|
|
269
|
+
readonly 'system/commands_changed': {
|
|
270
|
+
readonly handling: "declined";
|
|
271
|
+
readonly note: "the slash-command list changed mid-session. A capability fact — what the session can do.";
|
|
272
|
+
};
|
|
273
|
+
readonly 'system/notification': {
|
|
274
|
+
readonly handling: "declined";
|
|
275
|
+
readonly note: "a loop-side text notification with a priority. Display.";
|
|
276
|
+
};
|
|
277
|
+
readonly 'system/files_persisted': {
|
|
278
|
+
readonly handling: "declined";
|
|
279
|
+
readonly note: "file persistence results. The persistence layer's.";
|
|
280
|
+
};
|
|
281
|
+
readonly 'system/memory_recall': {
|
|
282
|
+
readonly handling: "declined";
|
|
283
|
+
readonly note: "memories surfaced into the turn. Context composition.";
|
|
284
|
+
};
|
|
285
|
+
readonly 'system/elicitation_complete': {
|
|
286
|
+
readonly handling: "declined";
|
|
287
|
+
readonly note: "a URL-mode elicitation confirmed complete by the MCP server. The ElicitationResult hook already closes the entry; consuming both would close it twice.";
|
|
288
|
+
};
|
|
289
|
+
readonly 'system/permission_denied': {
|
|
290
|
+
readonly handling: "declined";
|
|
291
|
+
readonly note: "mirrors the PermissionDenied hook, which is wired and additionally carries the deny reason. Consuming both would record one denial as two.";
|
|
292
|
+
};
|
|
293
|
+
readonly 'system/mirror_error': {
|
|
294
|
+
readonly handling: "declined";
|
|
295
|
+
readonly note: "a real degrade — a transcript-mirror batch was dropped after retries. Declined here because it changes what can be read later, not what the session is doing; it is the persistence layer's to surface, and it must not be lost there.";
|
|
296
|
+
};
|
|
297
|
+
readonly 'system/informational': {
|
|
298
|
+
readonly handling: "declined";
|
|
299
|
+
readonly note: "a generic text banner, including hook feedback. Content.";
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
/** Counts for the table's own summary line, computed rather than typed in — one less thing to rot. */
|
|
303
|
+
export declare function coverageTally(table: Record<string, CoverageRow>): {
|
|
304
|
+
total: number;
|
|
305
|
+
wired: number;
|
|
306
|
+
declined: number;
|
|
307
|
+
};
|
|
308
|
+
//# sourceMappingURL=coverage.d.ts.map
|