@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,214 @@
|
|
|
1
|
+
import { isRefusalReason } from '../core/refusal.js';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// state — the coarse lifecycle
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
export const SESSION_STATES = [
|
|
6
|
+
/** The process is up; the agent has not reported itself. It has no id yet. */
|
|
7
|
+
'spawning',
|
|
8
|
+
/**
|
|
9
|
+
* The agent reported itself: id, model, tool inventory, version receipt all known.
|
|
10
|
+
*
|
|
11
|
+
* It arrives mid-turn, and the trace goes `working -> ready -> working`. That reads like a
|
|
12
|
+
* step backwards and it is not: it is literally what happens. Observed on a real session:
|
|
13
|
+
*
|
|
14
|
+
* 1 spawning -> spawning control/create_requested
|
|
15
|
+
* 2 spawning -> spawning control/prompt_submitted
|
|
16
|
+
* 3 spawning -> working hook/UserPromptSubmit
|
|
17
|
+
* 4 working -> ready sdk-message/system/init <- here
|
|
18
|
+
* 5 ready -> working hook/PreToolUse
|
|
19
|
+
*
|
|
20
|
+
* The agent emits nothing at all until a turn is queued, and `UserPromptSubmit` fires ahead of
|
|
21
|
+
* `init`, so the session is genuinely already working when it says what it is. Ordering this
|
|
22
|
+
* state earlier would mean recording it before the event that causes it.
|
|
23
|
+
*
|
|
24
|
+
* It is kept rather than collapsed for two reasons. It is the only row where the session's
|
|
25
|
+
* identity becomes known — collapse it and "what is this session" needs the transcript. And a
|
|
26
|
+
* session created but never prompted never reaches it: it sits in `spawning` until the start
|
|
27
|
+
* timeout, so the state being missing is what makes that failure legible instead of a hang.
|
|
28
|
+
*/
|
|
29
|
+
'ready',
|
|
30
|
+
/** A turn is in flight. `activity` says what it is blocked on. */
|
|
31
|
+
'working',
|
|
32
|
+
/** A turn ended cleanly and the session is waiting for input. */
|
|
33
|
+
'idle',
|
|
34
|
+
/** A turn ended abnormally. The cause names which way. */
|
|
35
|
+
'errored',
|
|
36
|
+
/** An interrupt landed mid-turn. Distinct from `idle`: nobody chose to stop here. */
|
|
37
|
+
'interrupted',
|
|
38
|
+
/** Over. The cause says why, and it is never inferred from silence. */
|
|
39
|
+
'ended',
|
|
40
|
+
];
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// activity — what a working session is blocked on in the foreground
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
/**
|
|
45
|
+
* Where each activity kind's name comes from. Two are the SDK's own words; four extend it.
|
|
46
|
+
*
|
|
47
|
+
* `requesting` and `compacting` are `SDKStatus` verbatim — two values and a null. The SDK has no
|
|
48
|
+
* word for the other four situations, so this package names them, and this table is where that is
|
|
49
|
+
* recorded rather than left for a reader to guess.
|
|
50
|
+
*/
|
|
51
|
+
export const ACTIVITY_KINDS = [
|
|
52
|
+
/** SDK: `SDKStatus`. A model request is in flight. */
|
|
53
|
+
'requesting',
|
|
54
|
+
/** SDK: `SDKStatus`. Context compaction is running. */
|
|
55
|
+
'compacting',
|
|
56
|
+
/** This package's own. A tool call is in flight — `name` is the SDK's `tool_name`. */
|
|
57
|
+
'tool',
|
|
58
|
+
/** This package's own. A subagent is running — `name` is the SDK's `agent_type`. */
|
|
59
|
+
'subagent',
|
|
60
|
+
/** This package's own. A permission decision is outstanding — `name` is the tool it is about. */
|
|
61
|
+
'permission',
|
|
62
|
+
/** This package's own. An MCP server is asking for input — `name` is `mcp_server_name`. */
|
|
63
|
+
'elicitation',
|
|
64
|
+
];
|
|
65
|
+
/** Which activity kinds this package named itself, kept visible the way HOST_NOUNS is. */
|
|
66
|
+
export const HOST_ACTIVITY_KINDS = ['tool', 'subagent', 'permission', 'elicitation'];
|
|
67
|
+
/** SDK: `SDKStatus`'s two non-null values, adopted verbatim. */
|
|
68
|
+
export const SDK_ACTIVITY_KINDS = ['requesting', 'compacting'];
|
|
69
|
+
/** The display form — `tool:Bash`, `requesting`. For logs and humans; never parsed back. */
|
|
70
|
+
export function formatActivity(activity) {
|
|
71
|
+
if (activity === null)
|
|
72
|
+
return 'none';
|
|
73
|
+
return activity.name === null ? activity.kind : `${activity.kind}:${activity.name}`;
|
|
74
|
+
}
|
|
75
|
+
export function sameActivity(left, right) {
|
|
76
|
+
if (left === null || right === null)
|
|
77
|
+
return left === right;
|
|
78
|
+
return left.kind === right.kind && left.name === right.name;
|
|
79
|
+
}
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// cause — required, and closed at compile time
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
/**
|
|
84
|
+
* Where a transition came from. A transition that cannot name its cause is not recorded.
|
|
85
|
+
*
|
|
86
|
+
* `refusal` is the sixth and it is load-bearing: a permission denial is a hook event, but a
|
|
87
|
+
* permission-path outage has no hook at all, and with five kinds the difference would have to ride
|
|
88
|
+
* `detail` — free text a reader greps instead of a fact code branches on. Conflating an outage with
|
|
89
|
+
* a deliberate "no" is the most expensive confusion a controller can make, so the two differ in
|
|
90
|
+
* `kind` and in `event`, twice over, neither of them prose.
|
|
91
|
+
*/
|
|
92
|
+
export const CAUSE_KINDS = ['hook', 'sdk-message', 'control', 'timeout', 'process', 'refusal'];
|
|
93
|
+
/**
|
|
94
|
+
* The SDK's hook events, mirrored as values so they can be validated at runtime.
|
|
95
|
+
*
|
|
96
|
+
* Kept in step by two checks, because a list copied out of a type rots silently: `coverage.ts`
|
|
97
|
+
* declares its table `satisfies Record<HookEvent, …>` so a new SDK event breaks the build, and
|
|
98
|
+
* `pins/hook-coverage.test.ts` parses the union straight out of the shipped `sdk.d.ts` and fails
|
|
99
|
+
* when this list disagrees with it.
|
|
100
|
+
*/
|
|
101
|
+
export const HOOK_EVENTS = [
|
|
102
|
+
'PreToolUse',
|
|
103
|
+
'PostToolUse',
|
|
104
|
+
'PostToolUseFailure',
|
|
105
|
+
'PostToolBatch',
|
|
106
|
+
'Notification',
|
|
107
|
+
'UserPromptSubmit',
|
|
108
|
+
'UserPromptExpansion',
|
|
109
|
+
'SessionStart',
|
|
110
|
+
'SessionEnd',
|
|
111
|
+
'Stop',
|
|
112
|
+
'StopFailure',
|
|
113
|
+
'SubagentStart',
|
|
114
|
+
'SubagentStop',
|
|
115
|
+
'PreCompact',
|
|
116
|
+
'PostCompact',
|
|
117
|
+
'PermissionRequest',
|
|
118
|
+
'PermissionDenied',
|
|
119
|
+
'Setup',
|
|
120
|
+
'TeammateIdle',
|
|
121
|
+
'TaskCreated',
|
|
122
|
+
'TaskCompleted',
|
|
123
|
+
'Elicitation',
|
|
124
|
+
'ElicitationResult',
|
|
125
|
+
'ConfigChange',
|
|
126
|
+
'WorktreeCreate',
|
|
127
|
+
'WorktreeRemove',
|
|
128
|
+
'InstructionsLoaded',
|
|
129
|
+
'CwdChanged',
|
|
130
|
+
'FileChanged',
|
|
131
|
+
'DirectoryAdded',
|
|
132
|
+
'MessageDisplay',
|
|
133
|
+
];
|
|
134
|
+
/**
|
|
135
|
+
* The SDK message discriminators this model names as causes — `type` for the plain kinds,
|
|
136
|
+
* `type/subtype` for the `system` family. Only the ones that can move a session appear here;
|
|
137
|
+
* `coverage.ts` accounts for all 39 members of the union, wired or declined.
|
|
138
|
+
*/
|
|
139
|
+
export const MESSAGE_EVENTS = [
|
|
140
|
+
'system/init',
|
|
141
|
+
'system/status',
|
|
142
|
+
'system/session_state_changed',
|
|
143
|
+
'system/compact_boundary',
|
|
144
|
+
'system/task_started',
|
|
145
|
+
'system/task_updated',
|
|
146
|
+
'system/task_notification',
|
|
147
|
+
'system/permission_denied',
|
|
148
|
+
'system/elicitation_complete',
|
|
149
|
+
'system/worker_shutting_down',
|
|
150
|
+
'assistant',
|
|
151
|
+
'result',
|
|
152
|
+
];
|
|
153
|
+
/**
|
|
154
|
+
* Calls the host makes on itself or is asked to make. Not SDK events — decisions.
|
|
155
|
+
*
|
|
156
|
+
* `kind` and `event` are validated independently and never as a pair. `machine.ts`'s `nameable()`
|
|
157
|
+
* checks that the kind is a declared kind and that the event is a declared event, in two separate
|
|
158
|
+
* guards — so `{kind:'hook', event:'permission_denied'}` passes while being incoherent. Pairing a
|
|
159
|
+
* cause with the right kind is the author's job, not the machine's.
|
|
160
|
+
*
|
|
161
|
+
* `permission_denied` sits here rather than in HOOK_EVENTS because a gate's deny is a call the host
|
|
162
|
+
* makes on itself: it is decided by this package, not reported to it. The SDK's `PermissionDenied`
|
|
163
|
+
* hook is a different thing that belongs to the SDK's own prompt path and does not fire for a
|
|
164
|
+
* hook-authored deny — measured — so naming it here would put an event in the trace that never
|
|
165
|
+
* happened, and would make a reader grepping traces conclude the hook fires.
|
|
166
|
+
*/
|
|
167
|
+
export const CONTROL_EVENTS = [
|
|
168
|
+
'create_requested',
|
|
169
|
+
'prompt_submitted',
|
|
170
|
+
'interrupt_requested',
|
|
171
|
+
'stop_requested',
|
|
172
|
+
'permission_denied',
|
|
173
|
+
];
|
|
174
|
+
/**
|
|
175
|
+
* The agent process's own lifecycle, as causes.
|
|
176
|
+
*
|
|
177
|
+
* These subsume `SESSION_END_CAUSES` deliberately. `sessions/session.ts` states four end causes
|
|
178
|
+
* local to the handle, and its own comment says the layer owning the declared state model maps them
|
|
179
|
+
* onto its causes. This is that layer, and this is that mapping — one cause vocabulary, not two.
|
|
180
|
+
* `state/reconciliation.test.ts` asserts the containment so it cannot drift.
|
|
181
|
+
*/
|
|
182
|
+
export const PROCESS_EVENTS = [
|
|
183
|
+
'process_started',
|
|
184
|
+
'stop_requested',
|
|
185
|
+
'process_ended',
|
|
186
|
+
'process_failed',
|
|
187
|
+
'start_timed_out',
|
|
188
|
+
];
|
|
189
|
+
export const TIMEOUT_EVENTS = ['start_timed_out', 'hook_timed_out'];
|
|
190
|
+
/**
|
|
191
|
+
* The gate's expiry, named once.
|
|
192
|
+
*
|
|
193
|
+
* One concept with two spellings — `hook_timed_out` on the transition, and `hook-timed-out` as
|
|
194
|
+
* the prefix of the deny reason handed to the model — looks exactly like a declared refusal reason
|
|
195
|
+
* (its sibling branch really is one) while resolving to nothing, so a reader who looks it up finds
|
|
196
|
+
* no such name. `satisfies` ties it to the closed vocabulary above, so the two cannot drift apart
|
|
197
|
+
* without breaking the build.
|
|
198
|
+
*/
|
|
199
|
+
export const HOOK_TIMEOUT_EVENT = 'hook_timed_out';
|
|
200
|
+
const CAUSE_EVENT_SET = new Set([
|
|
201
|
+
...HOOK_EVENTS,
|
|
202
|
+
...MESSAGE_EVENTS,
|
|
203
|
+
...CONTROL_EVENTS,
|
|
204
|
+
...PROCESS_EVENTS,
|
|
205
|
+
...TIMEOUT_EVENTS,
|
|
206
|
+
]);
|
|
207
|
+
/** The runtime half. A frame decoded from the wire was never seen by the compiler. */
|
|
208
|
+
export function isCauseEvent(value) {
|
|
209
|
+
return CAUSE_EVENT_SET.has(value) || isRefusalReason(value);
|
|
210
|
+
}
|
|
211
|
+
export function isCauseKind(value) {
|
|
212
|
+
return CAUSE_KINDS.includes(value);
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one translation site: the SDK's events become this package's transitions here and nowhere
|
|
3
|
+
* else.
|
|
4
|
+
*
|
|
5
|
+
* What is consumed and what is not is decided by `coverage.ts`, not by this file. Every hook
|
|
6
|
+
* event and every message shape has a row there saying wired or declined, with a reason. This file
|
|
7
|
+
* implements the wired half; reading it alone will not tell you what was skipped, which is exactly
|
|
8
|
+
* why the table exists separately.
|
|
9
|
+
*
|
|
10
|
+
* It translates; it does not interpret. Nothing here knows what a session is for. It knows a
|
|
11
|
+
* tool call started and stopped, that a turn ended cleanly or badly, and that a task stopped
|
|
12
|
+
* holding the session. What any of that means is a controller's question.
|
|
13
|
+
*
|
|
14
|
+
* It never throws and never decides a permission. Every handler returns an empty hook output —
|
|
15
|
+
* the decision path is a separate handler on the same event, and the SDK runs both.
|
|
16
|
+
*/
|
|
17
|
+
import type { HookInput, SDKMessage } from '../host/agent-process.js';
|
|
18
|
+
import type { Result } from '../core/result.js';
|
|
19
|
+
import type { SessionTransition, TransitionCause, TransitionWhere } from './model.js';
|
|
20
|
+
import type { EntryOp, SessionStateMachine } from './machine.js';
|
|
21
|
+
/**
|
|
22
|
+
* Translates one session's SDK events into transitions on its machine.
|
|
23
|
+
*
|
|
24
|
+
* Per session because it holds the one correlation the SDK does not give for free: a background
|
|
25
|
+
* task is reported by `task_id`, while the entry it belongs to was opened under the tool's
|
|
26
|
+
* `tool_use_id`, and only `task_started` carries both.
|
|
27
|
+
*/
|
|
28
|
+
export declare class SessionObserver {
|
|
29
|
+
#private;
|
|
30
|
+
constructor(machine: SessionStateMachine);
|
|
31
|
+
get machine(): SessionStateMachine;
|
|
32
|
+
/** The first record of a session's life: someone asked for a process. */
|
|
33
|
+
created(detail: string): Result<SessionTransition>;
|
|
34
|
+
/** A turn was queued. Distinct from `UserPromptSubmit`, which is the agent confirming it. */
|
|
35
|
+
promptSubmitted(detail: string): Result<SessionTransition>;
|
|
36
|
+
interrupted(detail: string): Result<SessionTransition>;
|
|
37
|
+
/**
|
|
38
|
+
* The process is over, however it ended.
|
|
39
|
+
*
|
|
40
|
+
* Every still-open entry is marked abandoned here, not deleted. A session that died holding a
|
|
41
|
+
* tool call is the most useful thing this model can report, and erasing the entry to tidy up
|
|
42
|
+
* would destroy precisely that. The mark carries the reason and the entry keeps its age.
|
|
43
|
+
*/
|
|
44
|
+
ended(cause: TransitionCause): Result<SessionTransition>;
|
|
45
|
+
/** A named condition the host itself produced — a decision path that failed rather than denied. */
|
|
46
|
+
refused(cause: TransitionCause): Result<SessionTransition>;
|
|
47
|
+
/** The working directory moved under the session, so every later transition must carry the new one. */
|
|
48
|
+
relocated(where: TransitionWhere, cause: TransitionCause): Result<SessionTransition>;
|
|
49
|
+
/** Every message the agent emits. Returns what it recorded — often nothing, by the table. */
|
|
50
|
+
observeMessage(message: SDKMessage): Result<SessionTransition>[];
|
|
51
|
+
/** One hook firing. Returns what it recorded, so a caller can assert on it. */
|
|
52
|
+
observeHook(input: HookInput): Result<SessionTransition>[];
|
|
53
|
+
}
|
|
54
|
+
export type { EntryOp };
|
|
55
|
+
//# sourceMappingURL=observer.d.ts.map
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { discriminatorOf } from '../host/agent-process.js';
|
|
2
|
+
/**
|
|
3
|
+
* How this observer keys a permission entry. `PermissionRequest` carries no tool_use_id — see below.
|
|
4
|
+
*
|
|
5
|
+
* The name key belongs to this file's own lane — `PermissionRequest` opens it,
|
|
6
|
+
* `PermissionDenied` or the `PreToolUse` close below exits it — and to nobody else. The gate keys
|
|
7
|
+
* its hold entry by tool_use_id (`gate/outcome.ts`) precisely so the close below can never touch a
|
|
8
|
+
* hold whose decision is still outstanding: a second `PreToolUse` for the same tool would
|
|
9
|
+
* otherwise close the first call's live hold and record a resolution that never happened.
|
|
10
|
+
*/
|
|
11
|
+
const permissionEntry = (toolName) => `permission:${toolName}`;
|
|
12
|
+
/** One compaction at a time per session, from either of the two sources that report it. */
|
|
13
|
+
const COMPACTION_ENTRY = 'compaction';
|
|
14
|
+
/** The model-request lane, driven by SDKStatus. */
|
|
15
|
+
const REQUEST_ENTRY = 'request';
|
|
16
|
+
/**
|
|
17
|
+
* Translates one session's SDK events into transitions on its machine.
|
|
18
|
+
*
|
|
19
|
+
* Per session because it holds the one correlation the SDK does not give for free: a background
|
|
20
|
+
* task is reported by `task_id`, while the entry it belongs to was opened under the tool's
|
|
21
|
+
* `tool_use_id`, and only `task_started` carries both.
|
|
22
|
+
*/
|
|
23
|
+
export class SessionObserver {
|
|
24
|
+
#machine;
|
|
25
|
+
/** task_id -> the entryId that task's work is recorded under. */
|
|
26
|
+
#taskEntries = new Map();
|
|
27
|
+
constructor(machine) {
|
|
28
|
+
this.#machine = machine;
|
|
29
|
+
}
|
|
30
|
+
get machine() {
|
|
31
|
+
return this.#machine;
|
|
32
|
+
}
|
|
33
|
+
/** The first record of a session's life: someone asked for a process. */
|
|
34
|
+
created(detail) {
|
|
35
|
+
return this.#machine.record({
|
|
36
|
+
to: 'spawning',
|
|
37
|
+
cause: { kind: 'control', event: 'create_requested', detail },
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/** A turn was queued. Distinct from `UserPromptSubmit`, which is the agent confirming it. */
|
|
41
|
+
promptSubmitted(detail) {
|
|
42
|
+
return this.#machine.record({
|
|
43
|
+
to: this.#machine.state === 'spawning' ? 'spawning' : 'working',
|
|
44
|
+
cause: { kind: 'control', event: 'prompt_submitted', detail },
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
interrupted(detail) {
|
|
48
|
+
return this.#machine.record({
|
|
49
|
+
to: 'interrupted',
|
|
50
|
+
cause: { kind: 'control', event: 'interrupt_requested', detail },
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The process is over, however it ended.
|
|
55
|
+
*
|
|
56
|
+
* Every still-open entry is marked abandoned here, not deleted. A session that died holding a
|
|
57
|
+
* tool call is the most useful thing this model can report, and erasing the entry to tidy up
|
|
58
|
+
* would destroy precisely that. The mark carries the reason and the entry keeps its age.
|
|
59
|
+
*/
|
|
60
|
+
ended(cause) {
|
|
61
|
+
return this.#machine.record({
|
|
62
|
+
to: 'ended',
|
|
63
|
+
cause,
|
|
64
|
+
entry: { op: 'abandon-open', reason: `the session ended (${cause.event}) with this entry still open` },
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/** A named condition the host itself produced — a decision path that failed rather than denied. */
|
|
68
|
+
refused(cause) {
|
|
69
|
+
return this.#machine.record({ to: this.#machine.state, cause });
|
|
70
|
+
}
|
|
71
|
+
/** The working directory moved under the session, so every later transition must carry the new one. */
|
|
72
|
+
relocated(where, cause) {
|
|
73
|
+
return this.#machine.record({ to: this.#machine.state, cause, where });
|
|
74
|
+
}
|
|
75
|
+
// -------------------------------------------------------------------------
|
|
76
|
+
// The SDK message stream
|
|
77
|
+
// -------------------------------------------------------------------------
|
|
78
|
+
/** Every message the agent emits. Returns what it recorded — often nothing, by the table. */
|
|
79
|
+
observeMessage(message) {
|
|
80
|
+
return this.#requestsFor(message).map((request) => this.#machine.record(request));
|
|
81
|
+
}
|
|
82
|
+
#requestsFor(message) {
|
|
83
|
+
const event = discriminatorOf(message);
|
|
84
|
+
const cause = (detail) => ({
|
|
85
|
+
kind: 'sdk-message',
|
|
86
|
+
event: event,
|
|
87
|
+
detail,
|
|
88
|
+
});
|
|
89
|
+
if (message.type === 'system' && message.subtype === 'init') {
|
|
90
|
+
return [
|
|
91
|
+
{
|
|
92
|
+
to: 'ready',
|
|
93
|
+
sessionId: message.session_id,
|
|
94
|
+
cause: cause(`the agent reported itself: ${message.model} on CLI ${message.claude_code_version}`),
|
|
95
|
+
},
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
if (message.type === 'system' && message.subtype === 'status') {
|
|
99
|
+
return statusRequests(message.status, cause);
|
|
100
|
+
}
|
|
101
|
+
if (message.type === 'system' && message.subtype === 'session_state_changed') {
|
|
102
|
+
// `requires_action` maps to `working`, not to a state of its own: what it requires is
|
|
103
|
+
// already carried by the open permission or elicitation entry, and a second representation
|
|
104
|
+
// of one fact is how two vocabularies start.
|
|
105
|
+
const to = message.state === 'idle' ? 'idle' : 'working';
|
|
106
|
+
return [{ to, cause: cause(`the agent reported session state ${message.state}`) }];
|
|
107
|
+
}
|
|
108
|
+
if (message.type === 'system' && message.subtype === 'compact_boundary') {
|
|
109
|
+
return [
|
|
110
|
+
{
|
|
111
|
+
to: this.#machine.state,
|
|
112
|
+
entry: { op: 'close', entryId: COMPACTION_ENTRY },
|
|
113
|
+
cause: cause(`compaction completed (${message.compact_metadata.trigger})`),
|
|
114
|
+
},
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
if (message.type === 'system' && message.subtype === 'task_started') {
|
|
118
|
+
// No transition of its own — this is where the join is learned. The entry itself was opened
|
|
119
|
+
// by PreToolUse under the tool_use_id, which is the id every later report has to resolve to.
|
|
120
|
+
const entryId = message.tool_use_id ?? `task:${message.task_id}`;
|
|
121
|
+
this.#taskEntries.set(message.task_id, entryId);
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
if (message.type === 'system' && message.subtype === 'task_updated') {
|
|
125
|
+
return this.#taskUpdateRequests(message.task_id, message.patch, cause);
|
|
126
|
+
}
|
|
127
|
+
if (message.type === 'system' && message.subtype === 'task_notification') {
|
|
128
|
+
const entryId = message.tool_use_id ?? this.#taskEntries.get(message.task_id);
|
|
129
|
+
if (entryId === undefined)
|
|
130
|
+
return [];
|
|
131
|
+
this.#taskEntries.delete(message.task_id);
|
|
132
|
+
return [
|
|
133
|
+
{
|
|
134
|
+
to: this.#machine.state,
|
|
135
|
+
entry: { op: 'close', entryId },
|
|
136
|
+
cause: cause(`background task ${message.task_id} ${message.status}`),
|
|
137
|
+
},
|
|
138
|
+
];
|
|
139
|
+
}
|
|
140
|
+
if (message.type === 'system' && message.subtype === 'worker_shutting_down') {
|
|
141
|
+
return [{ to: this.#machine.state, cause: cause(`the worker is shutting down: ${message.reason}`) }];
|
|
142
|
+
}
|
|
143
|
+
if (message.type === 'system' && message.subtype === 'model_refusal_no_fallback') {
|
|
144
|
+
return [
|
|
145
|
+
{
|
|
146
|
+
to: this.#machine.state,
|
|
147
|
+
cause: cause(`the model refused and no fallback ran (${message.original_model})`),
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
}
|
|
151
|
+
if (message.type === 'result') {
|
|
152
|
+
const clean = message.subtype === 'success';
|
|
153
|
+
const why = clean
|
|
154
|
+
? `the turn completed in ${message.duration_ms}ms`
|
|
155
|
+
: `the turn ended ${message.subtype}${'terminal_reason' in message && message.terminal_reason !== undefined ? ` (${message.terminal_reason})` : ''}`;
|
|
156
|
+
return [{ to: clean ? 'idle' : 'errored', cause: cause(why) }];
|
|
157
|
+
}
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
#taskUpdateRequests(taskId, patch, cause) {
|
|
161
|
+
const entryId = this.#taskEntries.get(taskId);
|
|
162
|
+
if (entryId === undefined)
|
|
163
|
+
return [];
|
|
164
|
+
// The caused moment. A task stops holding the session exactly here, because the agent said
|
|
165
|
+
// so — never on a timer, never because an entry looked old. Backgrounding is not completion:
|
|
166
|
+
// the entry stays open, keeps ageing, and still gets its exit from task_notification.
|
|
167
|
+
if (patch.is_backgrounded === true) {
|
|
168
|
+
return [
|
|
169
|
+
{
|
|
170
|
+
to: 'idle',
|
|
171
|
+
entry: { op: 'background', entryId },
|
|
172
|
+
cause: cause(`task ${taskId} was backgrounded and no longer holds the session`),
|
|
173
|
+
},
|
|
174
|
+
];
|
|
175
|
+
}
|
|
176
|
+
if (patch.status === 'completed' || patch.status === 'failed' || patch.status === 'killed') {
|
|
177
|
+
this.#taskEntries.delete(taskId);
|
|
178
|
+
return [
|
|
179
|
+
{
|
|
180
|
+
to: this.#machine.state,
|
|
181
|
+
entry: { op: 'close', entryId },
|
|
182
|
+
cause: cause(`task ${taskId} ${patch.status}`),
|
|
183
|
+
},
|
|
184
|
+
];
|
|
185
|
+
}
|
|
186
|
+
return [];
|
|
187
|
+
}
|
|
188
|
+
// -------------------------------------------------------------------------
|
|
189
|
+
// The hook lane
|
|
190
|
+
// -------------------------------------------------------------------------
|
|
191
|
+
/** One hook firing. Returns what it recorded, so a caller can assert on it. */
|
|
192
|
+
observeHook(input) {
|
|
193
|
+
return this.#hookRequests(input).map((request) => this.#machine.record(request));
|
|
194
|
+
}
|
|
195
|
+
#hookRequests(input) {
|
|
196
|
+
const cause = (detail) => ({
|
|
197
|
+
kind: 'hook',
|
|
198
|
+
event: input.hook_event_name,
|
|
199
|
+
detail,
|
|
200
|
+
});
|
|
201
|
+
switch (input.hook_event_name) {
|
|
202
|
+
case 'PreToolUse': {
|
|
203
|
+
// Two records, in order: the tool running means any outstanding permission for it
|
|
204
|
+
// resolved, and that resolution is the permission entry's exit on the allow path.
|
|
205
|
+
const requests = [];
|
|
206
|
+
const held = permissionEntry(input.tool_name);
|
|
207
|
+
if (this.#isOpen(held)) {
|
|
208
|
+
requests.push({
|
|
209
|
+
to: 'working',
|
|
210
|
+
entry: { op: 'close', entryId: held },
|
|
211
|
+
cause: cause(`the permission for ${input.tool_name} resolved and the tool is running`),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
requests.push({
|
|
215
|
+
to: 'working',
|
|
216
|
+
entry: {
|
|
217
|
+
op: 'open',
|
|
218
|
+
entryId: input.tool_use_id,
|
|
219
|
+
activity: { kind: 'tool', name: input.tool_name },
|
|
220
|
+
agentId: input.agent_id ?? null,
|
|
221
|
+
},
|
|
222
|
+
cause: cause(`${input.tool_name} started`),
|
|
223
|
+
});
|
|
224
|
+
return requests;
|
|
225
|
+
}
|
|
226
|
+
case 'PostToolUse':
|
|
227
|
+
return [
|
|
228
|
+
{
|
|
229
|
+
to: 'working',
|
|
230
|
+
entry: { op: 'close', entryId: input.tool_use_id },
|
|
231
|
+
cause: cause(`${input.tool_name} finished`),
|
|
232
|
+
},
|
|
233
|
+
];
|
|
234
|
+
case 'PostToolUseFailure':
|
|
235
|
+
return [
|
|
236
|
+
{
|
|
237
|
+
to: 'working',
|
|
238
|
+
entry: { op: 'close', entryId: input.tool_use_id },
|
|
239
|
+
cause: cause(`${input.tool_name} failed: ${input.error}`),
|
|
240
|
+
},
|
|
241
|
+
];
|
|
242
|
+
case 'PostToolBatch':
|
|
243
|
+
// The backstop. Closes only what is still open — emitting a close for an entry that
|
|
244
|
+
// already exited would put an event in the trace that never happened.
|
|
245
|
+
return input.tool_calls
|
|
246
|
+
.filter((call) => this.#isOpen(call.tool_use_id))
|
|
247
|
+
.map((call) => ({
|
|
248
|
+
to: 'working',
|
|
249
|
+
entry: { op: 'close', entryId: call.tool_use_id },
|
|
250
|
+
cause: cause(`${call.tool_name} closed by the end of its batch`),
|
|
251
|
+
}));
|
|
252
|
+
case 'UserPromptSubmit':
|
|
253
|
+
return [{ to: 'working', cause: cause('a turn was submitted') }];
|
|
254
|
+
case 'Stop':
|
|
255
|
+
return [
|
|
256
|
+
{
|
|
257
|
+
to: 'idle',
|
|
258
|
+
cause: cause(`the turn ended cleanly${input.background_tasks !== undefined && input.background_tasks.length > 0
|
|
259
|
+
? ` with ${input.background_tasks.length} background task(s) still in flight`
|
|
260
|
+
: ''}`),
|
|
261
|
+
},
|
|
262
|
+
];
|
|
263
|
+
case 'StopFailure':
|
|
264
|
+
return [{ to: 'errored', cause: cause(`the turn ended abnormally: ${input.error}`) }];
|
|
265
|
+
case 'SessionStart':
|
|
266
|
+
return [{ to: this.#machine.state, cause: cause(`the session started (${input.source})`) }];
|
|
267
|
+
case 'SessionEnd':
|
|
268
|
+
return [
|
|
269
|
+
{
|
|
270
|
+
to: 'ended',
|
|
271
|
+
entry: {
|
|
272
|
+
op: 'abandon-open',
|
|
273
|
+
reason: `the session ended (${input.reason}) with this entry still open`,
|
|
274
|
+
},
|
|
275
|
+
cause: cause(`the session ended: ${input.reason}`),
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
case 'CwdChanged':
|
|
279
|
+
return [
|
|
280
|
+
{
|
|
281
|
+
to: this.#machine.state,
|
|
282
|
+
where: { ...this.#machine.where, cwd: input.new_cwd },
|
|
283
|
+
cause: cause(`the working directory moved from ${input.old_cwd}`),
|
|
284
|
+
},
|
|
285
|
+
];
|
|
286
|
+
case 'SubagentStart':
|
|
287
|
+
return [
|
|
288
|
+
{
|
|
289
|
+
to: 'working',
|
|
290
|
+
entry: {
|
|
291
|
+
op: 'open',
|
|
292
|
+
entryId: input.agent_id,
|
|
293
|
+
activity: { kind: 'subagent', name: input.agent_type },
|
|
294
|
+
agentId: input.agent_id,
|
|
295
|
+
},
|
|
296
|
+
cause: cause(`subagent ${input.agent_type} started`),
|
|
297
|
+
},
|
|
298
|
+
];
|
|
299
|
+
case 'SubagentStop':
|
|
300
|
+
return [
|
|
301
|
+
{
|
|
302
|
+
to: 'working',
|
|
303
|
+
entry: { op: 'close', entryId: input.agent_id },
|
|
304
|
+
cause: cause(`subagent ${input.agent_type} stopped`),
|
|
305
|
+
},
|
|
306
|
+
];
|
|
307
|
+
case 'PreCompact':
|
|
308
|
+
return [
|
|
309
|
+
{
|
|
310
|
+
to: 'working',
|
|
311
|
+
entry: { op: 'open', entryId: COMPACTION_ENTRY, activity: { kind: 'compacting', name: null } },
|
|
312
|
+
cause: cause(`compaction started (${input.trigger})`),
|
|
313
|
+
},
|
|
314
|
+
];
|
|
315
|
+
case 'PostCompact':
|
|
316
|
+
return [
|
|
317
|
+
{
|
|
318
|
+
to: 'working',
|
|
319
|
+
entry: { op: 'close', entryId: COMPACTION_ENTRY },
|
|
320
|
+
cause: cause(`compaction finished (${input.trigger})`),
|
|
321
|
+
},
|
|
322
|
+
];
|
|
323
|
+
case 'PermissionRequest':
|
|
324
|
+
// Keyed by tool name, because `PermissionRequestHookInput` carries no tool_use_id while
|
|
325
|
+
// `PermissionDeniedHookInput` does — there is no shared id to join on. Two simultaneous
|
|
326
|
+
// requests for the same tool would therefore share one entry; the exit still fires and the
|
|
327
|
+
// trace stays truthful, but the pair is not distinguishable. Stated rather than hidden.
|
|
328
|
+
return [
|
|
329
|
+
{
|
|
330
|
+
to: 'working',
|
|
331
|
+
entry: {
|
|
332
|
+
op: 'open',
|
|
333
|
+
entryId: permissionEntry(input.tool_name),
|
|
334
|
+
activity: { kind: 'permission', name: input.tool_name },
|
|
335
|
+
agentId: input.agent_id ?? null,
|
|
336
|
+
},
|
|
337
|
+
cause: cause(`a permission decision for ${input.tool_name} is outstanding`),
|
|
338
|
+
},
|
|
339
|
+
];
|
|
340
|
+
case 'PermissionDenied':
|
|
341
|
+
// A denial. An outage in the same path arrives as cause kind `refusal` with a refusal
|
|
342
|
+
// reason — two independent discriminators apart, because an infrastructure failure
|
|
343
|
+
// wearing a denial's clothes impersonates a human "no", and that is the most expensive
|
|
344
|
+
// confusion a controller can make.
|
|
345
|
+
return [
|
|
346
|
+
{
|
|
347
|
+
to: 'working',
|
|
348
|
+
entry: { op: 'close', entryId: permissionEntry(input.tool_name) },
|
|
349
|
+
cause: cause(`${input.tool_name} was denied: ${input.reason}`),
|
|
350
|
+
},
|
|
351
|
+
];
|
|
352
|
+
case 'Elicitation':
|
|
353
|
+
return [
|
|
354
|
+
{
|
|
355
|
+
to: 'working',
|
|
356
|
+
entry: {
|
|
357
|
+
op: 'open',
|
|
358
|
+
entryId: elicitationEntry(input.elicitation_id, input.mcp_server_name),
|
|
359
|
+
activity: { kind: 'elicitation', name: input.mcp_server_name },
|
|
360
|
+
},
|
|
361
|
+
cause: cause(`${input.mcp_server_name} asked for input`),
|
|
362
|
+
},
|
|
363
|
+
];
|
|
364
|
+
case 'ElicitationResult':
|
|
365
|
+
return [
|
|
366
|
+
{
|
|
367
|
+
to: 'working',
|
|
368
|
+
entry: { op: 'close', entryId: elicitationEntry(input.elicitation_id, input.mcp_server_name) },
|
|
369
|
+
cause: cause(`${input.mcp_server_name} elicitation ${input.action}`),
|
|
370
|
+
},
|
|
371
|
+
];
|
|
372
|
+
default:
|
|
373
|
+
// Declined by coverage.ts, every one of them with a stated reason. This is not a silent
|
|
374
|
+
// default: the table is the map, and an event absent from it is the gap.
|
|
375
|
+
return [];
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
#isOpen(entryId) {
|
|
379
|
+
return this.#machine.openEntries().some((entry) => entry.entryId === entryId);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
const elicitationEntry = (elicitationId, server) => `elicitation:${elicitationId ?? server}`;
|
|
383
|
+
/**
|
|
384
|
+
* `SDKStatus` -> the request/compaction lanes. Null is the exit, not an absence of information.
|
|
385
|
+
*/
|
|
386
|
+
function statusRequests(status, cause) {
|
|
387
|
+
if (status === 'requesting') {
|
|
388
|
+
return [
|
|
389
|
+
{
|
|
390
|
+
to: 'working',
|
|
391
|
+
entry: { op: 'open', entryId: REQUEST_ENTRY, activity: { kind: 'requesting', name: null } },
|
|
392
|
+
cause: cause('a model request is in flight'),
|
|
393
|
+
},
|
|
394
|
+
];
|
|
395
|
+
}
|
|
396
|
+
if (status === 'compacting') {
|
|
397
|
+
return [
|
|
398
|
+
{
|
|
399
|
+
to: 'working',
|
|
400
|
+
entry: { op: 'open', entryId: COMPACTION_ENTRY, activity: { kind: 'compacting', name: null } },
|
|
401
|
+
cause: cause('compaction is in flight'),
|
|
402
|
+
},
|
|
403
|
+
];
|
|
404
|
+
}
|
|
405
|
+
return [
|
|
406
|
+
{
|
|
407
|
+
to: 'working',
|
|
408
|
+
entry: { op: 'close', entryId: REQUEST_ENTRY },
|
|
409
|
+
cause: cause('the model request finished'),
|
|
410
|
+
},
|
|
411
|
+
];
|
|
412
|
+
}
|
|
413
|
+
//# sourceMappingURL=observer.js.map
|