@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,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What this host can say about the sessions it is holding.
|
|
3
|
+
*
|
|
4
|
+
* This is not a roster, and the distinction is not pedantry. A roster spans every session
|
|
5
|
+
* everywhere — and sessions run on different hosts, because an agent runs where its host runs. No
|
|
6
|
+
* single host can produce one; it can only produce its own row. A controller aggregates these into
|
|
7
|
+
* the thing people actually want.
|
|
8
|
+
*
|
|
9
|
+
* So this offers enumeration and nothing else: no filtering, no search, no notion of which sessions
|
|
10
|
+
* are interesting, no "who is stuck". Every one of those is a judgement, and a host that makes
|
|
11
|
+
* judgements is a host that has to be rebuilt for the next product. If this file grows a predicate,
|
|
12
|
+
* the line has been crossed.
|
|
13
|
+
*
|
|
14
|
+
* What it does owe: the open-entry age. A session that has been in one tool call for forty
|
|
15
|
+
* minutes is the single most useful thing this layer can report, and the age is what says so.
|
|
16
|
+
*/
|
|
17
|
+
import type { AgedEntry, SessionSnapshot } from './model.js';
|
|
18
|
+
import type { SessionStateMachine } from './machine.js';
|
|
19
|
+
export declare class SessionStateReporter {
|
|
20
|
+
#private;
|
|
21
|
+
/** Start reporting a session. Idempotent. */
|
|
22
|
+
add(machine: SessionStateMachine): void;
|
|
23
|
+
/**
|
|
24
|
+
* Stop reporting a session.
|
|
25
|
+
*
|
|
26
|
+
* A caller that removes an ended session loses its open-entry evidence from this view. That is
|
|
27
|
+
* the caller's decision to make and it is not made here: nothing self-evicts, because a session
|
|
28
|
+
* that ended holding work is exactly the one somebody will want to look at.
|
|
29
|
+
*/
|
|
30
|
+
remove(machine: SessionStateMachine): void;
|
|
31
|
+
get count(): number;
|
|
32
|
+
/** Every session this host holds. Raw material — see this file's header. */
|
|
33
|
+
list(): SessionSnapshot[];
|
|
34
|
+
/**
|
|
35
|
+
* Every entry that has not exited, across this host's sessions, with its age and its session.
|
|
36
|
+
*
|
|
37
|
+
* Reported, never reconciled: an unpaired entry is surfaced with how long it has been open and
|
|
38
|
+
* whether it was marked abandoned, and nothing here closes one. Cleanup may mark; it may not
|
|
39
|
+
* erase, because erasing destroys the only evidence that the thing happened at all.
|
|
40
|
+
*/
|
|
41
|
+
openEntries(): (AgedEntry & {
|
|
42
|
+
readonly sessionId: string | null;
|
|
43
|
+
})[];
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=reporter.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export class SessionStateReporter {
|
|
2
|
+
#machines = new Set();
|
|
3
|
+
/** Start reporting a session. Idempotent. */
|
|
4
|
+
add(machine) {
|
|
5
|
+
this.#machines.add(machine);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Stop reporting a session.
|
|
9
|
+
*
|
|
10
|
+
* A caller that removes an ended session loses its open-entry evidence from this view. That is
|
|
11
|
+
* the caller's decision to make and it is not made here: nothing self-evicts, because a session
|
|
12
|
+
* that ended holding work is exactly the one somebody will want to look at.
|
|
13
|
+
*/
|
|
14
|
+
remove(machine) {
|
|
15
|
+
this.#machines.delete(machine);
|
|
16
|
+
}
|
|
17
|
+
get count() {
|
|
18
|
+
return this.#machines.size;
|
|
19
|
+
}
|
|
20
|
+
/** Every session this host holds. Raw material — see this file's header. */
|
|
21
|
+
list() {
|
|
22
|
+
return [...this.#machines].map((machine) => machine.snapshot());
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Every entry that has not exited, across this host's sessions, with its age and its session.
|
|
26
|
+
*
|
|
27
|
+
* Reported, never reconciled: an unpaired entry is surfaced with how long it has been open and
|
|
28
|
+
* whether it was marked abandoned, and nothing here closes one. Cleanup may mark; it may not
|
|
29
|
+
* erase, because erasing destroys the only evidence that the thing happened at all.
|
|
30
|
+
*/
|
|
31
|
+
openEntries() {
|
|
32
|
+
return [...this.#machines].flatMap((machine) => machine.openEntries().map((entry) => ({ ...entry, sessionId: machine.sessionId })));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=reporter.js.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where recorded transitions are kept, and where anyone asking "what happened?" reads from.
|
|
3
|
+
*
|
|
4
|
+
* Separate from the machine on purpose. A machine that both emitted and answered questions about
|
|
5
|
+
* itself would let a test prove emission by asking the thing that emitted — the assertion and the
|
|
6
|
+
* subject would be the same object. This subscribes, so "no transition without a cause" is checked
|
|
7
|
+
* against what was actually retained rather than against what a call site claims it passed.
|
|
8
|
+
*
|
|
9
|
+
* Bounded, in memory, and not durable. It holds a window per session so a long-running host does
|
|
10
|
+
* not grow without limit; durability of the transition log belongs to the persistence layer, and
|
|
11
|
+
* this deliberately does not pretend to be it. `dropped` counts what fell out of the window rather
|
|
12
|
+
* than letting a full ring look like a quiet one.
|
|
13
|
+
*/
|
|
14
|
+
import type { SessionTransition } from './model.js';
|
|
15
|
+
import type { RejectedTransition, SessionStateMachine, Unsubscribe } from './machine.js';
|
|
16
|
+
export interface TransitionStoreOptions {
|
|
17
|
+
readonly windowPerSession?: number;
|
|
18
|
+
}
|
|
19
|
+
export declare class TransitionStore {
|
|
20
|
+
#private;
|
|
21
|
+
constructor(options?: TransitionStoreOptions);
|
|
22
|
+
/**
|
|
23
|
+
* Retain everything one machine records, and everything it refuses.
|
|
24
|
+
*
|
|
25
|
+
* Both halves matter: a store that kept only the successes would answer "were all transitions
|
|
26
|
+
* caused?" with a yes it earned by discarding the counter-examples.
|
|
27
|
+
*/
|
|
28
|
+
attach(machine: SessionStateMachine): Unsubscribe;
|
|
29
|
+
/**
|
|
30
|
+
* One session's transitions, oldest first.
|
|
31
|
+
*
|
|
32
|
+
* Transitions recorded before the agent named itself are re-keyed when it does. They were
|
|
33
|
+
* genuinely recorded without an id — that window is real — but leaving them in a separate bucket
|
|
34
|
+
* would mean the trace of a session's own start could not be read by that session's id, which is
|
|
35
|
+
* the one thing a reader has.
|
|
36
|
+
*/
|
|
37
|
+
forSession(sessionId: string): readonly SessionTransition[];
|
|
38
|
+
/** Transitions recorded while no session had reported itself yet, across every machine. */
|
|
39
|
+
unidentified(): readonly SessionTransition[];
|
|
40
|
+
/** Everything retained, across every session. The subject of the store-level assertions. */
|
|
41
|
+
all(): SessionTransition[];
|
|
42
|
+
sessionIds(): string[];
|
|
43
|
+
/** Every refused transition still held, with why. Never summarised into a count alone. */
|
|
44
|
+
rejected(): readonly RejectedTransition[];
|
|
45
|
+
/** How many transitions fell out of a window. A full ring must not read as a quiet one. */
|
|
46
|
+
get droppedCount(): number;
|
|
47
|
+
/**
|
|
48
|
+
* How many rejections fell out of their window.
|
|
49
|
+
*
|
|
50
|
+
* Counted separately from `droppedCount` because the two mean opposite things: transitions
|
|
51
|
+
* falling out is an ordinary busy session, rejections falling out means something is refusing
|
|
52
|
+
* faster than anyone is reading, and collapsing them into one number would hide that.
|
|
53
|
+
*/
|
|
54
|
+
get droppedRejectionCount(): number;
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=store.d.ts.map
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/** Per session. A session that outruns this loses its oldest transitions, and says how many. */
|
|
2
|
+
const DEFAULT_WINDOW = 500;
|
|
3
|
+
/** Keyed while a session has no id yet — see SessionTransition.sessionId. */
|
|
4
|
+
const UNIDENTIFIED = '(unidentified)';
|
|
5
|
+
export class TransitionStore {
|
|
6
|
+
#window;
|
|
7
|
+
#bySession = new Map();
|
|
8
|
+
#rejected = [];
|
|
9
|
+
#dropped = 0;
|
|
10
|
+
#droppedRejections = 0;
|
|
11
|
+
constructor(options = {}) {
|
|
12
|
+
this.#window = options.windowPerSession ?? DEFAULT_WINDOW;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Retain everything one machine records, and everything it refuses.
|
|
16
|
+
*
|
|
17
|
+
* Both halves matter: a store that kept only the successes would answer "were all transitions
|
|
18
|
+
* caused?" with a yes it earned by discarding the counter-examples.
|
|
19
|
+
*/
|
|
20
|
+
attach(machine) {
|
|
21
|
+
const dropTransitions = machine.onTransition((transition) => this.#retain(transition));
|
|
22
|
+
const dropRejections = machine.onRejected((rejected) => {
|
|
23
|
+
// Bounded on the same terms as the transitions, and for the same reason: a rejection arrives
|
|
24
|
+
// from whatever drives the machine, and once that is a wire path the rate is a stranger's to
|
|
25
|
+
// set. An uncapped array beside a capped ring would make the counter-examples the thing that
|
|
26
|
+
// grows without limit — the one collection nobody thinks to watch.
|
|
27
|
+
this.#rejected.push(rejected);
|
|
28
|
+
while (this.#rejected.length > this.#window) {
|
|
29
|
+
this.#rejected.shift();
|
|
30
|
+
this.#droppedRejections += 1;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return () => {
|
|
34
|
+
dropTransitions();
|
|
35
|
+
dropRejections();
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* One session's transitions, oldest first.
|
|
40
|
+
*
|
|
41
|
+
* Transitions recorded before the agent named itself are re-keyed when it does. They were
|
|
42
|
+
* genuinely recorded without an id — that window is real — but leaving them in a separate bucket
|
|
43
|
+
* would mean the trace of a session's own start could not be read by that session's id, which is
|
|
44
|
+
* the one thing a reader has.
|
|
45
|
+
*/
|
|
46
|
+
forSession(sessionId) {
|
|
47
|
+
return this.#bySession.get(sessionId) ?? [];
|
|
48
|
+
}
|
|
49
|
+
/** Transitions recorded while no session had reported itself yet, across every machine. */
|
|
50
|
+
unidentified() {
|
|
51
|
+
return this.#bySession.get(UNIDENTIFIED) ?? [];
|
|
52
|
+
}
|
|
53
|
+
/** Everything retained, across every session. The subject of the store-level assertions. */
|
|
54
|
+
all() {
|
|
55
|
+
return [...this.#bySession.values()].flat();
|
|
56
|
+
}
|
|
57
|
+
sessionIds() {
|
|
58
|
+
return [...this.#bySession.keys()].filter((key) => key !== UNIDENTIFIED);
|
|
59
|
+
}
|
|
60
|
+
/** Every refused transition still held, with why. Never summarised into a count alone. */
|
|
61
|
+
rejected() {
|
|
62
|
+
return this.#rejected;
|
|
63
|
+
}
|
|
64
|
+
/** How many transitions fell out of a window. A full ring must not read as a quiet one. */
|
|
65
|
+
get droppedCount() {
|
|
66
|
+
return this.#dropped;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* How many rejections fell out of their window.
|
|
70
|
+
*
|
|
71
|
+
* Counted separately from `droppedCount` because the two mean opposite things: transitions
|
|
72
|
+
* falling out is an ordinary busy session, rejections falling out means something is refusing
|
|
73
|
+
* faster than anyone is reading, and collapsing them into one number would hide that.
|
|
74
|
+
*/
|
|
75
|
+
get droppedRejectionCount() {
|
|
76
|
+
return this.#droppedRejections;
|
|
77
|
+
}
|
|
78
|
+
#retain(transition) {
|
|
79
|
+
const key = transition.sessionId ?? UNIDENTIFIED;
|
|
80
|
+
this.#adopt(transition);
|
|
81
|
+
const kept = this.#bySession.get(key) ?? [];
|
|
82
|
+
kept.push(transition);
|
|
83
|
+
while (kept.length > this.#window) {
|
|
84
|
+
kept.shift();
|
|
85
|
+
this.#dropped += 1;
|
|
86
|
+
}
|
|
87
|
+
this.#bySession.set(key, kept);
|
|
88
|
+
}
|
|
89
|
+
/** Move this machine's earlier, id-less transitions under the id it has just reported. */
|
|
90
|
+
#adopt(transition) {
|
|
91
|
+
const id = transition.sessionId;
|
|
92
|
+
if (id === null)
|
|
93
|
+
return;
|
|
94
|
+
const orphans = this.#bySession.get(UNIDENTIFIED);
|
|
95
|
+
if (orphans === undefined)
|
|
96
|
+
return;
|
|
97
|
+
// Only this machine's own: an id-less transition belongs to whichever machine produced the
|
|
98
|
+
// seq series it sits in, and a machine's seq is dense from 1. Anything at or above this
|
|
99
|
+
// transition's own seq cannot be its predecessor.
|
|
100
|
+
const mine = orphans.filter((orphan) => orphan.seq < transition.seq && sameOrigin(orphan, transition));
|
|
101
|
+
if (mine.length === 0)
|
|
102
|
+
return;
|
|
103
|
+
this.#bySession.set(UNIDENTIFIED, orphans.filter((orphan) => !mine.includes(orphan)));
|
|
104
|
+
const kept = this.#bySession.get(id) ?? [];
|
|
105
|
+
this.#bySession.set(id, [...mine, ...kept]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Whether two transitions came from the same machine, without either carrying a machine id.
|
|
110
|
+
*
|
|
111
|
+
* `where` is per-machine and fixed for its lifetime, and `correlationId` is whatever the controller
|
|
112
|
+
* supplied for that one session — so together they distinguish two concurrent id-less starts unless
|
|
113
|
+
* a controller starts two sessions in the same directory with no correlation id, in which case
|
|
114
|
+
* their early transitions are genuinely indistinguishable and both are adopted. That is a truthful
|
|
115
|
+
* outcome rather than a wrong one: nothing is lost, and nothing is invented to prevent it.
|
|
116
|
+
*/
|
|
117
|
+
function sameOrigin(left, right) {
|
|
118
|
+
return left.correlationId === right.correlationId && left.where.cwd === right.where.cwd;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage, cost and rate-limit facts — consumed from what the agent reports, never recomputed.
|
|
3
|
+
*
|
|
4
|
+
* There is no price table in this package and there must not be one. The agent reports a cost per
|
|
5
|
+
* model, with the provider and the id it priced against, so a mixed-model turn is correct without
|
|
6
|
+
* this host knowing any rate. A table would be wrong twice over and silently: stale the moment a
|
|
7
|
+
* price moves, and blind to a turn that used more than one model.
|
|
8
|
+
*/
|
|
9
|
+
export type { ModelSpend, RateLimitStanding, SpendTotal, TurnSpend } from './usage.js';
|
|
10
|
+
export { NO_SPEND, foldSpend, spendReconciles } from './usage.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a turn cost — consumed from what the agent reports, never computed from a price table.
|
|
3
|
+
*
|
|
4
|
+
* The whole point is that nothing here multiplies tokens by a rate. A host that keeps a per-model
|
|
5
|
+
* price table and does the arithmetic itself has two failure modes, and both are silent: a price
|
|
6
|
+
* changes and every historical figure is quietly wrong, and a mixed-model turn is costed at one
|
|
7
|
+
* model's rate because the table is keyed by "the session's model". The agent already reports a
|
|
8
|
+
* per-model cost, with the provider and the canonical id it priced against. So this reads.
|
|
9
|
+
*
|
|
10
|
+
* How that is actually proven, because "it only reads" is easy to claim and easy to regress: the
|
|
11
|
+
* tests feed a cost no price table could ever produce and assert it comes out unchanged. A
|
|
12
|
+
* reimplementation that started computing would have to reproduce an arbitrary number to stay green.
|
|
13
|
+
*
|
|
14
|
+
* No SDK types here. This is the model the layer above works in; `host/telemetry.ts` lifts it off
|
|
15
|
+
* the agent's own result message, the same way `readInitFacts` lifts the version receipt.
|
|
16
|
+
*/
|
|
17
|
+
/** What one model cost within a turn. Every number is reported, none derived. */
|
|
18
|
+
export interface ModelSpend {
|
|
19
|
+
/** The model string the agent was keyed by — provider-specific ids and aliases included. */
|
|
20
|
+
readonly model: string;
|
|
21
|
+
readonly costUsd: number;
|
|
22
|
+
readonly inputTokens: number;
|
|
23
|
+
readonly outputTokens: number;
|
|
24
|
+
readonly cacheReadInputTokens: number;
|
|
25
|
+
readonly cacheCreationInputTokens: number;
|
|
26
|
+
/** The window this model was serving with. Absent is null, never zero. */
|
|
27
|
+
readonly contextWindow: number | null;
|
|
28
|
+
/** The id the agent priced against, when it differs from `model`. */
|
|
29
|
+
readonly canonicalModel: string | null;
|
|
30
|
+
/** Which API served it, when reported. */
|
|
31
|
+
readonly provider: string | null;
|
|
32
|
+
}
|
|
33
|
+
/** One turn's spend. */
|
|
34
|
+
export interface TurnSpend {
|
|
35
|
+
/** The agent's own total. Not the sum of `byModel` — see `spendReconciles`. */
|
|
36
|
+
readonly totalCostUsd: number;
|
|
37
|
+
readonly byModel: readonly ModelSpend[];
|
|
38
|
+
}
|
|
39
|
+
/** Spend accumulated across turns. */
|
|
40
|
+
export interface SpendTotal {
|
|
41
|
+
readonly totalCostUsd: number;
|
|
42
|
+
readonly turnCount: number;
|
|
43
|
+
/** Per model, summed across every turn folded in. */
|
|
44
|
+
readonly byModel: readonly ModelSpend[];
|
|
45
|
+
}
|
|
46
|
+
export declare const NO_SPEND: SpendTotal;
|
|
47
|
+
/**
|
|
48
|
+
* Add one turn to a running total.
|
|
49
|
+
*
|
|
50
|
+
* Models are merged by their reported key, not by the canonical id. Two entries keyed differently
|
|
51
|
+
* that price against one canonical model are genuinely two things the agent distinguished — an alias
|
|
52
|
+
* and a provider-specific id can bill differently — and collapsing them here would throw away the
|
|
53
|
+
* distinction the report was making.
|
|
54
|
+
*/
|
|
55
|
+
export declare function foldSpend(prev: SpendTotal, turn: TurnSpend): SpendTotal;
|
|
56
|
+
/**
|
|
57
|
+
* Whether a turn's per-model costs add up to the total the agent reported.
|
|
58
|
+
*
|
|
59
|
+
* It is an observation, not a validation, and must not become one. Both numbers come from the
|
|
60
|
+
* agent; a mismatch means the agent counted something the per-model breakdown does not itemise, and
|
|
61
|
+
* that is the agent's business rather than a defect this host should refuse over. The reason to
|
|
62
|
+
* expose it is that a caller charging money wants to know which of the two it is quoting, and
|
|
63
|
+
* whether they agree.
|
|
64
|
+
*
|
|
65
|
+
* @param toleranceUsd absolute, because these are floats and an exact comparison would fail on
|
|
66
|
+
* arithmetic that is otherwise perfectly correct.
|
|
67
|
+
*/
|
|
68
|
+
export declare function spendReconciles(turn: TurnSpend, toleranceUsd?: number): boolean;
|
|
69
|
+
/** How a rate limit currently stands, as the agent reports it. */
|
|
70
|
+
export interface RateLimitStanding {
|
|
71
|
+
readonly status: 'allowed' | 'allowed_warning' | 'rejected';
|
|
72
|
+
/** Epoch seconds, as reported. Null when the agent did not say. */
|
|
73
|
+
readonly resetsAt: number | null;
|
|
74
|
+
readonly limitType: string | null;
|
|
75
|
+
/** 0–1 where reported, else null. Never defaulted to 0 — that would read as "plenty left". */
|
|
76
|
+
readonly utilization: number | null;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=usage.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a turn cost — consumed from what the agent reports, never computed from a price table.
|
|
3
|
+
*
|
|
4
|
+
* The whole point is that nothing here multiplies tokens by a rate. A host that keeps a per-model
|
|
5
|
+
* price table and does the arithmetic itself has two failure modes, and both are silent: a price
|
|
6
|
+
* changes and every historical figure is quietly wrong, and a mixed-model turn is costed at one
|
|
7
|
+
* model's rate because the table is keyed by "the session's model". The agent already reports a
|
|
8
|
+
* per-model cost, with the provider and the canonical id it priced against. So this reads.
|
|
9
|
+
*
|
|
10
|
+
* How that is actually proven, because "it only reads" is easy to claim and easy to regress: the
|
|
11
|
+
* tests feed a cost no price table could ever produce and assert it comes out unchanged. A
|
|
12
|
+
* reimplementation that started computing would have to reproduce an arbitrary number to stay green.
|
|
13
|
+
*
|
|
14
|
+
* No SDK types here. This is the model the layer above works in; `host/telemetry.ts` lifts it off
|
|
15
|
+
* the agent's own result message, the same way `readInitFacts` lifts the version receipt.
|
|
16
|
+
*/
|
|
17
|
+
export const NO_SPEND = { totalCostUsd: 0, turnCount: 0, byModel: [] };
|
|
18
|
+
/**
|
|
19
|
+
* Add one turn to a running total.
|
|
20
|
+
*
|
|
21
|
+
* Models are merged by their reported key, not by the canonical id. Two entries keyed differently
|
|
22
|
+
* that price against one canonical model are genuinely two things the agent distinguished — an alias
|
|
23
|
+
* and a provider-specific id can bill differently — and collapsing them here would throw away the
|
|
24
|
+
* distinction the report was making.
|
|
25
|
+
*/
|
|
26
|
+
export function foldSpend(prev, turn) {
|
|
27
|
+
const byModel = new Map();
|
|
28
|
+
for (const spend of prev.byModel)
|
|
29
|
+
byModel.set(spend.model, spend);
|
|
30
|
+
for (const spend of turn.byModel) {
|
|
31
|
+
const existing = byModel.get(spend.model);
|
|
32
|
+
byModel.set(spend.model, existing === undefined
|
|
33
|
+
? spend
|
|
34
|
+
: {
|
|
35
|
+
...existing,
|
|
36
|
+
costUsd: existing.costUsd + spend.costUsd,
|
|
37
|
+
inputTokens: existing.inputTokens + spend.inputTokens,
|
|
38
|
+
outputTokens: existing.outputTokens + spend.outputTokens,
|
|
39
|
+
cacheReadInputTokens: existing.cacheReadInputTokens + spend.cacheReadInputTokens,
|
|
40
|
+
cacheCreationInputTokens: existing.cacheCreationInputTokens + spend.cacheCreationInputTokens,
|
|
41
|
+
// Last report wins: these describe the model, not the usage, and the newest is truest.
|
|
42
|
+
contextWindow: spend.contextWindow ?? existing.contextWindow,
|
|
43
|
+
canonicalModel: spend.canonicalModel ?? existing.canonicalModel,
|
|
44
|
+
provider: spend.provider ?? existing.provider,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
totalCostUsd: prev.totalCostUsd + turn.totalCostUsd,
|
|
49
|
+
turnCount: prev.turnCount + 1,
|
|
50
|
+
byModel: [...byModel.values()],
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Whether a turn's per-model costs add up to the total the agent reported.
|
|
55
|
+
*
|
|
56
|
+
* It is an observation, not a validation, and must not become one. Both numbers come from the
|
|
57
|
+
* agent; a mismatch means the agent counted something the per-model breakdown does not itemise, and
|
|
58
|
+
* that is the agent's business rather than a defect this host should refuse over. The reason to
|
|
59
|
+
* expose it is that a caller charging money wants to know which of the two it is quoting, and
|
|
60
|
+
* whether they agree.
|
|
61
|
+
*
|
|
62
|
+
* @param toleranceUsd absolute, because these are floats and an exact comparison would fail on
|
|
63
|
+
* arithmetic that is otherwise perfectly correct.
|
|
64
|
+
*/
|
|
65
|
+
export function spendReconciles(turn, toleranceUsd = 1e-9) {
|
|
66
|
+
const summed = turn.byModel.reduce((total, spend) => total + spend.costUsd, 0);
|
|
67
|
+
return Math.abs(summed - turn.totalCostUsd) <= toleranceUsd;
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=usage.js.map
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A linked git worktree per session.
|
|
3
|
+
*
|
|
4
|
+
* The one rule here has destroyed committed work before: never `git worktree add -B` over a branch
|
|
5
|
+
* that already exists. `-B` is create-or-reset — it hard-resets the branch to the base ref — so
|
|
6
|
+
* re-provisioning a directory that was lost (a cleanup ran, a volume remounted) silently discards
|
|
7
|
+
* every commit made in it. The directory comes back looking correct and the work is gone. Nothing
|
|
8
|
+
* errors, nothing warns, and the loss is only visible to someone who knew what the branch used to
|
|
9
|
+
* point at.
|
|
10
|
+
*
|
|
11
|
+
* The rule: if the branch exists, attach to it and let it keep its own tip. Only a branch being
|
|
12
|
+
* created for the first time takes the `-B` form.
|
|
13
|
+
*
|
|
14
|
+
* That rule is a pure function here, and that is the point of the file's shape. `worktreeAddArgs`
|
|
15
|
+
* takes four values and returns an argv; the invariant is a unit test with no git, no disk and no
|
|
16
|
+
* repository. The alternative is a rule that only a destroyed branch could ever have proven.
|
|
17
|
+
*
|
|
18
|
+
* No key formula lives here. A branch naming convention is the controller's own convention, and
|
|
19
|
+
* one baked in here would be this package deciding what a session means. The caller supplies the
|
|
20
|
+
* branch name and the base ref.
|
|
21
|
+
*/
|
|
22
|
+
import type { Result } from '../core/result.js';
|
|
23
|
+
import { MAX_WORKSPACE_ID_LENGTH } from '../core/workspace-id.js';
|
|
24
|
+
import type { CommandEffects, ReleaseOptions, ReleaseReceipt, Workspace, WorkspaceEffects, WorkspaceInventory, WorkspaceProvider } from './provider.js';
|
|
25
|
+
/**
|
|
26
|
+
* The exact `git worktree add` argv, and the one decision that matters.
|
|
27
|
+
*
|
|
28
|
+
* `branchExists` decides between two different commands, not between two spellings of one.
|
|
29
|
+
* exists gives `worktree add <path> <branch>` attach: the branch keeps its own tip.
|
|
30
|
+
* new gives `worktree add -B <branch> <path> <baseRef>` create, off the base ref.
|
|
31
|
+
* Passing `-B` in the first case hard-resets the branch and discards its commits. See this file's
|
|
32
|
+
* header — that is not a hypothetical.
|
|
33
|
+
*
|
|
34
|
+
* Pure and total: no filesystem, no git, no I/O. Exported so the rule is testable directly rather
|
|
35
|
+
* than only through a provider that would need a repository to exercise.
|
|
36
|
+
*/
|
|
37
|
+
export declare function worktreeAddArgs(path: string, branch: string, baseRef: string, branchExists: boolean): readonly string[];
|
|
38
|
+
export interface GitWorktreeProviderOptions {
|
|
39
|
+
/** The repository the worktrees are linked to. Absolute. */
|
|
40
|
+
readonly repositoryRoot: string;
|
|
41
|
+
/** Where worktree directories are created. Absolute. */
|
|
42
|
+
readonly workspaceRoot: string;
|
|
43
|
+
/**
|
|
44
|
+
* The branch a session's worktree sits on, derived from its id.
|
|
45
|
+
*
|
|
46
|
+
* Supplied, not derived here. A branch naming convention is the caller's own; see the file
|
|
47
|
+
* header. Defaults to `periscope/{sessionId}` only so the provider is usable without one.
|
|
48
|
+
*/
|
|
49
|
+
readonly branchFor?: (sessionId: string) => string;
|
|
50
|
+
/** What a new branch is created from. Defaults to `HEAD`. */
|
|
51
|
+
readonly baseRef?: string;
|
|
52
|
+
readonly effects: WorkspaceEffects;
|
|
53
|
+
readonly commands: CommandEffects;
|
|
54
|
+
}
|
|
55
|
+
export declare class GitWorktreeProvider implements WorkspaceProvider {
|
|
56
|
+
#private;
|
|
57
|
+
/** The checkout this provider clones from — see `WorkspaceProvider.repositoryRoot`. */
|
|
58
|
+
get repositoryRoot(): string;
|
|
59
|
+
constructor(options: GitWorktreeProviderOptions);
|
|
60
|
+
/** Where this session's worktree is, whether or not it exists yet. */
|
|
61
|
+
pathFor(sessionId: string): string;
|
|
62
|
+
provision(sessionId: string): Promise<Result<Workspace>>;
|
|
63
|
+
/**
|
|
64
|
+
* Release the worktree.
|
|
65
|
+
*
|
|
66
|
+
* The branch is never deleted unless the caller names it in the ask. Removing a directory is
|
|
67
|
+
* reversible — the commits are still on the branch. Deleting a branch is not, so branch deletion
|
|
68
|
+
* is opt-in per call (`deleteBranch`), refused for an unmerged branch unless forced, and the
|
|
69
|
+
* merged check runs before anything is removed: a refusal never leaves a half-cleaned key. A
|
|
70
|
+
* deletion that fails after the directory went is a partial, stated on the receipt.
|
|
71
|
+
*
|
|
72
|
+
* The branch is the one the worktree is on (from `worktree list`), which may differ from the
|
|
73
|
+
* scheme's render if an agent checked out elsewhere; only when the directory is already gone is
|
|
74
|
+
* the scheme's render the name.
|
|
75
|
+
*/
|
|
76
|
+
release(sessionId: string, options?: ReleaseOptions): Promise<Result<ReleaseReceipt | undefined>>;
|
|
77
|
+
/** The key of the directory at `path` when it sits directly under the workspace root, else null. */
|
|
78
|
+
keyForPath(path: string): string | null;
|
|
79
|
+
/**
|
|
80
|
+
* Every worktree under the workspace root, from disk, with what git knows about its branch.
|
|
81
|
+
*
|
|
82
|
+
* Three commands for the whole list, plus one `rev-list --count` per branch entry: `worktree list --porcelain` for the worktrees, `for-each-ref`
|
|
83
|
+
* for every branch tip's date, and `branch --merged <default>` for the merged set. Only worktrees
|
|
84
|
+
* CONTAINED BY the workspace root are reported: the repository itself and any checkout the
|
|
85
|
+
* operator keeps elsewhere are not this provider's to list, so no cleanup composed from this
|
|
86
|
+
* answer can name them. `merged` is null when the repository names no default branch.
|
|
87
|
+
*/
|
|
88
|
+
inventory(): Promise<Result<WorkspaceInventory>>;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* A session id that cannot safely become a path segment or a branch-name component.
|
|
92
|
+
*
|
|
93
|
+
* Refused rather than sanitized. Rewriting an id to make it safe means the directory no longer
|
|
94
|
+
* corresponds to the id the caller used, so two different sessions can collapse onto one workspace —
|
|
95
|
+
* which is the exact isolation property the plain-directory tests check, broken quietly by a
|
|
96
|
+
* helpful-looking fix.
|
|
97
|
+
*
|
|
98
|
+
* The screen is the union of two rule sets. The id becomes both a directory segment (win32/POSIX)
|
|
99
|
+
* and a git branch-name component (`periscope/{id}` by default); an id containing a character that
|
|
100
|
+
* only git refuses would pass a separators-only screen and fail inside git as an unnamed
|
|
101
|
+
* `workspace-provision-failed`. So the guard screens every character either rule set refuses — the
|
|
102
|
+
* `git check-ref-format` component rules together with the win32 segment rules — and the refusal
|
|
103
|
+
* names the offending character and its rule. Deliberately not screened: win32 reserved device
|
|
104
|
+
* names (CON, NUL, …) — a name-list, not a character class; those still die loudly at provision
|
|
105
|
+
* under the named refusal below.
|
|
106
|
+
*/
|
|
107
|
+
declare function rejectUnusableId<T>(sessionId: string): Result<T> | null;
|
|
108
|
+
/**
|
|
109
|
+
* The refname/path-segment class check: the first way this id would be refused by
|
|
110
|
+
* `git check-ref-format` (as the component of a branch name) or by win32 (as a path segment), as a
|
|
111
|
+
* human sentence — or null for an id both rule sets accept. Pure and total, exported so the class
|
|
112
|
+
* is testable without a provider.
|
|
113
|
+
*/
|
|
114
|
+
export declare function refnameOrPathIllegality(sessionId: string): string | null;
|
|
115
|
+
export { rejectUnusableId };
|
|
116
|
+
/**
|
|
117
|
+
* The same screen as `rejectUnusableId`, as a sentence a caller can attach to its own noun.
|
|
118
|
+
*
|
|
119
|
+
* The host validates `session_new.workspaceKey` before any workspace is claimed, and its refusal
|
|
120
|
+
* must name the field — "the session id …" would send a controller author to the wrong key. The
|
|
121
|
+
* union class itself stays single-sourced in `refnameOrPathIllegality`; the two structural checks
|
|
122
|
+
* are restated here because their sentences are the caller's to phrase. Keep the three checks in
|
|
123
|
+
* step with `rejectUnusableId` above — a key this accepts and that refuses (or the reverse) would
|
|
124
|
+
* mean the host's guard and the provider's disagree about the same id.
|
|
125
|
+
*/
|
|
126
|
+
export declare function unusableKeyProblem(key: string): string | null;
|
|
127
|
+
/** The bound on a workspace id; declared in `core/` so the wire codec can enforce it too. */
|
|
128
|
+
export { MAX_WORKSPACE_ID_LENGTH };
|
|
129
|
+
/**
|
|
130
|
+
* A key safe to echo into a refusal detail: verbatim when short, truncated with its length named
|
|
131
|
+
* when not. Every screen above bounds accepted ids, but the refusal for an over-length id still
|
|
132
|
+
* has to describe the very string that broke the bound — this is the one place an unbounded input
|
|
133
|
+
* reaches a detail, so it is bounded here rather than at each caller's discretion.
|
|
134
|
+
*/
|
|
135
|
+
export declare function keyPreview(id: string): string;
|
|
136
|
+
/** Newest tip first; entries with no tip date (detached, unknown) after every dated one; ties by key. */
|
|
137
|
+
export declare function byNewestTip(a: {
|
|
138
|
+
readonly key: string;
|
|
139
|
+
readonly lastCommitAt: string | null;
|
|
140
|
+
}, b: {
|
|
141
|
+
readonly key: string;
|
|
142
|
+
readonly lastCommitAt: string | null;
|
|
143
|
+
}): number;
|
|
144
|
+
/**
|
|
145
|
+
* The key of `path` when it is an absolute path to a directory DIRECTLY under `root` (normalised),
|
|
146
|
+
* else null: the root itself, a nested path, a relative path and anything elsewhere are all null,
|
|
147
|
+
* so a release addressed by path can only ever name what a provision could have made.
|
|
148
|
+
*/
|
|
149
|
+
export declare function keyDirectlyUnder(path: string, root: string): string | null;
|
|
150
|
+
//# sourceMappingURL=git-worktree.d.ts.map
|