toolroll 0.9.94 → 0.9.96
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 +17 -1
- package/dist/agent-fence.d.ts +6 -0
- package/dist/agent-fence.js +2 -2
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +2 -0
- package/dist/attest.d.ts +3 -0
- package/dist/attest.js +10 -0
- package/dist/browser/terminal.js +1 -1
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +24 -24
- package/dist/browser-crew.js +2 -0
- package/dist/browser-workspace.d.ts +5 -0
- package/dist/build-review.js +2 -0
- package/dist/builder.d.ts +7 -2
- package/dist/builder.js +183 -79
- package/dist/card-catalog.d.ts +209 -0
- package/dist/card-catalog.js +133 -0
- package/dist/card-cli.d.ts +43 -0
- package/dist/card-cli.js +44 -0
- package/dist/card-render.d.ts +43 -0
- package/dist/card-render.js +260 -0
- package/dist/card-sources.d.ts +68 -0
- package/dist/card-sources.js +234 -0
- package/dist/chat-actions.d.ts +95 -8
- package/dist/chat-actions.js +449 -45
- package/dist/chat-approval.d.ts +13 -0
- package/dist/chat-approval.js +39 -9
- package/dist/chat-card-part.d.ts +11 -0
- package/dist/chat-card-part.js +18 -0
- package/dist/chat-card.d.ts +46 -0
- package/dist/chat-card.js +99 -0
- package/dist/chat-channel.d.ts +2 -0
- package/dist/chat-channel.js +26 -14
- package/dist/chat-controls.d.ts +4 -0
- package/dist/chat-controls.js +4 -0
- package/dist/chat-decide.d.ts +3 -14
- package/dist/chat-decide.js +43 -54
- package/dist/chat-delivery.js +13 -4
- package/dist/chat-flow.d.ts +0 -27
- package/dist/chat-flow.js +31 -2
- package/dist/chat-quiet.js +1 -1
- package/dist/chat-task-actions.d.ts +3 -2
- package/dist/chat-task-actions.js +19 -5
- package/dist/chat-terms.d.ts +14 -2
- package/dist/chat-terms.js +35 -0
- package/dist/check-failure.d.ts +2 -0
- package/dist/check-failure.js +34 -2
- package/dist/check-repair.d.ts +47 -0
- package/dist/check-repair.js +240 -0
- package/dist/claim.d.ts +6 -0
- package/dist/claim.js +13 -2
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +2 -0
- package/dist/cli.js +4 -0
- package/dist/coding-provider.d.ts +1 -1
- package/dist/coding-provider.js +9 -5
- package/dist/coding-workspace.d.ts +1 -1
- package/dist/coding-workspace.js +4 -4
- package/dist/contracts/chat-actions.d.ts +83 -0
- package/dist/contracts/chat-actions.js +18 -0
- package/dist/contracts/chat-card.d.ts +91 -0
- package/dist/contracts/chat-card.js +54 -0
- package/dist/contracts/chat-content.d.ts +191 -0
- package/dist/contracts/chat-content.js +4 -0
- package/dist/contracts/console-api.d.ts +2 -0
- package/dist/contracts/console-api.js +2 -0
- package/dist/contracts/flow-propose.d.ts +8 -1
- package/dist/contracts/flow-propose.js +3 -1
- package/dist/contracts/flow-send.d.ts +126 -0
- package/dist/contracts/flow-send.js +3 -0
- package/dist/contracts/lead-tools.d.ts +173 -0
- package/dist/contracts/lead-tools.js +15 -0
- package/dist/contracts/store-json.d.ts +16 -0
- package/dist/discord-chat.js +14 -0
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +7 -1
- package/dist/dispose.d.ts +19 -0
- package/dist/dispose.js +47 -7
- package/dist/eval-oracle.js +4 -1
- package/dist/evidence-pair.d.ts +53 -0
- package/dist/evidence-pair.js +128 -0
- package/dist/exhaustion.d.ts +17 -4
- package/dist/exhaustion.js +44 -4
- package/dist/export.js +1 -1
- package/dist/failing-first.d.ts +31 -0
- package/dist/failing-first.js +44 -0
- package/dist/fallback-schema.d.ts +8 -0
- package/dist/fallback-schema.js +40 -0
- package/dist/fallback.d.ts +138 -0
- package/dist/fallback.js +605 -0
- package/dist/findings.d.ts +224 -0
- package/dist/findings.js +449 -0
- package/dist/fleet-schema.d.ts +17 -0
- package/dist/fleet-schema.js +135 -0
- package/dist/flow-browser.js +2 -2
- package/dist/flow-code.d.ts +2 -2
- package/dist/flow-code.js +9 -5
- package/dist/flow-result-card.d.ts +22 -0
- package/dist/flow-result-card.js +55 -0
- package/dist/flow-send.js +12 -9
- package/dist/flow-steps.js +1 -1
- package/dist/flow-triggers.js +1 -1
- package/dist/flow-unattended.d.ts +26 -0
- package/dist/flow-unattended.js +12 -3
- package/dist/flows-ui.js +10 -2
- package/dist/follow-through-brief.d.ts +12 -0
- package/dist/follow-through-brief.js +31 -0
- package/dist/follow-through-cli.d.ts +23 -0
- package/dist/follow-through-cli.js +153 -0
- package/dist/git-incident.d.ts +16 -0
- package/dist/git-incident.js +69 -6
- package/dist/handover-note.d.ts +33 -0
- package/dist/handover-note.js +106 -0
- package/dist/invoke.d.ts +20 -1
- package/dist/invoke.js +108 -44
- package/dist/lead-cli.js +26 -24
- package/dist/lead-contract.js +1 -0
- package/dist/lead-doors.d.ts +4 -0
- package/dist/lead-doors.js +2 -2
- package/dist/lead-progress.js +2 -0
- package/dist/lead-tools.js +111 -32
- package/dist/lead.js +1 -1
- package/dist/local-owner.js +5 -0
- package/dist/modes.d.ts +6 -6
- package/dist/modes.js +3 -1
- package/dist/needs-you.d.ts +5 -2
- package/dist/needs-you.js +14 -2
- package/dist/operate-remote-arguments.js +1 -0
- package/dist/operate-remote.js +1 -1
- package/dist/operate.d.ts +1 -1
- package/dist/operate.js +165 -14
- package/dist/plan-login.d.ts +14 -0
- package/dist/plan-login.js +58 -0
- package/dist/planner.d.ts +1 -1
- package/dist/planner.js +2 -1
- package/dist/project-delete-ui.d.ts +12 -0
- package/dist/project-delete-ui.js +18 -1
- package/dist/project-delete.js +6 -0
- package/dist/project-knowledge.d.ts +6 -6
- package/dist/project-tools.d.ts +4 -4
- package/dist/project-tools.js +23 -7
- package/dist/protected-storage-look.d.ts +1 -0
- package/dist/protected-storage-look.js +13 -0
- package/dist/protected-storage.d.ts +70 -39
- package/dist/protected-storage.js +196 -147
- package/dist/provider-capture.d.ts +34 -0
- package/dist/provider-capture.js +53 -0
- package/dist/release-carry.js +10 -1
- package/dist/release.js +9 -1
- package/dist/scope.d.ts +22 -3
- package/dist/scope.js +71 -20
- package/dist/scout.d.ts +1 -1
- package/dist/scout.js +2 -1
- package/dist/serve.js +2 -0
- package/dist/server/chat.js +2 -2
- package/dist/server/console.css +50 -0
- package/dist/server/render-chat.js +14 -1
- package/dist/server/render-follow-through.d.ts +11 -0
- package/dist/server/render-follow-through.js +68 -0
- package/dist/server/render-results.js +1 -1
- package/dist/server/render-tasks.d.ts +3 -0
- package/dist/server/render-tasks.js +24 -5
- package/dist/server/route-table.js +3 -0
- package/dist/server/settings.js +37 -12
- package/dist/server/task-views.js +5 -0
- package/dist/server/tasks.js +76 -0
- package/dist/settings-groups-ui.d.ts +1 -0
- package/dist/settings-groups-ui.js +3 -1
- package/dist/settings-kit.js +3 -2
- package/dist/settings-proofs.js +3 -1
- package/dist/setup-guide.d.ts +2 -2
- package/dist/slack-chat.js +14 -1
- package/dist/steer-mailbox.d.ts +8 -1
- package/dist/steer-mailbox.js +164 -6
- package/dist/store.d.ts +11 -4
- package/dist/store.js +93 -30
- package/dist/surface.js +24 -4
- package/dist/task-control.d.ts +2 -1
- package/dist/task-status.js +5 -0
- package/dist/teardown-git.d.ts +2 -0
- package/dist/teardown-git.js +75 -19
- package/dist/telegram-lead.js +23 -6
- package/dist/telegram.js +12 -1
- package/dist/toolroll-update.js +2 -0
- package/dist/work-index.js +22 -7
- package/dist/work-summary.d.ts +1 -1
- package/dist/work-summary.js +5 -2
- package/dist/workspace-ui.d.ts +3 -1
- package/dist/workspace-ui.js +10 -1
- package/dist/worktree.js +23 -1
- package/package.json +2 -1
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A failed check gets one owner and a bounded repair (docs/plans/fleet-follow-through.md, part 2).
|
|
3
|
+
*
|
|
4
|
+
* One `check_repair` row per task family, revision and failure: the UNIQUE key is what keeps the same failure on the
|
|
5
|
+
* same revision from starting a second repair, however many times it is seen. The row's owner is the task that built
|
|
6
|
+
* the revision. The repair itself is the existing ledger's (`maybeTriggerRepair`, `repair_chain`), so it is an ordinary
|
|
7
|
+
* revision: the source scope verbatim, the ordinary rails, the ordinary approval.
|
|
8
|
+
*
|
|
9
|
+
* Who says yes doesn't change here. A mode a person signed with automatic repair and a limit drafts and approves through
|
|
10
|
+
* the seal that already exists. Without that signature nothing is drafted and nothing spends: the row waits for a person,
|
|
11
|
+
* and a person's repair is a draft that takes the normal plan approval. A model never moves a row past that.
|
|
12
|
+
*
|
|
13
|
+
* Failure text never reaches this file's decisions: the failure key comes from the sealed receipt and the log's failing
|
|
14
|
+
* file names, and it only ever matches rows, it grants nothing.
|
|
15
|
+
*/
|
|
16
|
+
import { createHash } from "node:crypto";
|
|
17
|
+
import { maybeTriggerRepair } from "./dispose.js";
|
|
18
|
+
import { readingFleet } from "./fleet-schema.js";
|
|
19
|
+
import { familyRootOf, settleFindingAnswers } from "./findings.js";
|
|
20
|
+
import { failingTestsOf } from "./gate-failure.js";
|
|
21
|
+
import { CHECK_REPAIR_LIMIT, modeTermsFromJson } from "./modes.js";
|
|
22
|
+
import { releaseCheckScope } from "./scope.js";
|
|
23
|
+
import { readVerificationView } from "./contracts/verification-view.js";
|
|
24
|
+
import { failedVerificationEvidence } from "./verification-evidence.js";
|
|
25
|
+
import { saveEvidencePair } from "./evidence-pair.js";
|
|
26
|
+
const repairOf = (row) => ({
|
|
27
|
+
id: Number(row["id"]), familyRoot: String(row["family_root"]), ownerTask: String(row["owner_task"]), repo: String(row["repo"]), revision: String(row["revision"]),
|
|
28
|
+
sourceRun: Number(row["source_run"]), state: String(row["state"]), why: String(row["why"]), repairTask: row["repair_task"] === null ? null : String(row["repair_task"]),
|
|
29
|
+
attempt: row["attempt"] === null ? null : Number(row["attempt"]), createdAt: String(row["created_at"]), updatedAt: String(row["updated_at"]),
|
|
30
|
+
});
|
|
31
|
+
export function checkRepairsOfTask(store, taskId) {
|
|
32
|
+
return readingFleet(() => store.handle.prepare("SELECT * FROM check_repair WHERE family_root = ? ORDER BY id DESC").all(familyRootOf(store, taskId)).map(repairOf), []);
|
|
33
|
+
}
|
|
34
|
+
export function checkRepairOfRun(store, runId) {
|
|
35
|
+
return readingFleet(() => {
|
|
36
|
+
const row = store.handle.prepare("SELECT * FROM check_repair WHERE source_run = ? ORDER BY id DESC LIMIT 1").get(runId);
|
|
37
|
+
return row === undefined ? null : repairOf(row);
|
|
38
|
+
}, null);
|
|
39
|
+
}
|
|
40
|
+
/** What makes this failure this failure: the command, how it ended, and which files failed. Never the log's own bytes,
|
|
41
|
+
* which differ on every run. */
|
|
42
|
+
export function failureKeyOf(receipt, log) {
|
|
43
|
+
const view = readVerificationView(receipt);
|
|
44
|
+
const facts = view.ok ? view.value : null;
|
|
45
|
+
const failing = failingTestsOf(log)?.map(one => one.file).sort() ?? [];
|
|
46
|
+
const result = facts === null ? null : facts.result;
|
|
47
|
+
return createHash("sha256").update(JSON.stringify(["toolroll:check-failure", facts?.command.digest ?? null, result?.exitCode ?? null, result?.failure ?? null, failing])).digest("hex").slice(0, 32);
|
|
48
|
+
}
|
|
49
|
+
/** Parked work stays quiet: a person already has this task, or it is stopped. */
|
|
50
|
+
function parkedWhy(store, taskRef, runId, now) {
|
|
51
|
+
if (store.activeHolds(taskRef, now).length > 0)
|
|
52
|
+
return "This task is on hold.";
|
|
53
|
+
if (store.applicableStopFor(runId) !== null)
|
|
54
|
+
return "This attempt was stopped.";
|
|
55
|
+
if (store.decisionsForTask(taskRef).some(one => one.state === "open" || one.state === "expired"))
|
|
56
|
+
return "This task is waiting for an answer.";
|
|
57
|
+
if (store.pendingSteerCount(taskRef) > 0)
|
|
58
|
+
return "This task has a note waiting for its next attempt.";
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
/** Why an approved plan can't cover a repair of this result, or null when it can. */
|
|
62
|
+
function notCovered(store, taskId, runId) {
|
|
63
|
+
const scope = store.getScope(taskId);
|
|
64
|
+
if (scope === null)
|
|
65
|
+
return "This task has no approved plan to repair under.";
|
|
66
|
+
if (scope.releaseTerms != null || releaseCheckScope(scope) || (scope.candidate ?? null) !== null)
|
|
67
|
+
return "Its plan only covers checking this commit. A fix needs a task of its own.";
|
|
68
|
+
if (store.lookupRef(taskId)?.deliverable === "report")
|
|
69
|
+
return "This task delivers a report: there's no code to repair.";
|
|
70
|
+
const publication = store.publicationForRun(runId);
|
|
71
|
+
if (publication !== null && !(publication.state === "intended" && publication.attempts === 0))
|
|
72
|
+
return "This result is published. Fix it through its pull request.";
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
/** The attempts this family's repair chain has spent. */
|
|
76
|
+
const attemptsUsed = (store, taskId) => store.repairLineageOf(taskId).attempts.reduce((highest, row) => Math.max(highest, row.attempt), 0);
|
|
77
|
+
function move(store, id, state, why, now, draft) {
|
|
78
|
+
store.handle.prepare("UPDATE check_repair SET state = ?, why = ?, updated_at = ?, repair_task = COALESCE(?, repair_task), attempt = COALESCE(?, attempt) WHERE id = ?")
|
|
79
|
+
.run(state, why, now.toISOString(), draft?.task ?? null, draft?.attempt ?? null, id);
|
|
80
|
+
store.bumpWake();
|
|
81
|
+
}
|
|
82
|
+
function askPerson(store, row, subject, body, now) {
|
|
83
|
+
store.enqueueNotification({ source: { run: row.sourceRun }, dedupeKey: `check-repair:${row.id}:${subject}`, kind: "check-repair", pushClass: "attention", subject, body, link: `/t/${encodeURIComponent(row.ownerTask)}` }, now);
|
|
84
|
+
}
|
|
85
|
+
/** Take the row one step, from where it stands now. A person's yes is never decided here: `person` only says a person asked for the draft. */
|
|
86
|
+
function advance(store, repo, root, row, now, person) {
|
|
87
|
+
const run = store.getRun(row.sourceRun);
|
|
88
|
+
const ref = run === null ? null : store.refById(run.taskRef);
|
|
89
|
+
const verdict = store.proofVerdictFor(row.sourceRun);
|
|
90
|
+
if (run === null || ref === null || verdict === null) {
|
|
91
|
+
move(store, row.id, "with-person", "The failed attempt's records can't be read.", now);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
const parked = person ? null : parkedWhy(store, run.taskRef, run.id, now);
|
|
95
|
+
if (parked !== null) {
|
|
96
|
+
move(store, row.id, "parked", parked, now);
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const uncovered = notCovered(store, row.ownerTask, run.id);
|
|
100
|
+
if (uncovered !== null) {
|
|
101
|
+
move(store, row.id, "with-person", uncovered, now);
|
|
102
|
+
// A release's check speaks for itself (release-adapters.ts): one voice for one failure.
|
|
103
|
+
if (store.getScope(row.ownerTask)?.releaseTerms == null)
|
|
104
|
+
askPerson(store, row, "A failed check needs you", `The check failed on ${row.revision.slice(0, 7)}, and a repair can't run under this plan. ${uncovered}`, now);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
const mode = store.activeMode(repo, now);
|
|
108
|
+
const terms = mode === null ? null : modeTermsFromJson(mode.termsJson);
|
|
109
|
+
const auto = terms?.repairAuto === true;
|
|
110
|
+
const limit = auto ? terms.repairMaxAttempts : CHECK_REPAIR_LIMIT;
|
|
111
|
+
const used = attemptsUsed(store, row.ownerTask);
|
|
112
|
+
if (used >= limit) {
|
|
113
|
+
const why = `${used} repair${used === 1 ? "" : "s"} tried, which is the limit. It's yours to decide.`;
|
|
114
|
+
move(store, row.id, "with-person", why, now);
|
|
115
|
+
askPerson(store, row, "Repairs ran out on a failed check", `The check still fails on ${row.revision.slice(0, 7)} after ${used} repair${used === 1 ? "" : "s"}. The failed check's log is on the result.`, now);
|
|
116
|
+
return null;
|
|
117
|
+
}
|
|
118
|
+
if (!auto && !person) {
|
|
119
|
+
move(store, row.id, "waiting", "Waiting for a person to start a repair.", now);
|
|
120
|
+
store.enqueueNotification({ source: { run: run.id }, dedupeKey: `check-repair:${row.id}:waiting`, kind: "check-repair", subject: "A check failed", body: `The check failed on ${row.revision.slice(0, 7)}. Start a repair on the task page.`, link: `/t/${encodeURIComponent(row.ownerTask)}` }, now);
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
const trigger = maybeTriggerRepair(store, repo, root, run.id, verdict.verdict, now, "verification", !person);
|
|
124
|
+
if (trigger.kind === "drafted") {
|
|
125
|
+
move(store, row.id, "repairing", trigger.approved ? `Repair ${trigger.draftTaskId} is approved under the signed mode and will build next.` : `Repair ${trigger.draftTaskId} is drafted and waits for its plan to be approved.`, now, { task: trigger.draftTaskId, attempt: trigger.attempt });
|
|
126
|
+
saveEvidencePair(store, root, { taskId: row.ownerTask, runId: run.id, by: "Toolroll" }, now);
|
|
127
|
+
}
|
|
128
|
+
else if (trigger.kind === "rerun") {
|
|
129
|
+
move(store, row.id, "rerunning", "The failure looks unrelated to this change, so the check runs once more on the same commit.", now);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const why = trigger.kind === "stopped" ? stopWords(trigger.reason) : "A repair can't start from this result. It's yours to decide.";
|
|
133
|
+
move(store, row.id, "with-person", why, now);
|
|
134
|
+
if (trigger.kind === "stopped")
|
|
135
|
+
askPerson(store, row, "A repair stopped", why, now);
|
|
136
|
+
}
|
|
137
|
+
return trigger;
|
|
138
|
+
}
|
|
139
|
+
const stopWords = (reason) => ({
|
|
140
|
+
"repair-attempts-spent": "The repairs allowed were used up.",
|
|
141
|
+
"repair-no-progress": "Two repairs in a row didn't reduce what's failing, so Toolroll stopped.",
|
|
142
|
+
"repair-refused-integrity": "The result disagrees with its approved terms, so a person reads it first.",
|
|
143
|
+
}[reason]);
|
|
144
|
+
/**
|
|
145
|
+
* Route one failed check. Runs at disposition. A failure already on record for this family and revision changes nothing,
|
|
146
|
+
* unless it was the one rerun of an unrelated failure, which now goes to a person through the ledger's own rerun stop.
|
|
147
|
+
*/
|
|
148
|
+
export function routeFailedCheck(store, repo, root, runId, now) {
|
|
149
|
+
const run = store.getRun(runId);
|
|
150
|
+
if (run === null || run.role !== "builder" || run.finishedAt === null || !run.headRevision)
|
|
151
|
+
return null;
|
|
152
|
+
const taskId = store.externalIdFor(run.taskRef);
|
|
153
|
+
if (taskId === null)
|
|
154
|
+
return null;
|
|
155
|
+
const failure = failedVerificationEvidence(store, root, runId);
|
|
156
|
+
if (failure.kind !== "failed")
|
|
157
|
+
return null;
|
|
158
|
+
const familyRoot = familyRootOf(store, taskId);
|
|
159
|
+
const key = failureKeyOf(failure.receipt, failure.log);
|
|
160
|
+
return store.transact(() => {
|
|
161
|
+
const earlier = store.handle.prepare("SELECT * FROM check_repair WHERE family_root = ? AND revision = ? AND failure_key = ?").get(familyRoot, run.headRevision, key);
|
|
162
|
+
if (earlier !== undefined) {
|
|
163
|
+
const row = repairOf(earlier);
|
|
164
|
+
if (row.state === "rerunning") {
|
|
165
|
+
store.handle.prepare("UPDATE check_repair SET source_run = ? WHERE id = ?").run(runId, row.id);
|
|
166
|
+
maybeTriggerRepair(store, repo, root, runId, store.proofVerdictFor(runId)?.verdict ?? "refuted", now, "verification", true);
|
|
167
|
+
move(store, row.id, "with-person", "It failed again on the same commit. It's yours to decide.", now);
|
|
168
|
+
}
|
|
169
|
+
return repairOf(store.handle.prepare("SELECT * FROM check_repair WHERE id = ?").get(row.id));
|
|
170
|
+
}
|
|
171
|
+
// A newer failure replaces an older one nobody took up.
|
|
172
|
+
store.handle.prepare("UPDATE check_repair SET state = 'closed', why = 'A newer attempt replaced this result.', updated_at = ? WHERE family_root = ? AND state IN ('waiting', 'parked', 'rerunning')").run(now.toISOString(), familyRoot);
|
|
173
|
+
const made = store.handle.prepare(`INSERT INTO check_repair (family_root, owner_task, repo, revision, failure_key, source_run, state, why, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, 'waiting', '', ?, ?)`)
|
|
174
|
+
.run(familyRoot, taskId, repo, run.headRevision, key, runId, now.toISOString(), now.toISOString());
|
|
175
|
+
const row = repairOf(store.handle.prepare("SELECT * FROM check_repair WHERE id = ?").get(Number(made.lastInsertRowid)));
|
|
176
|
+
advance(store, repo, root, row, now, false);
|
|
177
|
+
return repairOf(store.handle.prepare("SELECT * FROM check_repair WHERE id = ?").get(row.id));
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/** A person asks for the repair. The draft takes the normal plan approval: this is not that approval. */
|
|
181
|
+
export function startCheckRepair(store, root, runId, now) {
|
|
182
|
+
const row = checkRepairOfRun(store, runId);
|
|
183
|
+
if (row === null)
|
|
184
|
+
return { ok: false, reason: "unknown", message: `Run ${runId} has no failed check to repair.` };
|
|
185
|
+
if (row.state !== "waiting" && row.state !== "parked" && row.state !== "with-person")
|
|
186
|
+
return { ok: false, reason: "refused", message: row.repairTask !== null ? `Repair ${row.repairTask} already exists for this failure.` : row.why };
|
|
187
|
+
return store.transact(() => {
|
|
188
|
+
const trigger = advance(store, row.repo, root, row, now, true);
|
|
189
|
+
if (trigger?.kind === "drafted")
|
|
190
|
+
return { ok: true, draft: trigger.draftTaskId, attempt: trigger.attempt, approved: trigger.approved };
|
|
191
|
+
return { ok: false, reason: "refused", message: repairOf(store.handle.prepare("SELECT * FROM check_repair WHERE id = ?").get(row.id)).why };
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
/** A passing result in the family ends its failed-check rows. Rows the person already holds end too: the code passes now. */
|
|
195
|
+
export function settleCheckRepairs(store, taskId, verdict, now) {
|
|
196
|
+
if (verdict !== "verified" && verdict !== "attested")
|
|
197
|
+
return;
|
|
198
|
+
readingFleet(() => store.handle.prepare("UPDATE check_repair SET state = 'fixed', why = 'A later result passed its check.', updated_at = ? WHERE family_root = ? AND state NOT IN ('fixed', 'closed')").run(now.toISOString(), familyRootOf(store, taskId)), null);
|
|
199
|
+
}
|
|
200
|
+
/** Each sweep: a parked failure whose task is free again takes its next step, unless a newer attempt replaced it. */
|
|
201
|
+
export function resumeParkedRepairs(store, root, now) {
|
|
202
|
+
return readingFleet(() => {
|
|
203
|
+
let moved = 0;
|
|
204
|
+
for (const raw of store.handle.prepare("SELECT * FROM check_repair WHERE state = 'parked'").all()) {
|
|
205
|
+
const row = repairOf(raw);
|
|
206
|
+
const run = store.getRun(row.sourceRun);
|
|
207
|
+
if (run === null)
|
|
208
|
+
continue;
|
|
209
|
+
if (parkedWhy(store, run.taskRef, run.id, now) !== null)
|
|
210
|
+
continue;
|
|
211
|
+
store.transact(() => {
|
|
212
|
+
if (store.lastBuilderRun(run.taskRef)?.id !== run.id)
|
|
213
|
+
move(store, row.id, "closed", "A newer attempt replaced this result.", now);
|
|
214
|
+
else
|
|
215
|
+
advance(store, row.repo, root, row, now, false);
|
|
216
|
+
});
|
|
217
|
+
moved++;
|
|
218
|
+
}
|
|
219
|
+
return moved;
|
|
220
|
+
}, 0);
|
|
221
|
+
}
|
|
222
|
+
/** A build was accepted as finished: count what it answered, then route its check if it failed. Bookkeeping that never
|
|
223
|
+
* costs the build its result: a problem here leaves the finding open and the failed check on the result. */
|
|
224
|
+
export function followBuiltRun(store, repo, root, runId, worktree, now) {
|
|
225
|
+
try {
|
|
226
|
+
settleFindingAnswers(store, runId, worktree, now);
|
|
227
|
+
}
|
|
228
|
+
catch { /* the answers stay saved */ }
|
|
229
|
+
try {
|
|
230
|
+
store.transact(() => store.savepoint(() => routeFailedCheck(store, repo, root, runId, now)));
|
|
231
|
+
}
|
|
232
|
+
catch { /* the failure stays on the result */ }
|
|
233
|
+
}
|
|
234
|
+
/** How a failed check stands, for a person, in one line. */
|
|
235
|
+
export function checkRepairWords(row) {
|
|
236
|
+
const head = `Check failed on ${row.revision.slice(0, 7)} (build #${row.sourceRun})`;
|
|
237
|
+
if (row.state === "with-person")
|
|
238
|
+
return `${head}, needs you. ${row.why}`;
|
|
239
|
+
return row.state === "parked" ? `${head}. Paused: ${row.why}` : `${head}. ${row.why}`;
|
|
240
|
+
}
|
package/dist/claim.d.ts
CHANGED
|
@@ -214,6 +214,8 @@ export declare function acquireIfReady(store: Store, taskRef: number, runner: st
|
|
|
214
214
|
provider?: string;
|
|
215
215
|
/** The model this dispatch would run — the quota scope. */
|
|
216
216
|
model?: string;
|
|
217
|
+
/** A fallback entry's pinned auth mode: its quota is that credential's, never the base's. */
|
|
218
|
+
authMode?: "subscription" | "api-key";
|
|
217
219
|
/** What this dispatch is FOR. Re-proved inside the transaction — the
|
|
218
220
|
* caller's survey is not trusted (Codex planning review, finding 2). */
|
|
219
221
|
dispatchRole?: "builder" | "planner" | "scout";
|
|
@@ -348,6 +350,10 @@ export type FailureDisposition = {
|
|
|
348
350
|
ok: true;
|
|
349
351
|
disposition: "auth-expired";
|
|
350
352
|
provider: string;
|
|
353
|
+
} | {
|
|
354
|
+
ok: true;
|
|
355
|
+
disposition: "fallback";
|
|
356
|
+
provider: string;
|
|
351
357
|
} | {
|
|
352
358
|
ok: false;
|
|
353
359
|
reason: "fenced" | "unknown" | "stopped";
|
package/dist/claim.js
CHANGED
|
@@ -38,6 +38,7 @@ import { sealUnchangedPlanUnderMode } from "./plan-auto.js";
|
|
|
38
38
|
import { routeFromJson } from "./phase-routing.js";
|
|
39
39
|
import { contractChangesOf, decodePlannerSource, encodePlannerSource, canonicalContractJson, PLANNER_SOURCE_LIMITS, describeContractChanges, encodePlanContractRecord, plannerContractOf, plannerSourceDigest, } from "./planner-source.js";
|
|
40
40
|
import { storeEvidence, readVerifiedArtifact, EVIDENCE_CAPS } from "./evidence.js";
|
|
41
|
+
import { moveOnIfExhausted } from "./fallback.js";
|
|
41
42
|
/** How stale a mirror's last complete sync may be before admission refuses. */
|
|
42
43
|
export const SYNC_MAX_AGE_MS = 15 * 60_000;
|
|
43
44
|
/**
|
|
@@ -298,7 +299,7 @@ export function acquireIfReady(store, taskRef, runner, options) {
|
|
|
298
299
|
// Quota is keyed to the credential that actually exhausts: the RESOLVED
|
|
299
300
|
// provider, never a fixed binary name — codex quota must not collide
|
|
300
301
|
// with claude's, nor bypass it (Codex provider review, high finding 5).
|
|
301
|
-
const quota = store.quotaState(runner, options.provider ?? "claude", scope, options.now);
|
|
302
|
+
const quota = store.quotaState(runner, options.provider ?? "claude", scope, options.now, options.authMode ?? "subscription");
|
|
302
303
|
if (quota !== null && quota.state === "exhausted") {
|
|
303
304
|
return {
|
|
304
305
|
ok: false,
|
|
@@ -349,7 +350,7 @@ export function acquireIfReady(store, taskRef, runner, options) {
|
|
|
349
350
|
// exhausted with no probe in flight (finding 3). Same transaction, so
|
|
350
351
|
// consume-with-claim is all-or-nothing.
|
|
351
352
|
if (taken.ok && quota !== null) {
|
|
352
|
-
store.consumeHalfOpen(runner, options.provider ?? "claude", scope, options.now);
|
|
353
|
+
store.consumeHalfOpen(runner, options.provider ?? "claude", scope, options.now, options.authMode ?? "subscription");
|
|
353
354
|
}
|
|
354
355
|
return taken;
|
|
355
356
|
});
|
|
@@ -880,6 +881,16 @@ function sealFailure(store, leaseId, args) {
|
|
|
880
881
|
if (failureClass !== "commit-failure" && run.terminalClass === "auth-expired") {
|
|
881
882
|
return { ok: true, ...sealAuthExpired(store, run, taskId, now) };
|
|
882
883
|
}
|
|
884
|
+
// Out of plan on an approved chain: the walk moves on, no strike, and the next agent runs through the ordinary claim.
|
|
885
|
+
// Anything else about this ending (fallback.ts says what closed the walk) stays the ordinary road below.
|
|
886
|
+
// Only the agent's own exit moves: a lease, branch or HEAD law that failed, or a commit that failed, is that ending.
|
|
887
|
+
if (failureClass === "unknown") {
|
|
888
|
+
const moved = moveOnIfExhausted(store, runId, taskId, now);
|
|
889
|
+
if (moved !== null) {
|
|
890
|
+
store.finishRun(runId, { outcome: "failed", reason: "usage-exhausted", now });
|
|
891
|
+
return { ok: true, disposition: "fallback", provider: moved.to.provider };
|
|
892
|
+
}
|
|
893
|
+
}
|
|
883
894
|
store.finishRun(runId, { outcome: "failed", reason: failureClass, now });
|
|
884
895
|
if (failureClass === "commit-failure") {
|
|
885
896
|
// The work exists and is uncommitted; nothing times its way out of
|
package/dist/cli-help.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* The top-level usage text, on its own so `toolroll --help` can print it
|
|
3
3
|
* without loading every command family (see cli-fast.ts).
|
|
4
4
|
*/
|
|
5
|
-
export declare const HELP = "toolroll \u2014 hand coding work to agents, walk away, come back to pull requests\n\nUsage\n toolroll at your terminal: start Toolroll here (or attach) and open it in the terminal\n (--web: the browser; --project-root <dir>: where projects live)\n in a repository without a terminal, under an agent or with --json: only says what it would do\n toolroll open [--web] open this computer's Toolroll again, signed in\n toolroll welcome the three steps to a first result (--json: the model)\n toolroll password set add a password (typed twice, at your terminal)\n toolroll scan [path...] report what is in flight (also bare `toolroll` outside a repository)\n toolroll [path...] the same report for the paths given\n toolroll pulls report what is waiting on a person\n toolroll graph report which work graph is already here\n toolroll repos list connected repositories, and how to adjust\n toolroll repos add <path> add a project; it is in the console at once (no path: the repo you are in)\n toolroll repos remove <path>\n take it out of the console; its tasks and results stay\n toolroll repos add-from-github <owner/name> --root <dir>\n preview, then clone and connect (--yes)\n toolroll link put `toolroll` on your PATH\n toolroll unlink take it off again\n toolroll update update to the latest release: verified, drained, undoable\n (preview first; --yes, --now, --at HH:MM, --rollback)\n toolroll release <branch> release Toolroll itself: checks, your approval, deploy, merge, tag, publish\n (rerun the same command to continue; --help)\n toolroll skills install --claude-code [--dir <path>]\n install this binary's guides for Claude Code (preview first)\n toolroll skills install teach a repo's agents this queue exists (preview first)\n toolroll demo a seeded throwaway sandbox \u2014 see it working in 90 seconds\n toolroll up app + builder for every saved project \u2014 the normal start\n toolroll onboard make your agent Toolroll's lead: add this repo, install its skill (--yes), offer starter flows (--starter)\n toolroll status running, queued, ready results, release check and plan windows\n toolroll integrations which integrations work, and what to do about the ones that don't (--json)\n toolroll lead token mint your lead agent's sign-in: its own work pings nobody\n toolroll lead say \"<text>\" (the lead) one short message in its person's chat\n\nTasks \u2014 `toolroll task` prints the whole list,\nand any task command + --help prints it too\n toolroll approver add <name>\n add a person who can approve work\n toolroll task add <title> queue work\n toolroll task show <id> one task: its plan, builds and what it waits for\n toolroll task approve <id>\n the yes \u2014 nothing builds without one\n toolroll task accept <id> --finish\n accept the result you read (--despite \"<why>\": accept it with a reason, without finishing)\n toolroll task send-back <id> --feedback \"<what to change>\"\n send the result back for changes\n toolroll task retry <id> build it again\n toolroll task ready <id> can I merge it? each check, and what clears a blocker\n toolroll chat --as <you> --token <t>\n talk to your lead: one conversation across every\n project; it proposes, you confirm (--say \"\u2026\" for one turn)\n toolroll flows list | show <id> | create | edit | trigger | script | card | archive\n set up and inspect flows (writes preview until --yes)\n\nMachine and agent commands (the builder, agent tools, recovery):\n toolroll internals lists them; each keeps working as before\n\nWith nothing connected it reports everything it can find below the working\ndirectory. Once you connect repositories it reports those instead.\n\nOptions\n --all report everything discoverable, ignoring connected repos\n --depth <n> directory levels to descend (default: 6)\n --hidden include dot-directories, which are skipped by default\n --dirty also read each working tree for uncommitted files\n --local branches only: no network, no pull requests, no issues\n --json emit a machine-readable envelope instead of a report\n -o <file> also write that envelope to a file (requires --json)\n -h, --help show this\n\nNothing is written, installed, or configured. Every repository is read\nthrough git with --no-optional-locks, so a scan never contends for the\nindex lock with an editor you have open.\n\n--dirty is off by default because reading a working tree is unbounded\nwork: on a large or cloud-synced repo `git status` can take minutes,\nwhere listing branches takes milliseconds.\n\nlink shows what it would do and changes nothing; add --yes to apply it,\nor --to <dir> to choose where. It never uses sudo, never leaves your\nhome directory, and never touches a file it did not create.";
|
|
5
|
+
export declare const HELP = "toolroll \u2014 hand coding work to agents, walk away, come back to pull requests\n\nUsage\n toolroll at your terminal: start Toolroll here (or attach) and open it in the terminal\n (--web: the browser; --project-root <dir>: where projects live)\n in a repository without a terminal, under an agent or with --json: only says what it would do\n toolroll open [--web] open this computer's Toolroll again, signed in\n toolroll welcome the three steps to a first result (--json: the model)\n toolroll password set add a password (typed twice, at your terminal)\n toolroll scan [path...] report what is in flight (also bare `toolroll` outside a repository)\n toolroll [path...] the same report for the paths given\n toolroll pulls report what is waiting on a person\n toolroll graph report which work graph is already here\n toolroll repos list connected repositories, and how to adjust\n toolroll repos add <path> add a project; it is in the console at once (no path: the repo you are in)\n toolroll repos remove <path>\n take it out of the console; its tasks and results stay\n toolroll repos add-from-github <owner/name> --root <dir>\n preview, then clone and connect (--yes)\n toolroll link put `toolroll` on your PATH\n toolroll unlink take it off again\n toolroll update update to the latest release: verified, drained, undoable\n (preview first; --yes, --now, --at HH:MM, --rollback)\n toolroll release <branch> release Toolroll itself: checks, your approval, deploy, merge, tag, publish\n (rerun the same command to continue; --help)\n toolroll skills install --claude-code [--dir <path>]\n install this binary's guides for Claude Code (preview first)\n toolroll skills install teach a repo's agents this queue exists (preview first)\n toolroll demo a seeded throwaway sandbox \u2014 see it working in 90 seconds\n toolroll up app + builder for every saved project \u2014 the normal start\n toolroll onboard make your agent Toolroll's lead: add this repo, install its skill (--yes), offer starter flows (--starter)\n toolroll status running, queued, ready results, release check and plan windows\n toolroll integrations which integrations work, and what to do about the ones that don't (--json)\n toolroll lead token mint your lead agent's sign-in: its own work pings nobody\n toolroll lead say \"<text>\" (the lead) one short message in its person's chat\n\nTasks \u2014 `toolroll task` prints the whole list,\nand any task command + --help prints it too\n toolroll approver add <name>\n add a person who can approve work\n toolroll task add <title> queue work\n toolroll task show <id> one task: its plan, builds and what it waits for\n toolroll task approve <id>\n the yes \u2014 nothing builds without one\n toolroll task accept <id> --finish\n accept the result you read (--despite \"<why>\": accept it with a reason, without finishing)\n toolroll task send-back <id> --feedback \"<what to change>\"\n send the result back for changes\n toolroll task retry <id> build it again\n toolroll task ready <id> can I merge it? each check, and what clears a blocker\n toolroll chat --as <you> --token <t>\n talk to your lead: one conversation across every\n project; it proposes, you confirm (--say \"\u2026\" for one turn)\n toolroll card <source> the data chat shows as a card: tasks, results, flows, spend,\n projects, builders, mode, integrations, providers, findings, handover\n toolroll flows list | show <id> | create | edit | trigger | script | card | archive\n set up and inspect flows (writes preview until --yes)\n\nMachine and agent commands (the builder, agent tools, recovery):\n toolroll internals lists them; each keeps working as before\n\nWith nothing connected it reports everything it can find below the working\ndirectory. Once you connect repositories it reports those instead.\n\nOptions\n --all report everything discoverable, ignoring connected repos\n --depth <n> directory levels to descend (default: 6)\n --hidden include dot-directories, which are skipped by default\n --dirty also read each working tree for uncommitted files\n --local branches only: no network, no pull requests, no issues\n --json emit a machine-readable envelope instead of a report\n -o <file> also write that envelope to a file (requires --json)\n -h, --help show this\n\nNothing is written, installed, or configured. Every repository is read\nthrough git with --no-optional-locks, so a scan never contends for the\nindex lock with an editor you have open.\n\n--dirty is off by default because reading a working tree is unbounded\nwork: on a large or cloud-synced repo `git status` can take minutes,\nwhere listing branches takes milliseconds.\n\nlink shows what it would do and changes nothing; add --yes to apply it,\nor --to <dir> to choose where. It never uses sudo, never leaves your\nhome directory, and never touches a file it did not create.";
|
|
6
6
|
/** The commands people rarely type: the builder's own loop, the machine contract and recovery. They all keep working;
|
|
7
7
|
* `toolroll internals` (or `toolroll internals --help`) lists them so top-level help stays about the work. */
|
|
8
8
|
export declare const INTERNALS_HELP = "toolroll internals \u2014 machine and agent commands\n\n toolroll contract the machine contract: envelope version + capabilities\n (--commands dumps the declared command guide)\n toolroll skills list the guides this exact binary serves\n toolroll skills get <name> print one guide (version-matched, never stale)\n toolroll health recent server latency, write waits and live streams\n toolroll ready what could be started right now\n toolroll check-progress <run> current or final approved check progress\n toolroll task wait <id> [--timeout <seconds>]\n wait for this attempt to finish or need a person\n toolroll task scope <id> --goal <text>\n write a task's plan from the command line\n toolroll claim <id> --runner <name>\n toolroll heartbeat <lease> / worker release <lease> / reap\n toolroll tick --runner <name> --token <t> --repo <path>\n one unattended pass over the ready set\n toolroll serve --repo <path> run only the console\n (--editor vscode with --runner: file links\n open in VS Code on the device you browse from)\n toolroll watch / daemon install run only the builder\n toolroll doctor check that Toolroll's service holds no key a build could read\n toolroll reconcile recover what the last stretch left behind\n toolroll release --recover-deploy [--stage <dir>] [--yes]\n toolroll release --release-gate <id> [--yes]\n the release tool's recovery steps (toolroll release <branch> runs them for you)";
|
package/dist/cli-help.js
CHANGED
|
@@ -56,6 +56,8 @@ and any task command + --help prints it too
|
|
|
56
56
|
toolroll chat --as <you> --token <t>
|
|
57
57
|
talk to your lead: one conversation across every
|
|
58
58
|
project; it proposes, you confirm (--say "…" for one turn)
|
|
59
|
+
toolroll card <source> the data chat shows as a card: tasks, results, flows, spend,
|
|
60
|
+
projects, builders, mode, integrations, providers, findings, handover
|
|
59
61
|
toolroll flows list | show <id> | create | edit | trigger | script | card | archive
|
|
60
62
|
set up and inspect flows (writes preview until --yes)
|
|
61
63
|
|
package/dist/cli.js
CHANGED
|
@@ -141,6 +141,10 @@ export const OPERATE_COMMANDS = new Set([
|
|
|
141
141
|
"map",
|
|
142
142
|
"memory",
|
|
143
143
|
"flaky",
|
|
144
|
+
"card",
|
|
145
|
+
// Follow-through (follow-through-cli.ts): findings and saved provider failures.
|
|
146
|
+
"finding",
|
|
147
|
+
"captures",
|
|
144
148
|
"models",
|
|
145
149
|
"claim",
|
|
146
150
|
"heartbeat",
|
|
@@ -85,4 +85,4 @@ export declare function createCodexCodingProvider(options?: {
|
|
|
85
85
|
export declare function codingSessionPermissions(store: Pick<Store, "databaseFile" | "listWorktrees" | "projectSandbox">, worktreeRoot: string): (input: {
|
|
86
86
|
repo: string;
|
|
87
87
|
worktree: string;
|
|
88
|
-
}) => CodingPermissions
|
|
88
|
+
}) => Promise<CodingPermissions>;
|
package/dist/coding-provider.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { prepareProtectedStorage, storageRefusalOf } from "./protected-storage.js";
|
|
2
3
|
import { TextDecoder } from "node:util";
|
|
3
4
|
import { homedir, hostname } from "node:os";
|
|
4
5
|
import { readDenyPath } from "./project-sandbox.js";
|
|
@@ -441,20 +442,23 @@ export function createCodexCodingProvider(options = {}) {
|
|
|
441
442
|
export function codingSessionPermissions(store, worktreeRoot) {
|
|
442
443
|
return input => sessionPermissions(store, { ...input, worktreeRoot });
|
|
443
444
|
}
|
|
444
|
-
function sessionPermissions(store, input) {
|
|
445
|
+
async function sessionPermissions(store, input) {
|
|
445
446
|
const databaseFile = store.databaseFile();
|
|
446
447
|
if (databaseFile === null || databaseFile === "" || databaseFile === ":memory:")
|
|
447
448
|
return { problem: "The coding session has no state folder to fence, so it was not started." };
|
|
448
449
|
try {
|
|
449
450
|
const poolRoots = [join(dirname(databaseFile), "worktrees"), input.worktreeRoot, ...store.listWorktrees().map(record => poolRootOf(record.path, record.repo))];
|
|
450
451
|
const binding = trustedWorktreeBinding(input.repo, input.worktree, poolRoots);
|
|
451
|
-
|
|
452
|
+
// The storage check, awaited off this thread (protected-storage.ts).
|
|
453
|
+
const storage = await prepareProtectedStorage();
|
|
454
|
+
const boundary = taskBoundary({ worktree: input.worktree, provider: "codex", binding, policy: FENCE_POLICY_CURRENT, storage });
|
|
452
455
|
// The project's read denies, read live (a session seals none; they only narrow).
|
|
453
456
|
const readDeny = store.projectSandbox(input.repo).readDeny.map(one => readDenyPath(one, homedir()));
|
|
454
|
-
const launch = fenceFor("build", { databaseFile, worktree: input.worktree, boundary, binding, runFolders: runFolders(null), poolRoots, readDeny, policy: FENCE_POLICY_CURRENT });
|
|
457
|
+
const launch = fenceFor("build", { databaseFile, worktree: input.worktree, boundary, binding, runFolders: runFolders(null), poolRoots, readDeny, policy: FENCE_POLICY_CURRENT, storage });
|
|
455
458
|
return { config: codexFenceConfig(launch.fence, false, launch.fenceGrants) };
|
|
456
459
|
}
|
|
457
|
-
catch {
|
|
458
|
-
|
|
460
|
+
catch (error) {
|
|
461
|
+
// The storage check's own words survive: what didn't finish, and which program macOS may need to allow.
|
|
462
|
+
return { problem: storageRefusalOf(error)?.message ?? "The session's worktree couldn't be bound to its project, so the session was not started." };
|
|
459
463
|
}
|
|
460
464
|
}
|
|
@@ -39,7 +39,7 @@ type Options = {
|
|
|
39
39
|
permissions: (input: {
|
|
40
40
|
repo: string;
|
|
41
41
|
worktree: string;
|
|
42
|
-
}) => CodingPermissions
|
|
42
|
+
}) => CodingPermissions | Promise<CodingPermissions>;
|
|
43
43
|
provider?: () => CodingProvider;
|
|
44
44
|
admissionPaused?: () => boolean;
|
|
45
45
|
authorize?: (actor: CodingActor, repo: string) => boolean;
|
package/dist/coding-workspace.js
CHANGED
|
@@ -280,8 +280,8 @@ export class CodingWorkspace {
|
|
|
280
280
|
return { id, type, text, status: string(item['status']) || null, ...(type === 'userMessage' && clientId ? { clientId } : {}) };
|
|
281
281
|
}
|
|
282
282
|
/** The thread's confinement, made right before each start or resume from the plane's own records. */
|
|
283
|
-
permissionsFor(repo, worktree) {
|
|
284
|
-
const made = this.options.permissions({ repo, worktree });
|
|
283
|
+
async permissionsFor(repo, worktree) {
|
|
284
|
+
const made = await this.options.permissions({ repo, worktree });
|
|
285
285
|
if ('problem' in made)
|
|
286
286
|
throw Error(made.problem);
|
|
287
287
|
return made;
|
|
@@ -455,7 +455,7 @@ export class CodingWorkspace {
|
|
|
455
455
|
this.authorize(actor, repo);
|
|
456
456
|
this.admit();
|
|
457
457
|
nativeStartupAttempted = true;
|
|
458
|
-
const result = object(await this.request('thread/start', { cwd: worktree, approvalPolicy: 'on-request', approvalsReviewer: 'user', ...this.permissionsFor(repo, worktree), ...(input.model ? { model: input.model } : {}), developerInstructions: CODING_INSTRUCTIONS + (context?.text ?? ''), persistExtendedHistory: true }));
|
|
458
|
+
const result = object(await this.request('thread/start', { cwd: worktree, approvalPolicy: 'on-request', approvalsReviewer: 'user', ...(await this.permissionsFor(repo, worktree)), ...(input.model ? { model: input.model } : {}), developerInstructions: CODING_INSTRUCTIONS + (context?.text ?? ''), persistExtendedHistory: true }));
|
|
459
459
|
const thread = object(result['thread']), nativeThreadId = string(thread['id']);
|
|
460
460
|
if (!nativeThreadId)
|
|
461
461
|
throw Error('The agent did not return a session identity.');
|
|
@@ -504,7 +504,7 @@ export class CodingWorkspace {
|
|
|
504
504
|
this.authorize({ name: session.owner, generation: session.generation }, session.repo);
|
|
505
505
|
if (session.context)
|
|
506
506
|
verifyCodingContext(session.context, session.repo, session.base);
|
|
507
|
-
const result = object(await this.request('thread/resume', { threadId: session.nativeThreadId, cwd: session.worktree, approvalPolicy: 'on-request', approvalsReviewer: 'user', ...this.permissionsFor(session.repo, session.worktree), ...(session.model ? { model: session.model } : {}), developerInstructions: CODING_INSTRUCTIONS + (session.context?.text ?? ''), persistExtendedHistory: true }));
|
|
507
|
+
const result = object(await this.request('thread/resume', { threadId: session.nativeThreadId, cwd: session.worktree, approvalPolicy: 'on-request', approvalsReviewer: 'user', ...(await this.permissionsFor(session.repo, session.worktree)), ...(session.model ? { model: session.model } : {}), developerInstructions: CODING_INSTRUCTIONS + (session.context?.text ?? ''), persistExtendedHistory: true }));
|
|
508
508
|
const thread = object(result['thread']);
|
|
509
509
|
if (thread['id'] !== session.nativeThreadId)
|
|
510
510
|
throw Error('The agent returned a different conversation. Continuity could not be verified.');
|
|
@@ -264,6 +264,73 @@ export declare const chatActionRequestSchemas: {
|
|
|
264
264
|
repo: z.ZodString;
|
|
265
265
|
on: z.ZodBoolean;
|
|
266
266
|
}, z.core.$strict>;
|
|
267
|
+
readonly mode_revoke: z.ZodObject<{
|
|
268
|
+
repo: z.ZodString;
|
|
269
|
+
}, z.core.$strict>;
|
|
270
|
+
readonly provider_resume: z.ZodObject<{
|
|
271
|
+
provider: z.ZodString;
|
|
272
|
+
}, z.core.$strict>;
|
|
273
|
+
readonly worker_note: z.ZodObject<{
|
|
274
|
+
runner: z.ZodString;
|
|
275
|
+
note: z.ZodString;
|
|
276
|
+
}, z.core.$strict>;
|
|
277
|
+
readonly lead_identity: z.ZodObject<{
|
|
278
|
+
name: z.ZodString;
|
|
279
|
+
persona: z.ZodString;
|
|
280
|
+
}, z.core.$strict>;
|
|
281
|
+
readonly task_archive: z.ZodObject<{
|
|
282
|
+
task: z.ZodString;
|
|
283
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
284
|
+
}, z.core.$strict>;
|
|
285
|
+
readonly task_restore: z.ZodObject<{
|
|
286
|
+
task: z.ZodString;
|
|
287
|
+
}, z.core.$strict>;
|
|
288
|
+
readonly flow_archive: z.ZodObject<{
|
|
289
|
+
flow: z.ZodInt;
|
|
290
|
+
}, z.core.$strict>;
|
|
291
|
+
readonly flow_unattended_off: z.ZodObject<{
|
|
292
|
+
flow: z.ZodInt;
|
|
293
|
+
}, z.core.$strict>;
|
|
294
|
+
readonly incident_resolve: z.ZodObject<{
|
|
295
|
+
incident: z.ZodInt;
|
|
296
|
+
}, z.core.$strict>;
|
|
297
|
+
readonly finding_send: z.ZodObject<{
|
|
298
|
+
task: z.ZodString;
|
|
299
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
300
|
+
file: z.ZodOptional<z.ZodString>;
|
|
301
|
+
line: z.ZodOptional<z.ZodInt>;
|
|
302
|
+
pointer: z.ZodOptional<z.ZodString>;
|
|
303
|
+
run: z.ZodOptional<z.ZodInt>;
|
|
304
|
+
}, z.core.$strict>;
|
|
305
|
+
readonly finding_resend: z.ZodObject<{
|
|
306
|
+
finding: z.ZodInt;
|
|
307
|
+
}, z.core.$strict>;
|
|
308
|
+
readonly task_reopen: z.ZodObject<{
|
|
309
|
+
task: z.ZodString;
|
|
310
|
+
}, z.core.$strict>;
|
|
311
|
+
readonly revision_accept: z.ZodObject<{
|
|
312
|
+
task: z.ZodString;
|
|
313
|
+
revision: z.ZodOptional<z.ZodInt>;
|
|
314
|
+
}, z.core.$strict>;
|
|
315
|
+
readonly revision_reject: z.ZodObject<{
|
|
316
|
+
task: z.ZodString;
|
|
317
|
+
revision: z.ZodOptional<z.ZodInt>;
|
|
318
|
+
}, z.core.$strict>;
|
|
319
|
+
readonly flow_unattended: z.ZodObject<{
|
|
320
|
+
flow: z.ZodInt;
|
|
321
|
+
days: z.ZodInt;
|
|
322
|
+
budget: z.ZodOptional<z.ZodString>;
|
|
323
|
+
runs: z.ZodOptional<z.ZodString>;
|
|
324
|
+
include: z.ZodOptional<z.ZodArray<z.ZodInt>>;
|
|
325
|
+
}, z.core.$strict>;
|
|
326
|
+
readonly memory_decide: z.ZodObject<{
|
|
327
|
+
repo: z.ZodString;
|
|
328
|
+
proposal: z.ZodInt;
|
|
329
|
+
decision: z.ZodEnum<{
|
|
330
|
+
accept: "accept";
|
|
331
|
+
reject: "reject";
|
|
332
|
+
}>;
|
|
333
|
+
}, z.core.$strict>;
|
|
267
334
|
readonly code_map_holdout_end: z.ZodObject<{
|
|
268
335
|
repo: z.ZodString;
|
|
269
336
|
}, z.core.$strict>;
|
|
@@ -306,6 +373,9 @@ export declare const sharedActionSchema: z.ZodObject<{
|
|
|
306
373
|
code_map_switch: "code_map_switch";
|
|
307
374
|
decision_record: "decision_record";
|
|
308
375
|
decision_retire: "decision_retire";
|
|
376
|
+
finding_resend: "finding_resend";
|
|
377
|
+
finding_send: "finding_send";
|
|
378
|
+
flow_archive: "flow_archive";
|
|
309
379
|
flow_browser: "flow_browser";
|
|
310
380
|
flow_browser_off: "flow_browser_off";
|
|
311
381
|
flow_card_add: "flow_card_add";
|
|
@@ -325,13 +395,22 @@ export declare const sharedActionSchema: z.ZodObject<{
|
|
|
325
395
|
flow_trigger_pause: "flow_trigger_pause";
|
|
326
396
|
flow_trigger_remove: "flow_trigger_remove";
|
|
327
397
|
flow_trigger_resume: "flow_trigger_resume";
|
|
398
|
+
flow_unattended: "flow_unattended";
|
|
399
|
+
flow_unattended_off: "flow_unattended_off";
|
|
400
|
+
incident_resolve: "incident_resolve";
|
|
328
401
|
kit_setup: "kit_setup";
|
|
329
402
|
knowledge_instructions: "knowledge_instructions";
|
|
330
403
|
knowledge_remove: "knowledge_remove";
|
|
331
404
|
knowledge_restore: "knowledge_restore";
|
|
332
405
|
knowledge_save: "knowledge_save";
|
|
333
406
|
lead_about_you: "lead_about_you";
|
|
407
|
+
lead_identity: "lead_identity";
|
|
408
|
+
memory_decide: "memory_decide";
|
|
409
|
+
mode_revoke: "mode_revoke";
|
|
410
|
+
provider_resume: "provider_resume";
|
|
334
411
|
result_accept: "result_accept";
|
|
412
|
+
revision_accept: "revision_accept";
|
|
413
|
+
revision_reject: "revision_reject";
|
|
335
414
|
scope_approve: "scope_approve";
|
|
336
415
|
skill_disable: "skill_disable";
|
|
337
416
|
skill_enable: "skill_enable";
|
|
@@ -351,11 +430,15 @@ export declare const sharedActionSchema: z.ZodObject<{
|
|
|
351
430
|
suggestion_not_now: "suggestion_not_now";
|
|
352
431
|
sweep_cancel_rest: "sweep_cancel_rest";
|
|
353
432
|
sweep_file: "sweep_file";
|
|
433
|
+
task_archive: "task_archive";
|
|
354
434
|
task_cancel: "task_cancel";
|
|
435
|
+
task_reopen: "task_reopen";
|
|
436
|
+
task_restore: "task_restore";
|
|
355
437
|
task_resume: "task_resume";
|
|
356
438
|
tool_add: "tool_add";
|
|
357
439
|
tool_remove: "tool_remove";
|
|
358
440
|
trial_stop: "trial_stop";
|
|
441
|
+
worker_note: "worker_note";
|
|
359
442
|
}>;
|
|
360
443
|
request: z.ZodObject<{}, z.core.$catchall<z.ZodUnknown>>;
|
|
361
444
|
repo: z.ZodString;
|
|
@@ -86,6 +86,24 @@ export const chatActionRequestSchemas = {
|
|
|
86
86
|
// Slice 3: stopping a trial (starting one stays in Settings → Models, behind a step-up).
|
|
87
87
|
trial_stop: z.strictObject({ repo: words, trial: id }),
|
|
88
88
|
code_map_switch: z.strictObject({ repo: words, on: z.boolean() }),
|
|
89
|
+
// Chat and console parity (docs/plans/toolroll-cards.md): each runs the console's own write.
|
|
90
|
+
mode_revoke: z.strictObject({ repo: words }),
|
|
91
|
+
provider_resume: z.strictObject({ provider: words }),
|
|
92
|
+
worker_note: z.strictObject({ runner: words, note: words }),
|
|
93
|
+
lead_identity: z.strictObject({ name: words, persona: words }),
|
|
94
|
+
task_archive: z.strictObject({ task: words, reason: words.optional() }),
|
|
95
|
+
task_restore: z.strictObject({ task: words }),
|
|
96
|
+
flow_archive: z.strictObject({ flow: id }),
|
|
97
|
+
flow_unattended_off: z.strictObject({ flow: id }),
|
|
98
|
+
incident_resolve: z.strictObject({ incident: id }),
|
|
99
|
+
// Follow-through: send a finding to the task that owns the code. Text is checked and cleaned when the card is made.
|
|
100
|
+
finding_send: z.strictObject({ task: words, summary: words.optional(), file: words.optional(), line: z.int().min(1).optional(), pointer: words.optional(), run: id.optional() }),
|
|
101
|
+
finding_resend: z.strictObject({ finding: id }),
|
|
102
|
+
task_reopen: z.strictObject({ task: words }),
|
|
103
|
+
revision_accept: z.strictObject({ task: words, revision: id.optional() }),
|
|
104
|
+
revision_reject: z.strictObject({ task: words, revision: id.optional() }),
|
|
105
|
+
flow_unattended: z.strictObject({ flow: id, days: z.int().min(1), budget: words.optional(), runs: words.optional(), include: z.array(id).optional() }),
|
|
106
|
+
memory_decide: z.strictObject({ repo: words, proposal: id, decision: z.enum(["accept", "reject"]) }),
|
|
89
107
|
code_map_holdout_end: z.strictObject({ repo: words }),
|
|
90
108
|
/** The line, and (from 1) the line it replaces; 0 or none adds it. */
|
|
91
109
|
lead_about_you: z.strictObject({ line: words, replaces: z.int().min(0).optional() }),
|