toolroll 0.9.94 → 0.9.95
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 +12 -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 +26 -0
- package/dist/card-render.js +223 -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 +42 -16
- 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
package/dist/operate.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { runCardCommand } from "./card-cli.js";
|
|
1
2
|
import { mintSecret } from "./secret-mint.js";
|
|
2
3
|
import { ACP_AGENTS, ACP_PROVIDER_IDS, isAcpProvider, isKeyedProvider } from "./acp-agents.js";
|
|
3
4
|
import { inspectAcpAgent } from "./acp-inspect.js";
|
|
@@ -242,12 +243,15 @@ import { createScheduledFlow, describeSchedule, parseSchedule } from "./flow-sch
|
|
|
242
243
|
import { fileTaskProposal, validateTaskText } from "./proposal.js";
|
|
243
244
|
import { TEMPLATES, templateByName } from "./templates.js";
|
|
244
245
|
import { isDirectChatProvider, isSubscriptionChatProvider, priceOf, PRICED_MODELS } from "./converse.js";
|
|
246
|
+
import { clearFallbackChain, dispatchEntryOf, entryMovesOnWords, entryWords, fallbackSettingOf, parseEntry, reconcileWalks, setFallbackChain, setPaidGrant } from "./fallback.js";
|
|
245
247
|
import { resolvePhaseAgent, resolveScopeProfile, resolveRouteCandidates, routeOfTask, INSTALLATION_SCOPE } from "./agentconfig.js";
|
|
246
248
|
import { applyModelChoices, stateDigest } from "./model-choices.js";
|
|
247
249
|
import { isTaskSize, legOf, projectRoute, routeDigestOf, routeWords, TASK_SIZES, PHASES as ROUTE_PHASES } from "./phase-routing.js";
|
|
248
250
|
import { observeProviderReadiness, reportProviderReadinessAuthed } from "./runner.js";
|
|
249
251
|
import { retryVanishedWhenStopped } from "./vanished-retry.js";
|
|
250
252
|
import { requestMissingRechecks } from "./check-only.js";
|
|
253
|
+
import { checkRepairOfRun, resumeParkedRepairs, startCheckRepair } from "./check-repair.js";
|
|
254
|
+
import { capturesCommand, evidenceCommand, failingFirstCommand, findingCommand, handoverCommand } from "./follow-through-cli.js";
|
|
251
255
|
import { recordBuilderWish } from "./builder-restart.js";
|
|
252
256
|
import { expireFastStarts, fastRefusalWords, fastStartBuild, keepGrownChange, prepareFastStart } from "./fast-start.js";
|
|
253
257
|
import { fastTermsLines } from "./fast-terms.js";
|
|
@@ -321,6 +325,8 @@ export const OPERATE_HELP = `toolroll — operating the queue
|
|
|
321
325
|
toolroll project activity --json read the current project supervision report
|
|
322
326
|
toolroll project rules --repo <p> a project's approval rules; an instance operator changes them with
|
|
323
327
|
--not-requester on|off, --protect none|project, --protect-paths "a/**,b/**"
|
|
328
|
+
toolroll project fallback --repo <p> [--agents provider:model:plan|api-key,…] [--clear] [--allow-paid|--no-paid]
|
|
329
|
+
Which agents take over when one runs out of its plan. New tasks seal the chain; a paid agent also needs --allow-paid.
|
|
324
330
|
toolroll project network --repo <p> [--allow h,…] [--remove h,…] [--open|--allowlist] [--local any|none]
|
|
325
331
|
what the project's builds may reach on the network; an approver changes it
|
|
326
332
|
(new scopes seal it; approved ones keep theirs)
|
|
@@ -717,7 +723,7 @@ export const TASK_ACTIONS = [
|
|
|
717
723
|
"add", "new", "keep", "list", "show", "wait", "state", "block", "unblock", "next", "steer", "pause", "continue", "assign",
|
|
718
724
|
"reopen", "scope", "approve", "hold", "unhold", "require", "requeue", "regate", "plan",
|
|
719
725
|
"review", "accept", "repair", "route", "stop", "resume", "complete", "revise", "merge", "ask",
|
|
720
|
-
"checks", "add-tests", "archive", "restore", "tidy", "ship", "ready",
|
|
726
|
+
"checks", "add-tests", "archive", "restore", "tidy", "ship", "ready", "handover", "before-after", "failing-first",
|
|
721
727
|
];
|
|
722
728
|
export const PUBLISH_ACTIONS = ["setup", "grant", "revoke", "status", "unblock", "rearm", "merge", "refire"];
|
|
723
729
|
export const CONFIG_ACTIONS = ["show", "set", "clear"];
|
|
@@ -732,8 +738,8 @@ export const KEYS_ACTIONS = ["status", "set", "clear", "verify", "auth"];
|
|
|
732
738
|
/** Flags whose value is a secret someone types or pastes: taken as the next argument even when it starts with "--". */
|
|
733
739
|
export const SECRET_VALUE_FLAGS = new Set(["token", "password"]);
|
|
734
740
|
export const OPERATE_VALUE_FLAGS = new Set([
|
|
735
|
-
"key", "db", "runner", "ttl", "state", "on", "reason", "until", "id", "backend", "build", "from",
|
|
736
|
-
"allow", "selector", "paths", "credentials", "repo", "token", "capacity",
|
|
741
|
+
"key", "db", "runner", "ttl", "state", "flow", "on", "reason", "until", "id", "backend", "build", "from",
|
|
742
|
+
"allow", "selector", "paths", "credentials", "repo", "token", "capacity", "agents",
|
|
737
743
|
"goal", "not", "touches", "acceptance", "candidate", "by", "digest", "as", "branch", "pool", "base", "model", "turns",
|
|
738
744
|
"max", "cap", "probe", "kind", "expires", "cmd", "since", "repair-model",
|
|
739
745
|
"choose", "choice", "claim", "revision", "note", "max-open-decisions", "name", "days", "publication", "auto-approve", "review-auto", "port", "host", "allow-host",
|
|
@@ -756,7 +762,7 @@ export const OPERATE_VALUE_FLAGS = new Set([
|
|
|
756
762
|
"command", "timeout-seconds", "setup-digest", "stop-grace", "title", "name", "every", "lines",
|
|
757
763
|
"label", "reviewers", "limit", "role", "key-file", "weekly-usd", "daily-turns", "per-hour", "token-file", "budget-usd", "build-usd", "sync-max-age", "merge-method",
|
|
758
764
|
"phase", "tier", "clear-phase", "size", "risky",
|
|
759
|
-
"run", "containment", "agent",
|
|
765
|
+
"run", "containment", "agent", "path", "line", "action", "from-review", "from-check", "task",
|
|
760
766
|
// onboard: the starter flows to switch on.
|
|
761
767
|
"starter",
|
|
762
768
|
// flows: templates, steps, scripts and cards.
|
|
@@ -799,6 +805,8 @@ export const OPERATE_BOOLEAN_FLAGS = new Set([
|
|
|
799
805
|
"html", "csv", "alerts-only",
|
|
800
806
|
// project network: what its builds may reach (--open | --allowlist).
|
|
801
807
|
"open", "allowlist",
|
|
808
|
+
// project fallback: whether paid fallback agents may spend.
|
|
809
|
+
"allow-paid", "no-paid",
|
|
802
810
|
// v105: the full export.
|
|
803
811
|
"zip",
|
|
804
812
|
// Settings → Integrations: the last checks, without checking again.
|
|
@@ -1314,6 +1322,8 @@ async function dispatch(command, positional, flags, context) {
|
|
|
1314
1322
|
return projectConcurrencyCommand(positional, flags, context);
|
|
1315
1323
|
if (positional[0] === "network")
|
|
1316
1324
|
return projectNetworkCommand(positional, flags, context);
|
|
1325
|
+
if (positional[0] === "fallback")
|
|
1326
|
+
return projectFallbackCommand(positional, flags, context);
|
|
1317
1327
|
if (positional[0] === "writes")
|
|
1318
1328
|
return projectWritesCommand(positional, flags, context);
|
|
1319
1329
|
if (positional[0] === "check-access")
|
|
@@ -1369,8 +1379,14 @@ async function dispatch(command, positional, flags, context) {
|
|
|
1369
1379
|
const who = currentLocalOwner() !== null ? localReader(context) : verified !== null && verified.ok ? verified.who : null;
|
|
1370
1380
|
return runMemoryCommand(positional, flags, { store: context.store, write: context.write, json: context.json, now: context.clock(), actor: who?.name ?? null, repos: who?.repos ?? [], configDir: dirname(context.databaseFile), ...(context.evidenceRoot === undefined ? {} : { evidenceRoot: context.evidenceRoot }) });
|
|
1371
1381
|
}
|
|
1382
|
+
case "card":
|
|
1383
|
+
return cardCommand(positional, flags, context);
|
|
1372
1384
|
case "flaky":
|
|
1373
1385
|
return flakyCommand(positional, flags, context);
|
|
1386
|
+
case "finding":
|
|
1387
|
+
return findingCommand(positional, flags, followThroughContext(context, flags));
|
|
1388
|
+
case "captures":
|
|
1389
|
+
return capturesCommand(positional, followThroughContext(context, flags));
|
|
1374
1390
|
case "claim":
|
|
1375
1391
|
return claimCommand(positional, flags, context);
|
|
1376
1392
|
case "heartbeat":
|
|
@@ -2577,6 +2593,8 @@ async function tickCommand(flags, context) {
|
|
|
2577
2593
|
const ready = store.listReady(clock(), runner);
|
|
2578
2594
|
const considered = ready.length;
|
|
2579
2595
|
const dispatched = [];
|
|
2596
|
+
// Every walk settled before anything dispatches: a crash anywhere leaves a state this pass closes the same way.
|
|
2597
|
+
reconcileWalks(store, clock());
|
|
2580
2598
|
// The stale-scan (dispatch v3, finding 20): mirrors the courtesy filter
|
|
2581
2599
|
// kept out of ready are REPORTED here, typed, with a paged episode —
|
|
2582
2600
|
// an undispatakable tracker item is a 9am fact, not a silent absence.
|
|
@@ -2669,7 +2687,15 @@ async function tickCommand(flags, context) {
|
|
|
2669
2687
|
}
|
|
2670
2688
|
}
|
|
2671
2689
|
const planLeg = route === null ? null : legOf(route.route, "plan");
|
|
2672
|
-
|
|
2690
|
+
// A task whose agent ran out runs as the next approved entry: every gate below is keyed to that entry (fallback.ts).
|
|
2691
|
+
const fallback = route !== null && route.source === "approved" && !wantsPlan && !wantsScout ? dispatchEntryOf(store, ref.id, id, clock()) : null;
|
|
2692
|
+
if (fallback !== null && "problem" in fallback) {
|
|
2693
|
+
dispatched.push({ id, outcome: "skipped", reason: "agent-config", detail: `${id}: ${fallback.problem}` });
|
|
2694
|
+
continue;
|
|
2695
|
+
}
|
|
2696
|
+
const sealedBuildLeg = route !== null && route.source === "approved"
|
|
2697
|
+
? fallback !== null ? { provider: fallback.entry.profile.provider, model: fallback.entry.profile.model, chosen: "fallback" } : legOf(route.route, "build")
|
|
2698
|
+
: null;
|
|
2673
2699
|
const planFlagged = planProvider !== undefined || planModel !== undefined || providerFlag !== undefined || model !== undefined;
|
|
2674
2700
|
if (wantsPlan && planLeg !== null && planFlagged) {
|
|
2675
2701
|
// Pass flags cannot contradict the task's plan leg: a flag that names
|
|
@@ -2804,7 +2830,7 @@ async function tickCommand(flags, context) {
|
|
|
2804
2830
|
// existing approval refusals own that road, and attestation must
|
|
2805
2831
|
// never mask them. The gateway re-checks before spawn; this skip only
|
|
2806
2832
|
// keeps the normal road cheap.
|
|
2807
|
-
const skipProviders = wantsPlan
|
|
2833
|
+
const skipProviders = wantsPlan || fallback !== null
|
|
2808
2834
|
? [spec.provider]
|
|
2809
2835
|
: [store.getScope(id)?.approvedProfile?.provider].filter((one) => one !== null && one !== undefined);
|
|
2810
2836
|
let unattestedLane = null;
|
|
@@ -2841,7 +2867,7 @@ async function tickCommand(flags, context) {
|
|
|
2841
2867
|
}
|
|
2842
2868
|
// Sprint 8: the organisation policy before the claim (build() looks again before spawn, and lowers).
|
|
2843
2869
|
{
|
|
2844
|
-
const sealed = wantsPlan || wantsScout ? null : store.getScope(id)?.approvedProfile ?? null;
|
|
2870
|
+
const sealed = wantsPlan || wantsScout ? null : fallback?.entry.profile ?? store.getScope(id)?.approvedProfile ?? null;
|
|
2845
2871
|
const refused = policyHold([sealed !== null ? { profile: sealed }
|
|
2846
2872
|
: { provider: spec.provider, model: spec.model, ...(wantsPlan ? { session: "planning" } : wantsScout ? { session: "scouting" } : {}) }]);
|
|
2847
2873
|
if (refused !== null) {
|
|
@@ -2855,7 +2881,8 @@ async function tickCommand(flags, context) {
|
|
|
2855
2881
|
// Each provider as it bills now.
|
|
2856
2882
|
// An ACP agent bills as its declaration for the exact model it would run on this runner.
|
|
2857
2883
|
const sealedModel = store.getScope(id)?.approvedProfile?.model ?? null;
|
|
2858
|
-
|
|
2884
|
+
// A paid fallback entry is a dollar spend whatever its provider's current setting says.
|
|
2885
|
+
const budgeted = budgetHold(ref.id, fallback?.paid === true ? [{ provider: spec.provider, billing: "api-key" }] : [...new Set([spec.provider, ...skipProviders])].map(provider => isAcpProvider(provider) ? store.agentFor(provider, provider === spec.provider ? spec.model ?? sealedModel : sealedModel, runner) : store.agentsFor([provider])[0]));
|
|
2859
2886
|
if (budgeted.over !== null) {
|
|
2860
2887
|
dispatched.push({ id, outcome: "skipped", reason: "budget", detail: budgetWords(budgeted) });
|
|
2861
2888
|
continue;
|
|
@@ -2879,6 +2906,7 @@ async function tickCommand(flags, context) {
|
|
|
2879
2906
|
repo,
|
|
2880
2907
|
provider: spec.provider,
|
|
2881
2908
|
...(spec.model === null ? {} : { model: spec.model }),
|
|
2909
|
+
...(fallback === null ? {} : { authMode: fallback.entry.authMode }),
|
|
2882
2910
|
...(text(flags, "incarnation") === undefined ? {} : { incarnation: text(flags, "incarnation") }),
|
|
2883
2911
|
...(text(flags, "max-open-decisions") === undefined
|
|
2884
2912
|
? {}
|
|
@@ -3476,12 +3504,16 @@ async function tickCommand(flags, context) {
|
|
|
3476
3504
|
reason: disposition.sealed
|
|
3477
3505
|
? disposition.disposition === "auth-expired"
|
|
3478
3506
|
? "auth-expired — requeued, sign-in needed"
|
|
3479
|
-
:
|
|
3507
|
+
: disposition.disposition === "fallback"
|
|
3508
|
+
? `out of plan — moved to the next approved agent`
|
|
3509
|
+
: `${disposition.failureClass}${disposition.disposition === "backoff" ? ` — retry ${disposition.strikes}/3` : disposition.disposition === "stalled" ? " — stalled" : ""}`
|
|
3480
3510
|
: "fenced",
|
|
3481
3511
|
...(result.ok ? {} : { detail: result.message.slice(0, 200) }),
|
|
3482
3512
|
worktree: leased.worktree.path,
|
|
3483
3513
|
});
|
|
3484
|
-
|
|
3514
|
+
// Moving on isn't this pass breaking: the task is queued for its next agent.
|
|
3515
|
+
if (!(disposition.sealed && disposition.disposition === "fallback"))
|
|
3516
|
+
broke++;
|
|
3485
3517
|
break;
|
|
3486
3518
|
case "recorded":
|
|
3487
3519
|
// The standalone-only arm; unreachable under the tick policy.
|
|
@@ -5004,7 +5036,7 @@ async function modeCommand(positional, flags, context) {
|
|
|
5004
5036
|
if (acting === null)
|
|
5005
5037
|
return fail(write, json, "mode", "usage", "signing or revoking a mode takes `--as <you> --token <t>`", EXIT.usage);
|
|
5006
5038
|
// Signing a mode that lets a chat app approve is a credential act; any other mode signing or revoking is ordinary.
|
|
5007
|
-
const authed = (flag(flags, "chat-approve") ? credentialApprover(store, acting.name, acting.token) : authenticateApprover(store, acting.name, acting.token));
|
|
5039
|
+
const authed = (flag(flags, "chat-approve") || flag(flags, "repair-auto") ? credentialApprover(store, acting.name, acting.token) : authenticateApprover(store, acting.name, acting.token));
|
|
5008
5040
|
if (!authed.ok)
|
|
5009
5041
|
return fail(write, json, "mode", authed.reason, "that is not an active approver, or the token does not match", EXIT.refused);
|
|
5010
5042
|
if (action === "revoke") {
|
|
@@ -5032,8 +5064,18 @@ async function modeCommand(positional, flags, context) {
|
|
|
5032
5064
|
terms.autoApproveFiling = true;
|
|
5033
5065
|
if (flag(flags, "plan-auto"))
|
|
5034
5066
|
terms.planAuto = true;
|
|
5035
|
-
if (["review-auto", "review-retry-auto"
|
|
5036
|
-
return fail(write, json, "mode set", "invalid", "Separate model review
|
|
5067
|
+
if (["review-auto", "review-retry-auto"].some(name => flags.has(name)))
|
|
5068
|
+
return fail(write, json, "mode set", "invalid", "Separate model review has been removed; finished work goes to the lead or user.", EXIT.refused);
|
|
5069
|
+
// Repairing failed checks by themselves: never a preset default, only these flags, with a limit you sign.
|
|
5070
|
+
if (flags.has("repair-max-attempts") && !flag(flags, "repair-auto"))
|
|
5071
|
+
return fail(write, json, "mode set", "invalid", "--repair-max-attempts needs --repair-auto", EXIT.refused);
|
|
5072
|
+
if (flag(flags, "repair-auto")) {
|
|
5073
|
+
const limit = Number(text(flags, "repair-max-attempts") ?? 2);
|
|
5074
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 3)
|
|
5075
|
+
return fail(write, json, "mode set", "invalid", "--repair-max-attempts is 1, 2 or 3", EXIT.refused);
|
|
5076
|
+
terms.repairAuto = true;
|
|
5077
|
+
terms.repairMaxAttempts = limit;
|
|
5078
|
+
}
|
|
5037
5079
|
if (terms.planAuto && !terms.autoApproveFiling)
|
|
5038
5080
|
return fail(write, json, "mode set", "invalid", "--plan-auto requires automatic filing approval", EXIT.refused);
|
|
5039
5081
|
// Approving plans and merges from the signer's paired chat: never a preset default, only this flag.
|
|
@@ -6519,6 +6561,8 @@ async function runWatchLoop(args) {
|
|
|
6519
6561
|
void resumeHeldReleases(store, dirname(context.databaseFile), context.clock());
|
|
6520
6562
|
// A result whose verdict went missing asks for its one automatic recheck (check-only.ts).
|
|
6521
6563
|
requestMissingRechecks(store, context.evidenceRoot, context.clock());
|
|
6564
|
+
// A failed check that was parked takes its next step once its task is free again (check-repair.ts).
|
|
6565
|
+
resumeParkedRepairs(store, context.evidenceRoot, context.clock());
|
|
6522
6566
|
const ran = await runWaitingChecks(store, context.evidenceRoot, { now: context.clock, shouldStop: stopping });
|
|
6523
6567
|
if (ran > 0)
|
|
6524
6568
|
progress(`watch: ran ${ran} follow-up check(s)`);
|
|
@@ -8891,6 +8935,12 @@ function taskAction(action, rest, flags, context) {
|
|
|
8891
8935
|
return requeueTask(rest, flags, context);
|
|
8892
8936
|
case "regate":
|
|
8893
8937
|
return regateTaskCommand(rest, flags, context);
|
|
8938
|
+
case "handover":
|
|
8939
|
+
return handoverCommand(rest, followThroughContext(context, flags));
|
|
8940
|
+
case "before-after":
|
|
8941
|
+
return evidenceCommand(rest, flags, followThroughContext(context, flags));
|
|
8942
|
+
case "failing-first":
|
|
8943
|
+
return failingFirstCommand(rest, followThroughContext(context, flags));
|
|
8894
8944
|
case "plan":
|
|
8895
8945
|
return planTaskCommand(rest, flags, context);
|
|
8896
8946
|
case "review":
|
|
@@ -9585,9 +9635,18 @@ async function repairTaskCommand(positional, flags, context) {
|
|
|
9585
9635
|
const acting = await askCredentials(flags, context);
|
|
9586
9636
|
if (acting === null)
|
|
9587
9637
|
return fail(write, json, "task repair", "usage", "Evidence collection requires --as and --token.", EXIT.usage);
|
|
9588
|
-
|
|
9638
|
+
// A failed check names its project: the password proves the person, and their access to the run they named.
|
|
9639
|
+
const failed = checkRepairOfRun(store, runId);
|
|
9640
|
+
const authenticated = authenticateApprover(store, acting.name, acting.token, failed?.repo);
|
|
9589
9641
|
if (!authenticated.ok)
|
|
9590
9642
|
return fail(write, json, "task repair", authenticated.reason, describeApproveFailure(authenticated.reason, String(runId)), EXIT.refused);
|
|
9643
|
+
// A failed check waiting for a person: this draft is what they asked for, and its approval below is the normal one.
|
|
9644
|
+
if (failed !== null) {
|
|
9645
|
+
const started = startCheckRepair(store, context.evidenceRoot, runId, clock());
|
|
9646
|
+
if (!started.ok)
|
|
9647
|
+
return fail(write, json, "task repair", "refused", started.message, EXIT.refused);
|
|
9648
|
+
chain = store.repairChainFor(runId);
|
|
9649
|
+
}
|
|
9591
9650
|
const run = store.getRun(runId), proof = store.proofVerdictFor(runId);
|
|
9592
9651
|
const repo = run && store.refById(run.taskRef)?.repo;
|
|
9593
9652
|
if (repo && repo === repoFrom(flags) && proof?.matrix.some(row => row.assessment && row.review?.judgement === "cannot-tell") &&
|
|
@@ -13931,8 +13990,100 @@ export async function refreshLearning(store, now, reads) {
|
|
|
13931
13990
|
catch { /* advisory: the next pass tries again */ }
|
|
13932
13991
|
}
|
|
13933
13992
|
}
|
|
13993
|
+
/** `toolroll card <source>`: reads only, for the remembered local login (or --as/--token) and its projects. */
|
|
13994
|
+
async function cardCommand(positional, flags, context) {
|
|
13995
|
+
const acting = await askCredentials(flags, context);
|
|
13996
|
+
const verified = acting === null || currentLocalOwner() !== null ? null : verifyApproverByPassword(context.store, acting.name, acting.token, context.store.knownRepos().filter(repo => context.store.accountCanAccess(acting.name, repo)));
|
|
13997
|
+
const who = currentLocalOwner() !== null ? localReader(context) : verified !== null && verified.ok ? verified.who : null;
|
|
13998
|
+
return runCardCommand(positional, flags, { store: context.store, write: context.write, json: context.json, now: context.clock(), actor: who?.name ?? null, repos: who?.repos ?? [], evidenceRoot: context.evidenceRoot,
|
|
13999
|
+
ok: (result, lines) => succeed(context.write, context.json, "card", result, lines),
|
|
14000
|
+
no: (reason, message) => fail(context.write, context.json, "card", reason, message, reason === "usage" ? EXIT.usage : EXIT.refused) });
|
|
14001
|
+
}
|
|
13934
14002
|
/** The laptop's local owner as a reader: its name and its projects. Never a proof: every write proves its own act. */
|
|
13935
14003
|
function localReader(context) {
|
|
13936
14004
|
const local = currentLocalOwner();
|
|
13937
14005
|
return local === null ? null : { name: local.account, repos: context.store.knownRepos().filter(repo => context.store.accountCanAccess(local.account, repo)) };
|
|
13938
14006
|
}
|
|
14007
|
+
/** What the follow-through commands need from this file: its envelope, and a verified person (follow-through-cli.ts). */
|
|
14008
|
+
function followThroughContext(context, flags) {
|
|
14009
|
+
const { store, write, json, clock, evidenceRoot } = context;
|
|
14010
|
+
return {
|
|
14011
|
+
store, evidenceRoot, now: clock(),
|
|
14012
|
+
ok: (command, result, lines) => succeed(write, json, command, result, lines),
|
|
14013
|
+
no: (command, reason, message) => fail(write, json, command, reason, message, reason === "usage" ? EXIT.usage : EXIT.refused),
|
|
14014
|
+
prove: async (command, repo) => {
|
|
14015
|
+
const acting = await askCredentials(flags, context);
|
|
14016
|
+
if (acting === null) {
|
|
14017
|
+
fail(write, json, command, "usage", "this takes `--as <you> --token <t>`", EXIT.usage);
|
|
14018
|
+
return null;
|
|
14019
|
+
}
|
|
14020
|
+
const authed = authenticateApprover(store, acting.name, acting.token, repo);
|
|
14021
|
+
if (!authed.ok) {
|
|
14022
|
+
fail(write, json, command, "not-an-approver", "that is not an approver, or the token does not match", EXIT.refused);
|
|
14023
|
+
return null;
|
|
14024
|
+
}
|
|
14025
|
+
return acting.name;
|
|
14026
|
+
},
|
|
14027
|
+
};
|
|
14028
|
+
}
|
|
14029
|
+
/**
|
|
14030
|
+
* `project fallback --repo <p> [--agents a,b,c] [--clear] [--allow-paid|--no-paid]`: which agents take over when one runs
|
|
14031
|
+
* out of its plan (fallback.ts). New tasks seal the chain; approved ones keep theirs. Changing it is an approver's act
|
|
14032
|
+
* with a password typed now, in the ledger. A lead's token never gets past the store's own check.
|
|
14033
|
+
*/
|
|
14034
|
+
async function projectFallbackCommand(positional, flags, context) {
|
|
14035
|
+
const command = "project fallback";
|
|
14036
|
+
const allowed = new Set(["repo", "agents", "clear", "allow-paid", "no-paid", "as", "token", "token-file", "token-env", "db", "json"]);
|
|
14037
|
+
for (const name of flags.keys())
|
|
14038
|
+
if (!allowed.has(name))
|
|
14039
|
+
return fail(context.write, context.json, command, "usage", `--${name} is not a project fallback option.`, EXIT.usage);
|
|
14040
|
+
const repoFlag = text(flags, "repo");
|
|
14041
|
+
const agents = text(flags, "agents");
|
|
14042
|
+
const clearing = flags.has("clear"), allowing = flags.has("allow-paid"), withdrawing = flags.has("no-paid");
|
|
14043
|
+
if (positional.length > 1 || repoFlag === undefined || (clearing && agents !== undefined) || (allowing && withdrawing))
|
|
14044
|
+
return fail(context.write, context.json, command, "usage", "Use project fallback --repo <project path> [--agents provider:model:plan|api-key,…] [--clear] [--allow-paid|--no-paid].", EXIT.usage);
|
|
14045
|
+
const repo = await knownProject(context, repoFlag, { listed: true, canonical: true });
|
|
14046
|
+
if (repo === undefined)
|
|
14047
|
+
return fail(context.write, context.json, command, "not-found", "That isn't a project Toolroll knows.", EXIT.refused);
|
|
14048
|
+
const words = () => {
|
|
14049
|
+
const setting = fallbackSettingOf(context.store, repo);
|
|
14050
|
+
if (setting === null)
|
|
14051
|
+
return [`${repo}: no fallback agents. A task stops when its agent runs out of its plan.`];
|
|
14052
|
+
return [`${repo}: when an agent runs out of its plan, the work moves on to:`,
|
|
14053
|
+
...setting.entries.map((one, at) => ` ${at + 1}. ${entryWords(one)}. ${entryMovesOnWords(one)}.`),
|
|
14054
|
+
setting.paidGrant === null ? "Paid fallback: not allowed." : `Paid fallback: allowed by ${setting.paidGrant.by}.`];
|
|
14055
|
+
};
|
|
14056
|
+
const shown = () => { const setting = fallbackSettingOf(context.store, repo); return { repo, agents: setting?.entries ?? [], paidAllowed: setting?.paidGrant != null }; };
|
|
14057
|
+
if (agents === undefined && !clearing && !allowing && !withdrawing)
|
|
14058
|
+
return succeed(context.write, context.json, command, shown(), words);
|
|
14059
|
+
const parsed = agents === undefined ? [] : agents.split(",").map(one => parseEntry(one.trim()));
|
|
14060
|
+
const bad = parsed.find((one) => typeof one === "string");
|
|
14061
|
+
if (bad !== undefined)
|
|
14062
|
+
return fail(context.write, context.json, command, "usage", bad, EXIT.usage);
|
|
14063
|
+
const acting = await askCredentials(flags, context);
|
|
14064
|
+
const verified = acting === null ? null : credentialApprover(context.store, acting.name, acting.token, repo);
|
|
14065
|
+
if (acting === null || !verified?.ok || !context.store.accountCanAccess(acting.name, repo))
|
|
14066
|
+
return fail(context.write, context.json, command, "refused", "An approver for this project changes its fallback agents, with their password: pass --as and --token.", EXIT.refused);
|
|
14067
|
+
const now = context.clock();
|
|
14068
|
+
const done = context.store.transact(() => {
|
|
14069
|
+
if (clearing) {
|
|
14070
|
+
const one = clearFallbackChain(context.store, repo, acting.name, now);
|
|
14071
|
+
if (!one.ok)
|
|
14072
|
+
return one;
|
|
14073
|
+
}
|
|
14074
|
+
if (agents !== undefined) {
|
|
14075
|
+
const one = setFallbackChain(context.store, repo, parsed, acting.name, now);
|
|
14076
|
+
if (!one.ok)
|
|
14077
|
+
return one;
|
|
14078
|
+
}
|
|
14079
|
+
if (allowing || withdrawing) {
|
|
14080
|
+
const one = setPaidGrant(context.store, repo, allowing, acting.name, now);
|
|
14081
|
+
if (!one.ok)
|
|
14082
|
+
return one;
|
|
14083
|
+
}
|
|
14084
|
+
return { ok: true };
|
|
14085
|
+
});
|
|
14086
|
+
if (!done.ok)
|
|
14087
|
+
return fail(context.write, context.json, command, "refused", done.problem, EXIT.refused);
|
|
14088
|
+
return succeed(context.write, context.json, command, shown(), () => [...words(), "Tasks scoped from now on use this; tasks already approved keep the chain they were approved with."]);
|
|
14089
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Billing } from "./spend.js";
|
|
2
|
+
/** How Codex says it's signed in (`codex login status`): a ChatGPT sign-in is its plan; a key or Bedrock is a key;
|
|
3
|
+
* null when it says neither (not signed in, or it didn't answer). */
|
|
4
|
+
export declare function codexLoginBilling(binary: string, omitEnv: readonly string[], env?: Record<string, string | undefined>): Promise<Billing | null>;
|
|
5
|
+
/** Providers whose CLI can say, live and machine-readably, that the credential a run will use is a plan. */
|
|
6
|
+
export declare const PLAN_LOGIN_PROVABLE: ReadonlySet<string>;
|
|
7
|
+
/**
|
|
8
|
+
* How Claude says it is signed in (`claude auth status --json`), read with the run's own environment so it reads the
|
|
9
|
+
* credential the child will use. Strict: a plan only for a first-party claude.ai login with a named subscription; any
|
|
10
|
+
* other shape, a key, a cloud provider or no answer is not a plan.
|
|
11
|
+
*/
|
|
12
|
+
export declare function claudeLoginBilling(binary: string, omitEnv: readonly string[], env: Record<string, string | undefined>): Promise<Billing | null>;
|
|
13
|
+
/** The live plan proof for a provider, or null where none exists (the caller refuses). */
|
|
14
|
+
export declare function livePlanLogin(provider: string, binary: string, omitEnv: readonly string[], env: Record<string, string | undefined>): Promise<Billing | null>;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How a CLI says it is signed in, read live, right before a run: the gateway's billing evidence, and the proof a
|
|
3
|
+
* plan-pinned fallback agent needs before it runs (fallback.ts). Only providers listed in PLAN_LOGIN_PROVABLE can be a
|
|
4
|
+
* plan fallback: for any other there is no live proof, so filing and the chain setting refuse it.
|
|
5
|
+
*/
|
|
6
|
+
import { run as runCommand } from "./exec.js";
|
|
7
|
+
/** How Codex says it's signed in (`codex login status`): a ChatGPT sign-in is its plan; a key or Bedrock is a key;
|
|
8
|
+
* null when it says neither (not signed in, or it didn't answer). */
|
|
9
|
+
export async function codexLoginBilling(binary, omitEnv, env) {
|
|
10
|
+
try {
|
|
11
|
+
const status = await runCommand(binary, ["login", "status"], { timeoutMs: 15_000, omitEnv, processGroup: true,
|
|
12
|
+
...(env === undefined ? {} : { env: Object.fromEntries(Object.entries(env).filter((one) => one[1] !== undefined)) }) });
|
|
13
|
+
const said = `${status.stdout}\n${status.stderr}`;
|
|
14
|
+
// Strict: the status line itself, at the start of a line. A line naming both is never read as a plan.
|
|
15
|
+
if (/^Logged in using ChatGPT\s*$/m.test(said) && !/api key|bedrock/i.test(said))
|
|
16
|
+
return "subscription";
|
|
17
|
+
// Anything naming a key or Bedrock is read as a key (never a plan): that only ever narrows.
|
|
18
|
+
return /api key|bedrock/i.test(said) ? "api-key" : null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Providers whose CLI can say, live and machine-readably, that the credential a run will use is a plan. */
|
|
25
|
+
export const PLAN_LOGIN_PROVABLE = new Set(["claude", "codex"]);
|
|
26
|
+
/**
|
|
27
|
+
* How Claude says it is signed in (`claude auth status --json`), read with the run's own environment so it reads the
|
|
28
|
+
* credential the child will use. Strict: a plan only for a first-party claude.ai login with a named subscription; any
|
|
29
|
+
* other shape, a key, a cloud provider or no answer is not a plan.
|
|
30
|
+
*/
|
|
31
|
+
export async function claudeLoginBilling(binary, omitEnv, env) {
|
|
32
|
+
try {
|
|
33
|
+
const status = await runCommand(binary, ["auth", "status", "--json"], { timeoutMs: 15_000, omitEnv, processGroup: true,
|
|
34
|
+
env: Object.fromEntries(Object.entries(env).filter((one) => one[1] !== undefined)) });
|
|
35
|
+
if (status.code !== 0)
|
|
36
|
+
return null;
|
|
37
|
+
const read = JSON.parse(status.stdout);
|
|
38
|
+
if (read === null || typeof read !== "object" || Array.isArray(read))
|
|
39
|
+
return null;
|
|
40
|
+
const said = read;
|
|
41
|
+
if (said["loggedIn"] !== true)
|
|
42
|
+
return null;
|
|
43
|
+
if (said["authMethod"] === "claude.ai" && said["apiProvider"] === "firstParty" && typeof said["subscriptionType"] === "string" && /^[a-z0-9_-]{1,32}$/.test(said["subscriptionType"]))
|
|
44
|
+
return "subscription";
|
|
45
|
+
return "api-key";
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** The live plan proof for a provider, or null where none exists (the caller refuses). */
|
|
52
|
+
export async function livePlanLogin(provider, binary, omitEnv, env) {
|
|
53
|
+
if (provider === "codex")
|
|
54
|
+
return codexLoginBilling(binary, omitEnv, env);
|
|
55
|
+
if (provider === "claude")
|
|
56
|
+
return claudeLoginBilling(binary, omitEnv, env);
|
|
57
|
+
return null;
|
|
58
|
+
}
|
package/dist/planner.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { ProviderId } from "./contracts/provider.js";
|
|
|
2
2
|
import type { Store } from "./store.js";
|
|
3
3
|
import { type ParsedDecision, type Problem } from "./decision.js";
|
|
4
4
|
import { type ParsedPlan, type PlanProblem } from "./plan.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type Runner } from "./builder.js";
|
|
6
6
|
import { type PlannerSource } from "./planner-source.js";
|
|
7
7
|
export type PlanRequest = {
|
|
8
8
|
/** v105: what's left of a monthly budget this API-key work counts toward (the CLI's own cap), when one does. */
|
package/dist/planner.js
CHANGED
|
@@ -32,6 +32,7 @@ import { TEXT_LIMITS } from "./text-limits.js";
|
|
|
32
32
|
import { invokeAgent } from "./invoke.js";
|
|
33
33
|
import { TOKEN_ENVS as TELEGRAM_TOKEN_ENVS } from "./telegram.js";
|
|
34
34
|
import { evidenceRoot, mailboxName, planFileName, quarantineMailboxes, readMailbox, storeEvidence, writeEvidenceFile, } from "./evidence.js";
|
|
35
|
+
import { gitFailureWords } from "./builder.js";
|
|
35
36
|
import { MARKER as LEASE_MARKER } from "./worktree.js";
|
|
36
37
|
import { liveLogContext } from "./live-detail.js";
|
|
37
38
|
import { openLiveLog } from "./live.js";
|
|
@@ -537,7 +538,7 @@ export async function plan(store, request) {
|
|
|
537
538
|
}
|
|
538
539
|
const revision = await git(GIT, ["--no-optional-locks", "rev-parse", "HEAD"], { cwd: worktree });
|
|
539
540
|
if (revision.code !== 0) {
|
|
540
|
-
return { ok: false, kind: "failure", reason: "git", message: `could not read the base revision in ${worktree}` };
|
|
541
|
+
return { ok: false, kind: "failure", reason: "git", message: `could not read the base revision in ${worktree} (${gitFailureWords(revision)})` };
|
|
541
542
|
}
|
|
542
543
|
const baseRevision = revision.stdout.trim();
|
|
543
544
|
store.stampRun(request.runId, { baseRevision });
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings → Project: what Toolroll holds for a project, and deleting
|
|
3
|
+
* it. An instance operator deletes in two steps: type the project's name,
|
|
4
|
+
* then read exactly what goes and confirm with their password (or a fresh
|
|
5
|
+
* sign-in with the identity provider). The repository itself stays.
|
|
6
|
+
*/
|
|
7
|
+
import { type FallbackSetting } from "./fallback.js";
|
|
1
8
|
import type { ProjectSandbox } from "./project-sandbox.js";
|
|
2
9
|
import type { ProjectNetwork } from "./egress.js";
|
|
3
10
|
import type { WritesMode } from "./writes-mode.js";
|
|
@@ -49,6 +56,11 @@ export type ProjectSettingsView = {
|
|
|
49
56
|
setting: ProjectNetwork;
|
|
50
57
|
canChange: boolean;
|
|
51
58
|
};
|
|
59
|
+
/** Who takes over when an agent runs out of its plan (fallback.ts), and whether this person may change it. */
|
|
60
|
+
fallback?: {
|
|
61
|
+
setting: FallbackSetting | null;
|
|
62
|
+
canChange: boolean;
|
|
63
|
+
};
|
|
52
64
|
/** Where its builds may write (writes-mode.ts), and whether this person may change it. */
|
|
53
65
|
writes?: {
|
|
54
66
|
setting: WritesMode;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* then read exactly what goes and confirm with their password (or a fresh
|
|
5
5
|
* sign-in with the identity provider). The repository itself stays.
|
|
6
6
|
*/
|
|
7
|
+
import { entryMovesOnWords, entryWords } from "./fallback.js";
|
|
7
8
|
import { sandboxRuntimeWords } from "./sandbox-runtime.js";
|
|
8
9
|
import { CHECK_ACCESS_WARNINGS, signInHostsMissing } from "./check-access.js";
|
|
9
10
|
import { html, joinHtml, postForm } from "./html.js";
|
|
@@ -132,6 +133,22 @@ function networkHtml(view) {
|
|
|
132
133
|
note("Tasks scoped from now on use this; tasks already approved keep the network they were approved with."),
|
|
133
134
|
]) }))]) });
|
|
134
135
|
}
|
|
136
|
+
function fallbackHtml(view) {
|
|
137
|
+
const fallback = view.fallback;
|
|
138
|
+
if (fallback === undefined)
|
|
139
|
+
return "";
|
|
140
|
+
const { setting } = fallback;
|
|
141
|
+
const lead = setting === null ? html `When an agent runs out of its plan, the task stops.` : html `When an agent runs out of its plan, the work moves to the next agent here.`;
|
|
142
|
+
const lines = setting === null ? "" : joinHtml(setting.entries.map(one => note(`${entryWords(one)}. ${entryMovesOnWords(one)}.`)));
|
|
143
|
+
return settingsSection({ id: "fallback", title: "Fallback agents", state: setting === null ? "" : ` · ${setting.entries.length}`, lead,
|
|
144
|
+
body: joinHtml([lines, !fallback.canChange ? APPROVER_CHANGES : disclose("Change", kitSectionForm({ proof: "project-fallback", action: "/settings/project/fallback", id: "fallback-form", gate: true, hidden: { repo: view.repo },
|
|
145
|
+
body: joinHtml([
|
|
146
|
+
formRow("Agents, in order", "One per line: provider:model:plan or provider:model:api-key. Empty turns this off.", textarea({ id: "fallback-agents", name: "agents", value: setting === null ? "" : setting.entries.map(one => `${one.provider}:${one.model}:${one.authMode === "subscription" ? "plan" : "api-key"}`).join("\n"), rows: 3,
|
|
147
|
+
attrs: { autocomplete: "off", autocapitalize: "off", spellcheck: "false", placeholder: "codex:gpt-5.6:plan", "aria-label": "Fallback agents, in order" } }), "top"),
|
|
148
|
+
formRow("Paid fallback", "An agent on an API key spends money. It runs only while this is on.", switchInput({ id: "fallback-paid", name: "paid", label: "Allowed", on: setting?.paidGrant != null, attrs: { value: "on", "aria-label": "Allow paid fallback agents to spend" } }), "inline"),
|
|
149
|
+
note("Tasks scoped from now on use this; tasks already approved keep the chain they were approved with."),
|
|
150
|
+
]) }))]) });
|
|
151
|
+
}
|
|
135
152
|
/** Live view: hidden by default; showing commands takes the password, hiding them is one step. */
|
|
136
153
|
function liveViewHtml(view) {
|
|
137
154
|
const live = view.liveView;
|
|
@@ -205,7 +222,7 @@ export function projectSettingsHtml(view, said) {
|
|
|
205
222
|
formRow("Recorded remote", view.remoteUrl ? null : "None. Landed work waits for your review.", view.remoteUrl ? mono(view.remoteUrl) : html ``, view.remoteUrl ? "wide" : "inline"),
|
|
206
223
|
disclose("Change remote", joinHtml([note("An owner or instance operator can run:"), codeLine("toolroll repos add <project> --remote <url>")])),
|
|
207
224
|
]) });
|
|
208
|
-
const settings = joinHtml([buildsHtml(view), accessHtml(view), networkHtml(view), writesHtml(view), sandboxHtml(view), checkAccessHtml(view), liveViewHtml(view), codeMapHtml(view)]);
|
|
225
|
+
const settings = joinHtml([buildsHtml(view), accessHtml(view), networkHtml(view), fallbackHtml(view), writesHtml(view), sandboxHtml(view), checkAccessHtml(view), liveViewHtml(view), codeMapHtml(view)]);
|
|
209
226
|
if (!view.canDelete)
|
|
210
227
|
return joinHtml([answer, project, settings, note("An instance operator can remove or delete a project.")]);
|
|
211
228
|
const deleteBody = view.running.length > 0
|
package/dist/project-delete.js
CHANGED
|
@@ -50,6 +50,8 @@ const SETTINGS = [
|
|
|
50
50
|
["intake_grant", "repo"], ["capability", "repo"], ["project_tool", "repo"],
|
|
51
51
|
["learning_policy", "repo"], ["operating_mode", "repo"], ["project_knowledge", "repo"], ["live_detail_setting", "repo"],
|
|
52
52
|
["project_decision", "repo"], ["flow_script", "repo"], ["workflow_recipe", "repo"], ["team_lead_project", "project"],
|
|
53
|
+
// Its fallback agents and, on the same row, its paid fallback grant (fallback.ts).
|
|
54
|
+
["fallback_setting", "repo"],
|
|
53
55
|
];
|
|
54
56
|
/** The project's Models rows, counted with its settings and deleted by forgetProjectModels only. */
|
|
55
57
|
export const MODEL_SETTINGS = Object.freeze([["phase_config", "scope"], ["phase_tier_config", "scope"], ["phase_tier_alternate", "scope"]]);
|
|
@@ -264,6 +266,10 @@ function deleteRows(store, repo, d, now) {
|
|
|
264
266
|
del("diff_comment", `${IN("run")} OR ${IN("reviewer_run")} OR ${IN("artifact")}`, R, R, A);
|
|
265
267
|
del("review_request", `${IN("run")} OR ${IN("reviewer_run")}`, R, R);
|
|
266
268
|
del("repair_chain", IN("source_run"), R);
|
|
269
|
+
// What happens to a result after its agent finishes (fleet-schema.ts): a project's own rows, answers first.
|
|
270
|
+
del("finding_answer", "finding IN (SELECT id FROM finding WHERE repo = ?)", repo);
|
|
271
|
+
for (const table of ["finding", "check_repair", "evidence_pair", "failing_first", "provider_capture"])
|
|
272
|
+
del(table, "repo = ?", repo);
|
|
267
273
|
for (const table of ["incident", "proof_acceptance", "proof_verdict", "run_process", "run_route", "run_spend", "run_tool", "run_note", "run_stop", "run_checkpoint", "execution_slot"])
|
|
268
274
|
del(table, IN("run"), R);
|
|
269
275
|
del("code_map_delivery", IN("run"), R);
|
|
@@ -22,18 +22,18 @@ export type KnowledgeView = ReturnType<typeof knowledgeView>;
|
|
|
22
22
|
export declare function knowledgeVersion(store: Store, repo: string, actor: string, revision: number): Knowledge;
|
|
23
23
|
/** Chat reads only an admitted project. Keep the full source in the project UI. */
|
|
24
24
|
export declare function conversationKnowledge(store: Store, repo: string, actor: string, reference?: string, decision?: number): {
|
|
25
|
-
|
|
25
|
+
references?: never;
|
|
26
26
|
revision: number;
|
|
27
27
|
stale: boolean;
|
|
28
28
|
notice: string;
|
|
29
|
+
title?: never;
|
|
29
30
|
content?: never;
|
|
30
31
|
history?: never;
|
|
31
32
|
instructions?: never;
|
|
32
|
-
references?: never;
|
|
33
33
|
decisions?: never;
|
|
34
34
|
waiting?: never;
|
|
35
35
|
} | {
|
|
36
|
-
|
|
36
|
+
references?: never;
|
|
37
37
|
stale?: never;
|
|
38
38
|
decision: {
|
|
39
39
|
repo: string;
|
|
@@ -51,14 +51,15 @@ export declare function conversationKnowledge(store: Store, repo: string, actor:
|
|
|
51
51
|
};
|
|
52
52
|
afterwards?: string;
|
|
53
53
|
notice: string;
|
|
54
|
+
title?: never;
|
|
54
55
|
content?: never;
|
|
55
56
|
revision?: never;
|
|
56
57
|
history?: never;
|
|
57
58
|
instructions?: never;
|
|
58
|
-
references?: never;
|
|
59
59
|
decisions?: never;
|
|
60
60
|
waiting?: never;
|
|
61
61
|
} | {
|
|
62
|
+
references?: never;
|
|
62
63
|
stale?: never;
|
|
63
64
|
revision: number;
|
|
64
65
|
title: string;
|
|
@@ -66,12 +67,11 @@ export declare function conversationKnowledge(store: Store, repo: string, actor:
|
|
|
66
67
|
notice: string;
|
|
67
68
|
history?: never;
|
|
68
69
|
instructions?: never;
|
|
69
|
-
references?: never;
|
|
70
70
|
decisions?: never;
|
|
71
71
|
waiting?: never;
|
|
72
72
|
} | {
|
|
73
|
-
title?: never;
|
|
74
73
|
stale?: never;
|
|
74
|
+
title?: never;
|
|
75
75
|
content?: never;
|
|
76
76
|
revision: number;
|
|
77
77
|
history: {
|
package/dist/project-tools.d.ts
CHANGED
|
@@ -109,7 +109,7 @@ export declare function testTool(spec: ToolSpec, values: Record<string, string>,
|
|
|
109
109
|
timeoutMs?: number;
|
|
110
110
|
env?: NodeJS.ProcessEnv;
|
|
111
111
|
omitEnv?: readonly string[];
|
|
112
|
-
server: ToolServer
|
|
112
|
+
server: ToolServer | Promise<ToolServer>;
|
|
113
113
|
}): Promise<{
|
|
114
114
|
ok: true;
|
|
115
115
|
tools: string[];
|
|
@@ -126,7 +126,7 @@ export declare function listProjectToolActions(spec: ToolSpec, values: Record<st
|
|
|
126
126
|
timeoutMs?: number;
|
|
127
127
|
env?: NodeJS.ProcessEnv;
|
|
128
128
|
omitEnv?: readonly string[];
|
|
129
|
-
server: ToolServer
|
|
129
|
+
server: ToolServer | Promise<ToolServer>;
|
|
130
130
|
}): Promise<{
|
|
131
131
|
ok: true;
|
|
132
132
|
actions: ToolActionInfo[];
|
|
@@ -152,7 +152,7 @@ export declare function callProjectTool(spec: ToolSpec, values: Record<string, s
|
|
|
152
152
|
timeoutMs?: number;
|
|
153
153
|
env?: NodeJS.ProcessEnv;
|
|
154
154
|
omitEnv?: readonly string[];
|
|
155
|
-
server: ToolServer
|
|
155
|
+
server: ToolServer | Promise<ToolServer>;
|
|
156
156
|
}): Promise<ToolCall>;
|
|
157
157
|
/** The exact servers one launch gets, and those left out with why (`repo`: the project, from the run's record). */
|
|
158
158
|
export type ToolLaunch = {
|
|
@@ -312,7 +312,7 @@ export type ToolServer = {
|
|
|
312
312
|
export declare function toolServerFor(store: ToolServerStore, repo: string, options?: {
|
|
313
313
|
home?: string;
|
|
314
314
|
available?: () => boolean;
|
|
315
|
-
}): ToolServer
|
|
315
|
+
}): Promise<ToolServer>;
|
|
316
316
|
/** Each project's `.mcp.json` as the console last read it off its thread (its background pass): what its pages and the
|
|
317
317
|
* lead offer, so neither reads a project folder on the console's thread. */
|
|
318
318
|
export declare const savedProjectMcp: Map<string, Record<string, unknown> | null>;
|