@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.
Files changed (230) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +146 -0
  4. package/SECURITY.md +315 -0
  5. package/dist/bin/command.d.ts +97 -0
  6. package/dist/bin/command.js +141 -0
  7. package/dist/bin/config.d.ts +7 -0
  8. package/dist/bin/config.js +90 -0
  9. package/dist/bin/login.d.ts +48 -0
  10. package/dist/bin/login.js +82 -0
  11. package/dist/bin/main.d.ts +33 -0
  12. package/dist/bin/main.js +173 -0
  13. package/dist/bin/pair.d.ts +28 -0
  14. package/dist/bin/pair.js +198 -0
  15. package/dist/bin/periscope.d.ts +3 -0
  16. package/dist/bin/periscope.js +4 -0
  17. package/dist/bin/posture.d.ts +64 -0
  18. package/dist/bin/posture.js +157 -0
  19. package/dist/bin/reconfigure.d.ts +64 -0
  20. package/dist/bin/reconfigure.js +195 -0
  21. package/dist/bin/serve.d.ts +103 -0
  22. package/dist/bin/serve.js +441 -0
  23. package/dist/bin/status.d.ts +11 -0
  24. package/dist/bin/status.js +49 -0
  25. package/dist/bin/workspaces.d.ts +109 -0
  26. package/dist/bin/workspaces.js +215 -0
  27. package/dist/control/backoff.d.ts +25 -0
  28. package/dist/control/backoff.js +20 -0
  29. package/dist/control/codec.d.ts +15 -0
  30. package/dist/control/codec.js +501 -0
  31. package/dist/control/credential.d.ts +23 -0
  32. package/dist/control/credential.js +8 -0
  33. package/dist/control/frames.d.ts +1180 -0
  34. package/dist/control/frames.js +441 -0
  35. package/dist/control/link-state.d.ts +32 -0
  36. package/dist/control/link-state.js +39 -0
  37. package/dist/control/link.d.ts +109 -0
  38. package/dist/control/link.js +736 -0
  39. package/dist/control/queue.d.ts +110 -0
  40. package/dist/control/queue.js +174 -0
  41. package/dist/control/seq.d.ts +49 -0
  42. package/dist/control/seq.js +52 -0
  43. package/dist/control/stream-routing.d.ts +200 -0
  44. package/dist/control/stream-routing.js +167 -0
  45. package/dist/control/stream.d.ts +43 -0
  46. package/dist/control/stream.js +64 -0
  47. package/dist/core/async-queue.d.ts +26 -0
  48. package/dist/core/async-queue.js +63 -0
  49. package/dist/core/failure.d.ts +22 -0
  50. package/dist/core/failure.js +53 -0
  51. package/dist/core/index.d.ts +18 -0
  52. package/dist/core/index.js +7 -0
  53. package/dist/core/keyed-turns.d.ts +28 -0
  54. package/dist/core/keyed-turns.js +44 -0
  55. package/dist/core/paths.d.ts +25 -0
  56. package/dist/core/paths.js +77 -0
  57. package/dist/core/refusal.d.ts +43 -0
  58. package/dist/core/refusal.js +399 -0
  59. package/dist/core/result.d.ts +22 -0
  60. package/dist/core/result.js +16 -0
  61. package/dist/core/time.d.ts +19 -0
  62. package/dist/core/time.js +25 -0
  63. package/dist/core/vocab.d.ts +23 -0
  64. package/dist/core/vocab.js +37 -0
  65. package/dist/core/workspace-id.d.ts +12 -0
  66. package/dist/core/workspace-id.js +12 -0
  67. package/dist/gate/command.d.ts +136 -0
  68. package/dist/gate/command.js +551 -0
  69. package/dist/gate/decision.d.ts +111 -0
  70. package/dist/gate/decision.js +97 -0
  71. package/dist/gate/escalate.d.ts +82 -0
  72. package/dist/gate/escalate.js +92 -0
  73. package/dist/gate/gate.d.ts +173 -0
  74. package/dist/gate/gate.js +417 -0
  75. package/dist/gate/index.d.ts +39 -0
  76. package/dist/gate/index.js +31 -0
  77. package/dist/gate/jail.d.ts +92 -0
  78. package/dist/gate/jail.js +131 -0
  79. package/dist/gate/local.d.ts +69 -0
  80. package/dist/gate/local.js +51 -0
  81. package/dist/gate/outcome.d.ts +72 -0
  82. package/dist/gate/outcome.js +91 -0
  83. package/dist/gate/shell.d.ts +36 -0
  84. package/dist/gate/shell.js +226 -0
  85. package/dist/host/agent-process.d.ts +452 -0
  86. package/dist/host/agent-process.js +382 -0
  87. package/dist/host/bulk-post.d.ts +44 -0
  88. package/dist/host/bulk-post.js +114 -0
  89. package/dist/host/claude-transcripts.d.ts +84 -0
  90. package/dist/host/claude-transcripts.js +339 -0
  91. package/dist/host/config-file.d.ts +60 -0
  92. package/dist/host/config-file.js +216 -0
  93. package/dist/host/git-facts.d.ts +7 -0
  94. package/dist/host/git-facts.js +106 -0
  95. package/dist/host/hooks.d.ts +56 -0
  96. package/dist/host/hooks.js +75 -0
  97. package/dist/host/host.d.ts +356 -0
  98. package/dist/host/host.js +1294 -0
  99. package/dist/host/index.d.ts +35 -0
  100. package/dist/host/index.js +22 -0
  101. package/dist/host/link-state-file.d.ts +18 -0
  102. package/dist/host/link-state-file.js +66 -0
  103. package/dist/host/loopback.d.ts +38 -0
  104. package/dist/host/loopback.js +122 -0
  105. package/dist/host/machine.d.ts +9 -0
  106. package/dist/host/machine.js +19 -0
  107. package/dist/host/mcp-server.d.ts +11 -0
  108. package/dist/host/mcp-server.js +48 -0
  109. package/dist/host/package-facts.d.ts +3 -0
  110. package/dist/host/package-facts.js +26 -0
  111. package/dist/host/paired-credential-store.d.ts +32 -0
  112. package/dist/host/paired-credential-store.js +112 -0
  113. package/dist/host/paths.d.ts +80 -0
  114. package/dist/host/paths.js +165 -0
  115. package/dist/host/repository-read.d.ts +33 -0
  116. package/dist/host/repository-read.js +201 -0
  117. package/dist/host/session-store.d.ts +53 -0
  118. package/dist/host/session-store.js +100 -0
  119. package/dist/host/sign-in.d.ts +90 -0
  120. package/dist/host/sign-in.js +239 -0
  121. package/dist/host/telemetry.d.ts +42 -0
  122. package/dist/host/telemetry.js +74 -0
  123. package/dist/host/token-cache.d.ts +62 -0
  124. package/dist/host/token-cache.js +185 -0
  125. package/dist/host/transcript-fs.d.ts +4 -0
  126. package/dist/host/transcript-fs.js +104 -0
  127. package/dist/host/wire-request.d.ts +80 -0
  128. package/dist/host/wire-request.js +196 -0
  129. package/dist/host/workspace-fs.d.ts +14 -0
  130. package/dist/host/workspace-fs.js +84 -0
  131. package/dist/host/workspace-trust.d.ts +23 -0
  132. package/dist/host/workspace-trust.js +80 -0
  133. package/dist/identity/authorize.d.ts +59 -0
  134. package/dist/identity/authorize.js +72 -0
  135. package/dist/identity/config.d.ts +98 -0
  136. package/dist/identity/config.js +130 -0
  137. package/dist/identity/credential.d.ts +91 -0
  138. package/dist/identity/credential.js +76 -0
  139. package/dist/identity/device-code.d.ts +115 -0
  140. package/dist/identity/device-code.js +134 -0
  141. package/dist/identity/index.d.ts +33 -0
  142. package/dist/identity/index.js +10 -0
  143. package/dist/identity/mode.d.ts +106 -0
  144. package/dist/identity/mode.js +69 -0
  145. package/dist/identity/paired-credential.d.ts +52 -0
  146. package/dist/identity/paired-credential.js +43 -0
  147. package/dist/identity/pkce.d.ts +50 -0
  148. package/dist/identity/pkce.js +94 -0
  149. package/dist/identity/store.d.ts +43 -0
  150. package/dist/identity/store.js +43 -0
  151. package/dist/identity/token.d.ts +58 -0
  152. package/dist/identity/token.js +149 -0
  153. package/dist/index.d.ts +45 -0
  154. package/dist/index.js +53 -0
  155. package/dist/mcp/descriptor.d.ts +59 -0
  156. package/dist/mcp/descriptor.js +14 -0
  157. package/dist/mcp/index.d.ts +14 -0
  158. package/dist/mcp/index.js +4 -0
  159. package/dist/mcp/schema.d.ts +37 -0
  160. package/dist/mcp/schema.js +175 -0
  161. package/dist/mcp/server.d.ts +107 -0
  162. package/dist/mcp/server.js +66 -0
  163. package/dist/persistence/entry.d.ts +74 -0
  164. package/dist/persistence/entry.js +105 -0
  165. package/dist/persistence/index.d.ts +22 -0
  166. package/dist/persistence/index.js +8 -0
  167. package/dist/persistence/key.d.ts +46 -0
  168. package/dist/persistence/key.js +33 -0
  169. package/dist/persistence/mirror.d.ts +71 -0
  170. package/dist/persistence/mirror.js +57 -0
  171. package/dist/persistence/receipt.d.ts +111 -0
  172. package/dist/persistence/receipt.js +85 -0
  173. package/dist/persistence/retention.d.ts +68 -0
  174. package/dist/persistence/retention.js +68 -0
  175. package/dist/persistence/store.d.ts +106 -0
  176. package/dist/persistence/store.js +86 -0
  177. package/dist/persistence/transition-log.d.ts +73 -0
  178. package/dist/persistence/transition-log.js +133 -0
  179. package/dist/protocol.d.ts +27 -0
  180. package/dist/protocol.js +12 -0
  181. package/dist/sessions/index.d.ts +8 -0
  182. package/dist/sessions/index.js +4 -0
  183. package/dist/sessions/registry.d.ts +186 -0
  184. package/dist/sessions/registry.js +190 -0
  185. package/dist/sessions/session.d.ts +178 -0
  186. package/dist/sessions/session.js +288 -0
  187. package/dist/sessions/spawn-env.d.ts +88 -0
  188. package/dist/sessions/spawn-env.js +277 -0
  189. package/dist/state/coverage.d.ts +308 -0
  190. package/dist/state/coverage.js +315 -0
  191. package/dist/state/index.d.ts +12 -0
  192. package/dist/state/index.js +7 -0
  193. package/dist/state/machine.d.ts +112 -0
  194. package/dist/state/machine.js +237 -0
  195. package/dist/state/model.d.ts +233 -0
  196. package/dist/state/model.js +214 -0
  197. package/dist/state/observer.d.ts +55 -0
  198. package/dist/state/observer.js +413 -0
  199. package/dist/state/reporter.d.ts +45 -0
  200. package/dist/state/reporter.js +35 -0
  201. package/dist/state/store.d.ts +56 -0
  202. package/dist/state/store.js +120 -0
  203. package/dist/telemetry/index.d.ts +11 -0
  204. package/dist/telemetry/index.js +2 -0
  205. package/dist/telemetry/usage.d.ts +78 -0
  206. package/dist/telemetry/usage.js +69 -0
  207. package/dist/workspace/git-worktree.d.ts +150 -0
  208. package/dist/workspace/git-worktree.js +417 -0
  209. package/dist/workspace/index.d.ts +9 -0
  210. package/dist/workspace/index.js +4 -0
  211. package/dist/workspace/plain-dir.d.ts +34 -0
  212. package/dist/workspace/plain-dir.js +90 -0
  213. package/dist/workspace/provider.d.ts +152 -0
  214. package/dist/workspace/provider.js +2 -0
  215. package/dist/workspace/worktree-porcelain.d.ts +29 -0
  216. package/dist/workspace/worktree-porcelain.js +100 -0
  217. package/docs/architecture.md +277 -0
  218. package/docs/configuration.md +187 -0
  219. package/docs/gate.md +219 -0
  220. package/docs/identity.md +107 -0
  221. package/docs/protocol.md +501 -0
  222. package/docs/state-machine.md +160 -0
  223. package/examples/README.md +141 -0
  224. package/examples/minimal-controller/controller.ts +38 -0
  225. package/examples/parallel-run-proof/permission-mode-probe.ts +129 -0
  226. package/examples/parallel-run-proof/run.ts +850 -0
  227. package/examples/test-controller/controller.ts +655 -0
  228. package/examples/test-controller/serve.ts +29 -0
  229. package/examples/tsconfig.json +30 -0
  230. package/package.json +98 -0
