@naswerks/periscope 1.0.0 → 1.0.1

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.
Files changed (187) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +2 -2
  3. package/contracts/wire-vectors/bulk_delivered.full.json +21 -0
  4. package/contracts/wire-vectors/bulk_delivered.minimal.json +21 -0
  5. package/contracts/wire-vectors/bulk_delivered.without-stat-pair.json +22 -0
  6. package/contracts/wire-vectors/bulk_failed.declared-reason.json +22 -0
  7. package/contracts/wire-vectors/bulk_failed.unknown-reason.json +23 -0
  8. package/contracts/wire-vectors/bulk_request.full.json +21 -0
  9. package/contracts/wire-vectors/host_configure.set-and-remove.json +28 -0
  10. package/contracts/wire-vectors/host_configure_result.applied.json +34 -0
  11. package/contracts/wire-vectors/host_configure_result.refused.json +33 -0
  12. package/contracts/wire-vectors/link_ack.cursors.json +25 -0
  13. package/contracts/wire-vectors/link_ack.empty.json +16 -0
  14. package/contracts/wire-vectors/link_bye.cause.json +16 -0
  15. package/contracts/wire-vectors/link_hello.empty.json +33 -0
  16. package/contracts/wire-vectors/link_hello.full.json +44 -0
  17. package/contracts/wire-vectors/link_ping.nonce.json +16 -0
  18. package/contracts/wire-vectors/link_ping.unknown-payload-key.json +18 -0
  19. package/contracts/wire-vectors/link_pong.nonce.json +16 -0
  20. package/contracts/wire-vectors/link_welcome.empty.json +18 -0
  21. package/contracts/wire-vectors/link_welcome.full.json +25 -0
  22. package/contracts/wire-vectors/refused.empty.json +10 -0
  23. package/contracts/wire-vectors/refused.encode.empty-at.json +18 -0
  24. package/contracts/wire-vectors/refused.encode.empty-session-id.json +18 -0
  25. package/contracts/wire-vectors/refused.encode.missing-member.json +22 -0
  26. package/contracts/wire-vectors/refused.encode.seq-zero.json +18 -0
  27. package/contracts/wire-vectors/refused.encode.undeclared-configure-refusal.json +34 -0
  28. package/contracts/wire-vectors/refused.encode.undeclared-wire-refusal.json +27 -0
  29. package/contracts/wire-vectors/refused.missing-member.json +10 -0
  30. package/contracts/wire-vectors/refused.not-json.json +10 -0
  31. package/contracts/wire-vectors/refused.over-limit.json +10 -0
  32. package/contracts/wire-vectors/refused.seq-zero.json +10 -0
  33. package/contracts/wire-vectors/refused.unknown-frame.json +10 -0
  34. package/contracts/wire-vectors/refused.unknown-kind.json +10 -0
  35. package/contracts/wire-vectors/repository_list.root.json +19 -0
  36. package/contracts/wire-vectors/repository_list.subdirectory.json +19 -0
  37. package/contracts/wire-vectors/repository_list_result.entries.json +34 -0
  38. package/contracts/wire-vectors/repository_list_result.refused.json +24 -0
  39. package/contracts/wire-vectors/repository_read.head.json +20 -0
  40. package/contracts/wire-vectors/repository_read_result.refused.json +25 -0
  41. package/contracts/wire-vectors/repository_read_result.truncated.json +22 -0
  42. package/contracts/wire-vectors/session_cancel.bare.json +17 -0
  43. package/contracts/wire-vectors/session_configure.full.json +22 -0
  44. package/contracts/wire-vectors/session_configure.minimal.json +20 -0
  45. package/contracts/wire-vectors/session_delta.agent-message.json +27 -0
  46. package/contracts/wire-vectors/session_list.request.json +18 -0
  47. package/contracts/wire-vectors/session_list_result.empty.json +21 -0
  48. package/contracts/wire-vectors/session_list_result.full.json +36 -0
  49. package/contracts/wire-vectors/session_new.full.json +72 -0
  50. package/contracts/wire-vectors/session_new.minimal.json +22 -0
  51. package/contracts/wire-vectors/session_new.request-all-null.json +41 -0
  52. package/contracts/wire-vectors/session_prompt.empty.json +18 -0
  53. package/contracts/wire-vectors/session_prompt.multibyte.json +18 -0
  54. package/contracts/wire-vectors/session_prompt.text.json +18 -0
  55. package/contracts/wire-vectors/session_prompt.unknown-frame-key.json +20 -0
  56. package/contracts/wire-vectors/session_prompt.unknown-payload-key.json +20 -0
  57. package/contracts/wire-vectors/session_update.agent-message.json +26 -0
  58. package/contracts/wire-vectors/session_update.state-transition-with-activity.json +44 -0
  59. package/contracts/wire-vectors/session_update.state-transition.json +41 -0
  60. package/contracts/wire-vectors/session_update.wire-refusal.json +26 -0
  61. package/contracts/wire-vectors/transcript_failed.declared-reason.json +22 -0
  62. package/contracts/wire-vectors/transcript_list.from-start.json +19 -0
  63. package/contracts/wire-vectors/transcript_list.paged.json +19 -0
  64. package/contracts/wire-vectors/transcript_list_result.entry-without-cwd.json +30 -0
  65. package/contracts/wire-vectors/transcript_list_result.full.json +29 -0
  66. package/contracts/wire-vectors/transcript_list_result.last-page.json +29 -0
  67. package/contracts/wire-vectors/transcript_tail.any-user-entry.json +22 -0
  68. package/contracts/wire-vectors/transcript_tail.with-needle.json +22 -0
  69. package/contracts/wire-vectors/transcript_tail_result.absent.json +23 -0
  70. package/contracts/wire-vectors/transcript_tail_result.found.json +23 -0
  71. package/contracts/wire-vectors/workspace_list.from-start.json +19 -0
  72. package/contracts/wire-vectors/workspace_list_result.page.json +48 -0
  73. package/contracts/wire-vectors/workspace_list_result.refused.json +26 -0
  74. package/contracts/wire-vectors/workspace_release.by-path-delete-branch.json +22 -0
  75. package/contracts/wire-vectors/workspace_release.request.json +22 -0
  76. package/contracts/wire-vectors/workspace_release_bulk.two-entries.json +32 -0
  77. package/contracts/wire-vectors/workspace_release_bulk_result.mixed.json +37 -0
  78. package/contracts/wire-vectors/workspace_release_result.partial.json +26 -0
  79. package/contracts/wire-vectors/workspace_release_result.refused.json +26 -0
  80. package/contracts/wire-vectors/workspace_release_result.released.json +23 -0
  81. package/package.json +6 -1
  82. package/src/bin/command.ts +198 -0
  83. package/src/bin/config.ts +104 -0
  84. package/src/bin/login.ts +149 -0
  85. package/src/bin/main.ts +204 -0
  86. package/src/bin/pair.ts +246 -0
  87. package/src/bin/periscope.ts +3 -0
  88. package/src/bin/posture.ts +232 -0
  89. package/src/bin/reconfigure.ts +266 -0
  90. package/src/bin/serve.ts +615 -0
  91. package/src/bin/status.ts +61 -0
  92. package/src/bin/workspaces.ts +280 -0
  93. package/src/control/backoff.ts +40 -0
  94. package/src/control/codec.ts +557 -0
  95. package/src/control/credential.ts +33 -0
  96. package/src/control/frames.ts +1586 -0
  97. package/src/control/link-state.ts +67 -0
  98. package/src/control/link.ts +894 -0
  99. package/src/control/queue.ts +248 -0
  100. package/src/control/seq.ts +76 -0
  101. package/src/control/stream-routing.ts +216 -0
  102. package/src/control/stream.ts +149 -0
  103. package/src/core/async-queue.ts +63 -0
  104. package/src/core/failure.ts +61 -0
  105. package/src/core/index.ts +22 -0
  106. package/src/core/keyed-turns.ts +47 -0
  107. package/src/core/paths.ts +84 -0
  108. package/src/core/refusal.ts +430 -0
  109. package/src/core/result.ts +27 -0
  110. package/src/core/time.ts +34 -0
  111. package/src/core/vocab.ts +44 -0
  112. package/src/core/workspace-id.ts +11 -0
  113. package/src/gate/command.ts +617 -0
  114. package/src/gate/decision.ts +173 -0
  115. package/src/gate/escalate.ts +164 -0
  116. package/src/gate/gate.ts +635 -0
  117. package/src/gate/index.ts +53 -0
  118. package/src/gate/jail.ts +210 -0
  119. package/src/gate/local.ts +115 -0
  120. package/src/gate/outcome.ts +164 -0
  121. package/src/gate/shell.ts +295 -0
  122. package/src/host/agent-process.ts +694 -0
  123. package/src/host/bulk-post.ts +166 -0
  124. package/src/host/claude-transcripts.ts +404 -0
  125. package/src/host/config-file.ts +246 -0
  126. package/src/host/git-facts.ts +119 -0
  127. package/src/host/hooks.ts +102 -0
  128. package/src/host/host.ts +1901 -0
  129. package/src/host/index.ts +92 -0
  130. package/src/host/link-state-file.ts +79 -0
  131. package/src/host/loopback.ts +177 -0
  132. package/src/host/machine.ts +27 -0
  133. package/src/host/mcp-server.ts +66 -0
  134. package/src/host/package-facts.ts +25 -0
  135. package/src/host/paired-credential-store.ts +140 -0
  136. package/src/host/paths.ts +175 -0
  137. package/src/host/repository-read.ts +245 -0
  138. package/src/host/session-store.ts +133 -0
  139. package/src/host/sign-in.ts +352 -0
  140. package/src/host/telemetry.ts +105 -0
  141. package/src/host/token-cache.ts +214 -0
  142. package/src/host/transcript-fs.ts +110 -0
  143. package/src/host/wire-request.ts +285 -0
  144. package/src/host/workspace-fs.ts +90 -0
  145. package/src/host/workspace-trust.ts +92 -0
  146. package/src/identity/authorize.ts +127 -0
  147. package/src/identity/config.ts +227 -0
  148. package/src/identity/credential.ts +176 -0
  149. package/src/identity/device-code.ts +217 -0
  150. package/src/identity/index.ts +84 -0
  151. package/src/identity/mode.ts +161 -0
  152. package/src/identity/paired-credential.ts +89 -0
  153. package/src/identity/pkce.ts +113 -0
  154. package/src/identity/store.ts +98 -0
  155. package/src/identity/token.ts +203 -0
  156. package/src/index.ts +140 -0
  157. package/src/mcp/descriptor.ts +75 -0
  158. package/src/mcp/index.ts +23 -0
  159. package/src/mcp/schema.ts +214 -0
  160. package/src/mcp/server.ts +180 -0
  161. package/src/persistence/entry.ts +142 -0
  162. package/src/persistence/index.ts +47 -0
  163. package/src/persistence/key.ts +77 -0
  164. package/src/persistence/mirror.ts +118 -0
  165. package/src/persistence/receipt.ts +182 -0
  166. package/src/persistence/retention.ts +118 -0
  167. package/src/persistence/store.ts +183 -0
  168. package/src/persistence/transition-log.ts +176 -0
  169. package/src/protocol.ts +215 -0
  170. package/src/sessions/index.ts +27 -0
  171. package/src/sessions/registry.ts +392 -0
  172. package/src/sessions/session.ts +440 -0
  173. package/src/sessions/spawn-env.ts +318 -0
  174. package/src/state/coverage.ts +360 -0
  175. package/src/state/index.ts +58 -0
  176. package/src/state/machine.ts +359 -0
  177. package/src/state/model.ts +417 -0
  178. package/src/state/observer.ts +499 -0
  179. package/src/state/reporter.ts +60 -0
  180. package/src/state/store.ts +157 -0
  181. package/src/telemetry/index.ts +10 -0
  182. package/src/telemetry/usage.ts +117 -0
  183. package/src/workspace/git-worktree.ts +551 -0
  184. package/src/workspace/index.ts +17 -0
  185. package/src/workspace/plain-dir.ts +132 -0
  186. package/src/workspace/provider.ts +159 -0
  187. package/src/workspace/worktree-porcelain.ts +118 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * The durable transition log — the only place a gate decision is stored durably.
