@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,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
|