toolroll 0.9.61 → 0.9.63
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 +46 -0
- package/README.md +51 -3
- package/dist/agent-fence.d.ts +155 -10
- package/dist/agent-fence.js +473 -38
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +21 -21
- package/dist/browser-workspace.d.ts +28 -0
- package/dist/chat-actions.js +2 -2
- package/dist/check-levels.js +3 -0
- package/dist/claim.js +1 -1
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +1 -0
- package/dist/cli.js +31 -11
- package/dist/contracts/cli.d.ts +8 -0
- package/dist/contracts/cli.js +3 -1
- package/dist/contracts/console-api.d.ts +19 -1
- package/dist/contracts/console-api.js +2 -1
- package/dist/contracts/integration-metadata.d.ts +2 -1
- package/dist/contracts/integration-metadata.js +2 -1
- package/dist/desktop-update.d.ts +1 -1
- package/dist/dispatch.js +7 -4
- package/dist/doctor.d.ts +53 -0
- package/dist/doctor.js +427 -0
- package/dist/evidence-pack.d.ts +1 -1
- package/dist/evidence-pack.js +2 -2
- package/dist/exec.d.ts +3 -0
- package/dist/exec.js +5 -4
- package/dist/flow-code.d.ts +2 -2
- package/dist/flow-code.js +4 -15
- package/dist/flow-engine.js +11 -6
- package/dist/flow-triggers.d.ts +8 -4
- package/dist/flow-triggers.js +45 -25
- package/dist/flow-unattended-words.d.ts +28 -0
- package/dist/flow-unattended-words.js +50 -0
- package/dist/flow-unattended.d.ts +227 -0
- package/dist/flow-unattended.js +776 -0
- package/dist/flows-cli.d.ts +28 -1
- package/dist/flows-cli.js +55 -10
- package/dist/flows-ui.d.ts +2 -1
- package/dist/flows-ui.js +4 -1
- package/dist/google-mail.d.ts +1 -0
- package/dist/google-mail.js +61 -18
- package/dist/integrations-ui.d.ts +2 -1
- package/dist/integrations-ui.js +6 -3
- package/dist/integrations.d.ts +17 -3
- package/dist/integrations.js +38 -18
- package/dist/invoke.js +300 -168
- package/dist/landed-cleanup.d.ts +26 -12
- package/dist/landed-cleanup.js +199 -85
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +3 -3
- package/dist/lead-tools.js +11 -3
- package/dist/mcp-connect.d.ts +8 -7
- package/dist/mcp-connect.js +49 -51
- package/dist/oauth-run-token.d.ts +51 -0
- package/dist/oauth-run-token.js +127 -0
- package/dist/operate.js +74 -17
- package/dist/plan-auto.d.ts +11 -0
- package/dist/plan-auto.js +62 -11
- package/dist/process-recovery-eligibility.d.ts +1 -1
- package/dist/project-access.d.ts +3 -0
- package/dist/project-access.js +3 -0
- package/dist/project-admission.d.ts +10 -6
- package/dist/project-admission.js +33 -11
- package/dist/project-delete-ui.d.ts +1 -0
- package/dist/project-delete-ui.js +4 -1
- package/dist/project-delete.d.ts +3 -1
- package/dist/project-delete.js +10 -4
- package/dist/project-file.d.ts +3 -0
- package/dist/project-file.js +16 -0
- package/dist/project-tools.d.ts +99 -25
- package/dist/project-tools.js +394 -98
- package/dist/provider-home.d.ts +22 -0
- package/dist/provider-home.js +99 -0
- package/dist/provider.d.ts +17 -3
- package/dist/provider.js +34 -10
- package/dist/remote-main.d.ts +61 -0
- package/dist/remote-main.js +179 -0
- package/dist/scope.d.ts +1 -1
- package/dist/serve.js +11 -3
- package/dist/server/flows.js +27 -5
- package/dist/server/render-tasks.d.ts +3 -0
- package/dist/server/render-tasks.js +3 -0
- package/dist/server/route-table.js +3 -0
- package/dist/server/runtime.d.ts +1 -1
- package/dist/server/settings.js +14 -5
- package/dist/server/task-views.js +2 -0
- package/dist/storage-sweep-child.js +3 -1
- package/dist/storage-sweep-process.d.ts +14 -0
- package/dist/storage-sweep-process.js +37 -3
- package/dist/storage-sweep-schedule.d.ts +30 -0
- package/dist/storage-sweep-schedule.js +164 -0
- package/dist/storage-sweep.d.ts +6 -5
- package/dist/storage-sweep.js +10 -70
- package/dist/storage-ui.d.ts +2 -0
- package/dist/storage-ui.js +7 -4
- package/dist/store.d.ts +75 -7
- package/dist/store.js +241 -17
- package/dist/subagent-tools.d.ts +1 -1
- package/dist/surface.js +10 -6
- package/dist/task-base.d.ts +35 -0
- package/dist/task-base.js +51 -0
- package/dist/teardown-git.d.ts +14 -0
- package/dist/teardown-git.js +119 -0
- package/dist/tool-broker.d.ts +37 -0
- package/dist/tool-broker.js +369 -0
- package/dist/tool-launcher.js +5 -4
- package/dist/tool-secret-store.d.ts +170 -0
- package/dist/tool-secret-store.js +834 -0
- package/dist/toolroll-update.js +4 -2
- package/dist/tools-ui.d.ts +7 -1
- package/dist/tools-ui.js +17 -8
- package/dist/work-index.js +5 -3
- package/dist/worktree.d.ts +64 -6
- package/dist/worktree.js +171 -35
- package/package.json +3 -1
- package/scripts/provider-canary.mjs +210 -31
|
@@ -1115,6 +1115,34 @@ export type BrowserFlowView = {
|
|
|
1115
1115
|
/** Where the flow stands (v88): the live stream's nudge names it, and a page that already shows it doesn't read again. */
|
|
1116
1116
|
live: string | null;
|
|
1117
1117
|
canEdit: boolean;
|
|
1118
|
+
/** "Runs on its own" (flow-unattended.ts): the live grant, and whether this person can sign one (why not, when not). */
|
|
1119
|
+
unattended: {
|
|
1120
|
+
active: {
|
|
1121
|
+
until: string;
|
|
1122
|
+
untilWords: string;
|
|
1123
|
+
signedBy: string;
|
|
1124
|
+
budgetMicrousd: number | null;
|
|
1125
|
+
dailyRunCap: number | null;
|
|
1126
|
+
runsToday: number;
|
|
1127
|
+
words: string[];
|
|
1128
|
+
covers: string[];
|
|
1129
|
+
} | null;
|
|
1130
|
+
canSign: boolean;
|
|
1131
|
+
problem: string | null;
|
|
1132
|
+
maxDays: number;
|
|
1133
|
+
scheduled: boolean;
|
|
1134
|
+
terms: {
|
|
1135
|
+
head: string[];
|
|
1136
|
+
rules: string[];
|
|
1137
|
+
};
|
|
1138
|
+
/** The schedules a signature could cover: plain ones always, `optional` ones (outside content, said why) only when picked. */
|
|
1139
|
+
schedules: {
|
|
1140
|
+
id: number;
|
|
1141
|
+
words: string;
|
|
1142
|
+
optional: string | null;
|
|
1143
|
+
}[];
|
|
1144
|
+
schedulesDigest: string;
|
|
1145
|
+
};
|
|
1118
1146
|
approvers: string[];
|
|
1119
1147
|
/** The projects a Build zone may work in: the flow's own first, then others the viewer can reach. */
|
|
1120
1148
|
projects?: {
|
package/dist/chat-actions.js
CHANGED
|
@@ -1331,9 +1331,9 @@ function runFlowAction(store, payload, actor, repos, root, now) {
|
|
|
1331
1331
|
if (payload.operation.startsWith("flow_trigger_")) {
|
|
1332
1332
|
const trigger = store.getFlowTrigger(Number(req["trigger"]));
|
|
1333
1333
|
if (payload.operation === "flow_trigger_remove")
|
|
1334
|
-
removeFlowTrigger(store, trigger, now, configDirOf(store));
|
|
1334
|
+
removeFlowTrigger(store, trigger, now, configDirOf(store), actor);
|
|
1335
1335
|
else
|
|
1336
|
-
store.updateFlowTrigger(trigger.id, { state: payload.operation === "flow_trigger_pause" ? "paused" : "active" }, now);
|
|
1336
|
+
store.updateFlowTrigger(trigger.id, { state: payload.operation === "flow_trigger_pause" ? "paused" : "active" }, now, actor);
|
|
1337
1337
|
return { said: payload.operation === "flow_trigger_remove" ? "Trigger removed." : payload.operation === "flow_trigger_pause" ? "Trigger paused." : "Trigger on again.", href: `/flows/${trigger.flow}` };
|
|
1338
1338
|
}
|
|
1339
1339
|
const card = store.getFlowCard(Number(req["card"]));
|
package/dist/check-levels.js
CHANGED
|
@@ -153,6 +153,9 @@ export function suggestQuickCommand(repo) {
|
|
|
153
153
|
const has = (name) => typeof scripts[name] === "string";
|
|
154
154
|
const runner = existsSync(join(repo, "pnpm-lock.yaml")) ? "pnpm" : existsSync(join(repo, "yarn.lock")) ? "yarn" : "npm";
|
|
155
155
|
const run = (name) => runner === "npm" ? `npm run ${name}` : `${runner} ${name}`;
|
|
156
|
+
// A project's own quick script knows its tests best (Toolroll's runs the attack suite when a guard changes).
|
|
157
|
+
if (has("check:quick"))
|
|
158
|
+
return run("check:quick");
|
|
156
159
|
const exec = runner === "npm" ? "npx" : `${runner} exec`;
|
|
157
160
|
const typecheck = ["typecheck", "type-check", "check-types", "tsc"].find(has) ?? null;
|
|
158
161
|
const changed = "$(git diff --name-only HEAD~1)";
|
package/dist/claim.js
CHANGED
|
@@ -1116,7 +1116,7 @@ function sealPlan(store, leaseId, args) {
|
|
|
1116
1116
|
? ""
|
|
1117
1117
|
: "The filed goal, exclusions, touches, and acceptance criteria are reproduced exactly.\n"
|
|
1118
1118
|
: `It AMENDS the filed contract (${oneLine(describeContractChanges(contractChanges).join("; "), 300)})${amendment === null ? "" : ` — because: ${oneLine(amendment, 200)}`}\n`) +
|
|
1119
|
-
(autoApproved ? "Approved under the signed operating mode. The next build may proceed with the original terms and required quality checks
|
|
1119
|
+
(autoApproved ? `${store.getScope(taskId)?.approvalBasis === "grant" ? "Approved because its flow runs on its own." : "Approved under the signed operating mode."} The next build may proceed with the original terms and required quality checks.` : "Review, edit, and approve the scope — nothing builds until you do."),
|
|
1120
1120
|
}, now);
|
|
1121
1121
|
return { ok: true, changes: contractChanges.length, amendment };
|
|
1122
1122
|
}
|
package/dist/cli-help.d.ts
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
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 a control plane for unattended coding agents\n\nUsage\n toolroll [path...] report what is in flight\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: gate, your approval, deploy, merge, tag, publish\n (resumable: rerun to continue; --help)\n toolroll contract the machine contract: envelope version + capabilities\n (--commands dumps the declared command guide)\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 skills list the guides this exact binary serves\n toolroll skills get <name> print one guide (version-matched, never stale)\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 health recent server latency, write waits and live streams\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 credential: its own work pings nobody\n toolroll lead say \"<text>\" (the lead) one short message in its person's chat\n\nOperating the queue \u2014 `toolroll task` prints the whole surface,\nand any queue command + --help prints it too\n toolroll approver add <name>\n mint the credential that lets a person say yes\n toolroll ready what could be dispatched 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 add <title> queue work\n toolroll task scope <id> --goal <text>\n state what success is; approval binds to it\n toolroll task approve <id>\n the yes \u2014 nothing builds without one\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 flows list | show <id> | create | edit | trigger | script | card | archive\n set up and inspect flows (writes preview until --yes)\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 serve --repo <path> advanced: 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 advanced: run only the builder\n toolroll reconcile recover what the last stretch left behind\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 a control plane for unattended coding agents\n\nUsage\n toolroll [path...] report what is in flight\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: gate, your approval, deploy, merge, tag, publish\n (resumable: rerun to continue; --help)\n toolroll contract the machine contract: envelope version + capabilities\n (--commands dumps the declared command guide)\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 skills list the guides this exact binary serves\n toolroll skills get <name> print one guide (version-matched, never stale)\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 health recent server latency, write waits and live streams\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 credential: its own work pings nobody\n toolroll lead say \"<text>\" (the lead) one short message in its person's chat\n\nOperating the queue \u2014 `toolroll task` prints the whole surface,\nand any queue command + --help prints it too\n toolroll approver add <name>\n mint the credential that lets a person say yes\n toolroll ready what could be dispatched 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 add <title> queue work\n toolroll task scope <id> --goal <text>\n state what success is; approval binds to it\n toolroll task approve <id>\n the yes \u2014 nothing builds without one\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 flows list | show <id> | create | edit | trigger | script | card | archive\n set up and inspect flows (writes preview until --yes)\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 serve --repo <path> advanced: 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 advanced: 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\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.";
|
package/dist/cli-help.js
CHANGED
|
@@ -63,6 +63,7 @@ and any queue command + --help prints it too
|
|
|
63
63
|
(--editor vscode with --runner: file links
|
|
64
64
|
open in VS Code on the device you browse from)
|
|
65
65
|
toolroll watch / daemon install advanced: run only the builder
|
|
66
|
+
toolroll doctor check that Toolroll's service holds no key a build could read
|
|
66
67
|
toolroll reconcile recover what the last stretch left behind
|
|
67
68
|
|
|
68
69
|
With nothing connected it reports everything it can find below the working
|
package/dist/cli.js
CHANGED
|
@@ -313,6 +313,9 @@ async function dispatch(argv, write, mainOptions) {
|
|
|
313
313
|
// Command families below load when their command runs, so one command never pays for the others.
|
|
314
314
|
if (argv[0] === 'health')
|
|
315
315
|
return (await import("./health-cli.js")).runHealthCommand(argv.slice(1), write, mainOptions.team ?? mainOptions.remote);
|
|
316
|
+
// This computer's own service, read here and never on a central server.
|
|
317
|
+
if (argv[0] === 'doctor')
|
|
318
|
+
return (await import("./doctor.js")).runDoctorCommand(argv.slice(1), write);
|
|
316
319
|
const central = await maybeRunRemoteCommand(argv, mainOptions.remote ?? mainOptions.team);
|
|
317
320
|
if (typeof central === "number")
|
|
318
321
|
return central;
|
|
@@ -506,7 +509,14 @@ async function runReposCommand(argv, write, onboard) {
|
|
|
506
509
|
const tokenAt = argv.indexOf("--token");
|
|
507
510
|
const tokenFlag = tokenAt === -1 ? undefined : argv[tokenAt + 1];
|
|
508
511
|
const bare = argv.filter((argument, index) => argument !== "--json" && (tokenAt === -1 || (index !== tokenAt && index !== tokenAt + 1)));
|
|
509
|
-
const
|
|
512
|
+
const remoteAt = bare.indexOf("--remote");
|
|
513
|
+
const remoteUrl = remoteAt === -1 ? undefined : bare[remoteAt + 1];
|
|
514
|
+
const [action, ...paths] = bare.filter((_, i) => remoteAt === -1 || (i !== remoteAt && i !== remoteAt + 1));
|
|
515
|
+
if (remoteAt !== -1 && (action !== "add" || bare.filter(v => v === "--remote").length !== 1 || remoteUrl === undefined || remoteUrl.startsWith("-") || paths.length > 1)) {
|
|
516
|
+
const message = "Use repos add <project> --remote <url> for one project.";
|
|
517
|
+
write(json ? envelopeJson({ ok: false, command: "repos", reason: "usage", message }) : message);
|
|
518
|
+
return USAGE_EXIT;
|
|
519
|
+
}
|
|
510
520
|
const databaseFile = databasePath(process.env, homedir());
|
|
511
521
|
const file = registryBeside(databaseFile);
|
|
512
522
|
if (tokenAt !== -1 && (tokenFlag === undefined || tokenFlag.startsWith("-"))) {
|
|
@@ -529,11 +539,18 @@ async function runReposCommand(argv, write, onboard) {
|
|
|
529
539
|
return 1;
|
|
530
540
|
}
|
|
531
541
|
if (action === undefined) {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
542
|
+
const store = openStore(databaseFile);
|
|
543
|
+
try {
|
|
544
|
+
const projects = loaded.repos.map(repo => ({ repo, remoteUrl: store.projectRemote(repo) }));
|
|
545
|
+
if (json) {
|
|
546
|
+
write(envelopeJson({ ok: true, command: "repos", count: loaded.repos.length, repos: loaded.repos, projects, file }));
|
|
547
|
+
return 0;
|
|
548
|
+
}
|
|
549
|
+
return listRepos(loaded.repos, file, write, new Map(projects.map(p => [p.repo, p.remoteUrl])));
|
|
550
|
+
}
|
|
551
|
+
finally {
|
|
552
|
+
store.close();
|
|
535
553
|
}
|
|
536
|
-
return listRepos(loaded.repos, file, write);
|
|
537
554
|
}
|
|
538
555
|
if (action !== "add" && action !== "remove") {
|
|
539
556
|
const message = `unknown command \`repos ${action}\` — try \`repos\`, \`repos add\`, \`repos remove\`, or \`repos add-from-github <owner/name>\``;
|
|
@@ -545,7 +562,7 @@ async function runReposCommand(argv, write, onboard) {
|
|
|
545
562
|
const lines = [];
|
|
546
563
|
const said = {};
|
|
547
564
|
const say = json ? (line) => lines.push(line) : write;
|
|
548
|
-
const code = await withAdmission({ databaseFile, registryFile: file, ...(leadToken === undefined ? {} : { leadToken }) }, say, said, admission => action === "add" ? addToRepos(admission, targets, say, said) : removeFromRepos(admission, targets, say, said));
|
|
565
|
+
const code = await withAdmission({ databaseFile, registryFile: file, ...(leadToken === undefined ? {} : { leadToken }) }, say, said, admission => action === "add" ? addToRepos(admission, targets, say, said, remoteUrl) : removeFromRepos(admission, targets, say, said));
|
|
549
566
|
if (json) {
|
|
550
567
|
write(envelopeJson({
|
|
551
568
|
ok: code === 0,
|
|
@@ -582,7 +599,7 @@ async function withAdmission(where, write, said, body) {
|
|
|
582
599
|
store.close();
|
|
583
600
|
}
|
|
584
601
|
}
|
|
585
|
-
function listRepos(repos, file, write) {
|
|
602
|
+
function listRepos(repos, file, write, remotes) {
|
|
586
603
|
if (repos.length === 0) {
|
|
587
604
|
write("No repositories connected. Toolroll reports on everything it can find.");
|
|
588
605
|
write("");
|
|
@@ -592,8 +609,10 @@ function listRepos(repos, file, write) {
|
|
|
592
609
|
return 0;
|
|
593
610
|
}
|
|
594
611
|
write(`${repos.length === 1 ? "1 repository" : `${repos.length} repositories`} connected:`);
|
|
595
|
-
for (const repo of repos)
|
|
612
|
+
for (const repo of repos) {
|
|
596
613
|
write(` ${repo}${existsSync(repo) ? "" : " (missing)"}`);
|
|
614
|
+
write(` Recorded remote: ${remotes.get(repo) ?? "None"}`);
|
|
615
|
+
}
|
|
597
616
|
write("");
|
|
598
617
|
write(" toolroll repos add <path> add another; it is in the console at once");
|
|
599
618
|
write(" toolroll repos remove <path> take one out of the console; its tasks stay");
|
|
@@ -606,7 +625,7 @@ function listRepos(repos, file, write) {
|
|
|
606
625
|
function admissionExit(reason) {
|
|
607
626
|
return reason === "not-git" || reason === "unavailable" ? USAGE_EXIT : 1;
|
|
608
627
|
}
|
|
609
|
-
async function addToRepos(admission, targets, write, said) {
|
|
628
|
+
async function addToRepos(admission, targets, write, said, remoteUrl) {
|
|
610
629
|
// Every folder is proved before anything is written: one that is not a
|
|
611
630
|
// repository fails here, not later and further away.
|
|
612
631
|
let refused = false;
|
|
@@ -624,7 +643,7 @@ async function addToRepos(admission, targets, write, said) {
|
|
|
624
643
|
const running = consoleRunning(admission.store, now);
|
|
625
644
|
const projects = [];
|
|
626
645
|
for (const path of targets) {
|
|
627
|
-
const admitted = await admitProject(admission.store, { registryFile: admission.registryFile, path, actor: admission.actor, now });
|
|
646
|
+
const admitted = await admitProject(admission.store, { registryFile: admission.registryFile, path, actor: admission.actor, now, captureOrigin: true, ...(remoteUrl === undefined ? {} : { remoteUrl }) });
|
|
628
647
|
if (!admitted.ok) {
|
|
629
648
|
// The updater's own taxonomy survives (verification finding 4): a held
|
|
630
649
|
// lock or a malformed registry is an operational fact, never "usage".
|
|
@@ -633,8 +652,9 @@ async function addToRepos(admission, targets, write, said) {
|
|
|
633
652
|
write(admitted.message);
|
|
634
653
|
return admissionExit(admitted.reason);
|
|
635
654
|
}
|
|
636
|
-
projects.push({ repo: admitted.repo, name: admitted.name, added: admitted.added });
|
|
655
|
+
projects.push({ repo: admitted.repo, name: admitted.name, added: admitted.added, remoteUrl: admitted.remote.url });
|
|
637
656
|
write(addedWords(admitted.name, admitted.added, running));
|
|
657
|
+
write(`Recorded remote: ${admitted.remote.url ?? "None. Use repos add <project> --remote <url> to record one."}`);
|
|
638
658
|
}
|
|
639
659
|
said.projects = projects;
|
|
640
660
|
return 0;
|
package/dist/contracts/cli.d.ts
CHANGED
|
@@ -647,6 +647,14 @@ export declare const COMMAND_OUTPUTS: {
|
|
|
647
647
|
readable: z.ZodBoolean;
|
|
648
648
|
zones: z.ZodArray<z.ZodUnknown>;
|
|
649
649
|
triggers: z.ZodArray<z.ZodUnknown>;
|
|
650
|
+
unattended: z.ZodNullable<z.ZodObject<{
|
|
651
|
+
until: z.ZodString;
|
|
652
|
+
signedBy: z.ZodString;
|
|
653
|
+
budgetMicrousd: z.ZodNullable<z.ZodNumber>;
|
|
654
|
+
dailyRunCap: z.ZodNullable<z.ZodNumber>;
|
|
655
|
+
runsToday: z.ZodNumber;
|
|
656
|
+
covers: z.ZodArray<z.ZodString>;
|
|
657
|
+
}, z.core.$loose>>;
|
|
650
658
|
}, z.core.$loose>;
|
|
651
659
|
}, z.core.$loose>, z.ZodObject<{
|
|
652
660
|
envelopeVersion: z.ZodLiteral<1>;
|
package/dist/contracts/cli.js
CHANGED
|
@@ -184,7 +184,9 @@ export const COMMAND_OUTPUTS = {
|
|
|
184
184
|
"task unhold": answers("task unhold", { id: str }),
|
|
185
185
|
"task wait": answers("task wait", { task: str, outcome: str }),
|
|
186
186
|
"flows list": answers("flows list", { flows: z.array(flowListed) }),
|
|
187
|
-
"flows show": answers("flows show", { flow: z.looseObject({ id: int, name: str, repo: str, project: str, readable: z.boolean(), zones: list, triggers: list
|
|
187
|
+
"flows show": answers("flows show", { flow: z.looseObject({ id: int, name: str, repo: str, project: str, readable: z.boolean(), zones: list, triggers: list,
|
|
188
|
+
// Whether the flow runs on its own, and until when (null: it doesn't).
|
|
189
|
+
unattended: nullable(z.looseObject({ until: str, signedBy: str, budgetMicrousd: nullable(int), dailyRunCap: nullable(int), runsToday: int, covers: z.array(str) })) }) }),
|
|
188
190
|
grants: answers("grants", { count: int, grants: list }),
|
|
189
191
|
"runner list": answers("runner list", { count: int, runners: list }),
|
|
190
192
|
"incident list": answers("incident list", { incidents: list }),
|
|
@@ -73,7 +73,7 @@ export declare const CONSOLE_FORMS: {
|
|
|
73
73
|
readonly kit: FormContract<"repo">;
|
|
74
74
|
readonly subagents: FormContract<"choice" | "dailyTurns" | "id" | "manager" | "model" | "name" | "note" | "op" | "repo" | "schedule" | "soul" | "state" | "template" | "text" | "tool" | `field.${string}` | `over.${string}` | `undo.${string}` | `use.${string}`>;
|
|
75
75
|
readonly galleryUse: FormContract<string>;
|
|
76
|
-
readonly flows: FormContract<"about" | "address" | "answers" | "approve" | "body" | "choice" | "confirm" | "decision" | "definition" | "description" | "document" | "draft" | "entry" | "file" | "key" | "label" | "language" | "name" | "note" | "owner" | "password" | "previewed" | "remove" | "repo" | "revision" | "secret" | "stage" | "template" | "timeoutMinutes" | "title" | "trigger" | "url" | "value" | "watching" | `param.${string}`>;
|
|
76
|
+
readonly flows: FormContract<"about" | "address" | "answers" | "approve" | "body" | "budget" | "choice" | "confirm" | "days" | "decision" | "definition" | "description" | "document" | "draft" | "entry" | "file" | "include" | "key" | "label" | "language" | "name" | "note" | "owner" | "password" | "previewed" | "remove" | "repo" | "revision" | "runs" | "schedules" | "secret" | "stage" | "template" | "timeoutMinutes" | "title" | "trigger" | "url" | "value" | "watching" | `param.${string}`>;
|
|
77
77
|
readonly spendBudget: FormContract<"action" | "password" | "stop" | "target" | "usd">;
|
|
78
78
|
readonly projectConcurrency: FormContract<"concurrency" | "repo">;
|
|
79
79
|
readonly projectDelete: FormContract<"name" | "password" | "repo" | "step">;
|
|
@@ -318,6 +318,15 @@ export declare const flowViewSchema: z.ZodObject<{
|
|
|
318
318
|
selectedCard: z.ZodNullable<z.ZodNumber>;
|
|
319
319
|
live: z.ZodNullable<z.ZodString>;
|
|
320
320
|
canEdit: z.ZodBoolean;
|
|
321
|
+
unattended: z.ZodObject<{
|
|
322
|
+
active: z.ZodNullable<z.ZodObject<{
|
|
323
|
+
until: z.ZodString;
|
|
324
|
+
signedBy: z.ZodString;
|
|
325
|
+
}, z.core.$loose>>;
|
|
326
|
+
canSign: z.ZodBoolean;
|
|
327
|
+
problem: z.ZodNullable<z.ZodString>;
|
|
328
|
+
scheduled: z.ZodBoolean;
|
|
329
|
+
}, z.core.$loose>;
|
|
321
330
|
approvers: z.ZodArray<z.ZodString>;
|
|
322
331
|
kinds: z.ZodArray<z.ZodObject<{
|
|
323
332
|
kind: z.ZodString;
|
|
@@ -467,6 +476,15 @@ export declare const CONSOLE_RESPONSES: {
|
|
|
467
476
|
selectedCard: z.ZodNullable<z.ZodNumber>;
|
|
468
477
|
live: z.ZodNullable<z.ZodString>;
|
|
469
478
|
canEdit: z.ZodBoolean;
|
|
479
|
+
unattended: z.ZodObject<{
|
|
480
|
+
active: z.ZodNullable<z.ZodObject<{
|
|
481
|
+
until: z.ZodString;
|
|
482
|
+
signedBy: z.ZodString;
|
|
483
|
+
}, z.core.$loose>>;
|
|
484
|
+
canSign: z.ZodBoolean;
|
|
485
|
+
problem: z.ZodNullable<z.ZodString>;
|
|
486
|
+
scheduled: z.ZodBoolean;
|
|
487
|
+
}, z.core.$loose>;
|
|
470
488
|
approvers: z.ZodArray<z.ZodString>;
|
|
471
489
|
kinds: z.ZodArray<z.ZodObject<{
|
|
472
490
|
kind: z.ZodString;
|
|
@@ -82,7 +82,7 @@ export const CONSOLE_FORMS = {
|
|
|
82
82
|
kit: formContract("POST /kits/<kit>/<act>", ["repo"]),
|
|
83
83
|
subagents: formContract("POST /settings/lead/subagents/new, /settings/lead/subagents/<id>/<part>, /settings/lead/subagents/questions/<id>/answer", ["choice", "text", "repo", "template", "name", "soul", "op", "id", "schedule", "tool", "state", "note", "model", "dailyTurns", "manager"], { prefixes: ["use.", "undo.", "field.", "over."] }),
|
|
84
84
|
galleryUse: formContract("POST /flows/new/<template>", ["repo", "send-result", "name", "intent", "previewed"], { dynamic: true }),
|
|
85
|
-
flows: formContract("POST /flows/new, /flows/example, /flows/import, /flows/<id>/<act>, /flows/<id>/cards/<card>/<act>, /flows/<id>/triggers/<trigger>/<act>", ["repo", "document", "url", "confirm", "previewed", "template", "name", "answers", "password", "secret", "trigger", "approve", "remove", "about", "body", "timeoutMinutes", "language", "file", "address", "key", "definition", "owner", "revision", "value", "title", "description", "stage", "decision", "note", "draft", "choice", "entry", "label", "watching"], { prefixes: ["param."] }),
|
|
85
|
+
flows: formContract("POST /flows/new, /flows/example, /flows/import, /flows/<id>/<act>, /flows/<id>/cards/<card>/<act>, /flows/<id>/triggers/<trigger>/<act>", ["repo", "document", "url", "confirm", "previewed", "template", "name", "answers", "password", "secret", "trigger", "approve", "remove", "about", "body", "timeoutMinutes", "language", "file", "address", "key", "definition", "owner", "revision", "value", "title", "description", "stage", "decision", "note", "draft", "choice", "entry", "label", "watching", "days", "budget", "runs", "include", "schedules"], { prefixes: ["param."] }),
|
|
86
86
|
spendBudget: formContract("POST /spend/budget", ["password", "target", "action", "usd", "stop"]),
|
|
87
87
|
projectConcurrency: formContract("POST /settings/project/concurrency", ["repo", "concurrency"]),
|
|
88
88
|
projectDelete: formContract("POST /settings/project/delete", ["repo", "name", "step", "password"]),
|
|
@@ -255,6 +255,7 @@ export const flowViewSchema = z.looseObject({
|
|
|
255
255
|
selectedCard: z.number().nullable(),
|
|
256
256
|
live: z.string().nullable(),
|
|
257
257
|
canEdit: z.boolean(),
|
|
258
|
+
unattended: z.looseObject({ active: z.looseObject({ until: z.string(), signedBy: z.string() }).nullable(), canSign: z.boolean(), problem: z.string().nullable(), scheduled: z.boolean() }),
|
|
258
259
|
approvers: z.array(z.string()),
|
|
259
260
|
kinds: z.array(z.looseObject({ kind: z.string(), label: z.string() })),
|
|
260
261
|
colors: z.array(z.string()),
|
|
@@ -22,11 +22,12 @@ export declare const protectedResourceSchema: z.ZodObject<{
|
|
|
22
22
|
authorization_servers: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
23
23
|
scopes_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
24
24
|
}, z.core.$strip>;
|
|
25
|
-
/** RFC 8414 (or OpenID) authorization-server metadata: where to sign in, trade a code, and
|
|
25
|
+
/** RFC 8414 (or OpenID) authorization-server metadata: where to sign in, trade a code, register, and revoke. */
|
|
26
26
|
export declare const authorizationServerSchema: z.ZodObject<{
|
|
27
27
|
authorization_endpoint: z.ZodOptional<z.ZodString>;
|
|
28
28
|
token_endpoint: z.ZodOptional<z.ZodString>;
|
|
29
29
|
registration_endpoint: z.ZodOptional<z.ZodString>;
|
|
30
|
+
revocation_endpoint: z.ZodOptional<z.ZodString>;
|
|
30
31
|
code_challenge_methods_supported: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
31
32
|
}, z.core.$strip>;
|
|
32
33
|
/** RFC 7591 registration answer: the client Toolroll is, and its secret when the server gives one. */
|
|
@@ -44,11 +44,12 @@ export const protectedResourceSchema = z.object({
|
|
|
44
44
|
authorization_servers: z.array(z.string()).optional(),
|
|
45
45
|
scopes_supported: z.array(z.string().regex(SCOPE_TOKEN)).optional(),
|
|
46
46
|
});
|
|
47
|
-
/** RFC 8414 (or OpenID) authorization-server metadata: where to sign in, trade a code, and
|
|
47
|
+
/** RFC 8414 (or OpenID) authorization-server metadata: where to sign in, trade a code, register, and revoke. */
|
|
48
48
|
export const authorizationServerSchema = z.object({
|
|
49
49
|
authorization_endpoint: z.string().optional(),
|
|
50
50
|
token_endpoint: z.string().optional(),
|
|
51
51
|
registration_endpoint: z.string().optional(),
|
|
52
|
+
revocation_endpoint: z.string().optional(),
|
|
52
53
|
code_challenge_methods_supported: z.array(z.string()).optional(),
|
|
53
54
|
});
|
|
54
55
|
/** RFC 7591 registration answer: the client Toolroll is, and its secret when the server gives one. */
|
package/dist/desktop-update.d.ts
CHANGED
|
@@ -94,6 +94,7 @@ export declare function prepareDesktopUpdate(stateDir: string, installed: string
|
|
|
94
94
|
export declare function sqliteLock(file: string, busyTimeoutMs?: number): DatabaseSync | null;
|
|
95
95
|
export declare const installationLock: (j: UpdateJournal) => DatabaseSync | null;
|
|
96
96
|
export declare function desktopUpdateStatus(stateDir: string): {
|
|
97
|
+
version?: never;
|
|
97
98
|
active: boolean;
|
|
98
99
|
phase: string;
|
|
99
100
|
detail: string;
|
|
@@ -107,7 +108,6 @@ export declare function desktopUpdateStatus(stateDir: string): {
|
|
|
107
108
|
wasRunning?: never;
|
|
108
109
|
backupPath?: never;
|
|
109
110
|
updateId?: never;
|
|
110
|
-
version?: never;
|
|
111
111
|
buildId?: never;
|
|
112
112
|
workDir?: never;
|
|
113
113
|
} | {
|
package/dist/dispatch.js
CHANGED
|
@@ -15,6 +15,8 @@ import { REVIEW_TOKENS, resultStatusOf, reviewFactsOf } from "./workspace-ui.js"
|
|
|
15
15
|
import { replacedWords } from "./task-status.js";
|
|
16
16
|
import { failedAttemptSentence, latestFinishedAttempt } from "./needs-you.js";
|
|
17
17
|
import { buildReviewOf } from "./review-switch.js";
|
|
18
|
+
import { grantStandsSql } from "./flow-unattended.js";
|
|
19
|
+
import { accountReachesSql } from "./project-access.js";
|
|
18
20
|
import { BUILT_IN, parseCapabilityKey } from "./store.js";
|
|
19
21
|
export const DEFAULT_MAX_OPEN_DECISIONS = 5;
|
|
20
22
|
/**
|
|
@@ -75,13 +77,14 @@ export function scopeApprovedForDispatch(store, taskRef, now) {
|
|
|
75
77
|
.prepare(`SELECT 1 AS hit FROM task_scope
|
|
76
78
|
JOIN task_ref ON task_ref.id = ? AND task_scope.task_id = task_ref.external_id
|
|
77
79
|
WHERE task_scope.approved_digest = task_scope.digest AND task_scope.approved_at IS NOT NULL
|
|
78
|
-
AND (COALESCE(task_scope.approval_basis, 'password')
|
|
79
|
-
OR EXISTS (SELECT 1 FROM operating_mode om
|
|
80
|
+
AND (COALESCE(task_scope.approval_basis, 'password') NOT IN ('mode', 'grant')
|
|
81
|
+
OR (task_scope.approval_basis = 'mode' AND EXISTS (SELECT 1 FROM operating_mode om
|
|
80
82
|
JOIN approver signer ON signer.name = om.signed_by
|
|
81
83
|
WHERE om.repo = task_ref.repo AND om.revoked_at IS NULL
|
|
82
84
|
AND om.absolute_expiry > ? AND om.digest = task_scope.mode_digest
|
|
83
|
-
AND signer.revoked_at IS NULL AND signer.role = 'approver'))
|
|
84
|
-
|
|
85
|
+
AND signer.revoked_at IS NULL AND signer.role = 'approver' AND ${accountReachesSql("signer", "task_ref.repo")}))
|
|
86
|
+
OR (task_scope.approval_basis = 'grant' AND ${grantStandsSql("task_scope.mode_digest", "?", "task_ref.repo")}))`)
|
|
87
|
+
.get(taskRef, now.toISOString(), now.toISOString());
|
|
85
88
|
return row !== undefined;
|
|
86
89
|
}
|
|
87
90
|
/** The phase the ordinary dispatch loop would choose before it resolves an
|
package/dist/doctor.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type SupervisorRunner } from "./daemon.js";
|
|
2
|
+
/** Something that holds a credential a build could read, by name only, and what the owner does about it. */
|
|
3
|
+
export type DoctorFinding = {
|
|
4
|
+
where: string;
|
|
5
|
+
names: string[];
|
|
6
|
+
steps: string[];
|
|
7
|
+
};
|
|
8
|
+
/** Something the doctor could not read: never taken for clean. */
|
|
9
|
+
export type DoctorUnreadable = {
|
|
10
|
+
where: string;
|
|
11
|
+
problem: string;
|
|
12
|
+
steps: string[];
|
|
13
|
+
};
|
|
14
|
+
export type DoctorReport = {
|
|
15
|
+
ok: boolean;
|
|
16
|
+
checked: string[];
|
|
17
|
+
findings: DoctorFinding[];
|
|
18
|
+
unreadable: DoctorUnreadable[];
|
|
19
|
+
notes: string[];
|
|
20
|
+
};
|
|
21
|
+
export type DoctorDeps = {
|
|
22
|
+
platform?: NodeJS.Platform;
|
|
23
|
+
home?: string;
|
|
24
|
+
uid?: number;
|
|
25
|
+
/** Runs the read-only supervisor and process queries (launchctl, systemctl, ps). */
|
|
26
|
+
run?: SupervisorRunner;
|
|
27
|
+
/** Where Linux keeps its process files (a test's stand-in). */
|
|
28
|
+
procRoot?: string;
|
|
29
|
+
/** The doctor's own environment: the terminal a `toolroll up` would start from. */
|
|
30
|
+
env?: NodeJS.ProcessEnv;
|
|
31
|
+
};
|
|
32
|
+
/** The names among `pairs` that hold a credential: a provider key, a credential-looking name, or a key-shaped value.
|
|
33
|
+
* Values are only tested here and never leave. */
|
|
34
|
+
export declare function credentialNames(pairs: Iterable<readonly [string, string]>): string[];
|
|
35
|
+
/** A running process's environment names (never values) that hold a credential: macOS through `ps -E`, which reads
|
|
36
|
+
* KERN_PROCARGS2; Linux from /proc/<pid>/environ. */
|
|
37
|
+
export declare function processCredentialNames(pid: number, deps: {
|
|
38
|
+
platform: NodeJS.Platform;
|
|
39
|
+
run: SupervisorRunner;
|
|
40
|
+
procRoot?: string;
|
|
41
|
+
}): Promise<{
|
|
42
|
+
ok: true;
|
|
43
|
+
names: string[];
|
|
44
|
+
} | {
|
|
45
|
+
ok: false;
|
|
46
|
+
problem: string;
|
|
47
|
+
}>;
|
|
48
|
+
/** Audit what Toolroll's service holds. Reads only; the report names variables, never their values. */
|
|
49
|
+
export declare function auditServiceEnvironment(deps?: DoctorDeps): Promise<DoctorReport>;
|
|
50
|
+
/** The report as the owner reads it: one state, then each finding with its steps; detail after. */
|
|
51
|
+
export declare function doctorLines(report: DoctorReport): string[];
|
|
52
|
+
export declare const DOCTOR_HELP = "toolroll doctor [--json]\nChecks that Toolroll's service holds no API key, token or password a build could read: its service definitions, what `daemon install` writes, the environment its manager hands it, and its running process. Reads only: it names what it finds and the steps to fix it, and never changes a file or restarts anything.";
|
|
53
|
+
export declare function runDoctorCommand(argv: readonly string[], write: (line: string) => void, deps?: DoctorDeps): Promise<number>;
|