@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,226 @@
|
|
|
1
|
+
import { refusal } from '../core/refusal.js';
|
|
2
|
+
import { parseCommand } from './command.js';
|
|
3
|
+
/**
|
|
4
|
+
* Separators that end one top-level command, mirroring `command.ts`'s own set.
|
|
5
|
+
*
|
|
6
|
+
* Quote spans are not tracked here on purpose: an unbalanced or quoted separator yields more
|
|
7
|
+
* segments, and more segments can only ever make a pattern harder to satisfy... which would be the
|
|
8
|
+
* wrong direction. So the split runs over `scannable`, whose inert payloads are already masked, and
|
|
9
|
+
* a segment that cannot be determined is scanned as part of a larger one — the refusing direction.
|
|
10
|
+
*/
|
|
11
|
+
const SEGMENT_SPLIT = /[|&;\n\r()`]+/;
|
|
12
|
+
/** Does `pattern` match inside any single top-level segment of `text`? */
|
|
13
|
+
function matchesInAnySegment(pattern, text) {
|
|
14
|
+
return text.split(SEGMENT_SPLIT).some((segment) => pattern.test(segment));
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The denylist, in match order. `[\s\S]` rather than `.` throughout: a compound command spanning a
|
|
18
|
+
* newline must not be able to hide a boundary operation behind the line break.
|
|
19
|
+
*/
|
|
20
|
+
const BOUNDARY_RULES = [
|
|
21
|
+
{
|
|
22
|
+
name: 'git-push',
|
|
23
|
+
// The lookbehind excises exactly one token pair: `stash push`. `git stash push -- <paths>` is the
|
|
24
|
+
// only canonical partial-stash form, and it parsed as a publish — so that operation was literally
|
|
25
|
+
// unperformable. A stash cannot publish anything, and layer 2 admits `stash` as a known-safe verb.
|
|
26
|
+
// The exclusion is whole-token (a bare `\b` matches inside a hyphenated token, so `my-stash push`
|
|
27
|
+
// had the suffix of `my-stash` read as the excluded token) and adjacency-strict, so
|
|
28
|
+
// `git stash push … ; git push origin main` still matches on the second occurrence.
|
|
29
|
+
pattern: /\bgit\b[\s\S]*?(?<!(?:^|[\s;&|(){}])stash\s{1,8})\bpush\b/i,
|
|
30
|
+
detail: 'publishing to a remote is a human decision, not an agent one',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
name: 'git-remote-surgery',
|
|
34
|
+
// The lookbehind keeps `git ls-remote` — a remote query — out of this rule, whole-token for the
|
|
35
|
+
// same reason as above (a bare substring exclusion lets any token ending in `ls-` suppress it).
|
|
36
|
+
//
|
|
37
|
+
// The read-only forms are exempt, and the exemption is exactly these shapes: `-v`,
|
|
38
|
+
// `--verbose`, `show`, and the bare list. Everything mutating still refuses — `add`, `set-url`,
|
|
39
|
+
// `set-head`, `set-branches`, `rename`, `remove`, `prune`, `update`. `get-url` is read-only and
|
|
40
|
+
// still refuses on purpose: an exemption set that grows by inference is how a boundary erodes.
|
|
41
|
+
pattern: /\bgit\b[\s\S]*?(?<!(?:^|[\s;&|(){}])ls-)\bremote\b(?!\s+(?:-v|--verbose|show)\b)(?!\s*$)/i,
|
|
42
|
+
detail: 'changing where this repository publishes is a human decision',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
name: 'git-branch-delete',
|
|
46
|
+
// Plain `git branch`, `--show-current` and `checkout -b` all flow; `git push --delete` is caught
|
|
47
|
+
// by the publish rule above.
|
|
48
|
+
//
|
|
49
|
+
// Segment-scoped. `[\s\S]*?` crosses pipes, so a read-only listing would borrow its `-d` from
|
|
50
|
+
// a downstream `tr -d ' '` and refuse as a deletion. The publish rule is deliberately not
|
|
51
|
+
// scoped: a push anywhere in a piped command is a push.
|
|
52
|
+
pattern: /\bgit\b[\s\S]*?\bbranch\b[\s\S]*?(\s-[dD]\b|--delete\b)/i,
|
|
53
|
+
detail: 'deleting a branch destroys history a human may still need',
|
|
54
|
+
segmentScoped: true,
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'gh-pr-merge',
|
|
58
|
+
pattern: /\bgh\b[\s\S]*?\bpr\b[\s\S]*?\bmerge\b/i,
|
|
59
|
+
detail: 'merging is a human boundary',
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
/**
|
|
63
|
+
* Git verbs that are known-safe: reads, local-only writes, and download-only network.
|
|
64
|
+
*
|
|
65
|
+
* The organising idea is publish. None of these can send anything to a remote, which is why
|
|
66
|
+
* `fetch`, `pull` and `ls-remote` sit here despite touching the network, and why `commit` does too —
|
|
67
|
+
* a local commit publishes nothing.
|
|
68
|
+
*
|
|
69
|
+
* Exact-case: git subcommands are lowercase, so a cased oddity is not proven safe and refuses.
|
|
70
|
+
*/
|
|
71
|
+
const ALLOWED_GIT_VERBS = new Set([
|
|
72
|
+
// reads
|
|
73
|
+
'status',
|
|
74
|
+
'log',
|
|
75
|
+
'diff',
|
|
76
|
+
'show',
|
|
77
|
+
'rev-parse',
|
|
78
|
+
'describe',
|
|
79
|
+
'blame',
|
|
80
|
+
'grep',
|
|
81
|
+
'shortlog',
|
|
82
|
+
'reflog',
|
|
83
|
+
'ls-files',
|
|
84
|
+
'ls-tree',
|
|
85
|
+
'cat-file',
|
|
86
|
+
'merge-base',
|
|
87
|
+
'rev-list',
|
|
88
|
+
'check-ignore',
|
|
89
|
+
'check-attr',
|
|
90
|
+
// read-only terminal flags, surfaced as the verb when nothing else is: git prints and exits.
|
|
91
|
+
'--version',
|
|
92
|
+
'--help',
|
|
93
|
+
// local-only writes. `branch -d` and friends are already refused by the denylist above.
|
|
94
|
+
'add',
|
|
95
|
+
'commit',
|
|
96
|
+
'restore',
|
|
97
|
+
'checkout',
|
|
98
|
+
'switch',
|
|
99
|
+
'branch',
|
|
100
|
+
'stash',
|
|
101
|
+
'init',
|
|
102
|
+
'merge',
|
|
103
|
+
'rebase',
|
|
104
|
+
'cherry-pick',
|
|
105
|
+
'revert',
|
|
106
|
+
'reset',
|
|
107
|
+
'rm',
|
|
108
|
+
'mv',
|
|
109
|
+
'tag',
|
|
110
|
+
'clean',
|
|
111
|
+
'apply',
|
|
112
|
+
// `hash-object` sits with the local writes rather than the reads, and the placement is the point.
|
|
113
|
+
// Its common form only prints a sha, but `-w` writes a loose object — and this allowlist is
|
|
114
|
+
// verb-level, so admitting the verb admits `-w`. It qualifies on the same ground `init` does: an
|
|
115
|
+
// unreferenced loose object cannot publish anything.
|
|
116
|
+
'hash-object',
|
|
117
|
+
// download-only network — cannot publish.
|
|
118
|
+
'fetch',
|
|
119
|
+
'pull',
|
|
120
|
+
'ls-remote',
|
|
121
|
+
]);
|
|
122
|
+
/** `git config` flags that only read. Exact tokens, never prefixes. */
|
|
123
|
+
const CONFIG_READ_FLAGS = new Set([
|
|
124
|
+
'--get',
|
|
125
|
+
'--get-all',
|
|
126
|
+
'--get-regexp',
|
|
127
|
+
'--get-urlmatch',
|
|
128
|
+
'--get-color',
|
|
129
|
+
'--get-colorbool',
|
|
130
|
+
'--list',
|
|
131
|
+
'-l',
|
|
132
|
+
]);
|
|
133
|
+
/** `git config` flags that mutate. If present, refuse, even beside a read flag. */
|
|
134
|
+
const CONFIG_WRITE_FLAGS = new Set([
|
|
135
|
+
'--add',
|
|
136
|
+
'--unset',
|
|
137
|
+
'--unset-all',
|
|
138
|
+
'--replace-all',
|
|
139
|
+
'--set-all',
|
|
140
|
+
'--edit',
|
|
141
|
+
'-e',
|
|
142
|
+
'--rename-section',
|
|
143
|
+
'--remove-section',
|
|
144
|
+
'--default',
|
|
145
|
+
]);
|
|
146
|
+
/** The `git remote` shapes that only print. The same three the denylist above exempts. */
|
|
147
|
+
const REMOTE_READ_FLAGS = new Set(['-v', '--verbose']);
|
|
148
|
+
/**
|
|
149
|
+
* `git config` is a read verb and a write verb wearing one name, so it cannot sit in the allowlist:
|
|
150
|
+
* a bare `git config user.email you@example` is a write and reads identically to a query without
|
|
151
|
+
* this rule. Only the provably-reading shapes are admitted.
|
|
152
|
+
*/
|
|
153
|
+
function isReadOnlyConfig(args) {
|
|
154
|
+
let sawRead = false;
|
|
155
|
+
for (const argument of args) {
|
|
156
|
+
if (CONFIG_WRITE_FLAGS.has(argument))
|
|
157
|
+
return false;
|
|
158
|
+
if (CONFIG_READ_FLAGS.has(argument))
|
|
159
|
+
sawRead = true;
|
|
160
|
+
}
|
|
161
|
+
return sawRead;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* `git remote`, same problem and same treatment. Admitted: the bare list, `-v`/`--verbose`, and
|
|
165
|
+
* `show <name>`. Anything else — a subcommand, an unknown flag — is not proven read-only and refuses.
|
|
166
|
+
*
|
|
167
|
+
* Both layers must agree before a remote command flows. The vocabulary is stated twice on purpose
|
|
168
|
+
* rather than shared through one looser predicate: two independent statements of the same three
|
|
169
|
+
* shapes cannot both be widened by accident.
|
|
170
|
+
*/
|
|
171
|
+
function isReadOnlyRemote(args) {
|
|
172
|
+
for (const argument of args) {
|
|
173
|
+
if (argument === 'show')
|
|
174
|
+
return true;
|
|
175
|
+
if (REMOTE_READ_FLAGS.has(argument))
|
|
176
|
+
continue;
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
function isAllowedGitInvocation(verb, args) {
|
|
182
|
+
if (ALLOWED_GIT_VERBS.has(verb))
|
|
183
|
+
return true;
|
|
184
|
+
if (verb === 'config')
|
|
185
|
+
return isReadOnlyConfig(args);
|
|
186
|
+
if (verb === 'remote')
|
|
187
|
+
return isReadOnlyRemote(args);
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
// ---------------------------------------------------------------------------
|
|
191
|
+
/**
|
|
192
|
+
* Classify a shell command. Returns the refusal that fired, or null when nothing did.
|
|
193
|
+
*
|
|
194
|
+
* `parsed` may be supplied by a caller that already parsed the command — the credential check does,
|
|
195
|
+
* and parsing twice would be work for nothing.
|
|
196
|
+
*/
|
|
197
|
+
export function classifyShellCommand(command, parsed = parseCommand(command)) {
|
|
198
|
+
// Layer 1 — the denylist, over text the shell could actually execute.
|
|
199
|
+
for (const rule of BOUNDARY_RULES) {
|
|
200
|
+
const fired = rule.segmentScoped === true
|
|
201
|
+
? matchesInAnySegment(rule.pattern, parsed.scannable)
|
|
202
|
+
: rule.pattern.test(parsed.scannable);
|
|
203
|
+
if (fired) {
|
|
204
|
+
return refusal('shell-boundary-command', `${rule.name}: ${rule.detail}`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
// Layer 2 — the git verb allowlist. Non-git invocations are not this layer's business; shell
|
|
208
|
+
// commands as a whole are covered by the workspace jail, this file, and the audit trail together.
|
|
209
|
+
//
|
|
210
|
+
// It is deliberately git-only. Extending an allowlist posture to another program is a boundary
|
|
211
|
+
// decision that belongs to the embedder's policy (`ToolFamilies`, the local gate's options), not
|
|
212
|
+
// to this file.
|
|
213
|
+
for (const invocation of parsed.invocations) {
|
|
214
|
+
if (invocation.program !== 'git')
|
|
215
|
+
continue;
|
|
216
|
+
if (invocation.verb === null) {
|
|
217
|
+
return refusal('shell-verb-unrecognised', 'a git invocation with no determinable verb cannot be shown to be safe, so it is refused');
|
|
218
|
+
}
|
|
219
|
+
if (!isAllowedGitInvocation(invocation.verb, invocation.args)) {
|
|
220
|
+
return refusal('shell-verb-unrecognised', `git ${invocation.verb} is not a verb this host can prove is safe; the allowlist refuses ` +
|
|
221
|
+
'anything it cannot name, which is what covers the plumbing forms of publishing');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
//# sourceMappingURL=shell.js.map
|
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
import type { EffortLevel, HookCallbackMatcher, HookEvent, HookInput, HookJSONOutput, McpSdkServerConfigWithInstance, McpServerConfig, Options, PermissionMode, SDKMessage, SDKUserMessage, SdkPluginConfig, SessionStore, SessionStoreFlush, SettingSource, SpawnOptions, SpawnedProcess, ThinkingConfig } from '@anthropic-ai/claude-agent-sdk';
|
|
2
|
+
export type { HookCallbackMatcher, HookEvent, HookInput, HookJSONOutput, McpSdkServerConfigWithInstance, McpServerConfig, SDKMessage, SDKUserMessage, SdkPluginConfig, SessionStore, SessionStoreFlush, SettingSource, SpawnOptions, SpawnedProcess, ThinkingConfig, EffortLevel, PermissionMode, };
|
|
3
|
+
/** Exactly the shape `Options.hooks` takes, so a composer above cannot drift from it. */
|
|
4
|
+
export type HookRegistrations = Partial<Record<HookEvent, HookCallbackMatcher[]>>;
|
|
5
|
+
/**
|
|
6
|
+
* The message union's discriminator, as one string per shape — `type`, or `type/subtype` for the
|
|
7
|
+
* `system` family.
|
|
8
|
+
*
|
|
9
|
+
* Derived from the union rather than listed, so a message type the SDK adds shows up as a missing
|
|
10
|
+
* key wherever this is used exhaustively instead of being silently absent.
|
|
11
|
+
*/
|
|
12
|
+
type SystemSubtype = Extract<SDKMessage, {
|
|
13
|
+
type: 'system';
|
|
14
|
+
}>['subtype'];
|
|
15
|
+
export type MessageDiscriminator = Exclude<SDKMessage['type'], 'system'> | `system/${SystemSubtype}`;
|
|
16
|
+
/** The discriminator of one message, in the same form `MessageDiscriminator` names. */
|
|
17
|
+
export declare function discriminatorOf(message: SDKMessage): MessageDiscriminator;
|
|
18
|
+
/** How the agent's process is created. Replaces the SDK's own local spawn when supplied. */
|
|
19
|
+
export type SpawnAgentProcess = (options: SpawnOptions) => SpawnedProcess;
|
|
20
|
+
/**
|
|
21
|
+
* What the layer above asks for. Deliberately NOT the SDK's `Options`: composing it is pure work
|
|
22
|
+
* that belongs outside this directory, and keeping the SDK's shape on this side of the seam is what
|
|
23
|
+
* lets the composition be unit-tested with no process anywhere near it.
|
|
24
|
+
*/
|
|
25
|
+
export interface AgentProcessRequest {
|
|
26
|
+
/** Absolute, always. The agent's working directory. */
|
|
27
|
+
readonly cwd: string;
|
|
28
|
+
/** The complete environment. Never merged with `process.env` — see sessions/spawn-env.ts. */
|
|
29
|
+
readonly env: Record<string, string>;
|
|
30
|
+
/**
|
|
31
|
+
* Which on-disk settings tiers the agent may load. `[]` means none.
|
|
32
|
+
*
|
|
33
|
+
* The SDK loads ALL of them when this is omitted, so it is passed explicitly on every start: a
|
|
34
|
+
* default that reads whatever files happen to be beside the working directory is not a default a
|
|
35
|
+
* host can reason about.
|
|
36
|
+
*/
|
|
37
|
+
readonly settingSources: readonly SettingSource[];
|
|
38
|
+
readonly plugins: readonly SdkPluginConfig[] | null;
|
|
39
|
+
/**
|
|
40
|
+
* The hook callbacks this session runs. Passed straight through, never composed here.
|
|
41
|
+
*
|
|
42
|
+
* Each event maps to an ARRAY of matchers and the SDK runs every entry, so observation and
|
|
43
|
+
* decision are separate registrations on the same event rather than one handler doing both. See
|
|
44
|
+
* `hooks.ts` for the composer that merges them.
|
|
45
|
+
*/
|
|
46
|
+
readonly hooks: HookRegistrations | null;
|
|
47
|
+
/** A session id to continue. Null starts a new one. */
|
|
48
|
+
readonly resume: string | null;
|
|
49
|
+
/** With `resume`, continue into a NEW session id instead of extending the old one. */
|
|
50
|
+
readonly fork: boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Emit `stream_event` messages while the agent composes a turn.
|
|
53
|
+
*
|
|
54
|
+
* Nothing partial reaches the message stream without this: with it off there is no
|
|
55
|
+
* `SDKPartialAssistantMessage` at all, so text arrives only when a block completes and a turn
|
|
56
|
+
* cannot be rendered as it happens. It is also the precondition for any thinking prose.
|
|
57
|
+
*/
|
|
58
|
+
readonly includePartialMessages: boolean;
|
|
59
|
+
/**
|
|
60
|
+
* How much of the agent's reasoning is emitted. `null` leaves the SDK's own default.
|
|
61
|
+
*
|
|
62
|
+
* `{type:'adaptive'}` fires `thinking_delta` events whose prose is EMPTY;
|
|
63
|
+
* `{type:'adaptive', display:'summarized'}` streams real reasoning text — which costs tokens on
|
|
64
|
+
* the wire and puts reasoning into transcripts and mirrors, so it is asked for rather than
|
|
65
|
+
* assumed. Requires `includePartialMessages`.
|
|
66
|
+
*/
|
|
67
|
+
readonly thinking: ThinkingConfig | null;
|
|
68
|
+
/**
|
|
69
|
+
* Forward a subagent's full conversation, not only its tool calls.
|
|
70
|
+
*
|
|
71
|
+
* Off, the stream carries a subagent's `tool_use`/`tool_result` blocks and nothing else — enough
|
|
72
|
+
* to count activity, not enough to read. On, its text and thinking arrive as ordinary messages
|
|
73
|
+
* with `parent_tool_use_id` set, which is the only way a nested turn can be rendered live.
|
|
74
|
+
*/
|
|
75
|
+
readonly forwardSubagentText: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* The agent process's stderr, line by line.
|
|
78
|
+
*
|
|
79
|
+
* Wired on every start because some conditions are reported here and NOWHERE else — an untrusted
|
|
80
|
+
* workspace voids its permission rules with a stderr line and no error.
|
|
81
|
+
*/
|
|
82
|
+
readonly onStderr: ((data: string) => void) | null;
|
|
83
|
+
/**
|
|
84
|
+
* In-process MCP servers this session may call, by server name.
|
|
85
|
+
*
|
|
86
|
+
* This key enlarges the set of tools the gate must decide about. It is safe for a structural
|
|
87
|
+
* reason, stated at `TOOL_SURFACE_OPTION_KEYS` below, and not for the reason the streaming keys
|
|
88
|
+
* are safe.
|
|
89
|
+
*/
|
|
90
|
+
readonly mcpServers: Readonly<Record<string, McpServerConfig>> | null;
|
|
91
|
+
/**
|
|
92
|
+
* Use ONLY the servers above, ignoring project `.mcp.json`, user settings, plugin MCP and on-disk
|
|
93
|
+
* agent frontmatter. `null` leaves the SDK's own default.
|
|
94
|
+
*
|
|
95
|
+
* The composer above defaults this on (see sessions/registry.ts). It is the workspace-MCP
|
|
96
|
+
* attack-surface switch: without it, a repository the host provisioned as a workspace can declare
|
|
97
|
+
* its own MCP servers and the agent picks them up, which means a directory's CONTENTS decide what
|
|
98
|
+
* tools exist. That is the one lane where a workspace becomes a tool-registration channel.
|
|
99
|
+
*/
|
|
100
|
+
readonly strictMcpConfig: boolean | null;
|
|
101
|
+
/**
|
|
102
|
+
* Mirror this session's transcript to a store the embedder supplies. `null` mirrors nowhere.
|
|
103
|
+
*
|
|
104
|
+
* This object receives every message the agent saw. It is a confidentiality and egress surface,
|
|
105
|
+
* and it must not be filed with the streaming keys, which only widen what this host itself
|
|
106
|
+
* observes.
|
|
107
|
+
*
|
|
108
|
+
* What makes it safe is structural, and it is the same shape as the gate's no-matcher argument:
|
|
109
|
+
* a store is a live object with methods, so it cannot be expressed in JSON and cannot cross the
|
|
110
|
+
* link. It can only be supplied by the code that composes this host, which is the embedder. No
|
|
111
|
+
* controller, and nothing arriving over the wire, can name a destination for a transcript. Pinned
|
|
112
|
+
* by pins/persistence-egress.test.ts rather than left as a sentence.
|
|
113
|
+
*
|
|
114
|
+
* The one documented incompatibility is unreachable by construction. A store may not be
|
|
115
|
+
* combined with session persistence turned off, because the mirror runs after the local write
|
|
116
|
+
* succeeds. `persistSession` is not composable through this type, so the SDK's own default stands
|
|
117
|
+
* and the combination cannot be built here.
|
|
118
|
+
*/
|
|
119
|
+
readonly sessionStore: SessionStore | null;
|
|
120
|
+
/**
|
|
121
|
+
* How eagerly mirrored entries are flushed. `null` leaves the SDK's own default of batching.
|
|
122
|
+
*
|
|
123
|
+
* Eager gives near-real-time delivery at the cost of one call per frame with no coalescing, so it
|
|
124
|
+
* is asked for rather than assumed. Ignored entirely when no store is set.
|
|
125
|
+
*/
|
|
126
|
+
readonly sessionStoreFlush: SessionStoreFlush | null;
|
|
127
|
+
/**
|
|
128
|
+
* Create the process yourself, for a VM, a container or a remote machine.
|
|
129
|
+
*
|
|
130
|
+
* Two things a custom spawn loses, and both are silent.
|
|
131
|
+
* 1. `windowsHide`. The SDK's own spawn passes it; Node's default is `false`, so a custom spawn
|
|
132
|
+
* written without it allocates a real visible window per session wherever no ancestor process
|
|
133
|
+
* owns a console. See this file's header.
|
|
134
|
+
* 2. stderr. `SpawnedProcess` carries only stdin and stdout, so a custom spawn that does not
|
|
135
|
+
* route the child's stderr somewhere loses `onStderr` entirely, and the untrusted-workspace
|
|
136
|
+
* condition is reported only there.
|
|
137
|
+
*/
|
|
138
|
+
readonly spawn: SpawnAgentProcess | null;
|
|
139
|
+
/**
|
|
140
|
+
* Which model runs this session. `null` leaves the CLI's own default.
|
|
141
|
+
*
|
|
142
|
+
* Absent, not narrowed: `model` and `systemPrompt` were never among this type's keys, so adding
|
|
143
|
+
* them filled a gap; it did not widen a security narrowing. The eight `SHADOWING_LANES` below are
|
|
144
|
+
* a deliberate narrowing and they stay closed. Two different facts, and conflating them costs a
|
|
145
|
+
* reader a whole cycle on the wrong objection.
|
|
146
|
+
*
|
|
147
|
+
* What made the gap expensive: a host that cannot state a model cannot run the same agent twice on
|
|
148
|
+
* purpose, and every caller silently got whatever the installed CLI defaulted to.
|
|
149
|
+
*/
|
|
150
|
+
readonly model: string | null;
|
|
151
|
+
/**
|
|
152
|
+
* What the agent is told at the start. `null` leaves the CLI's own preset.
|
|
153
|
+
*
|
|
154
|
+
* The one composable key that cannot be proven from the agent's own report. `AgentInitFacts`
|
|
155
|
+
* below is lifted entirely out of the SDK's `system/init` message, and that message carries no
|
|
156
|
+
* system prompt, so unlike `model`, `tools` or `mcpServers`, nothing coming back from the agent
|
|
157
|
+
* confirms this took effect. It ships unproven-by-report, and that is stated here rather than left
|
|
158
|
+
* for someone to discover while trying to write the assertion.
|
|
159
|
+
*
|
|
160
|
+
* Typed off the SDK's own option so it cannot drift from what `query()` accepts.
|
|
161
|
+
*/
|
|
162
|
+
readonly systemPrompt: AgentSystemPrompt | null;
|
|
163
|
+
/** The effort level. `null` leaves the SDK's own default. */
|
|
164
|
+
readonly effort: EffortLevel | null;
|
|
165
|
+
/**
|
|
166
|
+
* The permission mode. `null` leaves the SDK's default. Deliberately opened: see
|
|
167
|
+
* `CLI_PARITY_OPTION_KEYS` for the reason stated as data, and `SHADOWING_LANES` for what stays
|
|
168
|
+
* closed. The gate's authority is the `PreToolUse` hook; a mode never pre-answers it.
|
|
169
|
+
*/
|
|
170
|
+
readonly permissionMode: PermissionMode | null;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* What the SDK accepts as a system prompt — a string, a list of strings, or a preset descriptor.
|
|
174
|
+
*
|
|
175
|
+
* Derived from `Options` rather than restated: this package's rule is that the types win, and a
|
|
176
|
+
* hand-copied union is a second copy that can disagree with the first.
|
|
177
|
+
*/
|
|
178
|
+
export type AgentSystemPrompt = NonNullable<Options['systemPrompt']>;
|
|
179
|
+
/**
|
|
180
|
+
* Every key `Options` is composed from, as data.
|
|
181
|
+
*
|
|
182
|
+
* This is the permission-config pin's subject, and it is why that pin is a compile error rather
|
|
183
|
+
* than a grep. The SDK's `Options` carries nine lanes that alter permission outcomes:
|
|
184
|
+
* `permissionMode`, `settings`, `managedSettings`, `toolAliases`, `permissionPromptToolName`,
|
|
185
|
+
* `allowedTools`, `disallowedTools`, `canUseTool`, and the `permissions` block a settings object can
|
|
186
|
+
* carry. Eight of them (`SHADOWING_LANES`) are unreachable, because a caller can only supply the
|
|
187
|
+
* keys below and `startAgentProcess` composes `Options` from exactly these; `permissionMode` is the
|
|
188
|
+
* one opened by name, in `CLI_PARITY_OPTION_KEYS`. Adding a composable option breaks this
|
|
189
|
+
* declaration, and the pin fails at build time instead of when someone remembers to look.
|
|
190
|
+
*
|
|
191
|
+
* The composable set is declared as several lists, each with its own reason, because the moment two
|
|
192
|
+
* different justifications share one constant, neither can be checked:
|
|
193
|
+
*
|
|
194
|
+
* - `STREAMING_OPTION_KEYS` select what the process emits, never what it may do. A widening for
|
|
195
|
+
* visibility is not a weakening for permission.
|
|
196
|
+
* - `TOOL_SURFACE_OPTION_KEYS` change which tools exist. Read that list's note for why it is
|
|
197
|
+
* nonetheless safe.
|
|
198
|
+
* - `PERSISTENCE_OPTION_KEYS` decide where the transcript goes: the egress lane. Its safety
|
|
199
|
+
* argument is structural and is pinned rather than stated.
|
|
200
|
+
* - `AGENT_SELECTION_OPTION_KEYS` choose which agent runs and what it is told at the start.
|
|
201
|
+
* - `CLI_PARITY_OPTION_KEYS` expose what the CLI itself exposes, including the one shadowing lane
|
|
202
|
+
* that is a posture rather than a rule file.
|
|
203
|
+
*
|
|
204
|
+
* The distinction that decides whether a widening is a weakening: `model` and `systemPrompt` were
|
|
205
|
+
* absent, not narrowed. They were never among the composable keys, so opening them filled a gap.
|
|
206
|
+
* The eight lanes in `SHADOWING_LANES` are a deliberate security narrowing and stay closed, and the
|
|
207
|
+
* pin still asserts each one by name. A gap filled and a narrowing widened are different acts and
|
|
208
|
+
* this file tells them apart.
|
|
209
|
+
*
|
|
210
|
+
* `toolAliases` is the one worth naming: it redirects tool names after the model emits them, so a
|
|
211
|
+
* gate matching on `tool_name` would see the alias source while the target executed. A silent
|
|
212
|
+
* mismatch, and unreachable here by construction.
|
|
213
|
+
*/
|
|
214
|
+
export declare const AGENT_PROCESS_REQUEST_KEYS: {
|
|
215
|
+
readonly cwd: true;
|
|
216
|
+
readonly env: true;
|
|
217
|
+
readonly settingSources: true;
|
|
218
|
+
readonly plugins: true;
|
|
219
|
+
readonly hooks: true;
|
|
220
|
+
readonly resume: true;
|
|
221
|
+
readonly fork: true;
|
|
222
|
+
readonly onStderr: true;
|
|
223
|
+
readonly spawn: true;
|
|
224
|
+
readonly includePartialMessages: true;
|
|
225
|
+
readonly thinking: true;
|
|
226
|
+
readonly forwardSubagentText: true;
|
|
227
|
+
readonly mcpServers: true;
|
|
228
|
+
readonly strictMcpConfig: true;
|
|
229
|
+
readonly sessionStore: true;
|
|
230
|
+
readonly sessionStoreFlush: true;
|
|
231
|
+
readonly model: true;
|
|
232
|
+
readonly systemPrompt: true;
|
|
233
|
+
readonly effort: true;
|
|
234
|
+
readonly permissionMode: true;
|
|
235
|
+
};
|
|
236
|
+
/**
|
|
237
|
+
* The eight closed lanes, as data: the subject both permission pins are about.
|
|
238
|
+
*
|
|
239
|
+
* The defect this guards against: the lanes were once written out twice, once per pin, and the two
|
|
240
|
+
* copies disagreed. The scan pin listed `permissionPrompt`, which does not exist in `sdk.d.ts` at
|
|
241
|
+
* all, and omitted `permissions`, which does. So the "two mechanisms, one invariant" argument (a
|
|
242
|
+
* type cannot see a module that reaches past the composer, a scan cannot see a type) was being made
|
|
243
|
+
* by two lists guarding different sets, with the real option covered by only one of them.
|
|
244
|
+
*
|
|
245
|
+
* One declaration now, derived by both, and every name checked against the shipped types.
|
|
246
|
+
*/
|
|
247
|
+
export declare const SHADOWING_LANES: readonly string[];
|
|
248
|
+
/**
|
|
249
|
+
* The keys that widened the composable set for visibility, kept as data so the reason is checkable
|
|
250
|
+
* rather than remembered.
|
|
251
|
+
*
|
|
252
|
+
* One reason covers all three: each selects what the process emits. None appears in any permission
|
|
253
|
+
* evaluation path, so none can change whether a tool runs, only how much of the run is visible.
|
|
254
|
+
* That is why widening the set here does not weaken the boundary above.
|
|
255
|
+
*
|
|
256
|
+
* The pin asserts every member is composable and is none of the eight shadowing lanes, so a later
|
|
257
|
+
* addition cannot join this list by assertion alone.
|
|
258
|
+
*/
|
|
259
|
+
export declare const STREAMING_OPTION_KEYS: readonly ["includePartialMessages", "thinking", "forwardSubagentText"];
|
|
260
|
+
/**
|
|
261
|
+
* The keys that change which tools exist: a different class from the streaming three, kept
|
|
262
|
+
* separate so the two reasons cannot be confused for one.
|
|
263
|
+
*
|
|
264
|
+
* These are not data keys. `mcpServers` introduces tools, and `PreToolUse` fires for MCP tools and
|
|
265
|
+
* inside subagents (observed, not assumed). So this widening enlarges the surface the gate must
|
|
266
|
+
* cover, and the streaming keys' reason ("it only selects what is emitted") is false of it.
|
|
267
|
+
*
|
|
268
|
+
* Nor are they shadowing lanes. The closed lanes share one property: they can answer a permission
|
|
269
|
+
* before the gate does. These answer nothing. What makes them safe is structural: `permissionHooks`
|
|
270
|
+
* registers `PreToolUse` with no `matcher`, so a tool this host has never heard of reaches exactly
|
|
271
|
+
* the same handler as `Bash`. Coverage is by construction rather than by enumeration, which is why a
|
|
272
|
+
* tool set the host cannot predict is still a tool set the host decides about.
|
|
273
|
+
*
|
|
274
|
+
* If a `matcher` were ever introduced, this classification stops being true, and these two keys
|
|
275
|
+
* become the first way to add a tool nothing decides about. That sentence is the whole reason this
|
|
276
|
+
* list exists as data instead of as a decision somebody made once.
|
|
277
|
+
*
|
|
278
|
+
* `strictMcpConfig` belongs here because it acts on the same surface, but it moves the opposite
|
|
279
|
+
* way: it only ever removes servers the host did not declare. It is the safe direction of the same
|
|
280
|
+
* lane, and grouping them keeps that visible.
|
|
281
|
+
*/
|
|
282
|
+
export declare const TOOL_SURFACE_OPTION_KEYS: readonly ["mcpServers", "strictMcpConfig"];
|
|
283
|
+
/**
|
|
284
|
+
* The keys that decide where a transcript goes: a third class again, and the reason is not the
|
|
285
|
+
* other two lists' reason.
|
|
286
|
+
*
|
|
287
|
+
* These are a confidentiality and egress surface. The streaming keys widen what this host observes
|
|
288
|
+
* about its own session. The tool-surface keys widen what the agent may do, and are covered because
|
|
289
|
+
* the gate has no matcher. These do neither: they hand a live object every message the agent saw,
|
|
290
|
+
* and a store is by definition somewhere else. Filing them under "it only selects what is emitted"
|
|
291
|
+
* would put a false sentence inside the constant whose entire job is to make the reason checkable.
|
|
292
|
+
*
|
|
293
|
+
* Nor are they shadowing lanes. The closed lanes share one property: they can answer a permission
|
|
294
|
+
* before the gate does. A store answers nothing and is never consulted about whether a tool runs.
|
|
295
|
+
*
|
|
296
|
+
* What makes them safe is that a destination cannot be named from off-box, and it is structural
|
|
297
|
+
* rather than enforced: `SessionStore` is an object with methods, so it has no JSON representation
|
|
298
|
+
* and cannot arrive over the link. The only code that can supply one is the code that composes this
|
|
299
|
+
* host. That is the same shape of argument as the gate's no-matcher coverage (a property of what
|
|
300
|
+
* the type is, not of a check somebody remembered to write) and it is pinned by
|
|
301
|
+
* pins/persistence-egress.test.ts.
|
|
302
|
+
*
|
|
303
|
+
* If a store ever becomes constructible from data (a URL, a connection string, a descriptor the
|
|
304
|
+
* host resolves into a client), this classification stops being true, and these become the first
|
|
305
|
+
* way a transcript can be sent somewhere the operator did not choose. That sentence is why this list
|
|
306
|
+
* is data instead of a decision somebody made once.
|
|
307
|
+
*/
|
|
308
|
+
export declare const PERSISTENCE_OPTION_KEYS: readonly ["sessionStore", "sessionStoreFlush"];
|
|
309
|
+
/**
|
|
310
|
+
* The keys that choose which agent runs and what it is told at the start: a fourth class, and its
|
|
311
|
+
* reason is not any of the other three's.
|
|
312
|
+
*
|
|
313
|
+
* These were absent, not narrowed, and that is the whole classification. `model` and
|
|
314
|
+
* `systemPrompt` were simply not in this type, so nothing was ever protecting them; there was no
|
|
315
|
+
* decision to reverse, only a capability nobody had wired. The eight `SHADOWING_LANES` are the
|
|
316
|
+
* opposite case: each was considered and closed. Opening a gap and re-opening a closed lane look
|
|
317
|
+
* identical in a diff, and this list is how they stop looking identical.
|
|
318
|
+
*
|
|
319
|
+
* Neither appears in any permission evaluation path. They do not pre-answer a call, do not
|
|
320
|
+
* redirect a tool name, do not load a rule file and are never consulted about whether a tool runs,
|
|
321
|
+
* which is exactly the property the closed lanes share and these do not. What they change is which
|
|
322
|
+
* weights answer and what standing instructions those weights start with.
|
|
323
|
+
*
|
|
324
|
+
* The cost, because a widening with no stated cost is a widening nobody checked: a controller that
|
|
325
|
+
* can set a system prompt can give the agent standing instructions this host will never see the
|
|
326
|
+
* effect of, since the SDK's init message does not report one. The gate still decides every tool
|
|
327
|
+
* call, so the boundary is unmoved, but "the host can state what this agent was told" is not a
|
|
328
|
+
* property this package has; `AgentInitFacts` is where that can be verified.
|
|
329
|
+
*
|
|
330
|
+
* The pin asserts every member is composable and is none of the eight, exactly as the other lists
|
|
331
|
+
* do.
|
|
332
|
+
*/
|
|
333
|
+
export declare const AGENT_SELECTION_OPTION_KEYS: readonly ["model", "systemPrompt"];
|
|
334
|
+
/**
|
|
335
|
+
* The CLI-parity keys, and the list that re-opens a closed lane on purpose. `effort` was absent (a
|
|
336
|
+
* gap, like `model`). `permissionMode` was once a shadowing lane, considered and closed, and it is
|
|
337
|
+
* opened here by name, with the reason:
|
|
338
|
+
*
|
|
339
|
+
* a session through this host must expose what `claude` exposes, and bypass is a common default
|
|
340
|
+
* for an operator. The gate never depended on the mode: `PreToolUse` fires under every mode, so
|
|
341
|
+
* the boundary set is still held under `bypassPermissions`. What the mode changes is the CLI's
|
|
342
|
+
* own prompt flow, which this host's gate already answers.
|
|
343
|
+
*
|
|
344
|
+
* The other eight stay closed: they are rule files and pre-answers, which is a different thing from a
|
|
345
|
+
* posture chosen in the open. Pinned by `pins/permission-config.test.ts`.
|
|
346
|
+
*/
|
|
347
|
+
export declare const CLI_PARITY_OPTION_KEYS: readonly ["effort", "permissionMode"];
|
|
348
|
+
/** A started agent process, in this package's terms. */
|
|
349
|
+
export interface AgentProcess {
|
|
350
|
+
/**
|
|
351
|
+
* Everything the agent emits, in order. One consumer.
|
|
352
|
+
*
|
|
353
|
+
* This is a narrowing wrapper, not the SDK's `Query`, and that is load-bearing. `query()`
|
|
354
|
+
* returns an object that IS an async generator AND carries `setPermissionMode`,
|
|
355
|
+
* `applyFlagSettings`, `setMcpServers` and `setMcpPermissionModeOverride` — four calls that change
|
|
356
|
+
* permission outcomes mid-session, after any construction-time check has run. Handing that object
|
|
357
|
+
* out under an `AsyncGenerator` annotation hides them from the compiler and from nobody else: one
|
|
358
|
+
* cast, or any plain JavaScript, reaches all four. So it is wrapped rather than annotated, and
|
|
359
|
+
* "the composed options cannot ship a shadowing setting" stays true without the words "unless you
|
|
360
|
+
* cast" attached to it.
|
|
361
|
+
*
|
|
362
|
+
* Where a mid-session control goes instead: a named method on this handle, beside `prompt`,
|
|
363
|
+
* `interrupt` and `close`. Never by widening this property back to the `Query`, and never by
|
|
364
|
+
* casting it; the point is that one file decides which of the SDK's controls this package
|
|
365
|
+
* offers.
|
|
366
|
+
*/
|
|
367
|
+
readonly messages: AsyncGenerator<SDKMessage, void>;
|
|
368
|
+
/** Queue a turn. Returns false once the process is closed. */
|
|
369
|
+
prompt(text: string): boolean;
|
|
370
|
+
/** Stop the current turn in band, leaving the session alive. */
|
|
371
|
+
interrupt(): Promise<void>;
|
|
372
|
+
/**
|
|
373
|
+
* The named mid-session controls this package offers (the doc above says where they go): the
|
|
374
|
+
* three members of `session_configure`, each the SDK's own streaming-input setter behind a method.
|
|
375
|
+
* `setPermissionMode` is here deliberately: the one permission mutator that is a posture, reached
|
|
376
|
+
* only from the wire through `readSessionConfigure`; the other three stay unreachable.
|
|
377
|
+
*/
|
|
378
|
+
setModel(model: string | null): Promise<void>;
|
|
379
|
+
setPermissionMode(mode: PermissionMode): Promise<void>;
|
|
380
|
+
setThinking(thinking: ThinkingConfig): Promise<void>;
|
|
381
|
+
/** End the process and release everything it holds. Idempotent. */
|
|
382
|
+
close(): void;
|
|
383
|
+
}
|
|
384
|
+
/**
|
|
385
|
+
* Facts the agent states about itself at start, lifted out of the SDK's init message.
|
|
386
|
+
*
|
|
387
|
+
* This is the per-spawn version receipt, and its shape is why it cannot rot: `cliVersion` arrives
|
|
388
|
+
* on the session's own stream, so there is no place to cache it even by accident. The CLI can
|
|
389
|
+
* auto-update underneath a long-running host, and a value read once at startup would then be a
|
|
390
|
+
* confident lie on every later session — which is the failure this receipt exists to catch.
|
|
391
|
+
*
|
|
392
|
+
* Absent is `null`, never an omitted property: this record is destined for the wire, where JSON has
|
|
393
|
+
* no `undefined` and an optional field makes "not reported" and "reported as unset" the same thing.
|
|
394
|
+
*/
|
|
395
|
+
export interface AgentInitFacts {
|
|
396
|
+
readonly sessionId: string;
|
|
397
|
+
readonly cliVersion: string;
|
|
398
|
+
readonly cwd: string;
|
|
399
|
+
readonly model: string;
|
|
400
|
+
readonly permissionMode: string;
|
|
401
|
+
/** Where the agent found its credentials. The evidence that ambient auth actually resolved. */
|
|
402
|
+
readonly apiKeySource: string;
|
|
403
|
+
readonly tools: readonly string[];
|
|
404
|
+
readonly skills: readonly string[];
|
|
405
|
+
readonly plugins: readonly AgentPluginFact[];
|
|
406
|
+
readonly capabilities: readonly string[];
|
|
407
|
+
/**
|
|
408
|
+
* The MCP servers this agent actually connected to, and what happened to each.
|
|
409
|
+
*
|
|
410
|
+
* The agent reports this on the init message. Without it, "did my server register?" is answered
|
|
411
|
+
* by inferring it from tool names, which cannot tell a server that failed to connect from one that
|
|
412
|
+
* connected and exposed no tools, and an HTTP or stdio server, unlike an in-process one, can fail
|
|
413
|
+
* to connect. With it, the answer is the agent's own word, per server, by name and status.
|
|
414
|
+
*
|
|
415
|
+
* The status string is carried verbatim and never interpreted here. It is the agent's vocabulary,
|
|
416
|
+
* not this package's, and mapping it onto this package's words would be a second translation
|
|
417
|
+
* site, the thing `state/observer.ts` exists to be the only one of.
|
|
418
|
+
*/
|
|
419
|
+
readonly mcpServers: readonly AgentMcpServerFact[];
|
|
420
|
+
}
|
|
421
|
+
/** One MCP server as the agent reported it at start. `status` is the SDK's word, carried as-is. */
|
|
422
|
+
export interface AgentMcpServerFact {
|
|
423
|
+
readonly name: string;
|
|
424
|
+
readonly status: string;
|
|
425
|
+
}
|
|
426
|
+
export interface AgentPluginFact {
|
|
427
|
+
readonly name: string;
|
|
428
|
+
readonly path: string;
|
|
429
|
+
readonly version: string | null;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* The init message's facts, or null for every other message.
|
|
433
|
+
*
|
|
434
|
+
* Reading it here rather than above the seam keeps the SDK's message union on this side; the layer
|
|
435
|
+
* above branches on a plain record.
|
|
436
|
+
*/
|
|
437
|
+
export declare function readInitFacts(message: SDKMessage): AgentInitFacts | null;
|
|
438
|
+
/**
|
|
439
|
+
* The message stream, and ONLY the message stream.
|
|
440
|
+
*
|
|
441
|
+
* `return` and `throw` are delegated, not just `next`. `for await…of` calls `iterator.return()`
|
|
442
|
+
* when the loop leaves early — a `break`, a `return`, or a throw inside the body — and that call is
|
|
443
|
+
* what lets the underlying query release the subprocess. A wrapper implementing only `next` would
|
|
444
|
+
* swallow it, leaking a session per abandoned loop with no error, no log and nothing null: exactly
|
|
445
|
+
* the silent-loss shape this package is built against. Pinned by agent-process.test.ts.
|
|
446
|
+
*
|
|
447
|
+
* Exported for that pin. It is not part of the package's public surface.
|
|
448
|
+
*/
|
|
449
|
+
export declare function messagesOf(source: AsyncGenerator<SDKMessage, void>): AsyncGenerator<SDKMessage, void>;
|
|
450
|
+
/** Start an agent process. The subprocess exists when this returns. */
|
|
451
|
+
export declare function startAgentProcess(request: AgentProcessRequest): AgentProcess;
|
|
452
|
+
//# sourceMappingURL=agent-process.d.ts.map
|