@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,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* All 31 hook events. 18 wired, 13 declined.
|
|
3
|
+
*
|
|
4
|
+
* `PreToolUse` appears here as an observation only. The permission decision is a separate
|
|
5
|
+
* concern with a separate handler; `HookCallbackMatcher.hooks` is an array and the SDK runs every
|
|
6
|
+
* entry, so a decision handler registers alongside this one without either editing the other.
|
|
7
|
+
*/
|
|
8
|
+
export const HOOK_COVERAGE = {
|
|
9
|
+
// --- wired: the tool lane -------------------------------------------------
|
|
10
|
+
PreToolUse: {
|
|
11
|
+
handling: 'wired',
|
|
12
|
+
note: 'opens a `tool` entry keyed by tool_use_id; state -> working. Observation only — the permission decision is a separate handler on the same event.',
|
|
13
|
+
},
|
|
14
|
+
PostToolUse: {
|
|
15
|
+
handling: 'wired',
|
|
16
|
+
note: 'closes the `tool` entry for its tool_use_id. The ordinary exit.',
|
|
17
|
+
},
|
|
18
|
+
PostToolUseFailure: {
|
|
19
|
+
handling: 'wired',
|
|
20
|
+
note: 'closes the `tool` entry naming the failure — the failure exit, distinct from the success one because "it stopped" and "it broke" are different answers.',
|
|
21
|
+
},
|
|
22
|
+
PostToolBatch: {
|
|
23
|
+
handling: 'wired',
|
|
24
|
+
note: "fires exactly once after every call in a batch resolves, so it closes any tool entry still open. The backstop for a PostToolUse that never arrived — measured on two live sessions, including an all-denied batch: it fires on a hook-authored deny and its tool_calls includes the denied tool_use_id, so a denied call's entry does not leak. It closes the entry at end of batch and says only that; the denial itself is a separate transition the gate emits.",
|
|
25
|
+
},
|
|
26
|
+
// --- wired: the turn lane -------------------------------------------------
|
|
27
|
+
UserPromptSubmit: {
|
|
28
|
+
handling: 'wired',
|
|
29
|
+
note: 'state -> working. The turn boundary opening; the counterpart to Stop.',
|
|
30
|
+
},
|
|
31
|
+
Stop: {
|
|
32
|
+
handling: 'wired',
|
|
33
|
+
note: 'state -> idle. The clean turn end, recorded for every session with no exemption — a turn boundary that is missing for any class of session leaves every reader inferring.',
|
|
34
|
+
},
|
|
35
|
+
StopFailure: {
|
|
36
|
+
handling: 'wired',
|
|
37
|
+
note: 'state -> errored, carrying the SDKAssistantMessageError. Never collapsed into Stop: a turn that broke is not a turn that finished.',
|
|
38
|
+
},
|
|
39
|
+
// --- wired: session lifecycle --------------------------------------------
|
|
40
|
+
SessionStart: {
|
|
41
|
+
handling: 'wired',
|
|
42
|
+
note: 'records how the session began (startup | resume | clear | compact | fork). No state change — it names a why the trace would otherwise have to infer. Measured not to fire on an SDK-hosted start: a real session running every wired hook saw PreToolUse, PostToolUse, PostToolBatch, UserPromptSubmit and Stop, and no SessionStart. The likely reason is ordering — `options.hooks` reach the CLI after it has already started — but that was not isolated. Wired and not observed, which is a different row from declined.',
|
|
43
|
+
},
|
|
44
|
+
SessionEnd: {
|
|
45
|
+
handling: 'wired',
|
|
46
|
+
note: 'state -> ended, carrying the ExitReason. Never inferred from silence.',
|
|
47
|
+
},
|
|
48
|
+
CwdChanged: {
|
|
49
|
+
handling: 'wired',
|
|
50
|
+
note: 'updates `where.cwd` for every later transition. Declining it would leave every subsequent record carrying a stale directory — a trace that lies quietly.',
|
|
51
|
+
},
|
|
52
|
+
// --- wired: subagents -----------------------------------------------------
|
|
53
|
+
SubagentStart: {
|
|
54
|
+
handling: 'wired',
|
|
55
|
+
note: 'opens a `subagent` entry keyed by agent_id, carrying agent_type.',
|
|
56
|
+
},
|
|
57
|
+
SubagentStop: {
|
|
58
|
+
handling: 'wired',
|
|
59
|
+
note: 'closes the `subagent` entry for its agent_id.',
|
|
60
|
+
},
|
|
61
|
+
// --- wired: compaction ----------------------------------------------------
|
|
62
|
+
PreCompact: {
|
|
63
|
+
handling: 'wired',
|
|
64
|
+
note: 'opens a `compacting` entry, carrying trigger (manual | auto).',
|
|
65
|
+
},
|
|
66
|
+
PostCompact: {
|
|
67
|
+
handling: 'wired',
|
|
68
|
+
note: 'closes the `compacting` entry.',
|
|
69
|
+
},
|
|
70
|
+
// --- wired: the permission lane -------------------------------------------
|
|
71
|
+
PermissionRequest: {
|
|
72
|
+
handling: 'wired',
|
|
73
|
+
note: "opens a `permission` entry for the tool. Measured not to fire for a hook-authored decision — `gate/outcome.ts` records the measurement — so the held-visibility this model buys is delivered by the gate's own hold entry (keyed by tool_use_id), not by this hook. Do not wait on this hook for entries the gate opens elsewhere; re-measure before relying on it for any other decision path.",
|
|
74
|
+
},
|
|
75
|
+
PermissionDenied: {
|
|
76
|
+
handling: 'wired',
|
|
77
|
+
note: 'closes the `permission` entry, cause kind `hook`, carrying the deny reason. A denial and a permission-path outage must not read alike: an outage arrives as cause kind `refusal` with a refusal reason, so the two differ in kind and event. Measured not to fire for a hook-authored deny: a real session whose PreToolUse returned `permissionDecision: deny` blocked the tool (no PostToolUse) but emitted no PermissionDenied — so this hook appears to belong to a decision path other than the hook lane. Whoever builds the decision path must not rely on it as the deny receipt without re-measuring.',
|
|
78
|
+
},
|
|
79
|
+
// --- wired: elicitation ---------------------------------------------------
|
|
80
|
+
Elicitation: {
|
|
81
|
+
handling: 'wired',
|
|
82
|
+
note: 'opens an `elicitation` entry for the MCP server. The session is genuinely blocked on an answer.',
|
|
83
|
+
},
|
|
84
|
+
ElicitationResult: {
|
|
85
|
+
handling: 'wired',
|
|
86
|
+
note: 'closes the `elicitation` entry, carrying the action (accept | decline | cancel).',
|
|
87
|
+
},
|
|
88
|
+
// --- declined -------------------------------------------------------------
|
|
89
|
+
Notification: {
|
|
90
|
+
handling: 'declined',
|
|
91
|
+
note: 'a display notification (message, title, notification_type). Nothing about it moves the session; it is content, and content belongs to the streaming layer.',
|
|
92
|
+
},
|
|
93
|
+
UserPromptExpansion: {
|
|
94
|
+
handling: 'declined',
|
|
95
|
+
note: 'a slash-command or MCP-prompt expansion of a prompt already submitted. The turn boundary was UserPromptSubmit; recording this too would add a transition carrying no new fact.',
|
|
96
|
+
},
|
|
97
|
+
Setup: {
|
|
98
|
+
handling: 'declined',
|
|
99
|
+
note: 'environment housekeeping (trigger: init | maintenance). It runs beside the session rather than in it, and produces no condition the session can be blocked on.',
|
|
100
|
+
},
|
|
101
|
+
TeammateIdle: {
|
|
102
|
+
handling: 'declined',
|
|
103
|
+
note: "reports that another session is idle. Recording it here would attribute a different session's state to this one — precisely the cross-session aggregation that belongs to the controller, not a host.",
|
|
104
|
+
},
|
|
105
|
+
TaskCreated: {
|
|
106
|
+
handling: 'declined',
|
|
107
|
+
note: 'the task-list surface (task_subject, teammate_name). Its overlap with the task_started/task_updated message lane has not been resolved against a live session, and wiring both would double-count one entry. Declined on the unresolved overlap, not on irrelevance.',
|
|
108
|
+
},
|
|
109
|
+
TaskCompleted: {
|
|
110
|
+
handling: 'declined',
|
|
111
|
+
note: 'the closing half of TaskCreated, declined for the same unresolved overlap. system/task_notification is the completion signal that is wired.',
|
|
112
|
+
},
|
|
113
|
+
ConfigChange: {
|
|
114
|
+
handling: 'declined',
|
|
115
|
+
note: "settings or skills changed on disk. It changes what the session can do, not what it is doing — a capability fact, and capability is the workspace layer's.",
|
|
116
|
+
},
|
|
117
|
+
InstructionsLoaded: {
|
|
118
|
+
handling: 'declined',
|
|
119
|
+
note: 'memory/CLAUDE.md loading. Context composition, which is what a turn is made of rather than a condition it is in.',
|
|
120
|
+
},
|
|
121
|
+
WorktreeCreate: {
|
|
122
|
+
handling: 'declined',
|
|
123
|
+
note: 'the agent created a worktree. It does not move the session — `where` still describes where the session runs. Relevant to whichever layer provisions workspaces.',
|
|
124
|
+
},
|
|
125
|
+
WorktreeRemove: {
|
|
126
|
+
handling: 'declined',
|
|
127
|
+
note: 'as WorktreeCreate. Note that removing the worktree a session is in would be visible through CwdChanged, which is wired.',
|
|
128
|
+
},
|
|
129
|
+
DirectoryAdded: {
|
|
130
|
+
handling: 'declined',
|
|
131
|
+
note: 'widens the set of directories the session may touch. A permission-surface fact, not a state one; it belongs with whatever enforces the path boundary.',
|
|
132
|
+
},
|
|
133
|
+
FileChanged: {
|
|
134
|
+
handling: 'declined',
|
|
135
|
+
note: 'a watched file changed (change | add | unlink). High volume, driven by the filesystem rather than by the session, and it holds nothing.',
|
|
136
|
+
},
|
|
137
|
+
MessageDisplay: {
|
|
138
|
+
handling: 'declined',
|
|
139
|
+
note: "one flush of an assistant message, indexed per delta. The highest-volume event in the set and pure content — the streaming layer's lane, not the state model's.",
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* All 39 members of the SDKMessage union, keyed by discriminator.
|
|
144
|
+
*
|
|
145
|
+
* Fewer keys than members: several shapes share one discriminator (`user` covers the ordinary and
|
|
146
|
+
* the replayed user message; `result` covers success and every error subtype). The pin walks the
|
|
147
|
+
* union member by member and checks each one's discriminator has a row, so the many-to-one is
|
|
148
|
+
* accounted for rather than hidden by the shorter key list.
|
|
149
|
+
*/
|
|
150
|
+
export const MESSAGE_COVERAGE = {
|
|
151
|
+
// --- wired ----------------------------------------------------------------
|
|
152
|
+
'system/init': {
|
|
153
|
+
handling: 'wired',
|
|
154
|
+
note: 'state -> ready, and the only place the session id, CLI version receipt, model, tool/skill/plugin inventory and apiKeySource arrive. Everything downstream keys off the id it carries.',
|
|
155
|
+
},
|
|
156
|
+
'system/status': {
|
|
157
|
+
handling: 'wired',
|
|
158
|
+
note: "SDKStatus — sets activity `requesting` or `compacting`, and closes it on null. The SDK's own word for what a session is doing, adopted verbatim.",
|
|
159
|
+
},
|
|
160
|
+
'system/session_state_changed': {
|
|
161
|
+
handling: 'wired',
|
|
162
|
+
note: 'documented as the authoritative turn-over signal — measured not to fire: never observed on any of the 3 real turns measured; `Stop` and `result` are the observed turn boundaries. When it does arrive: idle -> state idle, running -> state working. `requires_action` deliberately does not set state: what the session requires is already carried by the open permission or elicitation entry, and a second representation would be a second vocabulary.',
|
|
163
|
+
},
|
|
164
|
+
'system/compact_boundary': {
|
|
165
|
+
handling: 'wired',
|
|
166
|
+
note: 'compaction actually happened, with trigger and token counts. Closes the compacting entry as a backstop for a PostCompact that never arrived.',
|
|
167
|
+
},
|
|
168
|
+
'system/task_started': {
|
|
169
|
+
handling: 'wired',
|
|
170
|
+
note: 'no transition of its own — it supplies the task_id -> tool_use_id join, without which task_updated could not name the entry it backgrounds. Wired as correlation, and saying so is the point of this column.',
|
|
171
|
+
},
|
|
172
|
+
'system/task_updated': {
|
|
173
|
+
handling: 'wired',
|
|
174
|
+
note: '`patch.is_backgrounded` is the caused moment a task stops holding the session — the entry moves to the background lane there, never on a timer and never by inference. `patch.status` in completed | failed | killed closes it.',
|
|
175
|
+
},
|
|
176
|
+
'system/task_notification': {
|
|
177
|
+
handling: 'wired',
|
|
178
|
+
note: 'a background task finished (completed | failed | stopped) carrying tool_use_id. The exit for backgrounded work: backgrounding is not completion, so the entry stays open and ages until this arrives.',
|
|
179
|
+
},
|
|
180
|
+
'system/worker_shutting_down': {
|
|
181
|
+
handling: 'wired',
|
|
182
|
+
note: 'a named reason for a teardown that would otherwise present as the process simply ending. Its own type warns that absence is not a dead-host signal — handoffs and fatal paths emit nothing — so it is a why when present, never a liveness check.',
|
|
183
|
+
},
|
|
184
|
+
'system/model_refusal_no_fallback': {
|
|
185
|
+
handling: 'wired',
|
|
186
|
+
note: 'the turn ends with no retry. Records the why; the state change itself rides result/StopFailure, so the turn end is not recorded twice.',
|
|
187
|
+
},
|
|
188
|
+
result: {
|
|
189
|
+
handling: 'wired',
|
|
190
|
+
note: 'subtype success -> idle (the backstop for a missed Stop); every error subtype -> errored, carrying terminal_reason. This is where a turn that ended without any hook firing still gets a boundary.',
|
|
191
|
+
},
|
|
192
|
+
// --- declined -------------------------------------------------------------
|
|
193
|
+
assistant: {
|
|
194
|
+
handling: 'declined',
|
|
195
|
+
note: 'model output. Content, owned by the streaming layer; the state it implies is already carried by system/status and system/session_state_changed.',
|
|
196
|
+
},
|
|
197
|
+
user: {
|
|
198
|
+
handling: 'declined',
|
|
199
|
+
note: 'the turn input, including the replayed echo. UserPromptSubmit is the wired boundary; this is the payload that crossed it.',
|
|
200
|
+
},
|
|
201
|
+
stream_event: {
|
|
202
|
+
handling: 'declined',
|
|
203
|
+
note: "partial assistant deltas. The highest-volume message in the union and pure content — the streaming layer's.",
|
|
204
|
+
},
|
|
205
|
+
tool_progress: {
|
|
206
|
+
handling: 'declined',
|
|
207
|
+
note: "carries elapsed_time_seconds for a running tool. Declined deliberately: the open entry's age is computed from its own openedAt, and a second elapsed-time source is a number that can disagree with the trace.",
|
|
208
|
+
},
|
|
209
|
+
tool_use_summary: {
|
|
210
|
+
handling: 'declined',
|
|
211
|
+
note: 'a prose summary of preceding tool calls. Content.',
|
|
212
|
+
},
|
|
213
|
+
auth_status: {
|
|
214
|
+
handling: 'declined',
|
|
215
|
+
note: 'isAuthenticating plus provider output. An identity-plane fact; the session is not blocked on it in a way this model can name.',
|
|
216
|
+
},
|
|
217
|
+
rate_limit_event: {
|
|
218
|
+
handling: 'declined',
|
|
219
|
+
note: 'rate-limit windows. Usage and cost belong to the telemetry lane, which reads the same stream.',
|
|
220
|
+
},
|
|
221
|
+
prompt_suggestion: {
|
|
222
|
+
handling: 'declined',
|
|
223
|
+
note: 'a predicted next prompt. A suggestion for a human; nothing has happened.',
|
|
224
|
+
},
|
|
225
|
+
conversation_reset: {
|
|
226
|
+
handling: 'declined',
|
|
227
|
+
note: "/clear, plan-mode exit and fresh-session flows mint a new conversation id. The session continues — what changed is which transcript later reads attach to, which is the persistence layer's question.",
|
|
228
|
+
},
|
|
229
|
+
'system/api_retry': {
|
|
230
|
+
handling: 'declined',
|
|
231
|
+
note: "a retryable API failure being retried. The session stays in `requesting`, and a session stuck in retries is already visible as that entry's growing age rather than needing a state of its own.",
|
|
232
|
+
},
|
|
233
|
+
'system/control_request_progress': {
|
|
234
|
+
handling: 'declined',
|
|
235
|
+
note: 'progress for a client-originated control request. It belongs to whoever made that request, correlated by its own request_id.',
|
|
236
|
+
},
|
|
237
|
+
'system/model_refusal_fallback': {
|
|
238
|
+
handling: 'declined',
|
|
239
|
+
note: 'the turn was retried on a fallback model and continues. Nothing ended; the no-fallback counterpart is the one that is wired.',
|
|
240
|
+
},
|
|
241
|
+
'system/local_command_output': {
|
|
242
|
+
handling: 'declined',
|
|
243
|
+
note: 'output from a local slash command. Content.',
|
|
244
|
+
},
|
|
245
|
+
'system/hook_started': {
|
|
246
|
+
handling: 'declined',
|
|
247
|
+
note: 'the lifecycle of a command hook (hook_id, hook_name, stdout/stderr). This host installs in-process callbacks and runs no command hooks, so these describe a mechanism it does not use.',
|
|
248
|
+
},
|
|
249
|
+
'system/hook_progress': {
|
|
250
|
+
handling: 'declined',
|
|
251
|
+
note: 'as system/hook_started — the command-hook mechanism, unused here.',
|
|
252
|
+
},
|
|
253
|
+
'system/hook_response': {
|
|
254
|
+
handling: 'declined',
|
|
255
|
+
note: 'as system/hook_started. Worth revisiting by whoever builds the permission decision path if command hooks are ever installed alongside the in-process ones.',
|
|
256
|
+
},
|
|
257
|
+
'system/plugin_install': {
|
|
258
|
+
handling: 'declined',
|
|
259
|
+
note: 'headless plugin installation progress. A provisioning fact that precedes work rather than being work.',
|
|
260
|
+
},
|
|
261
|
+
'system/task_progress': {
|
|
262
|
+
handling: 'declined',
|
|
263
|
+
note: "periodic progress for a running task. High volume, and the entry's own age is the number that matters.",
|
|
264
|
+
},
|
|
265
|
+
'system/background_tasks_changed': {
|
|
266
|
+
handling: 'declined',
|
|
267
|
+
note: "declined on the type's own instruction. It is a level signal with replace semantics whose docs say the payload carries ids only and must not be correlated with the edge stream — so consuming it would mean rebuilding the entry set from ids, losing every entry's age and opening cause. The edges (task_started / task_updated / task_notification) are wired instead, and an edge that goes missing surfaces as an open entry with an age, which is the useful signal rather than the one a rebuild would erase.",
|
|
268
|
+
},
|
|
269
|
+
'system/thinking_tokens': {
|
|
270
|
+
handling: 'declined',
|
|
271
|
+
note: "a live thinking-token estimate for spinners. Explicitly approximate in its own docs, and telemetry's if anyone wants it.",
|
|
272
|
+
},
|
|
273
|
+
'system/commands_changed': {
|
|
274
|
+
handling: 'declined',
|
|
275
|
+
note: 'the slash-command list changed mid-session. A capability fact — what the session can do.',
|
|
276
|
+
},
|
|
277
|
+
'system/notification': {
|
|
278
|
+
handling: 'declined',
|
|
279
|
+
note: 'a loop-side text notification with a priority. Display.',
|
|
280
|
+
},
|
|
281
|
+
'system/files_persisted': {
|
|
282
|
+
handling: 'declined',
|
|
283
|
+
note: "file persistence results. The persistence layer's.",
|
|
284
|
+
},
|
|
285
|
+
'system/memory_recall': {
|
|
286
|
+
handling: 'declined',
|
|
287
|
+
note: 'memories surfaced into the turn. Context composition.',
|
|
288
|
+
},
|
|
289
|
+
'system/elicitation_complete': {
|
|
290
|
+
handling: 'declined',
|
|
291
|
+
note: 'a URL-mode elicitation confirmed complete by the MCP server. The ElicitationResult hook already closes the entry; consuming both would close it twice.',
|
|
292
|
+
},
|
|
293
|
+
'system/permission_denied': {
|
|
294
|
+
handling: 'declined',
|
|
295
|
+
note: 'mirrors the PermissionDenied hook, which is wired and additionally carries the deny reason. Consuming both would record one denial as two.',
|
|
296
|
+
},
|
|
297
|
+
'system/mirror_error': {
|
|
298
|
+
handling: 'declined',
|
|
299
|
+
note: "a real degrade — a transcript-mirror batch was dropped after retries. Declined here because it changes what can be read later, not what the session is doing; it is the persistence layer's to surface, and it must not be lost there.",
|
|
300
|
+
},
|
|
301
|
+
'system/informational': {
|
|
302
|
+
handling: 'declined',
|
|
303
|
+
note: 'a generic text banner, including hook feedback. Content.',
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
/** Counts for the table's own summary line, computed rather than typed in — one less thing to rot. */
|
|
307
|
+
export function coverageTally(table) {
|
|
308
|
+
const rows = Object.values(table);
|
|
309
|
+
return {
|
|
310
|
+
total: rows.length,
|
|
311
|
+
wired: rows.filter((row) => row.handling === 'wired').length,
|
|
312
|
+
declined: rows.filter((row) => row.handling === 'declined').length,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=coverage.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The declared state model: what a session is, what caused it, and what is still open. */
|
|
2
|
+
export type { ActivityKind, AgedEntry, CauseEvent, CauseKind, ControlEventName, EntryLane, HookEventName, MessageEventName, OpenEntry, ProcessEventName, SessionActivity, SessionSnapshot, SessionState, SessionTransition, TimeoutEventName, TransitionCause, TransitionWhere, } from './model.js';
|
|
3
|
+
export { ACTIVITY_KINDS, CAUSE_KINDS, CONTROL_EVENTS, HOOK_EVENTS, HOST_ACTIVITY_KINDS, MESSAGE_EVENTS, PROCESS_EVENTS, SDK_ACTIVITY_KINDS, SESSION_STATES, TIMEOUT_EVENTS, formatActivity, isCauseEvent, isCauseKind, sameActivity, } from './model.js';
|
|
4
|
+
export type { EntryOp, RejectedTransition, RejectionListener, StateMachineOptions, TransitionListener, TransitionRequest, } from './machine.js';
|
|
5
|
+
export { SessionStateMachine } from './machine.js';
|
|
6
|
+
export type { TransitionStoreOptions } from './store.js';
|
|
7
|
+
export { TransitionStore } from './store.js';
|
|
8
|
+
export { SessionObserver } from './observer.js';
|
|
9
|
+
export { SessionStateReporter } from './reporter.js';
|
|
10
|
+
export type { CoverageHandling, CoverageRow } from './coverage.js';
|
|
11
|
+
export { HOOK_COVERAGE, MESSAGE_COVERAGE, coverageTally } from './coverage.js';
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { ACTIVITY_KINDS, CAUSE_KINDS, CONTROL_EVENTS, HOOK_EVENTS, HOST_ACTIVITY_KINDS, MESSAGE_EVENTS, PROCESS_EVENTS, SDK_ACTIVITY_KINDS, SESSION_STATES, TIMEOUT_EVENTS, formatActivity, isCauseEvent, isCauseKind, sameActivity, } from './model.js';
|
|
2
|
+
export { SessionStateMachine } from './machine.js';
|
|
3
|
+
export { TransitionStore } from './store.js';
|
|
4
|
+
export { SessionObserver } from './observer.js';
|
|
5
|
+
export { SessionStateReporter } from './reporter.js';
|
|
6
|
+
export { HOOK_COVERAGE, MESSAGE_COVERAGE, coverageTally } from './coverage.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The state machine for one session. It records; it never decides what a session means.
|
|
3
|
+
*
|
|
4
|
+
* The state is the record, and that is the whole design.
|
|
5
|
+
* `state`, `activity` and `sessionId` are all derived from the last recorded transition and the
|
|
6
|
+
* open-entry set. There is no separate field to assign, so a code path that skips the record also
|
|
7
|
+
* fails to change the state — suppression is self-defeating instead of silent.
|
|
8
|
+
*
|
|
9
|
+
* That inversion is the point. With a separate state field, one early return can skip a publish
|
|
10
|
+
* and a state stamp together: the control concern wins, and the observability loss is invisible —
|
|
11
|
+
* no error, no null, just a column that stays `running` forever while every reader infers. Here
|
|
12
|
+
* the same edit would leave the session stuck in its previous state, visibly, in the next thing
|
|
13
|
+
* anyone read.
|
|
14
|
+
*
|
|
15
|
+
* What is deliberately not in scope here, so the suppressing branch cannot be written.
|
|
16
|
+
* This module imports the pure core and its own model, and nothing else. It cannot see an observer
|
|
17
|
+
* count, a controller link, a registry, or how a session was created — so there is no session-class
|
|
18
|
+
* fact available to condition an emission on, whatever a later editor intends. Pinned by
|
|
19
|
+
* pins/state-record.test.ts.
|
|
20
|
+
*
|
|
21
|
+
* It never throws. A transition that cannot name its cause is refused, counted, and reported on
|
|
22
|
+
* `onRejected` — but not thrown, because these calls run inside hook handlers that are wrapped in
|
|
23
|
+
* `try/catch` by contract, and a thrown rejection would be swallowed there. Silent loss is the one
|
|
24
|
+
* failure this module exists to make impossible, so "loud" here means observable and counted.
|
|
25
|
+
*/
|
|
26
|
+
import type { Clock, Ticker } from '../core/time.js';
|
|
27
|
+
import type { Refusal } from '../core/refusal.js';
|
|
28
|
+
import type { Result } from '../core/result.js';
|
|
29
|
+
import type { AgedEntry, SessionActivity, SessionSnapshot, SessionState, SessionTransition, TransitionCause, TransitionWhere } from './model.js';
|
|
30
|
+
/** Opens, closes or backgrounds one entry. `null` on a request means the entry set is untouched. */
|
|
31
|
+
export type EntryOp = {
|
|
32
|
+
readonly op: 'open';
|
|
33
|
+
readonly entryId: string;
|
|
34
|
+
readonly activity: SessionActivity;
|
|
35
|
+
/** Set when the entry belongs to a subagent rather than the main thread. */
|
|
36
|
+
readonly agentId?: string | null;
|
|
37
|
+
} | {
|
|
38
|
+
readonly op: 'close';
|
|
39
|
+
readonly entryId: string;
|
|
40
|
+
} | {
|
|
41
|
+
readonly op: 'background';
|
|
42
|
+
readonly entryId: string;
|
|
43
|
+
}
|
|
44
|
+
/** Mark every still-open entry abandoned. Marks — never erases. */
|
|
45
|
+
| {
|
|
46
|
+
readonly op: 'abandon-open';
|
|
47
|
+
readonly reason: string;
|
|
48
|
+
};
|
|
49
|
+
export interface TransitionRequest {
|
|
50
|
+
readonly to: SessionState;
|
|
51
|
+
readonly cause: TransitionCause;
|
|
52
|
+
readonly entry?: EntryOp | null;
|
|
53
|
+
/** Supplied once, when the agent reports itself. Carried forward automatically after that. */
|
|
54
|
+
readonly sessionId?: string | null;
|
|
55
|
+
/**
|
|
56
|
+
* A new `where`, when this transition is what changed it.
|
|
57
|
+
*
|
|
58
|
+
* The agent can change its own working directory mid-session, and every later transition has to
|
|
59
|
+
* carry the new one — a trace whose `where` silently describes a directory the session left is
|
|
60
|
+
* worse than one with no `where` at all. Carried forward automatically when absent, so it is
|
|
61
|
+
* derived from the record like everything else rather than living in a field of its own.
|
|
62
|
+
*/
|
|
63
|
+
readonly where?: TransitionWhere | null;
|
|
64
|
+
/** Overrides the clock. For a caller that already stamped the originating event. */
|
|
65
|
+
readonly at?: string | null;
|
|
66
|
+
}
|
|
67
|
+
export interface RejectedTransition {
|
|
68
|
+
readonly refusal: Refusal;
|
|
69
|
+
readonly at: string;
|
|
70
|
+
readonly attempted: TransitionRequest;
|
|
71
|
+
}
|
|
72
|
+
export type TransitionListener = (transition: SessionTransition) => void;
|
|
73
|
+
export type RejectionListener = (rejected: RejectedTransition) => void;
|
|
74
|
+
export type Unsubscribe = () => void;
|
|
75
|
+
export interface StateMachineOptions {
|
|
76
|
+
readonly where: TransitionWhere;
|
|
77
|
+
readonly clock: Clock;
|
|
78
|
+
readonly ticker: Ticker;
|
|
79
|
+
/** Opaque and never interpreted. See SessionTransition.correlationId. */
|
|
80
|
+
readonly correlationId?: string | null;
|
|
81
|
+
}
|
|
82
|
+
export declare class SessionStateMachine {
|
|
83
|
+
#private;
|
|
84
|
+
constructor(options: StateMachineOptions);
|
|
85
|
+
/** Derived from the last transition. There is no field to set. */
|
|
86
|
+
get state(): SessionState;
|
|
87
|
+
/** Derived from the open-entry set. There is no field to set. */
|
|
88
|
+
get activity(): SessionActivity | null;
|
|
89
|
+
/** Null until the agent reports itself; carried forward after. There is no field to set. */
|
|
90
|
+
get sessionId(): string | null;
|
|
91
|
+
get correlationId(): string | null;
|
|
92
|
+
/** Derived from the last transition. There is no field to set. */
|
|
93
|
+
get where(): TransitionWhere;
|
|
94
|
+
get transitionCount(): number;
|
|
95
|
+
/** How many transitions were refused for an unnameable cause. Never silently zero. */
|
|
96
|
+
get rejectedCount(): number;
|
|
97
|
+
/** Every entry that has not exited, with the number a human wants: how long it has been open. */
|
|
98
|
+
openEntries(): AgedEntry[];
|
|
99
|
+
onTransition(listener: TransitionListener): Unsubscribe;
|
|
100
|
+
onRejected(listener: RejectionListener): Unsubscribe;
|
|
101
|
+
/**
|
|
102
|
+
* Record a transition. The only way a session's state changes.
|
|
103
|
+
*
|
|
104
|
+
* The only thing this branches on is whether the cause can be named. Nothing about which
|
|
105
|
+
* session this is, how it was created, or whether anyone is listening is reachable from here —
|
|
106
|
+
* see this file's header. `#commit` below is straight-line by construction.
|
|
107
|
+
*/
|
|
108
|
+
record(request: TransitionRequest): Result<SessionTransition>;
|
|
109
|
+
/** What this host can say about this session. Raw material for a controller — not a roster. */
|
|
110
|
+
snapshot(): SessionSnapshot;
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=machine.d.ts.map
|