toolroll 0.9.60 → 0.9.61
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 +15 -0
- package/dist/agent-board.d.ts +208 -0
- package/dist/agent-board.js +454 -0
- package/dist/agent-fence.d.ts +19 -0
- package/dist/agent-fence.js +100 -8
- package/dist/agent-pulse.d.ts +43 -0
- package/dist/agent-pulse.js +214 -0
- package/dist/agent-turns.d.ts +52 -0
- package/dist/agent-turns.js +137 -0
- package/dist/agents-board.d.ts +101 -0
- package/dist/agents-board.js +225 -0
- package/dist/assignment-adapters.d.ts +1 -0
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment.d.ts +4 -0
- package/dist/assignment.js +8 -6
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +29 -25
- package/dist/browser-crew.js +2 -0
- package/dist/browser-workspace.d.ts +79 -1
- package/dist/browser-workspace.js +1 -0
- package/dist/builder.d.ts +8 -0
- package/dist/builder.js +113 -13
- package/dist/chat-channel.js +9 -0
- package/dist/chat-core.d.ts +6 -0
- package/dist/chat-core.js +8 -0
- package/dist/chat-delivery.js +22 -0
- package/dist/chat-messages.js +8 -1
- package/dist/chat-quiet.js +1 -0
- package/dist/chat-voice.d.ts +2 -0
- package/dist/chat-voice.js +2 -0
- package/dist/checkout-cleanup.d.ts +3 -0
- package/dist/checkout-cleanup.js +11 -2
- package/dist/contracts/console-api.d.ts +3 -1
- package/dist/contracts/console-api.js +5 -1
- package/dist/contracts/lead-tools.d.ts +16 -2
- package/dist/contracts/lead-tools.js +5 -1
- package/dist/desktop-update.d.ts +1 -1
- package/dist/evidence-pack.js +1 -1
- package/dist/evidence.d.ts +6 -0
- package/dist/evidence.js +14 -3
- package/dist/exec.d.ts +32 -0
- package/dist/exec.js +229 -10
- package/dist/invoke.js +110 -18
- package/dist/landed-cleanup.d.ts +157 -0
- package/dist/landed-cleanup.js +707 -0
- package/dist/lead-cli.js +22 -19
- package/dist/lead-contract.js +1 -1
- package/dist/lead-doors.js +45 -1
- package/dist/lead-tools.js +40 -7
- package/dist/live-input.d.ts +68 -0
- package/dist/live-input.js +254 -0
- package/dist/live.d.ts +1 -1
- package/dist/live.js +4 -2
- package/dist/maintenance.d.ts +2 -0
- package/dist/maintenance.js +5 -2
- package/dist/operate-remote-arguments.js +5 -1
- package/dist/operate-remote.js +1 -1
- package/dist/operate.d.ts +1 -1
- package/dist/operate.js +188 -16
- package/dist/orphan-sweep.d.ts +2 -0
- package/dist/orphan-sweep.js +5 -3
- package/dist/project-delete-ui.d.ts +11 -0
- package/dist/project-delete-ui.js +29 -2
- package/dist/project-tools.d.ts +7 -11
- package/dist/project-tools.js +13 -37
- package/dist/provider.d.ts +65 -8
- package/dist/provider.js +80 -21
- package/dist/result-completion.d.ts +7 -0
- package/dist/result-completion.js +9 -0
- package/dist/runner.d.ts +4 -2
- package/dist/runner.js +3 -3
- package/dist/serve.js +19 -1
- package/dist/server/live.d.ts +3 -0
- package/dist/server/live.js +35 -2
- package/dist/server/pages.js +59 -35
- package/dist/server/render-chat.js +12 -0
- package/dist/server/render-tasks.d.ts +12 -2
- package/dist/server/render-tasks.js +27 -7
- package/dist/server/route-table.js +4 -1
- package/dist/server/settings.js +2 -1
- package/dist/server/task-views.js +9 -0
- package/dist/server/tasks.js +63 -0
- package/dist/shared-deps.d.ts +9 -4
- package/dist/shared-deps.js +89 -13
- package/dist/staged-runtimes.d.ts +16 -4
- package/dist/staged-runtimes.js +160 -38
- package/dist/steer-live.d.ts +19 -0
- package/dist/steer-live.js +93 -0
- package/dist/steer-status.d.ts +8 -2
- package/dist/steer-status.js +19 -5
- package/dist/storage-sweep-child.d.ts +1 -0
- package/dist/storage-sweep-child.js +76 -0
- package/dist/storage-sweep-process.d.ts +41 -0
- package/dist/storage-sweep-process.js +187 -0
- package/dist/storage-sweep.d.ts +30 -6
- package/dist/storage-sweep.js +214 -28
- package/dist/storage-ui.d.ts +2 -0
- package/dist/storage-ui.js +16 -4
- package/dist/storage.d.ts +6 -0
- package/dist/storage.js +35 -0
- package/dist/store.d.ts +163 -2
- package/dist/store.js +350 -15
- package/dist/surface.js +4 -2
- package/dist/sweep-quarantine.d.ts +28 -0
- package/dist/sweep-quarantine.js +153 -0
- package/dist/task-status.d.ts +2 -0
- package/dist/task-status.js +5 -1
- package/dist/test-temp.d.ts +6 -3
- package/dist/test-temp.js +15 -9
- package/dist/toolroll-update.js +3 -2
- package/dist/work-index.d.ts +1 -0
- package/dist/work-index.js +32 -12
- package/dist/work-summary.d.ts +2 -2
- package/dist/workspace-revision.js +3 -1
- package/dist/worktree.d.ts +3 -1
- package/dist/worktree.js +93 -10
- package/package.json +1 -1
- package/scripts/provider-canary.mjs +235 -147
package/dist/invoke.js
CHANGED
|
@@ -16,19 +16,21 @@
|
|
|
16
16
|
* (`adapterFor`), and only builder, planner, reviewer, and scout import
|
|
17
17
|
* `invokeAgent`.
|
|
18
18
|
*/
|
|
19
|
-
import { adapterFor, auditOf } from "./provider.js";
|
|
19
|
+
import { adapterFor, auditOf, isolationUnsupported } from "./provider.js";
|
|
20
20
|
import { readProviderKey, readAuthModeStrict, PROVIDER_KEY_ENV, OWN_KEY_ENV } from "./keys.js";
|
|
21
21
|
import { classifyTerminal } from "./exhaustion.js";
|
|
22
22
|
import { liftAuthPause } from "./provider-auth.js";
|
|
23
23
|
import { attestProvider } from "./attest.js";
|
|
24
|
-
import { run as runCommand, runOwnerTag } from "./exec.js";
|
|
24
|
+
import { run as runCommand, runOwnerTag, streamsInput } from "./exec.js";
|
|
25
25
|
import { currentContainment } from "./containment.js";
|
|
26
26
|
import { witnessedRunner } from "./process-custody.js";
|
|
27
27
|
import { underStopWatch } from "./task-control.js";
|
|
28
28
|
import { childDatabaseEnv, isolatedChildDatabase, removeChildDatabase as removeAgentDatabase } from "./child-database.js";
|
|
29
29
|
import { noToolsArgs, prepareRunTools } from "./project-tools.js";
|
|
30
|
-
import { realpathSync } from "node:fs";
|
|
31
|
-
import {
|
|
30
|
+
import { readFileSync, realpathSync } from "node:fs";
|
|
31
|
+
import { homedir } from "node:os";
|
|
32
|
+
import { join } from "node:path";
|
|
33
|
+
import { agentFence, linuxFenceAvailable, macosFenceAvailable, taskBoundary } from "./agent-fence.js";
|
|
32
34
|
import { billingOf, claudeBillingFrom, seenBilling } from "./spend.js";
|
|
33
35
|
/** The claude binary name — kept for the legacy quota rows and tests that
|
|
34
36
|
* describe history; new code carries a resolved AgentSpec instead. */
|
|
@@ -36,6 +38,46 @@ export const PROVIDER_BINARY = "claude";
|
|
|
36
38
|
/** "Within seconds": how soon a silent failed run must end for its stderr to
|
|
37
39
|
* be read for the sign-in signal. */
|
|
38
40
|
const AUTH_EARLY_EXIT_MS = 30_000;
|
|
41
|
+
// Fixed names only: neither setting values nor arbitrary JSON keys belong in
|
|
42
|
+
// diagnostics. The CLI's existing login stays where it is; no config is copied.
|
|
43
|
+
const CLAUDE_AUTH_ENV = [
|
|
44
|
+
"ANTHROPIC_API_KEY", "ANTHROPIC_BASE_URL", "ANTHROPIC_AUTH_TOKEN",
|
|
45
|
+
"CLAUDE_CODE_USE_BEDROCK", "CLAUDE_CODE_SKIP_BEDROCK_AUTH", "ANTHROPIC_BEDROCK_BASE_URL",
|
|
46
|
+
"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY", "AWS_SESSION_TOKEN", "AWS_PROFILE",
|
|
47
|
+
"AWS_REGION", "AWS_DEFAULT_REGION", "AWS_BEARER_TOKEN_BEDROCK", "ANTHROPIC_SMALL_FAST_MODEL_AWS_REGION",
|
|
48
|
+
"CLAUDE_CODE_USE_VERTEX", "CLAUDE_CODE_SKIP_VERTEX_AUTH", "ANTHROPIC_VERTEX_BASE_URL",
|
|
49
|
+
"ANTHROPIC_VERTEX_PROJECT_ID", "CLOUD_ML_REGION", "GOOGLE_APPLICATION_CREDENTIALS", "GOOGLE_CLOUD_PROJECT",
|
|
50
|
+
];
|
|
51
|
+
/** A value-only preflight: a settings-dependent login cannot survive empty setting sources. */
|
|
52
|
+
function claudeSettingsAuthProblem(configDir) {
|
|
53
|
+
let text;
|
|
54
|
+
try {
|
|
55
|
+
text = readFileSync(join(configDir, "settings.json"), "utf8");
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
if (error.code === "ENOENT")
|
|
59
|
+
return null;
|
|
60
|
+
return "Can't read your Claude settings.json to check sign-in. Restore access to that file and try again. Isolated builds do not load it.";
|
|
61
|
+
}
|
|
62
|
+
let settings;
|
|
63
|
+
try {
|
|
64
|
+
settings = JSON.parse(text);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return "Your Claude settings.json is not valid JSON. Fix that file and try again. Isolated builds do not load it, so Toolroll must check whether sign-in depends on it first.";
|
|
68
|
+
}
|
|
69
|
+
if (settings === null || typeof settings !== "object" || Array.isArray(settings)) {
|
|
70
|
+
return "Your Claude settings.json must contain an object. Fix that file and try again. Isolated builds do not load it.";
|
|
71
|
+
}
|
|
72
|
+
const value = settings;
|
|
73
|
+
const keys = ["apiKeyHelper", "forceLoginMethod"].filter(key => Object.hasOwn(value, key));
|
|
74
|
+
const env = value["env"];
|
|
75
|
+
if (env !== null && typeof env === "object" && !Array.isArray(env)) {
|
|
76
|
+
keys.push(...CLAUDE_AUTH_ENV.filter(key => Object.hasOwn(env, key)).map(key => `env.${key}`));
|
|
77
|
+
}
|
|
78
|
+
return keys.length === 0 ? null
|
|
79
|
+
: `Your Claude settings.json defines sign-in settings: ${keys.join(", ")}. Isolated builds do not load that file. Use a Claude login that works without these settings, or select another build provider in Settings. Toolroll has not changed your login or copied these values.`;
|
|
80
|
+
}
|
|
39
81
|
/**
|
|
40
82
|
* A provider can run the repository's own CLI. In a self-hosting build that
|
|
41
83
|
* must never mean "open the database that launched me": a migration from the
|
|
@@ -104,9 +146,15 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
|
|
|
104
146
|
throw new Error(`run ${runId} already records session ${recordedSessionId} but this invocation neither resumes nor starts under it`);
|
|
105
147
|
}
|
|
106
148
|
const adapter = adapterFor(spec.provider);
|
|
107
|
-
const { startSessionId: _untrustedStartSessionId, ...invocationWithoutStart } = invocation;
|
|
149
|
+
const { startSessionId: _untrustedStartSessionId, streamInput: _untrustedStreamInput, ...invocationWithoutStart } = invocation;
|
|
150
|
+
// Live input (live-input.ts): only Claude's builds, only through a transport that keeps stdin open. The brief is
|
|
151
|
+
// the session's first stdin message and never rides the argv; every other road keeps its prompt where it was.
|
|
152
|
+
const liveInput = spec.provider === "claude" && invocation.phase === "build" && options.liveInput !== undefined && streamsInput(options.runner ?? adapter.defaultRunner)
|
|
153
|
+
? options.liveInput
|
|
154
|
+
: null;
|
|
108
155
|
const resolvedInvocation = {
|
|
109
156
|
...invocationWithoutStart,
|
|
157
|
+
...(liveInput === null ? {} : { streamInput: true }),
|
|
110
158
|
model: spec.model,
|
|
111
159
|
resumeSession: requestedResumeSessionId,
|
|
112
160
|
...(startSessionId === undefined ? {} : { startSessionId }),
|
|
@@ -142,7 +190,7 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
|
|
|
142
190
|
// re-supply survives resolveChildEnv's chat-key strip); in
|
|
143
191
|
// "subscription" mode the key is STRIPPED from the child so the CLI's
|
|
144
192
|
// own login is used — its key is retained, just not handed over.
|
|
145
|
-
const { runner, clock: _clock, versionProbe: _probe, keyHome, timeoutMs: _hardTimeout, idleTimeoutMs: _idleTimeout, stdin: _callerStdin, accumulateUsage: _accumulateUsage, ...runOptions } = options;
|
|
193
|
+
const { runner, clock: _clock, versionProbe: _probe, keyHome, timeoutMs: _hardTimeout, idleTimeoutMs: _idleTimeout, stdin: _callerStdin, accumulateUsage: _accumulateUsage, liveInput: _liveInput, ...runOptions } = options;
|
|
146
194
|
// The live setting is read STRICTLY (atomic authority closure): a
|
|
147
195
|
// present mode file that says neither word is a value-shaped refusal
|
|
148
196
|
// before any stamp or process — never the subscription default a
|
|
@@ -158,6 +206,11 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
|
|
|
158
206
|
};
|
|
159
207
|
}
|
|
160
208
|
const authMode = strict.mode;
|
|
209
|
+
if (spec.provider === "claude" && invocation.phase !== "review") {
|
|
210
|
+
const problem = claudeSettingsAuthProblem(options.env?.["CLAUDE_CONFIG_DIR"] ?? process.env["CLAUDE_CONFIG_DIR"] ?? join(keyHome ?? homedir(), ".claude"));
|
|
211
|
+
if (problem !== null)
|
|
212
|
+
return { kind: "refused", reason: "auth-mode", providerVersion: attested?.version ?? null, diagnostic: problem };
|
|
213
|
+
}
|
|
161
214
|
const ownKeyEnv = OWN_KEY_ENV[spec.provider];
|
|
162
215
|
const managedKey = authMode === "api-key"
|
|
163
216
|
? readProviderKey(spec.provider, keyHome) ?? (process.env[PROVIDER_KEY_ENV[spec.provider]] || null)
|
|
@@ -239,6 +292,8 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
|
|
|
239
292
|
store.stampProviderStart(runId, clock(), attested.version);
|
|
240
293
|
else
|
|
241
294
|
store.stampProviderStart(runId, clock());
|
|
295
|
+
// The brief is the live session's first message, queued before the spawn writes it.
|
|
296
|
+
liveInput?.begin(invocation.brief);
|
|
242
297
|
const spawn = witnessedRunner(store, runId, clock, runner ?? adapter.defaultRunner);
|
|
243
298
|
let transportAnnouncedSessionId = null;
|
|
244
299
|
let transportSessionConflict = false;
|
|
@@ -255,10 +310,16 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
|
|
|
255
310
|
// The agent fence: Toolroll's own secrets, database and other runs'
|
|
256
311
|
// evidence stay out of reach, whatever the permission mode. A review is
|
|
257
312
|
// already confined to its sealed files.
|
|
258
|
-
|
|
313
|
+
// A build's fence also holds its task boundary: home configuration and
|
|
314
|
+
// the repositories beside its own, fresh and resumed alike.
|
|
315
|
+
const fenceOf = invocation.phase === "review" ? { fence: [], fileTools: [] } : runFence(store, runId, spec.provider, options.keyHome);
|
|
316
|
+
const baseFence = fenceOf.fence;
|
|
259
317
|
const toolsDir = (spec.provider === "codex" || spec.provider === "openrouter") && baseFence.length > 0 ? tools?.privateDir : undefined;
|
|
260
318
|
const fence = toolsDir === undefined ? baseFence : [...baseFence, realpathSync(toolsDir)];
|
|
261
|
-
|
|
319
|
+
// Where the build runs: Claude reads the repository's CLAUDE.md from here.
|
|
320
|
+
const workspace = invocation.phase === "review" ? null : runOptions.cwd ?? run.worktree ?? null;
|
|
321
|
+
const buildInvocation = { ...resolvedInvocation, ...(tools === null ? {} : { toolArgv: tools.argv, fence, fileToolFence: fenceOf.fileTools }), ...(workspace === null ? {} : { workspace }) };
|
|
322
|
+
const launchArgv = tools === null ? argv : adapter.argv(buildInvocation);
|
|
262
323
|
const fenced = fenceLaunch(spec.provider, fence);
|
|
263
324
|
if (invocation.phase !== "review")
|
|
264
325
|
store.recordRunFence(runId, { method: fenced.method, paths: fence.length }, clock());
|
|
@@ -302,11 +363,13 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
|
|
|
302
363
|
throw new Error("the attempt was stopped before spawn custody completed");
|
|
303
364
|
},
|
|
304
365
|
...(stdin === undefined ? {} : { stdin }),
|
|
366
|
+
...(liveInput === null ? {} : { liveInput }),
|
|
305
367
|
...(hardTimeoutMs === undefined ? {} : { timeoutMs: hardTimeoutMs }),
|
|
306
368
|
...(idleTimeoutMs === undefined ? {} : { idleTimeoutMs }),
|
|
307
369
|
...(defaultTimeoutMs === undefined ? {} : { timeoutMs: defaultTimeoutMs }),
|
|
308
370
|
env: {
|
|
309
371
|
...(runOptions.env ?? {}),
|
|
372
|
+
...(adapter.env?.(buildInvocation) ?? {}),
|
|
310
373
|
...(tools?.env ?? {}),
|
|
311
374
|
...(managedKey === null ? {} : { [PROVIDER_KEY_ENV[spec.provider]]: managedKey }),
|
|
312
375
|
// This key is deliberately last: no caller may point an agent back at
|
|
@@ -360,6 +423,12 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
|
|
|
360
423
|
}
|
|
361
424
|
store.fixRunBilling(runId, authMode === "api-key" ? "api-key" : evidence ?? billingOf(spec.provider, store.handle), new Date());
|
|
362
425
|
const envelope = adapter.parse(result.stdout);
|
|
426
|
+
// An installed CLI too old for a build's own setup refuses its flags
|
|
427
|
+
// before it starts: nothing ran, and the run says what to update.
|
|
428
|
+
const unsupported = result.code !== 0 && envelope.initObserved !== true && invocation.phase !== "review" ? isolationUnsupported(spec.provider, result.stderr) : null;
|
|
429
|
+
if (unsupported !== null) {
|
|
430
|
+
return { kind: "refused", reason: "provider-unattested", providerVersion: attested === null ? null : attested.version, diagnostic: unsupported };
|
|
431
|
+
}
|
|
363
432
|
// The callback is the crash-safe early stamp; the parsed envelope is the
|
|
364
433
|
// authoritative completed-stream account. Stamp it as a fallback for an
|
|
365
434
|
// alternate transport. On fresh runs COALESCE makes callback/parser
|
|
@@ -384,11 +453,19 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
|
|
|
384
453
|
? "the provider returned a session id different from the exact identity recorded for resume"
|
|
385
454
|
: "the provider session id was different from the identity durably recorded when the stream initialized"
|
|
386
455
|
: null;
|
|
456
|
+
// A run whose cost could not be read may still have spent up to its cap: it is charged the cap, never nothing, so
|
|
457
|
+
// the next run's remaining budget stays honest. With no cap it is unmeasured, as a Codex run is, and says so.
|
|
458
|
+
const unknownCost = envelope.costUnknown === true;
|
|
459
|
+
const cap = typeof invocation.maxBudgetUsd === "number" && Number.isFinite(invocation.maxBudgetUsd) && invocation.maxBudgetUsd > 0 ? invocation.maxBudgetUsd : null;
|
|
460
|
+
const costUsd = unknownCost ? cap : envelope.costUsd;
|
|
461
|
+
const usageJson = unknownCost
|
|
462
|
+
? JSON.stringify({ cost_note: cap === null ? "cost unknown, no dollar cap applied: unmeasured" : "cost unknown, charged at the cap", ...usageOf(envelope.usageRaw) })
|
|
463
|
+
: envelope.usageRaw;
|
|
387
464
|
store.recordUsage(runId, {
|
|
388
465
|
...(envelope.tokensIn === null ? {} : { tokensIn: envelope.tokensIn + (options.accumulateUsage ? run.tokensIn ?? 0 : 0) }),
|
|
389
466
|
...(envelope.tokensOut === null ? {} : { tokensOut: envelope.tokensOut + (options.accumulateUsage ? run.tokensOut ?? 0 : 0) }),
|
|
390
|
-
...(
|
|
391
|
-
...(
|
|
467
|
+
...(costUsd === null ? {} : { costUsd: costUsd + (options.accumulateUsage ? run.costUsd ?? 0 : 0) }),
|
|
468
|
+
...(usageJson === null ? {} : { usageJson }),
|
|
392
469
|
});
|
|
393
470
|
// How the attempt ended, classified HERE where the structural terminal
|
|
394
471
|
// still exists; a sign-in that no longer works pauses its provider.
|
|
@@ -501,7 +578,7 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
|
|
|
501
578
|
usage: {
|
|
502
579
|
tokensIn: envelope.tokensIn,
|
|
503
580
|
tokensOut: envelope.tokensOut,
|
|
504
|
-
costUsd
|
|
581
|
+
costUsd,
|
|
505
582
|
},
|
|
506
583
|
// Gated on the run having NOTHING to show (Codex M5-M8 audit, C-8),
|
|
507
584
|
// where "nothing to show" is STRUCTURAL when the transport can say so
|
|
@@ -520,17 +597,32 @@ export async function invokeAgent(store, runId, spec, invocation, options) {
|
|
|
520
597
|
},
|
|
521
598
|
};
|
|
522
599
|
}
|
|
523
|
-
/** The
|
|
524
|
-
function
|
|
600
|
+
/** The reported usage, when its kept text is whole JSON, to sit beside a cost note. */
|
|
601
|
+
function usageOf(raw) {
|
|
602
|
+
if (raw === null)
|
|
603
|
+
return {};
|
|
604
|
+
try {
|
|
605
|
+
return { usage: JSON.parse(raw) };
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
return {};
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
/** The paths this run's agent may not reach: the state folder around the live database (but not its own worktree),
|
|
612
|
+
* ~/.toolroll (or its older name), and its task boundary; plus what only Claude's file tools refuse. */
|
|
613
|
+
function runFence(store, runId, provider, keyHome) {
|
|
525
614
|
try {
|
|
526
615
|
// A plane always has a database file; an in-memory store (a test) has nothing to fence.
|
|
527
616
|
const databaseFile = store.databaseFile();
|
|
528
617
|
if (databaseFile === null)
|
|
529
|
-
return [];
|
|
530
|
-
|
|
618
|
+
return { fence: [], fileTools: [] };
|
|
619
|
+
const worktree = store.getRun(runId)?.worktree ?? null;
|
|
620
|
+
const home = keyHome === undefined ? {} : { home: keyHome };
|
|
621
|
+
const boundary = worktree === null ? null : taskBoundary({ worktree, provider, ...home });
|
|
622
|
+
return { fence: agentFence({ databaseFile, worktree, ...home, boundary }), fileTools: boundary?.fileTools ?? [] };
|
|
531
623
|
}
|
|
532
624
|
catch {
|
|
533
|
-
return [];
|
|
625
|
+
return { fence: [], fileTools: [] };
|
|
534
626
|
}
|
|
535
627
|
}
|
|
536
628
|
/** How the fence reaches a provider: Codex through its own sandbox profile
|
|
@@ -551,10 +643,10 @@ function fenceLaunch(provider, fence) {
|
|
|
551
643
|
/** One launch's tools, or — when they cannot be prepared — the same isolation with none, never the operator's global servers. */
|
|
552
644
|
function runTools(store, runId, spec, keyHome, clock, readOnly, withheld) {
|
|
553
645
|
try {
|
|
554
|
-
return prepareRunTools(store, runId, spec.provider, { ...(keyHome === undefined ? {} : { home: keyHome }), now: clock(),
|
|
646
|
+
return prepareRunTools(store, runId, spec.provider, { ...(keyHome === undefined ? {} : { home: keyHome }), now: clock(), ...(readOnly === undefined ? {} : { readOnly }), ...(withheld === undefined ? {} : { withheld }) });
|
|
555
647
|
}
|
|
556
648
|
catch {
|
|
557
|
-
return noToolsArgs(spec.provider
|
|
649
|
+
return noToolsArgs(spec.provider);
|
|
558
650
|
}
|
|
559
651
|
}
|
|
560
652
|
/** How Codex says it's signed in (`codex login status`): a ChatGPT sign-in is its plan; a key or Bedrock is a key;
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { type ExecResult } from "./exec.js";
|
|
2
|
+
import type { Store } from "./store.js";
|
|
3
|
+
import { type Runner, type WorktreePool } from "./worktree.js";
|
|
4
|
+
/** `storage clean --landed` takes only what has waited at least this long. */
|
|
5
|
+
export declare const BACKLOG_DAYS = 7;
|
|
6
|
+
/** The local branches the one-time pass looks at: Toolroll's build and plan branches, under either name. */
|
|
7
|
+
export declare const BACKLOG_PREFIXES: readonly string[];
|
|
8
|
+
/** The ledger action that closes a teardown. */
|
|
9
|
+
export declare const LANDED_CLEANED_ACTION = "landed work cleaned up";
|
|
10
|
+
/** Reconcile looks at most this many results and finishes at most this many items a pass; the pass by hand has no cap. */
|
|
11
|
+
export declare const AUTO_LOOKS = 20;
|
|
12
|
+
export declare const AUTO_ITEMS = 40;
|
|
13
|
+
export type Git = (cwd: string, args: readonly string[]) => Promise<ExecResult>;
|
|
14
|
+
export declare function gitOf(runner?: Runner): Git;
|
|
15
|
+
export type LandedHow = "merged" | "in-main" | "content-in-main";
|
|
16
|
+
export type LandedProof = {
|
|
17
|
+
landed: true;
|
|
18
|
+
how: LandedHow;
|
|
19
|
+
reason: string;
|
|
20
|
+
} | {
|
|
21
|
+
landed: false;
|
|
22
|
+
why: string;
|
|
23
|
+
};
|
|
24
|
+
/** What Toolroll last saw of the result's pull request. */
|
|
25
|
+
export type PullRequestFact = {
|
|
26
|
+
merged: boolean;
|
|
27
|
+
headSha: string | null;
|
|
28
|
+
number: number | null;
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Whether this commit's work landed, read from what Toolroll recorded and the local repository only. Fails closed:
|
|
32
|
+
* every doubt is "not proven", with the reason in a few words.
|
|
33
|
+
*/
|
|
34
|
+
export declare function landedProof(git: Git, repo: string, head: string | null, pullRequest?: PullRequestFact | null): Promise<LandedProof>;
|
|
35
|
+
export type LandedTarget = {
|
|
36
|
+
kind: "checkout" | "branch";
|
|
37
|
+
target: string;
|
|
38
|
+
expect: string;
|
|
39
|
+
};
|
|
40
|
+
export type LandedKeep = {
|
|
41
|
+
repo: string;
|
|
42
|
+
what: string;
|
|
43
|
+
why: string;
|
|
44
|
+
};
|
|
45
|
+
export type LandedCandidate = {
|
|
46
|
+
repo: string;
|
|
47
|
+
taskId: string;
|
|
48
|
+
rootId: string;
|
|
49
|
+
runId: number;
|
|
50
|
+
receipt: string;
|
|
51
|
+
head: string;
|
|
52
|
+
finishedAt: string;
|
|
53
|
+
/** False when it is already complete: only its teardown is left. */
|
|
54
|
+
completes: boolean;
|
|
55
|
+
how: LandedHow;
|
|
56
|
+
reason: string;
|
|
57
|
+
targets: LandedTarget[];
|
|
58
|
+
keeps: LandedKeep[];
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Results in a project whose work landed, with what their teardown would remove and what stays. Reads only.
|
|
62
|
+
* `limit` caps how many results are read against git (reconcile's bound; results read before and unchanged since
|
|
63
|
+
* don't count); `before` takes only results finished before it (the one-time pass's seven days).
|
|
64
|
+
*/
|
|
65
|
+
export declare function landedResults(store: Store, git: Git, repo: string, root: string, now: Date, options?: {
|
|
66
|
+
limit?: number;
|
|
67
|
+
before?: string;
|
|
68
|
+
memo?: boolean;
|
|
69
|
+
}): Promise<{
|
|
70
|
+
found: LandedCandidate[];
|
|
71
|
+
kept: LandedKeep[];
|
|
72
|
+
}>;
|
|
73
|
+
/**
|
|
74
|
+
* Mark a landed result complete (unless it already is) and record its teardown, in one transaction, bound to the
|
|
75
|
+
* exact result read: refused when the result changed since. The completion's ledger entry says why. Returns the
|
|
76
|
+
* teardown's id; a result already taken returns its existing teardown.
|
|
77
|
+
*/
|
|
78
|
+
export declare function recordLandedIntent(store: Store, candidate: LandedCandidate, now: Date, root: string, actor?: string): {
|
|
79
|
+
ok: true;
|
|
80
|
+
id: number;
|
|
81
|
+
completed: boolean;
|
|
82
|
+
} | {
|
|
83
|
+
ok: false;
|
|
84
|
+
why: string;
|
|
85
|
+
};
|
|
86
|
+
/** A landed branch no task names: its teardown, on its own. */
|
|
87
|
+
export type LandedBranch = {
|
|
88
|
+
repo: string;
|
|
89
|
+
branch: string;
|
|
90
|
+
oid: string;
|
|
91
|
+
how: LandedHow;
|
|
92
|
+
reason: string;
|
|
93
|
+
checkout: string | null;
|
|
94
|
+
};
|
|
95
|
+
export declare function recordBranchIntent(store: Store, branch: LandedBranch, now: Date, actor: string): number;
|
|
96
|
+
export type LandedStep = {
|
|
97
|
+
kind: "checkout" | "branch";
|
|
98
|
+
repo: string;
|
|
99
|
+
target: string;
|
|
100
|
+
taskId: string | null;
|
|
101
|
+
outcome: "removed" | "already gone" | "waits" | "kept";
|
|
102
|
+
why: string | null;
|
|
103
|
+
};
|
|
104
|
+
/**
|
|
105
|
+
* Finish what teardowns intended: pending items, oldest first (checkouts before branches). Each is checked again
|
|
106
|
+
* right before it goes, and recorded right after. Doubt leaves it pending for the next pass; a branch that moved, or
|
|
107
|
+
* a result that has newer work, is kept for good. `only` limits it to these teardowns.
|
|
108
|
+
*/
|
|
109
|
+
export declare function finishLanded(store: Store, pool: WorktreePool, git: Git, now: () => Date, options?: {
|
|
110
|
+
repo?: string;
|
|
111
|
+
limit?: number;
|
|
112
|
+
only?: ReadonlySet<number>;
|
|
113
|
+
}): Promise<LandedStep[]>;
|
|
114
|
+
/**
|
|
115
|
+
* Reconcile's part: finish teardowns a crash cut short, then find newly landed results (a bounded few), mark them
|
|
116
|
+
* complete and tear them down.
|
|
117
|
+
*/
|
|
118
|
+
export declare function reconcileLanded(store: Store, pool: WorktreePool, git: Git, repo: string, root: string, now: () => Date): Promise<{
|
|
119
|
+
completed: string[];
|
|
120
|
+
steps: LandedStep[];
|
|
121
|
+
}>;
|
|
122
|
+
export type LandedSummary = {
|
|
123
|
+
completed: number;
|
|
124
|
+
checkouts: number;
|
|
125
|
+
branches: number;
|
|
126
|
+
/** What a teardown still waits on, and why. */
|
|
127
|
+
waiting: {
|
|
128
|
+
target: string;
|
|
129
|
+
why: string | null;
|
|
130
|
+
}[];
|
|
131
|
+
};
|
|
132
|
+
/** What landed-work teardowns did so far, and what they still wait on (Settings → Storage). Reads only. */
|
|
133
|
+
export declare function landedSummary(store: Store): LandedSummary;
|
|
134
|
+
export type LandedPlan = {
|
|
135
|
+
at: string;
|
|
136
|
+
cutoff: string;
|
|
137
|
+
results: LandedCandidate[];
|
|
138
|
+
branches: LandedBranch[];
|
|
139
|
+
kept: LandedKeep[];
|
|
140
|
+
/** Items an earlier teardown still has to finish. */
|
|
141
|
+
pending: number;
|
|
142
|
+
};
|
|
143
|
+
/**
|
|
144
|
+
* What `storage clean --landed` would do now, across every project: landed results finished at least a week ago,
|
|
145
|
+
* and Toolroll's own branches (toolroll/, toolroll-plan/, standing-orders/) no task uses whose last commit is at
|
|
146
|
+
* least a week old and landed in main. Reads only.
|
|
147
|
+
*/
|
|
148
|
+
export declare function landedBacklog(store: Store, git: Git, root: string, now: Date, options?: {
|
|
149
|
+
repos?: readonly string[];
|
|
150
|
+
}): Promise<LandedPlan>;
|
|
151
|
+
/** Act on a preview: record every teardown first, then carry them out. Refuses nothing it previewed silently: what
|
|
152
|
+
* changed since is reported as kept. */
|
|
153
|
+
export declare function cleanLandedBacklog(store: Store, pool: WorktreePool, git: Git, root: string, now: () => Date, plan: LandedPlan, actor: string): Promise<{
|
|
154
|
+
completed: string[];
|
|
155
|
+
steps: LandedStep[];
|
|
156
|
+
refused: LandedKeep[];
|
|
157
|
+
}>;
|