@@ -0,0 +1,215 @@
1
+ import { GitWorktreeProvider, keyPreview, refnameOrPathIllegality, unusableKeyProblem, } from '../workspace/git-worktree.js';
2
+ import { PlainDirProvider } from '../workspace/plain-dir.js';
3
+ import { nodeCommandEffects, nodeWorkspaceEffects } from '../host/workspace-fs.js';
4
+ /** The two placeholder names a branch scheme may use. Anything else refuses at startup by name. */
5
+ const SCHEME_PLACEHOLDERS = ['key', 'repo'];
6
+ /** Render a branch scheme. Pure; exported so the rule is testable without a provider. */
7
+ export function renderBranch(scheme, key, repo) {
8
+ return scheme.replaceAll('{key}', key).replaceAll('{repo}', repo);
9
+ }
10
+ /**
11
+ * The first way this rendered branch name is illegal, or null.
12
+ *
13
+ * A legal key can render an illegal refname: a `{repo}` carrying a dot-prefixed segment, a
14
+ * scheme with a trailing slash, a literal ending `.lock`. Validating the key and then building an
15
+ * unvalidated branch from it would let a branch die inside git on every open, so the result is
16
+ * screened: per slash-separated component, against the same union rule the key already passed
17
+ * (`refnameOrPathIllegality`, the single source of that class).
18
+ */
19
+ export function branchNameProblem(branch) {
20
+ if (branch.trim() === '')
21
+ return 'is empty — a branch must have a name';
22
+ for (const component of branch.split('/')) {
23
+ if (component === '')
24
+ return `has an empty component — '${branch}' contains a doubled or edge slash`;
25
+ const illegality = refnameOrPathIllegality(component);
26
+ if (illegality !== null)
27
+ return `has a component '${component}' that ${illegality}`;
28
+ }
29
+ return null;
30
+ }
31
+ /**
32
+ * What is wrong with this workspace posture, at startup, or null when it is usable.
33
+ *
34
+ * Screened when the process boots, not at the first session. A machine that will refuse every
35
+ * session (an unusable default key, a scheme with a typo'd placeholder) must say so when it starts,
36
+ * where the one person who can fix it is looking, not days later when someone finally opens a
37
+ * session. Pure, and exported precisely so it is testable without starting a host.
38
+ *
39
+ * Three families of refusal, each by name:
40
+ * - a setting that depends on another that is absent (a key with no provider, a scheme with no
41
+ * repository); silently ignoring either is a misconfiguration nobody finds;
42
+ * - a default workspace key that fails the same union screen a wire-supplied key must pass;
43
+ * - a branch scheme with an unknown placeholder or an unmatched brace (never rendered literally;
44
+ * `{repoo}` or a trailing `{repo` in a branch name is a silent wrong answer), or whose literal
45
+ * text already renders illegally.
46
+ */
47
+ export function workspacePostureProblem(posture) {
48
+ const hasProvider = posture.workspaceRoot !== null && posture.workspaceRoot !== '';
49
+ const hasRepository = posture.repositoryRoot !== null && posture.repositoryRoot !== '';
50
+ if (posture.workspaceKey !== null && posture.workspaceKey !== '') {
51
+ if (!hasProvider) {
52
+ return ('PERISCOPE_WORKSPACE_KEY is set but PERISCOPE_WORKSPACE_ROOT is not — with no workspace ' +
53
+ 'provider there is nothing to provision at that key, so the setting would be silently ignored');
54
+ }
55
+ const problem = unusableKeyProblem(posture.workspaceKey);
56
+ if (problem !== null) {
57
+ return `PERISCOPE_WORKSPACE_KEY ${keyPreview(posture.workspaceKey)} ${problem} — every session on this host would refuse`;
58
+ }
59
+ }
60
+ if (posture.branchScheme !== null && posture.branchScheme !== '') {
61
+ if (!hasProvider || !hasRepository) {
62
+ return ('PERISCOPE_BRANCH_SCHEME is set but sessions get no git worktrees here (it needs both ' +
63
+ 'PERISCOPE_WORKSPACE_ROOT and PERISCOPE_REPOSITORY_ROOT) — the setting would be silently ignored');
64
+ }
65
+ for (const match of posture.branchScheme.matchAll(/\{([^}]*)\}/g)) {
66
+ const placeholder = match[1] ?? '';
67
+ if (!SCHEME_PLACEHOLDERS.includes(placeholder)) {
68
+ return (`PERISCOPE_BRANCH_SCHEME uses an unknown placeholder '{${placeholder}}' — the two it may use are ` +
69
+ `{key} and {repo}, and an unknown one is refused rather than rendered literally into a branch name`);
70
+ }
71
+ }
72
+ // A brace that survives placeholder substitution is by definition not a placeholder this host
73
+ // understands. The loop above sees only well-formed `{...}` groups, so a malformed brace is
74
+ // invisible to it: `periscope/{key}/{repo` would pass and render the literal branch
75
+ // `periscope/K/{repo`, and `{` is refname-legal, so the render screen below cannot catch it
76
+ // either. Screened on the scheme text, not the rendered name, deliberately: a repository
77
+ // directory legitimately named with a brace must still render, so `branchNameProblem` is the
78
+ // wrong layer for this rule.
79
+ const residue = posture.branchScheme.replace(/\{[^}]*\}/g, '');
80
+ if (residue.includes('{') || residue.includes('}')) {
81
+ return (`PERISCOPE_BRANCH_SCHEME contains an unmatched '{' or '}' — a brace that survives placeholder ` +
82
+ `substitution is not a placeholder this host understands, and it would render literally into a branch name`);
83
+ }
84
+ // The scheme's literal text, screened with benign placeholder values: a trailing slash or a
85
+ // literal `.lock` is wrong for every key, so it is caught here; a violation only a particular
86
+ // key or repo name produces is caught at render, inside the provider's own named refusal.
87
+ const rendered = renderBranch(posture.branchScheme, 'k', 'r');
88
+ const problem = branchNameProblem(rendered);
89
+ if (problem !== null) {
90
+ return `PERISCOPE_BRANCH_SCHEME renders an illegal branch name (${rendered} ${problem})`;
91
+ }
92
+ }
93
+ return null;
94
+ }
95
+ /** The last path segment of a repository root — what `{repo}` renders as. */
96
+ function repositoryName(repositoryRoot) {
97
+ const segments = repositoryRoot.split(/[\\/]+/).filter((segment) => segment !== '');
98
+ return segments[segments.length - 1] ?? '';
99
+ }
100
+ /**
101
+ * The branch scheme a git-worktree host renders when none is configured: the repository's name,
102
+ * then the key. With keys named after the platform's own identities (`session-150`,
103
+ * `run-34`) this puts every session on `repo/session-150`, the name an operator reads in
104
+ * the session's own URL. Explicitly configured schemes override it; the `workspace:branch-scheme`
105
+ * marker means "explicitly configured", never "this default".
106
+ */
107
+ export const DEFAULT_BRANCH_SCHEME = '{repo}/{key}';
108
+ /**
109
+ * The `branchFor` a scheme produces, extracted so the render-and-screen rule is testable without
110
+ * a provider, a repository, or a real git (the same reason this whole file exists).
111
+ *
112
+ * A render the screen refuses throws, and the throw is the contract: the provider calls this
113
+ * inside its own try, so the message lands in a named `workspace-provision-failed` refusal rather
114
+ * than dying inside git. This is the runtime half of the screen, for the violations only a
115
+ * particular key or repo name produces (the startup screen already caught the scheme's literal
116
+ * text).
117
+ */
118
+ export function branchRenderer(scheme, repositoryRoot) {
119
+ const repo = repositoryName(repositoryRoot);
120
+ return (sessionId) => {
121
+ const branch = renderBranch(scheme, sessionId, repo);
122
+ const problem = branchNameProblem(branch);
123
+ if (problem !== null) {
124
+ throw new Error(`the branch scheme '${scheme}' rendered an illegal branch name: '${branch}' ${problem}`);
125
+ }
126
+ return branch;
127
+ };
128
+ }
129
+ /**
130
+ * The provider selector: which workspace provider, if any, the shipped entry point composes.
131
+ *
132
+ * With no workspace root there is no provider at all and every session on a host shares the
133
+ * controller's `cwd` verbatim, the weakest isolation available. With a workspace root alone, each
134
+ * session gets a plain directory and no branch. With a repository root as well, each session gets
135
+ * a linked git worktree on its own branch, which is what lets a controller admit a push to a
136
+ * session's own branch.
137
+ *
138
+ * The presence of a repository root is the mode switch: a deployment that does not set one gets
139
+ * plain directories. There is no boolean to get backwards, and the git
140
+ * mode cannot be selected without naming the repository it would link worktrees to, which is the one
141
+ * fact the provider cannot default.
142
+ *
143
+ * No branch formula lives in the provider. `git-worktree.ts`'s header states that branch naming is
144
+ * the caller's decision, and this file is the caller: it always hands a `branchFor` in — the
145
+ * configured scheme, else `DEFAULT_BRANCH_SCHEME` — so the provider's own `periscope/{sessionId}`
146
+ * fallback is reached only by a hand composition that names nothing. The rendered name is
147
+ * screened: a render only a particular key or repo produces illegally throws here, which the
148
+ * provider's own try/catch turns into a named `workspace-provision-failed` refusal carrying this
149
+ * message, never an unnamed death inside git.
150
+ */
151
+ /** The scheme a git-worktree host renders: the configured one, else the default. */
152
+ function effectiveScheme(configured) {
153
+ return configured === null || configured === '' ? DEFAULT_BRANCH_SCHEME : configured;
154
+ }
155
+ export function workspacesFor(config) {
156
+ if (config.workspaceRoot === null || config.workspaceRoot === '')
157
+ return null;
158
+ if (config.repositoryRoot !== null && config.repositoryRoot !== '') {
159
+ return new GitWorktreeProvider({
160
+ repositoryRoot: config.repositoryRoot,
161
+ workspaceRoot: config.workspaceRoot,
162
+ effects: nodeWorkspaceEffects,
163
+ commands: nodeCommandEffects(),
164
+ branchFor: branchRenderer(effectiveScheme(config.branchScheme), config.repositoryRoot),
165
+ });
166
+ }
167
+ return new PlainDirProvider({ root: config.workspaceRoot, effects: nodeWorkspaceEffects });
168
+ }
169
+ /**
170
+ * The mode, as capability markers for the hello: the read half of what `periscope config` writes.
171
+ * A host that can be configured but cannot report how it is configured gives a controller nothing
172
+ * to verify.
173
+ *
174
+ * A pure twin of `workspacesFor`, kept beside it so the two cannot drift: the marker is derived
175
+ * from the same predicates that choose the provider, never from a second reading of the
176
+ * environment. Exactly one `workspace:*` mode marker is always present; absence of all three in a
177
+ * hello therefore means "this build does not report", which is what lets a controller render
178
+ * "not reported" instead of a default.
179
+ *
180
+ * Markers, not values. Which repository root, which scheme text: those would be payload members
181
+ * and a protocol-version change, deliberately not smuggled into marker strings.
182
+ */
183
+ export function workspaceCapabilitiesOf(config) {
184
+ if (config.workspaceRoot === null || config.workspaceRoot === '')
185
+ return ['workspace:none'];
186
+ if (config.repositoryRoot !== null && config.repositoryRoot !== '') {
187
+ const hasScheme = config.branchScheme !== null && config.branchScheme !== '';
188
+ return hasScheme ? ['workspace:git-worktree', 'workspace:branch-scheme'] : ['workspace:git-worktree'];
189
+ }
190
+ return ['workspace:plain'];
191
+ }
192
+ /**
193
+ * The values behind the markers: what the hello reports as `configuration`.
194
+ *
195
+ * The other pure twin of `workspacesFor`. Read from the same `WorkspaceConfig` the selector and
196
+ * the markers consume, so a root the selector treats as unset (null or empty) is reported as
197
+ * null here, never as an empty string a controller would render as a path. A value present here
198
+ * and a `workspace:none` marker cannot both be true of one config, and the test pins that.
199
+ */
200
+ export function hostConfigurationOf(config, extras) {
201
+ const setOrNull = (value) => (value === null || value === '' ? null : value);
202
+ const gitMode = setOrNull(config.workspaceRoot) !== null && setOrNull(config.repositoryRoot) !== null;
203
+ return {
204
+ repositoryRoot: setOrNull(config.repositoryRoot),
205
+ workspaceRoot: setOrNull(config.workspaceRoot),
206
+ // The EFFECTIVE scheme: what a provision would render. In git mode that is never null; the
207
+ // marker beside it says whether it was configured or defaulted.
208
+ branchScheme: gitMode ? effectiveScheme(config.branchScheme) : setOrNull(config.branchScheme),
209
+ transcriptsRoot: setOrNull(extras.transcriptsRoot),
210
+ controllerUrl: setOrNull(extras.controllerUrl),
211
+ decisionUrl: setOrNull(extras.decisionUrl),
212
+ agentHome: setOrNull(extras.agentHome),
213
+ };
214
+ }
215
+ //# sourceMappingURL=workspaces.js.map
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Reconnect delay: exponential, and jittered.
3
+ *
4
+ * The jitter is the load-bearing half. A controller restart drops every host at the same instant;
5
+ * without jitter they all wait the same exponential delay and reconnect in lockstep, which knocks
6
+ * the controller over again and does it harder on each round.
7
+ */
8
+ export interface BackoffOptions {
9
+ /** Floor, and the first attempt's ceiling. */
10
+ readonly baseMs: number;
11
+ /** Ceiling, however many attempts have failed. */
12
+ readonly maxMs: number;
13
+ /** Growth per attempt. */
14
+ readonly factor: number;
15
+ }
16
+ export declare const DEFAULT_BACKOFF: BackoffOptions;
17
+ /**
18
+ * The delay before attempt `attempt` (0-based, so 0 is the first retry).
19
+ *
20
+ * Jittered across the whole window above the floor rather than a fixed fraction of it, so two
21
+ * hosts drawing from different streams separate on the first retry instead of converging.
22
+ * `random` is injected: a caller that cannot control it cannot test that the spread exists.
23
+ */
24
+ export declare function nextDelayMs(attempt: number, random: () => number, options?: BackoffOptions): number;
25
+ //# sourceMappingURL=backoff.d.ts.map
@@ -0,0 +1,20 @@
1
+ export const DEFAULT_BACKOFF = {
2
+ baseMs: 500,
3
+ maxMs: 30_000,
4
+ factor: 2,
5
+ };
6
+ /**
7
+ * The delay before attempt `attempt` (0-based, so 0 is the first retry).
8
+ *
9
+ * Jittered across the whole window above the floor rather than a fixed fraction of it, so two
10
+ * hosts drawing from different streams separate on the first retry instead of converging.
11
+ * `random` is injected: a caller that cannot control it cannot test that the spread exists.
12
+ */
13
+ export function nextDelayMs(attempt, random, options = DEFAULT_BACKOFF) {
14
+ const safeAttempt = Math.max(0, Math.floor(attempt));
15
+ const uncapped = options.baseMs * Math.pow(options.factor, safeAttempt);
16
+ const ceiling = Math.min(options.maxMs, uncapped);
17
+ const span = Math.max(0, ceiling - options.baseMs);
18
+ return Math.round(options.baseMs + random() * span);
19
+ }
20
+ //# sourceMappingURL=backoff.js.map
@@ -0,0 +1,15 @@
1
+ import type { Result } from '../core/result.js';
2
+ import type { Frame } from './frames.js';
3
+ /**
4
+ * A frame to a wire string.
5
+ *
6
+ * Refuses a frame that does not match the shape `decode` accepts, with the same per-kind schema, so
7
+ * a malformed frame is refused at the caller and never minted a sequence number; a frame that only
8
+ * the peer refused would burn a number the receiver then waits on. Refuses anything past
9
+ * MAX_FRAME_BYTES, naming the bulk lane — this is the enforcement point for "commands only, never
10
+ * payloads", so the check is here rather than at each call site that might forget it.
11
+ */
12
+ export declare function encode(frame: Frame): Result<string>;
13
+ /** A wire string to a frame. Never throws: every failure path returns a named refusal. */
14
+ export declare function decode(raw: string): Result<Frame>;
15
+ //# sourceMappingURL=codec.d.ts.map