@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,195 @@
1
+ import { isAbsolutePath, normalizePath } from '../core/paths.js';
2
+ import { ok, refuse } from '../core/result.js';
3
+ import { claudeTranscriptResolver, defaultAgentHome, transcriptsRootUnder, } from '../host/claude-transcripts.js';
4
+ import { RESTART_KEYS, WIRE_CONFIGURABLE_KEYS, isWireConfigurableKey, readConfigFile, withConfigFallback, writeConfigEntries, } from '../host/config-file.js';
5
+ import { hostConfigurationOf, workspaceCapabilitiesOf, workspacePostureProblem, workspacesFor, } from './workspaces.js';
6
+ function setOrNull(value) {
7
+ return value === undefined || value === '' ? null : value;
8
+ }
9
+ function postureOf(merged) {
10
+ return {
11
+ workspaceRoot: setOrNull(merged['PERISCOPE_WORKSPACE_ROOT']),
12
+ repositoryRoot: setOrNull(merged['PERISCOPE_REPOSITORY_ROOT']),
13
+ branchScheme: setOrNull(merged['PERISCOPE_BRANCH_SCHEME']),
14
+ workspaceKey: setOrNull(merged['PERISCOPE_WORKSPACE_KEY']),
15
+ agentHome: setOrNull(merged['PERISCOPE_AGENT_HOME']),
16
+ controllerUrl: setOrNull(merged['PERISCOPE_CONTROLLER_URL']),
17
+ decisionUrl: setOrNull(merged['PERISCOPE_DECISION_URL']),
18
+ };
19
+ }
20
+ /**
21
+ * Why a control-plane address cannot be written, or null. The link is dialled as a WebSocket and
22
+ * the decision endpoint is POSTed to, so each key takes the schemes its act can use and nothing
23
+ * else: a value that fits the other key would fail at the first act after the restart it waits for.
24
+ */
25
+ /**
26
+ * The first thing wrong with ONE value for ONE key, or null: the per-key half of `candidateProblem`,
27
+ * shared with `periscope config` and the daemon's start-up read so a value the wire would refuse is
28
+ * refused wherever it is typed. A key this screen does not know passes; the closed key set is
29
+ * checked by the writer.
30
+ */
31
+ export function configValueProblem(key, value) {
32
+ switch (key) {
33
+ case 'PERISCOPE_CONTROLLER_URL':
34
+ return addressProblem(key, value, ['ws:', 'wss:']);
35
+ case 'PERISCOPE_DECISION_URL':
36
+ return addressProblem(key, value, ['http:', 'https:']);
37
+ case 'PERISCOPE_WORKSPACE_ROOT':
38
+ case 'PERISCOPE_REPOSITORY_ROOT':
39
+ case 'PERISCOPE_AGENT_HOME':
40
+ return isAbsolutePath(value) ? null : `${key} must be an absolute path — got '${value}'`;
41
+ case 'PERISCOPE_BRANCH_SCHEME':
42
+ return value.includes('{key}')
43
+ ? null
44
+ : `PERISCOPE_BRANCH_SCHEME '${value}' has no {key} placeholder — every workspace would render the same branch`;
45
+ default:
46
+ return null;
47
+ }
48
+ }
49
+ function addressProblem(key, value, schemes) {
50
+ if (value === null)
51
+ return null;
52
+ let parsed;
53
+ try {
54
+ parsed = new URL(value);
55
+ }
56
+ catch {
57
+ return `${key} must be an absolute URL — got '${value}'`;
58
+ }
59
+ if (!schemes.includes(parsed.protocol)) {
60
+ return `${key} must use ${schemes.map((scheme) => scheme.slice(0, -1)).join(' or ')} — got '${value}'`;
61
+ }
62
+ return null;
63
+ }
64
+ function sameRoot(a, b) {
65
+ if (a === null || b === null)
66
+ return a === b;
67
+ return normalizePath(a).toLowerCase() === normalizePath(b).toLowerCase();
68
+ }
69
+ /** The wire-settable keys the environment sets: their file values are unreachable. */
70
+ export function overriddenByEnvironment(raw) {
71
+ return WIRE_CONFIGURABLE_KEYS.filter((key) => {
72
+ const value = raw[key];
73
+ return value !== undefined && value !== '';
74
+ });
75
+ }
76
+ /**
77
+ * The first thing wrong with a candidate posture, or null. The same screen start-up runs
78
+ * (`workspacePostureProblem`) plus the rules the wire needs that start-up gets from the shell: a
79
+ * root must be absolute, and a scheme must render distinct branches (contain `{key}`).
80
+ */
81
+ export function candidateProblem(posture) {
82
+ for (const [key, value] of [
83
+ ['PERISCOPE_WORKSPACE_ROOT', posture.workspaceRoot],
84
+ ['PERISCOPE_REPOSITORY_ROOT', posture.repositoryRoot],
85
+ ['PERISCOPE_AGENT_HOME', posture.agentHome],
86
+ ]) {
87
+ if (value !== null && !isAbsolutePath(value))
88
+ return `${key} must be an absolute path — got '${value}'`;
89
+ }
90
+ if (posture.branchScheme !== null && !posture.branchScheme.includes('{key}')) {
91
+ return `PERISCOPE_BRANCH_SCHEME '${posture.branchScheme}' has no {key} placeholder — every workspace would render the same branch`;
92
+ }
93
+ const controller = addressProblem('PERISCOPE_CONTROLLER_URL', posture.controllerUrl, ['ws:', 'wss:']);
94
+ if (controller !== null)
95
+ return controller;
96
+ const decision = addressProblem('PERISCOPE_DECISION_URL', posture.decisionUrl, ['http:', 'https:']);
97
+ if (decision !== null)
98
+ return decision;
99
+ return workspacePostureProblem({
100
+ workspaceRoot: posture.workspaceRoot,
101
+ repositoryRoot: posture.repositoryRoot,
102
+ branchScheme: posture.branchScheme,
103
+ workspaceKey: posture.workspaceKey,
104
+ });
105
+ }
106
+ /**
107
+ * Apply the entries: screen every key, screen the candidate posture as a whole, refuse a roots
108
+ * change while busy, write the file once, and rebuild what the host swaps in.
109
+ */
110
+ export function reconfigureHost(raw, entries, hostBusy, live) {
111
+ const seen = new Set();
112
+ for (const entry of entries) {
113
+ if (!isWireConfigurableKey(entry.key)) {
114
+ return refuse('config-key-unknown', `'${entry.key}' is not a key this host takes over the wire — the settable keys are ${WIRE_CONFIGURABLE_KEYS.join(', ')}`);
115
+ }
116
+ if (seen.has(entry.key)) {
117
+ return refuse('config-value-invalid', `'${entry.key}' is named twice in one ask`);
118
+ }
119
+ seen.add(entry.key);
120
+ }
121
+ const file = readConfigFile(raw);
122
+ if (file.problem !== null) {
123
+ return refuse('config-write-failed', `${file.problem} — fix or remove it before writing`);
124
+ }
125
+ const before = postureOf(withConfigFallback(raw, file.values));
126
+ // The candidate: the file with the entries applied, under the environment. An empty value is the
127
+ // documented twin of unset everywhere this package reads, so it removes the key like null does.
128
+ const nextValues = { ...file.values };
129
+ const writes = [];
130
+ for (const entry of entries) {
131
+ const value = entry.value === null || entry.value === '' ? null : entry.value;
132
+ if (value === null)
133
+ delete nextValues[entry.key];
134
+ else
135
+ nextValues[entry.key] = value;
136
+ writes.push({ key: entry.key, value });
137
+ }
138
+ // A written address is screened on its own, before the merge: the environment may shadow it today,
139
+ // and a bad value would still be the one the next start reads once the shadow lifts.
140
+ for (const write of writes) {
141
+ if (write.value === null)
142
+ continue;
143
+ const problem = write.key === 'PERISCOPE_CONTROLLER_URL'
144
+ ? addressProblem(write.key, write.value, ['ws:', 'wss:'])
145
+ : write.key === 'PERISCOPE_DECISION_URL'
146
+ ? addressProblem(write.key, write.value, ['http:', 'https:'])
147
+ : null;
148
+ if (problem !== null)
149
+ return refuse('config-value-invalid', problem);
150
+ }
151
+ const merged = withConfigFallback(raw, nextValues);
152
+ const after = postureOf(merged);
153
+ const problem = candidateProblem(after);
154
+ if (problem !== null)
155
+ return refuse('config-value-invalid', problem);
156
+ const rootsChange = !sameRoot(before.workspaceRoot, after.workspaceRoot) ||
157
+ !sameRoot(before.repositoryRoot, after.repositoryRoot);
158
+ if (rootsChange && hostBusy) {
159
+ return refuse('config-host-busy', 'a workspace root cannot change while a session is live or opening on this host — close every session and ask again');
160
+ }
161
+ const written = writeConfigEntries(raw, writes);
162
+ if (written !== null)
163
+ return refuse('config-write-failed', written);
164
+ const workspaceConfig = {
165
+ workspaceRoot: after.workspaceRoot,
166
+ repositoryRoot: after.repositoryRoot,
167
+ branchScheme: after.branchScheme,
168
+ };
169
+ const agentHome = after.agentHome ?? defaultAgentHome(raw);
170
+ const transcriptsRoot = agentHome === null ? null : transcriptsRootUnder(agentHome);
171
+ // The addresses in effect are what the host dialled at start; absent a caller's word, the merged
172
+ // view before this ask is the closest thing to it. A key whose file value differs is pending.
173
+ const dialled = live ?? {
174
+ controllerUrl: before.controllerUrl,
175
+ decisionUrl: before.decisionUrl,
176
+ };
177
+ const pendingRestart = RESTART_KEYS.filter((key) => key === 'PERISCOPE_CONTROLLER_URL'
178
+ ? after.controllerUrl !== dialled.controllerUrl
179
+ : after.decisionUrl !== dialled.decisionUrl);
180
+ return ok({
181
+ workspaces: workspacesFor(workspaceConfig) ?? undefined,
182
+ transcriptsRoot: transcriptsRoot ?? undefined,
183
+ bulk: transcriptsRoot === null ? undefined : claudeTranscriptResolver(transcriptsRoot),
184
+ linkCapabilities: workspaceCapabilitiesOf(workspaceConfig),
185
+ configuration: hostConfigurationOf(workspaceConfig, {
186
+ transcriptsRoot,
187
+ controllerUrl: after.controllerUrl,
188
+ decisionUrl: after.decisionUrl,
189
+ agentHome,
190
+ }),
191
+ overriddenByEnvironment: overriddenByEnvironment(raw),
192
+ pendingRestart,
193
+ });
194
+ }
195
+ //# sourceMappingURL=reconfigure.js.map
@@ -0,0 +1,103 @@
1
+ /**
2
+ * `periscope serve`: dial the controller and serve sessions. The daemon every supervisor starts.
3
+ *
4
+ * Two views of the environment arrive, and the split is deliberate. Configuration (`readConfig`)
5
+ * comes from the merged view, where the config file fills absences. Credential material, the
6
+ * environment sessions are filtered from, and the home directory come from the raw view: nothing
7
+ * outside the closed config key set may arrive from a file, and the credential files are found by
8
+ * the same `PERISCOPE_CONFIG_DIR` that says where the config file itself is.
9
+ *
10
+ * Every process-level edge is a dependency: where lines go, how the exit code is latched, how the
11
+ * process is exited, how signals are subscribed. The link and the agent process are injectable for
12
+ * the same reason, so the whole start-up sequence runs in a test with no socket and no agent.
13
+ */
14
+ import type { ControllerCredential } from '../control/credential.js';
15
+ import type { LinkHandlers } from '../control/link.js';
16
+ import type { HostEvent } from '../host/index.js';
17
+ import type { HostLink } from '../host/host.js';
18
+ import { PeriscopeHost } from '../host/index.js';
19
+ import type { AgentProcess, AgentProcessRequest } from '../host/agent-process.js';
20
+ import type { EscalationTransport } from '../gate/escalate.js';
21
+ /**
22
+ * How long the fatal-credential path holds the event loop open before exiting explicitly.
23
+ *
24
+ * Long enough for the stdout trace and the stderr remedy to flush through their pipes, short enough
25
+ * that a supervisor sees the exit promptly. It is not a grace period for work in flight; the host
26
+ * is already stopped by the time this runs.
27
+ */
28
+ export declare const FATAL_EXIT_FLUSH_MS = 50;
29
+ /** The two environments the daemon reads. See the module header for which reads which. */
30
+ export interface ServeViews {
31
+ /** The process environment as received. Credentials, the session base env and the home read it. */
32
+ readonly raw: NodeJS.ProcessEnv;
33
+ /**
34
+ * The environment with the config file filling absences, or the problem that made the file
35
+ * unusable. Checked after the root refusal, so a host that cannot run at all is told that first.
36
+ */
37
+ readonly merged: NodeJS.ProcessEnv | string;
38
+ }
39
+ /** The edges. Every one has a production default in `main.ts`; a test replaces them all. */
40
+ export interface ServeDeps {
41
+ /** One stdout line, without its newline: the trace. */
42
+ readonly log: (line: string) => void;
43
+ /** One stderr line, without its newline: refusals and the fatal remedy. */
44
+ readonly stderr: (line: string) => void;
45
+ readonly setExitCode: (code: number) => void;
46
+ /** Ends the process now. Called only on the fatal-credential path, after the flush delay. */
47
+ readonly exit: (code: number) => void;
48
+ readonly onSignal: (signal: 'SIGTERM' | 'SIGINT', handler: () => void) => void;
49
+ /** The decision POST's transport. Defaults to global fetch. */
50
+ readonly transport?: EscalationTransport;
51
+ /** How the link is built. Defaults to the real `ControllerLink`. */
52
+ readonly link?: (handlers: LinkHandlers) => HostLink;
53
+ /** The effective uid, or null where the platform has none (win32). Defaults to the process's. */
54
+ readonly getuid?: (() => number) | null;
55
+ /** How an agent process is started. Defaults to the real one. */
56
+ readonly startProcess?: (request: AgentProcessRequest) => AgentProcess;
57
+ }
58
+ export type ServeOutcome = {
59
+ readonly ok: true;
60
+ readonly host: PeriscopeHost;
61
+ } | {
62
+ readonly ok: false;
63
+ readonly detail: string;
64
+ };
65
+ /** One trace line: `<clock> [channel] message — detail`. */
66
+ export type Logger = (channel: string, message: string, detail: string | null) => void;
67
+ /** What `readCredential` decided: the credential (or none), and, when the paired branch chose, the
68
+ * host id the credential is bound to, which overrides the configured one at `link_hello`. */
69
+ export interface CredentialPosture {
70
+ readonly credential: ControllerCredential | null;
71
+ readonly pairedHostId: string | null;
72
+ }
73
+ /**
74
+ * Which credential this host presents, or null when it presents none.
75
+ *
76
+ * The paired credential is preferred over the OIDC cache, and the preference is the point of
77
+ * pairing: an OIDC refresh token can expire after a period of inactivity, the paired credential has
78
+ * no clock. A machine that has run `periscope pair` dials on the paired credential even when a
79
+ * token cache also exists. A missing paired file falls through to the OIDC postures; a corrupt one
80
+ * is fatal rather than a fallback, because silently degrading to a maybe-dead refresh token would
81
+ * put the host back in exactly the ambiguous posture pairing exists to end.
82
+ *
83
+ * The OIDC three-way decision itself lives in `identity/config.ts` and is tested there; this is the
84
+ * wiring. A misconfiguration returns a string, which `runServe` treats as fatal: a host that was
85
+ * told to use identity and cannot must not come up looking healthy while authenticating as nobody.
86
+ *
87
+ * The `absent` posture returns null rather than a refusing credential, which is the whole
88
+ * difference between "no identity" and "no tool calls". `UnconfiguredCredential` refuses by name on
89
+ * every call, and `escalatingDecider` treats a refusing credential as an outage and does not send
90
+ * the request, so handing one to it would turn every escalation in every session into
91
+ * `permission-decision-unavailable`. That is the exact outcome `readConfig` above refuses to ship
92
+ * for the missing-URL case, in the same file, for the same stated reason: a host that comes up
93
+ * healthy, accepts sessions, and blocks every tool call reads to an operator as the agent being
94
+ * broken. Null means the option is omitted, which `EscalationOptions.credential` documents as the
95
+ * supported no-identity mode; the placeholder stays the exported shape for an embedder that wants a
96
+ * credential that says no out loud.
97
+ */
98
+ export declare function readCredential(env: NodeJS.ProcessEnv, log: Logger): CredentialPosture | string;
99
+ /** Start the daemon. Returns the running host, or the refusal already written to stderr. */
100
+ export declare function runServe(views: ServeViews, deps: ServeDeps): ServeOutcome;
101
+ /** Every named thing the host reports, as one line each. The only output this process produces. */
102
+ export declare function report(event: HostEvent, log: Logger): void;
103
+ //# sourceMappingURL=serve.d.ts.map