@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,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed vocabulary of reasons this package refuses something.
|
|
3
|
+
*
|
|
4
|
+
* A degrade is a named outcome. Nothing here returns a bare `false`, an empty string, or a silent
|
|
5
|
+
* skip: a caller that cannot proceed learns which of these it hit, and the set is closed so a
|
|
6
|
+
* reader can enumerate every way a call can fail without reading every call site.
|
|
7
|
+
*/
|
|
8
|
+
export declare const REFUSAL_REASONS: readonly ["frame-not-json", "frame-malformed", "frame-too-large", "seq-gap", "seq-regressed", "link-not-open", "link-closed", "link-send-failed", "link-unauthorized", "link-write-deferred", "queue-dropped-droppable", "queue-overflow-undroppable", "retention-released-unacked", "credential-unavailable", "session-unknown", "session-spawn-failed", "workspace-untrusted", "transition-cause-unnamed", "permission-decision-unavailable", "permission-decision-unrecognised", "permission-grant-shadows-settings", "gate-deadlines-inverted", "bulk-target-invalid", "bulk-target-not-controller", "bulk-delivery-failed", "path-not-absolute", "path-escapes-root", "path-input-missing", "path-unresolvable", "credential-path-denied", "shell-command-missing", "shell-boundary-command", "shell-verb-unrecognised", "identity-not-configured", "identity-config-invalid", "token-unavailable", "auth-state-mismatch", "auth-callback-refused", "pkce-method-unsupported", "token-request-failed", "token-response-invalid", "token-grant-rejected", "credential-cache-unreadable", "credential-cache-write-failed", "credential-mode-too-wide", "credential-mode-unenforced", "credential-mode-unobservable", "device-code-not-enabled", "device-code-declined", "workspace-provision-failed", "workspace-release-failed", "resume-cwd-not-honoured", "mcp-descriptor-invalid", "mcp-schema-unsupported", "mcp-tool-input-invalid", "transcript-key-invalid", "transcript-entry-malformed", "transcript-read-failed", "transcript-write-failed", "receipt-anchor-unknown", "retention-window-invalid", "gate-cannot-grant", "transcript-path-escape", "config-key-unknown", "config-value-invalid", "config-host-busy", "config-write-failed", "workspace-list-failed", "repository-path-escape", "repository-read-failed", "branch-not-merged"];
|
|
9
|
+
export type RefusalReason = (typeof REFUSAL_REASONS)[number];
|
|
10
|
+
/** A named refusal. `detail` is for a human reading a log; `reason` is what code branches on. */
|
|
11
|
+
export interface Refusal {
|
|
12
|
+
readonly reason: RefusalReason;
|
|
13
|
+
readonly detail: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function refusal(reason: RefusalReason, detail: string): Refusal;
|
|
16
|
+
/**
|
|
17
|
+
* The reasons a retry cannot help with: the credential is gone until a person restores it.
|
|
18
|
+
*
|
|
19
|
+
* This is a predicate and not a comment because the link's reconnect loop is correct for every
|
|
20
|
+
* transient failure and actively harmful for these: it dials a door that structurally cannot open,
|
|
21
|
+
* forever, while the one fact the operator could act on scrolls past as another retry. So "is this
|
|
22
|
+
* terminal" has to be a thing code branches on, and it has to have one home; a second list
|
|
23
|
+
* somewhere else is how the link and the reporter end up disagreeing about whether a host is dead.
|
|
24
|
+
*
|
|
25
|
+
* The set is deliberately small, and erring toward transient is the safe direction. Treating a
|
|
26
|
+
* recoverable failure as terminal takes down a host that would have healed; treating a terminal one
|
|
27
|
+
* as recoverable costs a retry loop that is loud. Only reasons whose own detail already ends in
|
|
28
|
+
* "sign in again" belong here.
|
|
29
|
+
*
|
|
30
|
+
* `link-unauthorized` is the third member, and it is the only one the token layer cannot raise.
|
|
31
|
+
* The provider judged the material and approved it; the controller refused it at the upgrade. The
|
|
32
|
+
* conclusion is identical (the next dial with the same identity refuses identically, and only a
|
|
33
|
+
* person can change what is presented), so it takes the same exit rather than a second, private
|
|
34
|
+
* notion of "terminal" living in the link.
|
|
35
|
+
*
|
|
36
|
+
* `credential-unavailable` is not one of them, and must never be. It is what a host with no
|
|
37
|
+
* identity configured reports on every dial, and that host is supposed to keep connecting without a
|
|
38
|
+
* header, the supported no-identity mode. Putting it here would turn the most common unconfigured
|
|
39
|
+
* setup into a host that refuses to start.
|
|
40
|
+
*/
|
|
41
|
+
export declare function needsHumanReauthentication(reason: RefusalReason): boolean;
|
|
42
|
+
export declare function isRefusalReason(value: string): value is RefusalReason;
|
|
43
|
+
//# sourceMappingURL=refusal.d.ts.map
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The closed vocabulary of reasons this package refuses something.
|
|
3
|
+
*
|
|
4
|
+
* A degrade is a named outcome. Nothing here returns a bare `false`, an empty string, or a silent
|
|
5
|
+
* skip: a caller that cannot proceed learns which of these it hit, and the set is closed so a
|
|
6
|
+
* reader can enumerate every way a call can fail without reading every call site.
|
|
7
|
+
*/
|
|
8
|
+
export const REFUSAL_REASONS = [
|
|
9
|
+
// Codec — the wire edge.
|
|
10
|
+
'frame-not-json',
|
|
11
|
+
'frame-malformed',
|
|
12
|
+
'frame-too-large',
|
|
13
|
+
// Sequencing.
|
|
14
|
+
'seq-gap',
|
|
15
|
+
'seq-regressed',
|
|
16
|
+
// The outbound link.
|
|
17
|
+
// link-write-deferred The controller's own send outcome: the frame was validated,
|
|
18
|
+
// minted its seq and is retained, but the socket write did not land
|
|
19
|
+
// (closed, or faulted mid-write); the reconnect replay delivers it. It is
|
|
20
|
+
// not `link-send-failed`: that means never-queued, nothing coming. The
|
|
21
|
+
// split exists so that neither is reported as success on the controller
|
|
22
|
+
// (bytes that went nowhere, a caller told "sent"). Never emitted by this
|
|
23
|
+
// host; declared here because the vocabulary is shared.
|
|
24
|
+
// link-unauthorized The controller refused this host's identity at the door: the
|
|
25
|
+
// WebSocket upgrade answered 401 or 403, so no socket ever opened. It is
|
|
26
|
+
// not `link-send-failed`: that names a transport that faltered, and this
|
|
27
|
+
// transport worked perfectly while the peer said no. The token layer's own
|
|
28
|
+
// split (`token-grant-rejected`) cannot see this one, because the material
|
|
29
|
+
// was minted fine and refused later, at the controller; a revoked or
|
|
30
|
+
// mis-scoped credential presents exactly here. Terminal by predicate
|
|
31
|
+
// (see `needsHumanReauthentication`): redialling a door that has refused
|
|
32
|
+
// the identity is a loop with no exit. Sign in again.
|
|
33
|
+
'link-not-open',
|
|
34
|
+
'link-closed',
|
|
35
|
+
'link-send-failed',
|
|
36
|
+
'link-unauthorized',
|
|
37
|
+
'link-write-deferred',
|
|
38
|
+
// The offline queue.
|
|
39
|
+
// retention-released-unacked An ended session's written frames, released because the hold
|
|
40
|
+
// outlived its bound, never because they were acked. It is not
|
|
41
|
+
// `queue-dropped-droppable`: that names a delta discarded before it
|
|
42
|
+
// was numbered, which the wire never misses. These are stamped,
|
|
43
|
+
// undroppable frames a controller could still have asked for, so
|
|
44
|
+
// losing them is a real loss and it says so. Named rather than
|
|
45
|
+
// silent, because "the host released them" and "they were never
|
|
46
|
+
// sent" are indistinguishable from the outside and only one of them
|
|
47
|
+
// is something to investigate.
|
|
48
|
+
'queue-dropped-droppable',
|
|
49
|
+
'queue-overflow-undroppable',
|
|
50
|
+
'retention-released-unacked',
|
|
51
|
+
// No credential configured. The link connects without a header in that case (see `link.ts`).
|
|
52
|
+
'credential-unavailable',
|
|
53
|
+
// Sessions. Three reasons, and they must not be collapsed into one: they are three different
|
|
54
|
+
// kinds of failure and only the caller can decide what to do about each.
|
|
55
|
+
// session-unknown a caller error: an id this registry does not hold. Retrying is futile.
|
|
56
|
+
// session-spawn-failed an outage: the agent process did not come up. Retrying may work.
|
|
57
|
+
// workspace-untrusted a degrade: the work proceeds, but settings-file rules are silently
|
|
58
|
+
// void, so anything depending on them is not in force.
|
|
59
|
+
// Merging them would let an outage arrive wearing a refusal's clothes and impersonate a
|
|
60
|
+
// deliberate "no".
|
|
61
|
+
'session-unknown',
|
|
62
|
+
'session-spawn-failed',
|
|
63
|
+
'workspace-untrusted',
|
|
64
|
+
// The declared state model. A transition that cannot name what caused it is refused rather than
|
|
65
|
+
// recorded, because a state nobody can explain is worse than no state at all: it reads as fact.
|
|
66
|
+
'transition-cause-unnamed',
|
|
67
|
+
// The permission decision. Two reasons, and they must not be collapsed into one; neither is a
|
|
68
|
+
// denial, which is a deliberate answer and carries `cause.kind:'control'` instead.
|
|
69
|
+
// permission-decision-unavailable an outage: the decider threw, the escalation transport
|
|
70
|
+
// failed, or the controller answered non-2xx. Nobody decided.
|
|
71
|
+
// permission-decision-unrecognised a decision value this build has never seen: a controller
|
|
72
|
+
// running ahead of a host, a new tier, a rolled-back deploy.
|
|
73
|
+
// An outage is an infrastructure fix and an unrecognised value is a controller-version fix, so
|
|
74
|
+
// telling them apart is the difference between two different investigations. Both block the tool.
|
|
75
|
+
// permission-grant-shadows-settings A composition this package will not assemble, refused
|
|
76
|
+
// before any session exists. The gate can make its allow
|
|
77
|
+
// effective (see `grantOnAllow` in gate/gate.ts), but an
|
|
78
|
+
// effective allow short-circuits every permission check that
|
|
79
|
+
// runs after it, and when settings files are loaded those
|
|
80
|
+
// checks include the operator's own deny rules. The two are
|
|
81
|
+
// individually reasonable and jointly mean "the gate silently
|
|
82
|
+
// overrides rules the operator wrote". Refused by name rather
|
|
83
|
+
// than resolved by precedence, because either precedence is a
|
|
84
|
+
// surprise to somebody.
|
|
85
|
+
// gate-deadlines-inverted The second composition this package will not assemble,
|
|
86
|
+
// reachable from off-box because gate timings ride
|
|
87
|
+
// `session_new`. `permissionHooks` throws at construction
|
|
88
|
+
// when the host's own deadline does not expire before the
|
|
89
|
+
// matcher's (correct, deliberate, and pinned, because a
|
|
90
|
+
// matcher expiring first blocks the tool with nothing
|
|
91
|
+
// recorded and reads as a hang). But a throw escaping the
|
|
92
|
+
// payload dispatcher is a command that vanishes, which reads
|
|
93
|
+
// to a controller as a host that hung: the exact failure the
|
|
94
|
+
// dispatcher refuses everything by name to avoid. So the
|
|
95
|
+
// throw stays where an embedder meets it and the composer
|
|
96
|
+
// converts it into this named refusal, before any process
|
|
97
|
+
// exists.
|
|
98
|
+
'permission-decision-unavailable',
|
|
99
|
+
'permission-decision-unrecognised',
|
|
100
|
+
'permission-grant-shadows-settings',
|
|
101
|
+
'gate-deadlines-inverted',
|
|
102
|
+
// The bulk lane.
|
|
103
|
+
//
|
|
104
|
+
// `bulk-target-not-controller` is a separate reason from `bulk-target-invalid`, deliberately.
|
|
105
|
+
// A malformed or wrong-scheme URL is a peer that sent nonsense; an origin that parses perfectly
|
|
106
|
+
// and simply is not this host's controller is a peer trying to make the host POST a transcript
|
|
107
|
+
// and its durable credential somewhere else. The second is the only one of the two a reader
|
|
108
|
+
// should be able to grep for, alert on, or count; folding it into the first would make an
|
|
109
|
+
// exfiltration attempt indistinguishable from a typo in a URL.
|
|
110
|
+
'bulk-target-invalid',
|
|
111
|
+
'bulk-target-not-controller',
|
|
112
|
+
'bulk-delivery-failed',
|
|
113
|
+
// Paths.
|
|
114
|
+
'path-not-absolute',
|
|
115
|
+
'path-escapes-root',
|
|
116
|
+
// The host's own gate: refusals this package decides by itself, with no controller involved.
|
|
117
|
+
//
|
|
118
|
+
// They are refusal reasons rather than deny messages, and that is the contract.
|
|
119
|
+
// A deny decided by a controller is recorded `control/permission_denied`. If a locally-decided
|
|
120
|
+
// refusal were also expressed as a deny it would carry that same cause, and the only thing telling
|
|
121
|
+
// a local policy answer apart from a remote one would be free text in `detail` — which is
|
|
122
|
+
// documented as never branched on. Naming them here makes the difference a fact code can read:
|
|
123
|
+
// `state/model.ts`'s `CauseEvent` already admits every RefusalReason, so each of these becomes
|
|
124
|
+
// `refusal/<name>` on the transition, distinct both from a controller's deny and from
|
|
125
|
+
// `permission-decision-unavailable`, which means nobody decided at all.
|
|
126
|
+
//
|
|
127
|
+
// path-input-missing a path-taking tool whose input carries no readable path. A call
|
|
128
|
+
// whose target cannot be found is not one that can be bounded.
|
|
129
|
+
// path-unresolvable normalizing the path failed. Unresolvable is not provably inside.
|
|
130
|
+
// credential-path-denied the call targets the host's own credential material. The agent runs
|
|
131
|
+
// as the same OS user as the host, so file permissions are not a
|
|
132
|
+
// boundary against it and this is the only control that is.
|
|
133
|
+
// shell-command-missing a shell tool whose input carries no readable command.
|
|
134
|
+
// shell-boundary-command a named boundary operation: publishing, remote surgery, branch
|
|
135
|
+
// deletion, merging. Reported with the rule that fired.
|
|
136
|
+
// shell-verb-unrecognised a git invocation whose verb is not provably safe. Not the same as
|
|
137
|
+
// the above: this one fires on a verb nobody enumerated, the layer
|
|
138
|
+
// that exists because a denylist under-includes (the plumbing behind
|
|
139
|
+
// a publish can match none of its patterns).
|
|
140
|
+
'path-input-missing',
|
|
141
|
+
'path-unresolvable',
|
|
142
|
+
'credential-path-denied',
|
|
143
|
+
'shell-command-missing',
|
|
144
|
+
'shell-boundary-command',
|
|
145
|
+
'shell-verb-unrecognised',
|
|
146
|
+
// Identity: acquiring, holding and presenting the user's own token.
|
|
147
|
+
//
|
|
148
|
+
// The split between `identity-not-configured` and `token-unavailable` is the one that matters.
|
|
149
|
+
// The first says the operator has not set this host up; the second says they have, and nobody has
|
|
150
|
+
// signed in yet (or the token aged out and there is no refresh token). They are different people's
|
|
151
|
+
// problems on different days, and collapsing them would make the host's most common message
|
|
152
|
+
// useless: "not configured" sent to someone who configured it a week ago reads as a bug.
|
|
153
|
+
//
|
|
154
|
+
// identity-not-configured no authority/client id is set. The host cannot even try.
|
|
155
|
+
// identity-config-invalid set, but unusable: a non-https authority, a malformed redirect.
|
|
156
|
+
// token-unavailable configured and valid, but there is no usable token right now.
|
|
157
|
+
//
|
|
158
|
+
// `auth-state-mismatch` is a security event, not a protocol hiccup, and it is named separately
|
|
159
|
+
// for that reason. The loopback listener accepts a callback on 127.0.0.1, and the agent runs as
|
|
160
|
+
// the same OS user as this host, the same fact that makes file permissions useless here. So any
|
|
161
|
+
// local process can reach that listener, and an unverified callback would let one hand this host
|
|
162
|
+
// an authorization code it obtained itself. The `state` value is what makes the callback provably
|
|
163
|
+
// the answer to the request this host made. A mismatch is someone else talking.
|
|
164
|
+
//
|
|
165
|
+
// auth-state-mismatch the callback's `state` is not the one this host minted.
|
|
166
|
+
// auth-callback-refused the callback carried no authorization code: the user declined, or
|
|
167
|
+
// the provider returned an `error` instead.
|
|
168
|
+
// pkce-method-unsupported a code-challenge method other than S256 was asked for. RFC 7636
|
|
169
|
+
// permits `plain`; this host refuses it rather than merely not using
|
|
170
|
+
// it, because an unused branch is one a later reader restores.
|
|
171
|
+
//
|
|
172
|
+
// The token endpoint's two failure kinds are split for the same reason the permission decision's
|
|
173
|
+
// are: an outage and a shape this host does not recognise are different investigations.
|
|
174
|
+
//
|
|
175
|
+
// token-request-failed transport error, or a non-2xx this host may retry its way out of.
|
|
176
|
+
// token-response-invalid 2xx, and the body is not a token response this host can use.
|
|
177
|
+
// token-grant-rejected The grant itself is dead, and no amount of retrying fixes it.
|
|
178
|
+
// RFC 6749 calls this `invalid_grant`: the refresh token is expired,
|
|
179
|
+
// revoked, or was issued to someone else. A person has to sign in.
|
|
180
|
+
// It is split from `token-request-failed` because the two need
|
|
181
|
+
// opposite actions. A provider that is briefly unreachable wants
|
|
182
|
+
// patience; a grant that has lapsed wants a human, and a host that
|
|
183
|
+
// waits politely for one to fix itself waits forever (a refresh token
|
|
184
|
+
// that dies after a day of inactivity is a common provider policy).
|
|
185
|
+
// Some providers append their own diagnostic code to
|
|
186
|
+
// `error_description`; the code branches on the RFC name.
|
|
187
|
+
//
|
|
188
|
+
// The cache, and the mode check. Three outcomes rather than two, because on win32 a file written
|
|
189
|
+
// 0600, one chmod'ed to 0600 and a deliberately world-readable 0666 all read back as 666.
|
|
190
|
+
//
|
|
191
|
+
// credential-cache-unreadable the cache exists and could not be read or parsed.
|
|
192
|
+
// credential-cache-write-failed the cache could not be written.
|
|
193
|
+
// credential-mode-too-wide verify-after-write found the file readable by more than its
|
|
194
|
+
// owner on a platform that enforces modes. A real finding.
|
|
195
|
+
// credential-mode-unenforced A named degrade, not a failure. This filesystem records only
|
|
196
|
+
// the write bit, so 0600 and 0666 are indistinguishable and
|
|
197
|
+
// privacy cannot be confirmed. The win32 shape. "Verified after
|
|
198
|
+
// write" is not available here, and saying so matters, because
|
|
199
|
+
// the alternative is a check that passes vacuously and reads as
|
|
200
|
+
// proof.
|
|
201
|
+
// credential-mode-unobservable Inconclusive, and deliberately not the same reason. Nothing
|
|
202
|
+
// about a mode was observable at all, not even clearing the
|
|
203
|
+
// write bit, which win32 does record. So the probe cannot tell
|
|
204
|
+
// a filesystem that records nothing apart from a broken probe,
|
|
205
|
+
// and that is a thing to investigate rather than a fact about
|
|
206
|
+
// privacy. An instrument with no inconclusive state reports
|
|
207
|
+
// confidence it has not earned.
|
|
208
|
+
//
|
|
209
|
+
// device-code-not-enabled the device-code flow was reached without being configured. It is
|
|
210
|
+
// never a silent fallback: the provider calls it a high-risk method
|
|
211
|
+
// and recommends blocking it, so it must be asked for by name.
|
|
212
|
+
// device-code-declined the device-code flow ended without a token: declined, expired, or
|
|
213
|
+
// refused by the tenant.
|
|
214
|
+
'identity-not-configured',
|
|
215
|
+
'identity-config-invalid',
|
|
216
|
+
'token-unavailable',
|
|
217
|
+
'auth-state-mismatch',
|
|
218
|
+
'auth-callback-refused',
|
|
219
|
+
'pkce-method-unsupported',
|
|
220
|
+
'token-request-failed',
|
|
221
|
+
'token-response-invalid',
|
|
222
|
+
'token-grant-rejected',
|
|
223
|
+
'credential-cache-unreadable',
|
|
224
|
+
'credential-cache-write-failed',
|
|
225
|
+
'credential-mode-too-wide',
|
|
226
|
+
'credential-mode-unenforced',
|
|
227
|
+
'credential-mode-unobservable',
|
|
228
|
+
'device-code-not-enabled',
|
|
229
|
+
'device-code-declined',
|
|
230
|
+
// The workspace a session runs in. Two reasons, split for the same reason as the session trio
|
|
231
|
+
// above: provisioning failed means the session never got a directory and must not start;
|
|
232
|
+
// releasing failed means the work is done and something was left behind. The first blocks, the
|
|
233
|
+
// second is cleanup debt, and a host that reported both as one could never tell an operator
|
|
234
|
+
// which of the two it was looking at.
|
|
235
|
+
//
|
|
236
|
+
// workspace-provision-failed no usable directory. Never a silent fallback to a temp path or to
|
|
237
|
+
// the host's own cwd, which is how a session ends up writing into
|
|
238
|
+
// the directory the host itself is running in.
|
|
239
|
+
// workspace-release-failed the directory or worktree could not be released. Named rather than
|
|
240
|
+
// swallowed: a release that quietly fails leaks a directory per
|
|
241
|
+
// session, which is invisible until a disk fills.
|
|
242
|
+
// resume-cwd-not-honoured a resume named a directory this host's provider would not honour
|
|
243
|
+
// (anything but its own repository root). The CLI keeps transcripts
|
|
244
|
+
// per cwd, so a resume moved into a provisioned workspace finds
|
|
245
|
+
// nothing and becomes a fresh session that says nothing; refused by
|
|
246
|
+
// name instead, on the wire.
|
|
247
|
+
'workspace-provision-failed',
|
|
248
|
+
'workspace-release-failed',
|
|
249
|
+
'resume-cwd-not-honoured',
|
|
250
|
+
// Tool descriptors the controller hands over, and the schemas they carry.
|
|
251
|
+
//
|
|
252
|
+
// `mcp-schema-unsupported` is the one that matters, and it exists to prevent a false green.
|
|
253
|
+
// A converter that met a construct it did not recognise and fell back to a permissive schema would
|
|
254
|
+
// register the tool successfully and validate nothing, so the property "a malformed call is
|
|
255
|
+
// rejected" would be silently false for exactly the tools nobody checked, while every test stayed
|
|
256
|
+
// green. Refusing at registration is loud, happens before any session exists, and names the
|
|
257
|
+
// construct. A permissive fallback is the fail-open shape this package is built against.
|
|
258
|
+
//
|
|
259
|
+
// mcp-descriptor-invalid the descriptor itself is unusable: no name, no schema, a duplicate
|
|
260
|
+
// name within one server.
|
|
261
|
+
// mcp-schema-unsupported a schema construct this host cannot convert. Refused, never widened.
|
|
262
|
+
// mcp-tool-input-invalid a call whose arguments failed the tool's own schema. The one of the
|
|
263
|
+
// three that happens at run time rather than at registration.
|
|
264
|
+
'mcp-descriptor-invalid',
|
|
265
|
+
'mcp-schema-unsupported',
|
|
266
|
+
'mcp-tool-input-invalid',
|
|
267
|
+
// Durability: the transcript mirror, the transition log, and the receipt read.
|
|
268
|
+
//
|
|
269
|
+
// `receipt-anchor-unknown` is the one that matters, and it exists because the alternative is
|
|
270
|
+
// invisible. A delivery receipt answers "did the text land?", and the only two honest answers are
|
|
271
|
+
// yes and no. A third situation exists (the baseline anchor is not in the transcript at all, so
|
|
272
|
+
// the question cannot be evaluated) and reporting that as "no" is the failure this whole read
|
|
273
|
+
// path is built against: a controller that reads a false "no" pastes the text again. Naming it
|
|
274
|
+
// makes "could not tell" a fact code branches on rather than a silence.
|
|
275
|
+
//
|
|
276
|
+
// transcript-key-invalid an empty project key or session id, or a subpath present and
|
|
277
|
+
// empty. The store's key type calls an empty subpath invalid and
|
|
278
|
+
// says to omit the field instead, so it is refused, not coerced;
|
|
279
|
+
// coercing would silently address the main transcript when a
|
|
280
|
+
// subagent's was asked for.
|
|
281
|
+
// transcript-entry-malformed a stored line that will not round-trip. Round-tripping is the
|
|
282
|
+
// only invariant the adapter contract requires, so a line failing
|
|
283
|
+
// it is named rather than skipped: skipping is how a transcript
|
|
284
|
+
// quietly loses entries nobody counted.
|
|
285
|
+
// transcript-read-failed the store or the local file could not be read.
|
|
286
|
+
// transcript-write-failed the append did not land anywhere.
|
|
287
|
+
// receipt-anchor-unknown see above. Not a negative receipt.
|
|
288
|
+
// retention-window-invalid a retention window that is negative or not a number. Refused
|
|
289
|
+
// rather than defaulted, because a default here silently deletes
|
|
290
|
+
// on a schedule nobody chose.
|
|
291
|
+
// gate-cannot-grant A degrade, not a blocked call, and the only entry here that
|
|
292
|
+
// reports on the gate's own configuration rather than on a request.
|
|
293
|
+
// Raised once per gate, on the first allow issued while
|
|
294
|
+
// `grantOnAllow` is off: the call was approved and the tool still
|
|
295
|
+
// will not run, because a silent allow leaves the agent's own
|
|
296
|
+
// permission mode to decide and a host has nobody to answer it.
|
|
297
|
+
// Named because the alternative is nothing at all, on every
|
|
298
|
+
// approved call, for the life of the session.
|
|
299
|
+
'transcript-key-invalid',
|
|
300
|
+
'transcript-entry-malformed',
|
|
301
|
+
'transcript-read-failed',
|
|
302
|
+
'transcript-write-failed',
|
|
303
|
+
'receipt-anchor-unknown',
|
|
304
|
+
'retention-window-invalid',
|
|
305
|
+
'gate-cannot-grant',
|
|
306
|
+
// The discovery door: reading the agent CLI's own transcript directory, jailed.
|
|
307
|
+
//
|
|
308
|
+
// transcript-path-escape A name that could shape a path outside the projects root. One
|
|
309
|
+
// reason for all three layers of the jail (the name allowlist, the
|
|
310
|
+
// explicit dot-name reject, and the resolve-then-containment check)
|
|
311
|
+
// with `detail` naming which layer refused. It is not
|
|
312
|
+
// `path-escapes-root`: that names the workspace jail around a
|
|
313
|
+
// session's own files; this names the read-only door over a directory
|
|
314
|
+
// the host reads on the controller's behalf. An absent transcript is
|
|
315
|
+
// not this: absence is a value the door reports, never a refusal.
|
|
316
|
+
'transcript-path-escape',
|
|
317
|
+
// The configure ask: a controller changing this host's own config file over the wire.
|
|
318
|
+
//
|
|
319
|
+
// config-key-unknown An entry names a key this host does not take over the wire: a key
|
|
320
|
+
// outside the closed config set, or one of the keys that name the
|
|
321
|
+
// controller itself (the URLs, the host id). A controller re-pointing
|
|
322
|
+
// a host at another controller has no legitimate use, so those keys
|
|
323
|
+
// are refused here by name; the detail lists the settable set.
|
|
324
|
+
// config-value-invalid A value the host would refuse at start-up: a root that is not
|
|
325
|
+
// absolute, a branch scheme with no `{key}`, an unknown placeholder,
|
|
326
|
+
// an unmatched brace, a literal that renders an illegal branch, a
|
|
327
|
+
// scheme with no repository root behind it. Screened as a whole set
|
|
328
|
+
// before any write, so a half-applied posture never lands.
|
|
329
|
+
// config-host-busy The ask changes a workspace root while a session is live or
|
|
330
|
+
// opening. A session releases through the provider that provisioned
|
|
331
|
+
// it; swapping roots under one turns that release into a guess. Close
|
|
332
|
+
// the sessions and ask again.
|
|
333
|
+
// config-write-failed The file could not be written: the directory is unwritable, the
|
|
334
|
+
// existing file is corrupt (fix or remove it first), or this host was
|
|
335
|
+
// composed without a configuration seam at all.
|
|
336
|
+
'config-key-unknown',
|
|
337
|
+
'config-value-invalid',
|
|
338
|
+
'config-host-busy',
|
|
339
|
+
'config-write-failed',
|
|
340
|
+
// The inventory: listing the worktrees under the workspace root from disk.
|
|
341
|
+
//
|
|
342
|
+
// workspace-list-failed The host could not look: no workspace provider, a provider that
|
|
343
|
+
// keeps no inventory, or git refusing to list. The detail names which.
|
|
344
|
+
'workspace-list-failed',
|
|
345
|
+
// The repository read: a controller listing a directory or reading a text file of the operator's
|
|
346
|
+
// checkout through this host, jailed to the repository root.
|
|
347
|
+
//
|
|
348
|
+
// repository-path-escape The asked path resolves outside the repository root, or this host
|
|
349
|
+
// has no repository root to read under. The same shape as
|
|
350
|
+
// `transcript-path-escape`, over a different root; an absent file or
|
|
351
|
+
// directory is not this, it is `repository-read-failed`.
|
|
352
|
+
// repository-read-failed The path is not a directory (for a listing) or not a file (for a
|
|
353
|
+
// read), the file holds a NUL byte in its head (binary; the answer is
|
|
354
|
+
// a string), or the filesystem refused. The detail names which.
|
|
355
|
+
'repository-path-escape',
|
|
356
|
+
'repository-read-failed',
|
|
357
|
+
// The branch half of a release: deleting a branch is not reversible, so it is refused by name
|
|
358
|
+
// rather than done quietly.
|
|
359
|
+
//
|
|
360
|
+
// branch-not-merged The ask said deleteBranch without force and the branch is not in
|
|
361
|
+
// the default branch (or no default branch exists to judge by). Nothing
|
|
362
|
+
// was removed: the check runs before the directory goes.
|
|
363
|
+
'branch-not-merged',
|
|
364
|
+
];
|
|
365
|
+
export function refusal(reason, detail) {
|
|
366
|
+
return { reason, detail };
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* The reasons a retry cannot help with: the credential is gone until a person restores it.
|
|
370
|
+
*
|
|
371
|
+
* This is a predicate and not a comment because the link's reconnect loop is correct for every
|
|
372
|
+
* transient failure and actively harmful for these: it dials a door that structurally cannot open,
|
|
373
|
+
* forever, while the one fact the operator could act on scrolls past as another retry. So "is this
|
|
374
|
+
* terminal" has to be a thing code branches on, and it has to have one home; a second list
|
|
375
|
+
* somewhere else is how the link and the reporter end up disagreeing about whether a host is dead.
|
|
376
|
+
*
|
|
377
|
+
* The set is deliberately small, and erring toward transient is the safe direction. Treating a
|
|
378
|
+
* recoverable failure as terminal takes down a host that would have healed; treating a terminal one
|
|
379
|
+
* as recoverable costs a retry loop that is loud. Only reasons whose own detail already ends in
|
|
380
|
+
* "sign in again" belong here.
|
|
381
|
+
*
|
|
382
|
+
* `link-unauthorized` is the third member, and it is the only one the token layer cannot raise.
|
|
383
|
+
* The provider judged the material and approved it; the controller refused it at the upgrade. The
|
|
384
|
+
* conclusion is identical (the next dial with the same identity refuses identically, and only a
|
|
385
|
+
* person can change what is presented), so it takes the same exit rather than a second, private
|
|
386
|
+
* notion of "terminal" living in the link.
|
|
387
|
+
*
|
|
388
|
+
* `credential-unavailable` is not one of them, and must never be. It is what a host with no
|
|
389
|
+
* identity configured reports on every dial, and that host is supposed to keep connecting without a
|
|
390
|
+
* header, the supported no-identity mode. Putting it here would turn the most common unconfigured
|
|
391
|
+
* setup into a host that refuses to start.
|
|
392
|
+
*/
|
|
393
|
+
export function needsHumanReauthentication(reason) {
|
|
394
|
+
return (reason === 'token-grant-rejected' || reason === 'token-unavailable' || reason === 'link-unauthorized');
|
|
395
|
+
}
|
|
396
|
+
export function isRefusalReason(value) {
|
|
397
|
+
return REFUSAL_REASONS.includes(value);
|
|
398
|
+
}
|
|
399
|
+
//# sourceMappingURL=refusal.js.map
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Refusal, RefusalReason } from './refusal.js';
|
|
2
|
+
/**
|
|
3
|
+
* The return type of anything that can decline. There is no thrown-exception path for expected
|
|
4
|
+
* failure — a caller cannot forget to handle a `Result` the way it can forget a `try`.
|
|
5
|
+
*/
|
|
6
|
+
export type Result<T> = {
|
|
7
|
+
readonly ok: true;
|
|
8
|
+
readonly value: T;
|
|
9
|
+
} | {
|
|
10
|
+
readonly ok: false;
|
|
11
|
+
readonly refusal: Refusal;
|
|
12
|
+
};
|
|
13
|
+
export declare function ok<T>(value: T): Result<T>;
|
|
14
|
+
export declare function refuse<T>(reason: RefusalReason, detail: string): Result<T>;
|
|
15
|
+
/** Narrow without destructuring, for call sites that only branch. */
|
|
16
|
+
export declare function isOk<T>(result: Result<T>): result is {
|
|
17
|
+
ok: true;
|
|
18
|
+
value: T;
|
|
19
|
+
};
|
|
20
|
+
/** The value, or a fallback. For call sites where a refusal is genuinely uninteresting. */
|
|
21
|
+
export declare function valueOr<T>(result: Result<T>, fallback: T): T;
|
|
22
|
+
//# sourceMappingURL=result.d.ts.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { refusal } from './refusal.js';
|
|
2
|
+
export function ok(value) {
|
|
3
|
+
return { ok: true, value };
|
|
4
|
+
}
|
|
5
|
+
export function refuse(reason, detail) {
|
|
6
|
+
return { ok: false, refusal: refusal(reason, detail) };
|
|
7
|
+
}
|
|
8
|
+
/** Narrow without destructuring, for call sites that only branch. */
|
|
9
|
+
export function isOk(result) {
|
|
10
|
+
return result.ok;
|
|
11
|
+
}
|
|
12
|
+
/** The value, or a fallback. For call sites where a refusal is genuinely uninteresting. */
|
|
13
|
+
export function valueOr(result, fallback) {
|
|
14
|
+
return result.ok ? result.value : fallback;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=result.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time as an injected capability, so anything that stamps a frame is testable without waiting.
|
|
3
|
+
* The one real clock is built in the composition root and threaded down.
|
|
4
|
+
*/
|
|
5
|
+
/** Returns the current instant as an ISO-8601 UTC string — the `at` on every frame. */
|
|
6
|
+
export type Clock = () => string;
|
|
7
|
+
/** Milliseconds since the epoch, for elapsed-time arithmetic (deadlines, backoff, heartbeat). */
|
|
8
|
+
export type Ticker = () => number;
|
|
9
|
+
export declare const systemClock: Clock;
|
|
10
|
+
export declare const systemTicker: Ticker;
|
|
11
|
+
/** A clock that advances only when told to. */
|
|
12
|
+
export declare function fixedClock(startMs: number): Clock & {
|
|
13
|
+
advance: (ms: number) => void;
|
|
14
|
+
};
|
|
15
|
+
/** A ticker that advances only when told to. Pairs with `fixedClock` for deadline tests. */
|
|
16
|
+
export declare function fixedTicker(startMs: number): Ticker & {
|
|
17
|
+
advance: (ms: number) => void;
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=time.d.ts.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time as an injected capability, so anything that stamps a frame is testable without waiting.
|
|
3
|
+
* The one real clock is built in the composition root and threaded down.
|
|
4
|
+
*/
|
|
5
|
+
export const systemClock = () => new Date().toISOString();
|
|
6
|
+
export const systemTicker = () => Date.now();
|
|
7
|
+
/** A clock that advances only when told to. */
|
|
8
|
+
export function fixedClock(startMs) {
|
|
9
|
+
let now = startMs;
|
|
10
|
+
const clock = (() => new Date(now).toISOString());
|
|
11
|
+
clock.advance = (ms) => {
|
|
12
|
+
now += ms;
|
|
13
|
+
};
|
|
14
|
+
return clock;
|
|
15
|
+
}
|
|
16
|
+
/** A ticker that advances only when told to. Pairs with `fixedClock` for deadline tests. */
|
|
17
|
+
export function fixedTicker(startMs) {
|
|
18
|
+
let now = startMs;
|
|
19
|
+
const ticker = (() => now);
|
|
20
|
+
ticker.advance = (ms) => {
|
|
21
|
+
now += ms;
|
|
22
|
+
};
|
|
23
|
+
return ticker;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=time.js.map
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The nouns this package is allowed to name things after — the Claude Agent SDK's own words.
|
|
3
|
+
*
|
|
4
|
+
* Every wire discriminator begins with one of these, which is what makes the naming rule
|
|
5
|
+
* mechanically checkable instead of a style note: a payload kind naming a concept the SDK does not
|
|
6
|
+
* have is a concept this layer should not be modelling.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SDK_NOUNS: readonly ["session", "message", "hook", "tool", "subagent", "turn", "result", "permission", "compaction"];
|
|
9
|
+
export type SdkNoun = (typeof SDK_NOUNS)[number];
|
|
10
|
+
/**
|
|
11
|
+
* Nouns this package owns because the SDK has no concept for them: the outbound link itself, and
|
|
12
|
+
* the out-of-band transfer that keeps bulk bytes off it. Kept separate from `SDK_NOUNS` so the
|
|
13
|
+
* distinction stays visible: these are the words this package invented rather than borrowed.
|
|
14
|
+
*/
|
|
15
|
+
export declare const HOST_NOUNS: readonly ["link", "bulk"];
|
|
16
|
+
export type HostNoun = (typeof HOST_NOUNS)[number];
|
|
17
|
+
/**
|
|
18
|
+
* A discriminator is `<noun>_<verb>` or `<noun>/<method>`. Returns the leading noun, or null when
|
|
19
|
+
* the string does not start with a declared one.
|
|
20
|
+
*/
|
|
21
|
+
export declare function nounOf(discriminator: string): string | null;
|
|
22
|
+
export declare function isDeclaredNoun(value: string): boolean;
|
|
23
|
+
//# sourceMappingURL=vocab.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The nouns this package is allowed to name things after — the Claude Agent SDK's own words.
|
|
3
|
+
*
|
|
4
|
+
* Every wire discriminator begins with one of these, which is what makes the naming rule
|
|
5
|
+
* mechanically checkable instead of a style note: a payload kind naming a concept the SDK does not
|
|
6
|
+
* have is a concept this layer should not be modelling.
|
|
7
|
+
*/
|
|
8
|
+
export const SDK_NOUNS = [
|
|
9
|
+
'session',
|
|
10
|
+
'message',
|
|
11
|
+
'hook',
|
|
12
|
+
'tool',
|
|
13
|
+
'subagent',
|
|
14
|
+
'turn',
|
|
15
|
+
'result',
|
|
16
|
+
'permission',
|
|
17
|
+
'compaction',
|
|
18
|
+
];
|
|
19
|
+
/**
|
|
20
|
+
* Nouns this package owns because the SDK has no concept for them: the outbound link itself, and
|
|
21
|
+
* the out-of-band transfer that keeps bulk bytes off it. Kept separate from `SDK_NOUNS` so the
|
|
22
|
+
* distinction stays visible: these are the words this package invented rather than borrowed.
|
|
23
|
+
*/
|
|
24
|
+
export const HOST_NOUNS = ['link', 'bulk'];
|
|
25
|
+
const ALL_NOUNS = [...SDK_NOUNS, ...HOST_NOUNS];
|
|
26
|
+
/**
|
|
27
|
+
* A discriminator is `<noun>_<verb>` or `<noun>/<method>`. Returns the leading noun, or null when
|
|
28
|
+
* the string does not start with a declared one.
|
|
29
|
+
*/
|
|
30
|
+
export function nounOf(discriminator) {
|
|
31
|
+
const head = discriminator.split(/[_/]/, 1)[0] ?? '';
|
|
32
|
+
return ALL_NOUNS.includes(head) ? head : null;
|
|
33
|
+
}
|
|
34
|
+
export function isDeclaredNoun(value) {
|
|
35
|
+
return ALL_NOUNS.includes(value);
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=vocab.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bound on a workspace id (a session key, a workspace key, a worktree's directory name).
|
|
3
|
+
*
|
|
4
|
+
* It exists so a refusal can always be sent. Every refusal echoes the key, so a key near
|
|
5
|
+
* `MAX_FRAME_BYTES` would make its own refusal unencodable and the named answer would degrade to
|
|
6
|
+
* silence. 200 keeps every echo-bearing detail far under the frame cap; a controller storing keys
|
|
7
|
+
* should refuse the same bound so both halves reject the same population. It lives in `core/` so
|
|
8
|
+
* the wire codec can enforce it on an inventory entry without the protocol closure reaching the
|
|
9
|
+
* workspace provider.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MAX_WORKSPACE_ID_LENGTH = 200;
|
|
12
|
+
//# sourceMappingURL=workspace-id.d.ts.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bound on a workspace id (a session key, a workspace key, a worktree's directory name).
|
|
3
|
+
*
|
|
4
|
+
* It exists so a refusal can always be sent. Every refusal echoes the key, so a key near
|
|
5
|
+
* `MAX_FRAME_BYTES` would make its own refusal unencodable and the named answer would degrade to
|
|
6
|
+
* silence. 200 keeps every echo-bearing detail far under the frame cap; a controller storing keys
|
|
7
|
+
* should refuse the same bound so both halves reject the same population. It lives in `core/` so
|
|
8
|
+
* the wire codec can enforce it on an inventory entry without the protocol closure reaching the
|
|
9
|
+
* workspace provider.
|
|
10
|
+
*/
|
|
11
|
+
export const MAX_WORKSPACE_ID_LENGTH = 200;
|
|
12
|
+
//# sourceMappingURL=workspace-id.js.map
|