3
+ *
4
+ * Read this before adding any cleanup, prune or tidy-up. `state/store.ts` retains transitions in
5
+ * a bounded in-memory ring that actively drops its oldest once a session outruns the window, and
6
+ * its own header says durability belongs here. Every recorded transition also reaches the link —
7
+ * `forwardSession` in `control/stream.ts` subscribes to the machine and forwards whichever lane
8
+ * produced it — but the link is a stream, not a record: once a frame has been sent, nothing in
9
+ * this package can read it back. So every local refusal this host decides — a path-jail denial, a
10
+ * boundary command, a credential-path read, an unconvertible tool descriptor — is held in-process
11
+ * only by a ring that will eventually overwrite it. This file is what makes such a decision
12
+ * readable afterwards. A tidy-up here does not lose a log line; it loses the only stored evidence
13
+ * that the package's headline feature ever ran.
14
+ *
15
+ * Append-only, and an abandoned entry is marked rather than erased. "This session sat in
16
+ * `tool:Bash` for forty minutes" is the most useful sentence this system can produce, and a
17
+ * reconciler that quietly closed the entry would destroy exactly that. So `markAbandoned` writes a
18
+ * new record carrying the reason; it never rewrites or removes the one that is already stored.
19
+ *
20
+ * `cause` survives the round trip or the read refuses. A transition read back without the event
21
+ * that produced it would leave a reader with a state change and no way to know what caused it,
22
+ * which is exactly the inference the declared model exists to eliminate. So the encoder writes all
23
+ * three parts of a cause and the decoder refuses an entry that lost any of them, rather than
24
+ * substituting a plausible default.
25
+ */
26
+ import type { OpenEntry, SessionTransition } from '../state/model.js';
27
+ import { isCauseEvent, isCauseKind } from '../state/model.js';
28
+ import type { Result } from '../core/result.js';
29
+ import { ok, refuse } from '../core/result.js';
30
+ import type { TranscriptEntry } from './entry.js';
31
+
32
+ /**
33
+ * The entry `type` a stored transition carries.
34
+ *
35
+ * Namespaced so it cannot collide with a transcript entry. These records may share a store with
36
+ * mirrored transcript lines, whose `type` values are the CLI's. A prefix nothing in that vocabulary
37
+ * uses keeps the two readable side by side and keeps a reader of either from mistaking one for the
38
+ * other.
39
+ */
40
+ export const TRANSITION_ENTRY_TYPE = 'periscope.transition';
41
+
42
+ /** The entry `type` an abandonment mark carries. A record in its own right, never an edit. */
43
+ export const ABANDONED_ENTRY_TYPE = 'periscope.entry_abandoned';
44
+
45
+ /** Encode one transition as a storable entry. Lossless for everything a reader branches on. */
46
+ export function encodeTransition(transition: SessionTransition): TranscriptEntry {
47
+ return {
48
+ type: TRANSITION_ENTRY_TYPE,
49
+ uuid: transitionUuid(transition),
50
+ timestamp: transition.at,
51
+ sessionId: transition.sessionId,
52
+ seq: transition.seq,
53
+ from: transition.from,
54
+ to: transition.to,
55
+ activity: transition.activity,
56
+ entryId: transition.entryId,
57
+ cause: { kind: transition.cause.kind, event: transition.cause.event, detail: transition.cause.detail },
58
+ where: transition.where,
59
+ correlationId: transition.correlationId,
60
+ };
61
+ }
62
+
63
+ /**
64
+ * Read a stored transition back.
65
+ *
66
+ * A lost or unrecognised cause is a refusal. Both halves are checked independently — the same way
67
+ * the machine validates them — because a cause that survived as a shape but not as a declared value
68
+ * is a record a reader would branch on wrongly.
69
+ */
70
+ export function decodeTransition(entry: TranscriptEntry): Result<SessionTransition> {
71
+ if (entry.type !== TRANSITION_ENTRY_TYPE) {
72
+ return refuse<SessionTransition>(
73
+ 'transcript-entry-malformed',
74
+ `not a transition entry: type ${entry.type}`,
75
+ );
76
+ }
77
+
78
+ const cause = entry['cause'];
79
+ if (cause === null || typeof cause !== 'object' || Array.isArray(cause)) {
80
+ return refuse<SessionTransition>('transcript-entry-malformed', 'the transition carries no cause');
81
+ }
82
+ const { kind, event, detail } = cause as Record<string, unknown>;
83
+ if (typeof kind !== 'string' || !isCauseKind(kind)) {
84
+ return refuse<SessionTransition>(
85
+ 'transcript-entry-malformed',
86
+ `the cause kind is not a declared kind: ${String(kind)}`,
87
+ );
88
+ }
89
+ if (typeof event !== 'string' || !isCauseEvent(event)) {
90
+ return refuse<SessionTransition>(
91
+ 'transcript-entry-malformed',
92
+ `the cause event is not a declared event: ${String(event)}`,
93
+ );
94
+ }
95
+
96
+ const seq = entry['seq'];
97
+ if (typeof seq !== 'number' || !Number.isFinite(seq)) {
98
+ return refuse<SessionTransition>('transcript-entry-malformed', 'the transition carries no seq');
99
+ }
100
+ const where = entry['where'];
101
+ if (where === null || typeof where !== 'object') {
102
+ return refuse<SessionTransition>('transcript-entry-malformed', 'the transition carries no where');
103
+ }
104
+
105
+ return ok({
106
+ sessionId: (entry['sessionId'] as string | null) ?? null,
107
+ seq,
108
+ at: typeof entry.timestamp === 'string' ? entry.timestamp : '',
109
+ from: entry['from'] as SessionTransition['from'],
110
+ to: entry['to'] as SessionTransition['to'],
111
+ activity: (entry['activity'] as SessionTransition['activity']) ?? null,
112
+ entryId: (entry['entryId'] as string | null) ?? null,
113
+ cause: { kind, event, detail: typeof detail === 'string' ? detail : '' },
114
+ where: where as SessionTransition['where'],
115
+ correlationId: (entry['correlationId'] as string | null) ?? null,
116
+ });
117
+ }
118
+
119
+ /**
120
+ * A record that an open entry was abandoned.
121
+ *
122
+ * This is an append, not an edit, and that is the whole design. The entry it refers to stays
123
+ * exactly as it was written, with the time it opened and the cause that opened it. A reader
124
+ * assembling the two sees "opened at X, still open at Y, marked abandoned because Z" — which is the
125
+ * signal. Rewriting the original would leave "closed", which is the signal's opposite and is
126
+ * indistinguishable from an ordinary completion.
127
+ */
128
+ export function markAbandoned(entry: OpenEntry, at: string, reason: string): TranscriptEntry {
129
+ return {
130
+ type: ABANDONED_ENTRY_TYPE,
131
+ uuid: `abandoned:${entry.entryId}:${at}`,
132
+ timestamp: at,
133
+ entryId: entry.entryId,
134
+ activity: entry.activity,
135
+ lane: entry.lane,
136
+ openedAt: entry.openedAt,
137
+ agentId: entry.agentId,
138
+ reason,
139
+ cause: { kind: entry.cause.kind, event: entry.cause.event, detail: entry.cause.detail },
140
+ };
141
+ }
142
+
143
+ /** Every stored transition in an entry list, in stored order. Non-transition entries are ignored. */
144
+ export function transitionsIn(entries: readonly TranscriptEntry[]): Result<SessionTransition[]> {
145
+ const found: SessionTransition[] = [];
146
+ for (const entry of entries) {
147
+ if (entry.type !== TRANSITION_ENTRY_TYPE) continue;
148
+ const decoded = decodeTransition(entry);
149
+ if (!decoded.ok) return refuse<SessionTransition[]>(decoded.refusal.reason, decoded.refusal.detail);
150
+ found.push(decoded.value);
151
+ }
152
+ return ok(found);
153
+ }
154
+
155
+ /** Every abandonment mark in an entry list, by the entry id it refers to. */
156
+ export function abandonmentsIn(entries: readonly TranscriptEntry[]): Map<string, TranscriptEntry> {
157
+ const marks = new Map<string, TranscriptEntry>();
158
+ for (const entry of entries) {
159
+ if (entry.type !== ABANDONED_ENTRY_TYPE) continue;
160
+ const entryId = entry['entryId'];
161
+ if (typeof entryId === 'string') marks.set(entryId, entry);
162
+ }
163
+ return marks;
164
+ }
165
+
166
+ /**
167
+ * A stable uuid for one transition, so a re-append is deduplicated rather than doubled.
168
+ *
169
+ * Derived, not minted. The adapter contract treats `uuid` as an idempotency key, and a mirror
170
+ * retries. A random id would make every retry a new row, so the same transition written twice must
171
+ * carry the same id — and a session's `seq` is dense from 1 per machine, which makes the pair
172
+ * unique without inventing anything.
173
+ */
174
+ export function transitionUuid(transition: SessionTransition): string {
175
+ return `transition:${transition.sessionId ?? 'unidentified'}:${transition.correlationId ?? '-'}:${transition.seq}`;
176
+ }
@@ -0,0 +1,215 @@
1
+ /**
2
+ * The wire contract, on its own subpath — `@naswerks/periscope/protocol`.
3
+ *
4
+ * This is the only import path into the package that structurally cannot reach `src/host/`, so a
5
+ * controller written against it cannot transitively acquire `node:fs`, `node:child_process` or
6
+ * `node:os`, and does not pull the Agent SDK. That turns the package's blast-radius claim into
7
+ * something a consumer can verify at the package boundary instead of taking on trust from the
8
+ * package's own test suite. Pinned by src/pins/protocol-closure.test.ts.
9
+ *
10
+ * The condition that keeps it true: `exports` in package.json has exactly these two entries and no
11
+ * catch-all `"./*"`. A third subpath re-opens the boundary and is a decision, not a refactor.
12
+ */
13
+ export type {
14
+ AgentMessageUpdate,
15
+ BulkDelivered,
16
+ BulkFailed,
17
+ BulkRequest,
18
+ ControlFrame,
19
+ ControlPayload,
20
+ ControlPayloadKind,
21
+ Frame,
22
+ HostConfiguration,
23
+ HostConfigure,
24
+ HostConfigureEntry,
25
+ HostConfigureResult,
26
+ JsonObject,
27
+ JsonValue,
28
+ LinkAck,
29
+ LinkBye,
30
+ LinkHello,
31
+ LinkPing,
32
+ LinkPong,
33
+ LinkWelcome,
34
+ ProtocolRange,
35
+ ReadRefusal,
36
+ SessionCancel,
37
+ SessionConfigure,
38
+ SessionCursor,
39
+ SessionDelta,
40
+ SessionFrame,
41
+ SessionList,
42
+ SessionListEntry,
43
+ SessionListResult,
44
+ SessionNew,
45
+ SessionNewEnv,
46
+ SessionNewGate,
47
+ SessionNewPlugin,
48
+ SessionNewRequest,
49
+ SessionPayload,
50
+ SessionPayloadKind,
51
+ SessionPrompt,
52
+ SessionUpdate,
53
+ SessionUpdateBody,
54
+ StateTransitionUpdate,
55
+ TranscriptFailed,
56
+ TranscriptList,
57
+ TranscriptListEntry,
58
+ TranscriptListResult,
59
+ TranscriptTail,
60
+ TranscriptTailResult,
61
+ WireRefusal,
62
+ WireRefusalUpdate,
63
+ RepositoryEntry,
64
+ RepositoryList,
65
+ RepositoryListResult,
66
+ RepositoryRead,
67
+ RepositoryReadResult,
68
+ WorkspaceList,
69
+ WorkspaceListEntry,
70
+ WorkspaceListResult,
71
+ WorkspaceRelease,
72
+ WorkspaceReleaseBulk,
73
+ WorkspaceReleaseBulkResult,
74
+ WorkspaceReleaseEntry,
75
+ WorkspaceReleaseEntryResult,
76
+ WorkspaceReleaseFlags,
77
+ WorkspaceReleaseResult,
78
+ } from './control/frames.js';
79
+
80
+ // The declared state model travels with the wire: a `session_update` carries a transition, so a
81
+ // consumer parsing frames needs the vocabulary to read one. This is the model's public home — the
82
+ // controller reads these types and builds whatever its own conventions mean on top of them.
83
+ export type {
84
+ ActivityKind,
85
+ AgedEntry,
86
+ CauseEvent,
87
+ CauseKind,
88
+ EntryLane,
89
+ HookEventName,
90
+ OpenEntry,
91
+ SessionActivity,
92
+ SessionSnapshot,
93
+ SessionState,
94
+ SessionTransition,
95
+ TransitionCause,
96
+ TransitionWhere,
97
+ } from './state/model.js';
98
+ export {
99
+ ACTIVITY_KINDS,
100
+ CAUSE_KINDS,
101
+ HOOK_EVENTS,
102
+ HOST_ACTIVITY_KINDS,
103
+ SDK_ACTIVITY_KINDS,
104
+ SESSION_STATES,
105
+ formatActivity,
106
+ isCauseEvent,
107
+ isCauseKind,
108
+ sameActivity,
109
+ } from './state/model.js';
110
+
111
+ export {
112
+ DROPPABLE_KINDS,
113
+ MAX_CONFIGURATION_VALUE_LENGTH,
114
+ MAX_BULK_RELEASES,
115
+ MAX_CONFIGURE_ENTRIES,
116
+ MAX_FRAME_BYTES,
117
+ MAX_REPOSITORY_ENTRIES,
118
+ MAX_REPOSITORY_READ_BYTES,
119
+ PROTOCOL_VERSION,
120
+ PROTOCOL_VERSION_MIN,
121
+ TRANSCRIPT_PAGE_SIZE,
122
+ TRANSCRIPT_WHAT_PREFIX,
123
+ WORKSPACE_PAGE_SIZE,
124
+ agentMessageDelta,
125
+ agentMessageUpdate,
126
+ bulkDelivered,
127
+ frameId,
128
+ hostConfigure,
129
+ hostConfigureResult,
130
+ isControlFrame,
131
+ isDroppable,
132
+ isSessionFrame,
133
+ readAgentMessage,
134
+ readRefusal,
135
+ readStateTransition,
136
+ readWireRefusal,
137
+ repositoryList,
138
+ repositoryListResult,
139
+ repositoryRead,
140
+ repositoryReadResult,
141
+ sessionList,
142
+ sessionListResult,
143
+ sessionNew,
144
+ sessionNewRequest,
145
+ stateTransitionUpdate,
146
+ transcriptFailed,
147
+ transcriptList,
148
+ transcriptListResult,
149
+ transcriptTail,
150
+ transcriptTailResult,
151
+ unsetHostConfiguration,
152
+ wireRefusalUpdate,
153
+ workspaceList,
154
+ workspaceListResult,
155
+ workspaceRelease,
156
+ workspaceReleaseBulk,
157
+ workspaceReleaseBulkResult,
158
+ workspaceReleaseEntryResult,
159
+ workspaceReleaseResult,
160
+ } from './control/frames.js';
161
+
162
+ // `control/stream-routing.ts` is not exported here, deliberately. It is keyed off the SDK's own
163
+ // message union, so it reaches `host/` and would drag the privileged module into this subpath's
164
+ // closure — the one thing this file exists to prevent. What a wire consumer needs is already here:
165
+ // `DROPPABLE_KINDS` and `isDroppable` say which payload kinds may be lost. The routing table is the
166
+ // host's emission policy, and it ships from the main barrel.
167
+
168
+ export { decode, encode } from './control/codec.js';
169
+
170
+ export type { InboundCheck } from './control/seq.js';
171
+ export { SeqTracker } from './control/seq.js';
172
+
173
+ // The refusal vocabulary travels with the wire: a `bulk_failed` frame carries one, so a consumer
174
+ // parsing frames needs the type to read it.
175
+ export type { Refusal, RefusalReason } from './core/refusal.js';
176
+ export { REFUSAL_REASONS, isRefusalReason } from './core/refusal.js';
177
+
178
+ export type { Result } from './core/result.js';
179
+
180
+ // ---------------------------------------------------------------------------
181
+ // The permission surface — the one thing a controller is required to implement, so the one thing
182
+ // this subpath must be able to type.
183
+ //
184
+ // The package's headline safety claim is that `@naswerks/periscope/protocol` structurally cannot reach
185
+ // `src/host/`. Without these re-exports, a stranger writing a controller would have to import the
186
+ // permission types from the main barrel — forfeiting the boundary the subpath exists to provide —
187
+ // or hand-copy them, or resort to `ReturnType<typeof …>`. The one surface they cannot avoid
188
+ // implementing would be the one surface the safe import path did not carry.
189
+ //
190
+ // These cost the boundary nothing, which is why they can ship here. `gate/decision.ts` has zero
191
+ // imports and `gate/escalate.ts` imports only `./decision.js`; both are type-only from this file's
192
+ // point of view. `src/pins/protocol-closure.test.ts` is the arbiter, and it stays green: no `host/`
193
+ // reach, no `node:` builtin, no Node-only global.
194
+ //
195
+ // Note what is not here, because the omission is deliberate. `SessionStore` and `SessionKey` are
196
+ // re-exports of the Agent SDK's own types (`host/session-store.ts`), and `readTurnSpend` is a
197
+ // runtime function in `host/telemetry.ts`. Routing any of the three through here would either drag
198
+ // `host/` into this closure or make the wire subpath import the Agent SDK — contradicting this
199
+ // file's own header claim that it does not. Whether the wire should grow its own session/spend
200
+ // types, independent of the SDK's, is a wire-vocabulary decision and belongs to whoever designs
201
+ // the API. It is deliberately not taken here.
202
+ // ---------------------------------------------------------------------------
203
+
204
+ export type { Decider, Decision, DecisionReading, DecisionRequest } from './gate/decision.js';
205
+ export { describeRaw, readDecision } from './gate/decision.js';
206
+
207
+ export type { EscalationOptions, EscalationResponse, EscalationTransport } from './gate/escalate.js';
208
+
209
+ // `EscalationOptions` names a credential, so the credential's own types have to be reachable from
210
+ // the same import path or the option is untypeable by the consumer it exists for. They cost the
211
+ // closure nothing: `control/credential.ts` imports `core/result.ts` and nothing else — no `host/`
212
+ // reach, no `node:` builtin, no Node-only global — so `pins/protocol-closure.test.ts` stays the
213
+ // arbiter and stays green. The one implementation is deliberately not re-exported here: it refuses
214
+ // by name, and a wire consumer types against the interface rather than instantiating the package's.
215
+ export type { Authorization, ControllerCredential } from './control/credential.js';
@@ -0,0 +1,27 @@
1
+ /** The agent session lifecycle and the registry of live sessions. */
2
+ export type { EnvSource, SpawnEnvPolicy } from './spawn-env.js';
3
+ export {
4
+ DECLARED_EXACT_KEYS,
5
+ DECLARED_PREFIXES,
6
+ DECLARED_SUFFIXES,
7
+ STRIPPED_HOST_SESSION_KEYS,
8
+ composeSpawnEnv,
9
+ isDeclaredSpawnEnvKey,
10
+ redactProxyCredential,
11
+ } from './spawn-env.js';
12
+
13
+ export type {
14
+ HostedSessionFacts,
15
+ SessionDegrade,
16
+ SessionDegradeListener,
17
+ SessionEndCause,
18
+ SessionEndListener,
19
+ SessionEnded,
20
+ SessionLifecycle,
21
+ SessionListener,
22
+ Unsubscribe,
23
+ } from './session.js';
24
+ export { SESSION_END_CAUSES, HostedSession } from './session.js';
25
+
26
+ export type { SessionRegistryOptions, SessionRequest } from './registry.js';
27
+ export { SessionRegistry } from './registry.js';