toolroll 0.9.62 → 0.9.65
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 +80 -0
- package/README.md +66 -8
- package/dist/acp-agents.d.ts +43 -0
- package/dist/acp-agents.js +62 -0
- package/dist/acp-attest.d.ts +44 -0
- package/dist/acp-attest.js +269 -0
- package/dist/acp-check.d.ts +28 -0
- package/dist/acp-check.js +410 -0
- package/dist/acp-client.d.ts +163 -0
- package/dist/acp-client.js +738 -0
- package/dist/acp-inspect.d.ts +23 -0
- package/dist/acp-inspect.js +109 -0
- package/dist/agent-board.d.ts +19 -6
- package/dist/agent-board.js +30 -9
- package/dist/agent-fence.d.ts +203 -10
- package/dist/agent-fence.js +623 -41
- package/dist/agent-pulse.js +9 -2
- package/dist/agent-state-words.d.ts +19 -0
- package/dist/agent-state-words.js +20 -0
- package/dist/agent-state.d.ts +65 -0
- package/dist/agent-state.js +229 -0
- package/dist/agentconfig.js +36 -13
- package/dist/agents-board.d.ts +34 -2
- package/dist/agents-board.js +110 -1
- package/dist/approval-rules-ui.js +2 -2
- package/dist/assignment-delivery.d.ts +14 -0
- package/dist/assignment-delivery.js +13 -0
- package/dist/assignment.js +13 -0
- package/dist/attest.js +7 -0
- package/dist/backup-ui.js +2 -2
- package/dist/browser/THIRD_PARTY_NOTICES.txt +48 -0
- package/dist/browser/terminal.css +37 -0
- package/dist/browser/terminal.js +117 -0
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +42 -38
- package/dist/browser-shell.js +6 -3
- package/dist/browser-workspace.d.ts +263 -1
- package/dist/browser-workspace.js +60 -0
- package/dist/builder.d.ts +6 -1
- package/dist/builder.js +34 -13
- package/dist/chat-actions.d.ts +13 -1
- package/dist/chat-actions.js +199 -17
- package/dist/chat-ask.d.ts +4 -0
- package/dist/chat-ask.js +14 -0
- package/dist/chat-channel.js +20 -0
- package/dist/chat-continuity.js +5 -4
- package/dist/chat-decide.d.ts +0 -14
- package/dist/chat-decide.js +4 -1
- package/dist/chat-delivery.d.ts +9 -0
- package/dist/chat-delivery.js +45 -6
- package/dist/chat-messages.d.ts +8 -0
- package/dist/chat-messages.js +36 -4
- package/dist/check-levels-ui.js +2 -2
- package/dist/check-levels.js +3 -0
- package/dist/checkout-proof.d.ts +28 -0
- package/dist/checkout-proof.js +62 -0
- package/dist/claim.d.ts +12 -0
- package/dist/claim.js +20 -1
- package/dist/cli-help.d.ts +1 -1
- package/dist/cli-help.js +5 -1
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +24 -2
- package/dist/coding-ui.js +6 -4
- package/dist/console-instance.d.ts +124 -0
- package/dist/console-instance.js +337 -0
- package/dist/contracts/chat-actions.d.ts +16 -0
- package/dist/contracts/chat-actions.js +4 -0
- package/dist/contracts/chat-content.d.ts +19 -0
- package/dist/contracts/chat-content.js +6 -0
- package/dist/contracts/cli.d.ts +8 -0
- package/dist/contracts/cli.js +3 -1
- package/dist/contracts/console-api.d.ts +30 -4
- package/dist/contracts/console-api.js +13 -4
- package/dist/contracts/flow-card.d.ts +4 -0
- package/dist/contracts/flow-card.js +3 -0
- package/dist/contracts/flow.d.ts +46 -0
- package/dist/contracts/flow.js +11 -2
- package/dist/contracts/integration-metadata.d.ts +2 -1
- package/dist/contracts/integration-metadata.js +2 -1
- package/dist/contracts/lead-tools.d.ts +92 -0
- package/dist/contracts/lead-tools.js +13 -0
- package/dist/contracts/provider.d.ts +11 -1
- package/dist/contracts/provider.js +1 -1
- package/dist/contracts/route.d.ts +76 -1
- package/dist/contracts/route.js +1 -1
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/control-setup.d.ts +1 -1
- package/dist/control-ui.js +2 -2
- package/dist/converse.d.ts +16 -0
- package/dist/converse.js +32 -4
- package/dist/demo-chat.js +4 -4
- package/dist/demo.js +2 -2
- package/dist/desktop-update-gate.js +6 -0
- package/dist/discord-chat.js +4 -1
- package/dist/dispatch.js +7 -4
- package/dist/doctor.d.ts +53 -0
- package/dist/doctor.js +427 -0
- package/dist/egress-bridge.d.ts +17 -0
- package/dist/egress-bridge.js +111 -0
- package/dist/egress.d.ts +119 -0
- package/dist/egress.js +423 -0
- package/dist/evidence-pack.d.ts +1 -1
- package/dist/evidence-pack.js +2 -2
- package/dist/exec.d.ts +81 -0
- package/dist/exec.js +368 -9
- package/dist/export-ui.js +2 -2
- package/dist/flow-code.d.ts +2 -2
- package/dist/flow-code.js +4 -15
- package/dist/flow-engine.js +35 -11
- package/dist/flow-gallery-ui.js +2 -2
- package/dist/flow-gallery.js +27 -8
- package/dist/flow-share.js +16 -5
- package/dist/flow-triggers.d.ts +84 -11
- package/dist/flow-triggers.js +554 -87
- 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 +63 -12
- package/dist/flows-ui.d.ts +2 -3
- package/dist/flows-ui.js +56 -5
- package/dist/flows.d.ts +4 -0
- package/dist/flows.js +9 -1
- package/dist/google-mail.d.ts +1 -0
- package/dist/google-mail.js +61 -18
- package/dist/html.d.ts +9 -0
- package/dist/html.js +11 -0
- 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.d.ts +17 -0
- package/dist/invoke.js +510 -182
- package/dist/keys.d.ts +18 -14
- package/dist/keys.js +5 -0
- package/dist/landed-cleanup.d.ts +3 -2
- package/dist/landed-cleanup.js +12 -5
- package/dist/lead-capabilities.js +3 -2
- package/dist/lead-contract.js +2 -0
- package/dist/lead-doors.d.ts +2 -0
- package/dist/lead-doors.js +7 -0
- package/dist/lead-follow.d.ts +2 -0
- package/dist/lead-follow.js +58 -2
- package/dist/lead-progress.js +1 -0
- package/dist/lead-queue-runtime.d.ts +46 -0
- package/dist/lead-queue-runtime.js +191 -0
- package/dist/lead-queue.d.ts +104 -0
- package/dist/lead-queue.js +187 -0
- package/dist/lead-sweep.d.ts +67 -0
- package/dist/lead-sweep.js +99 -0
- package/dist/lead-tools.d.ts +22 -0
- package/dist/lead-tools.js +163 -4
- package/dist/lead.d.ts +13 -5
- package/dist/lead.js +89 -29
- package/dist/limits-ui.d.ts +8 -1
- package/dist/limits-ui.js +26 -0
- package/dist/live-bus.d.ts +8 -0
- package/dist/live-bus.js +40 -4
- package/dist/live-detail.d.ts +27 -0
- package/dist/live-detail.js +57 -0
- package/dist/live-notes.d.ts +20 -0
- package/dist/live-notes.js +47 -0
- package/dist/live-records.d.ts +140 -0
- package/dist/live-records.js +935 -0
- package/dist/live-terminal.d.ts +258 -0
- package/dist/live-terminal.js +649 -0
- package/dist/live.d.ts +38 -27
- package/dist/live.js +142 -124
- package/dist/mcp-connect.d.ts +8 -7
- package/dist/mcp-connect.js +49 -51
- package/dist/mcp-oauth.d.ts +6 -3
- package/dist/mcp-oauth.js +8 -4
- package/dist/memory-pass.js +2 -2
- package/dist/mobile-viewport.d.ts +5 -1
- package/dist/mobile-viewport.js +18 -2
- package/dist/model-catalog.js +4 -0
- package/dist/monitoring-ui.js +2 -2
- package/dist/needs-you.d.ts +1 -1
- package/dist/needs-you.js +11 -4
- package/dist/oauth-run-token.d.ts +51 -0
- package/dist/oauth-run-token.js +127 -0
- package/dist/operate.d.ts +2 -0
- package/dist/operate.js +282 -18
- package/dist/passwordless.d.ts +23 -0
- package/dist/passwordless.js +26 -0
- package/dist/peek-cli.d.ts +12 -4
- package/dist/peek-cli.js +19 -11
- package/dist/phase-routing.js +10 -3
- package/dist/plan-auto.d.ts +11 -0
- package/dist/plan-auto.js +62 -11
- package/dist/planner-source.d.ts +3 -0
- package/dist/planner-source.js +14 -0
- package/dist/planner.d.ts +2 -1
- package/dist/planner.js +7 -1
- package/dist/policy-ui.js +2 -2
- package/dist/policy.d.ts +1 -20
- package/dist/policy.js +29 -2
- 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-delete-ui.d.ts +12 -0
- package/dist/project-delete-ui.js +41 -9
- package/dist/project-delete.d.ts +3 -1
- package/dist/project-delete.js +12 -6
- package/dist/project-file.d.ts +3 -0
- package/dist/project-file.js +16 -0
- package/dist/project-memory.d.ts +3 -1
- package/dist/project-memory.js +57 -25
- package/dist/project-tools.d.ts +103 -27
- package/dist/project-tools.js +399 -100
- package/dist/provider-auth.d.ts +4 -3
- package/dist/provider-auth.js +5 -4
- package/dist/provider-connection.d.ts +2 -1
- package/dist/provider-connection.js +5 -0
- package/dist/provider-home.d.ts +22 -0
- package/dist/provider-home.js +99 -0
- package/dist/provider.d.ts +42 -5
- package/dist/provider.js +97 -19
- package/dist/pull-request-ui.js +2 -2
- package/dist/push.js +1 -1
- package/dist/quick-start.d.ts +24 -0
- package/dist/quick-start.js +127 -0
- package/dist/release.d.ts +3 -0
- package/dist/release.js +14 -8
- package/dist/remote-main.d.ts +4 -0
- package/dist/remote-main.js +3 -3
- package/dist/request-budget-ui.js +2 -2
- package/dist/result-review.d.ts +1 -1
- package/dist/result-review.js +9 -3
- package/dist/retention-ui.js +2 -2
- package/dist/retention.js +10 -1
- package/dist/run-efficiency.d.ts +73 -0
- package/dist/run-efficiency.js +228 -0
- package/dist/runner.d.ts +5 -1
- package/dist/runner.js +22 -1
- package/dist/scope.d.ts +171 -6
- package/dist/scope.js +251 -31
- package/dist/scout-net.d.ts +4 -1
- package/dist/scout-net.js +37 -12
- package/dist/scout.d.ts +20 -6
- package/dist/scout.js +27 -3
- package/dist/serve.js +258 -43
- package/dist/server/chat.d.ts +1 -1
- package/dist/server/chat.js +122 -31
- package/dist/server/chrome.d.ts +6 -1
- package/dist/server/chrome.js +18 -7
- package/dist/server/console.css +52 -38
- package/dist/server/flows.js +65 -13
- package/dist/server/guards.d.ts +1 -0
- package/dist/server/guards.js +23 -9
- package/dist/server/http.d.ts +7 -0
- package/dist/server/http.js +3 -2
- package/dist/server/live.d.ts +34 -1
- package/dist/server/live.js +39 -17
- package/dist/server/pages-assets.js +5 -0
- package/dist/server/pages.js +90 -13
- package/dist/server/people-tokens.d.ts +3 -0
- package/dist/server/people-tokens.js +55 -17
- package/dist/server/remote-hooks.js +12 -6
- package/dist/server/remote.d.ts +2 -1
- package/dist/server/remote.js +14 -5
- package/dist/server/render-chat.d.ts +12 -3
- package/dist/server/render-chat.js +47 -16
- package/dist/server/render-pages.d.ts +4 -1
- package/dist/server/render-pages.js +5 -4
- package/dist/server/render-people.d.ts +3 -2
- package/dist/server/render-people.js +3 -2
- package/dist/server/render-results.d.ts +18 -2
- package/dist/server/render-results.js +37 -28
- package/dist/server/render-settings.js +6 -5
- package/dist/server/render-tasks.d.ts +35 -10
- package/dist/server/render-tasks.js +110 -27
- package/dist/server/request-context.d.ts +2 -0
- package/dist/server/request-context.js +2 -1
- package/dist/server/route-table.d.ts +3 -0
- package/dist/server/route-table.js +49 -5
- package/dist/server/runtime.d.ts +34 -3
- package/dist/server/session.d.ts +25 -1
- package/dist/server/session.js +18 -4
- package/dist/server/settings.js +132 -13
- package/dist/server/sweeps.d.ts +4 -0
- package/dist/server/sweeps.js +58 -0
- package/dist/server/task-views.d.ts +2 -1
- package/dist/server/task-views.js +96 -6
- package/dist/server/tasks.js +320 -74
- package/dist/server/terminals.d.ts +31 -0
- package/dist/server/terminals.js +365 -0
- package/dist/setup-guide.js +3 -2
- package/dist/slack-chat.js +4 -1
- package/dist/spend-ui.js +3 -3
- package/dist/spend.js +2 -1
- package/dist/steer-capabilities.d.ts +32 -0
- package/dist/steer-capabilities.js +60 -0
- package/dist/steer-controls.d.ts +14 -0
- package/dist/steer-controls.js +36 -0
- package/dist/steer-status.js +2 -2
- package/dist/storage-sweep-child.js +3 -1
- package/dist/storage-sweep-process.d.ts +12 -5
- package/dist/storage-sweep-process.js +23 -6
- package/dist/storage-ui.js +4 -4
- package/dist/store-baseline.d.ts +1 -1
- package/dist/store-baseline.js +1 -1
- package/dist/store.d.ts +466 -19
- package/dist/store.js +1185 -63
- package/dist/subagent-desk.js +1 -1
- package/dist/subagent-tools.d.ts +1 -1
- package/dist/summary.js +3 -0
- package/dist/surface.js +6 -4
- package/dist/task-source.d.ts +58 -0
- package/dist/task-source.js +93 -0
- package/dist/team-leads.js +1 -1
- package/dist/teams-chat.js +4 -1
- package/dist/teardown-git.d.ts +14 -0
- package/dist/teardown-git.js +119 -0
- package/dist/telegram-lead.d.ts +3 -0
- package/dist/telegram-lead.js +44 -7
- package/dist/telegram.d.ts +11 -2
- package/dist/telegram.js +51 -17
- package/dist/terminal-catalog.d.ts +11 -0
- package/dist/terminal-catalog.js +129 -0
- package/dist/text-limits.d.ts +2 -0
- package/dist/text-limits.js +2 -0
- package/dist/tool-broker.d.ts +39 -0
- package/dist/tool-broker.js +370 -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-ui.js +2 -2
- package/dist/toolroll-update.js +14 -3
- package/dist/tools-ui.d.ts +7 -1
- package/dist/tools-ui.js +19 -10
- package/dist/webhook-limits.d.ts +23 -0
- package/dist/webhook-limits.js +30 -0
- package/dist/webhook-readers.d.ts +56 -0
- package/dist/webhook-readers.js +138 -0
- package/dist/webhook-signing.d.ts +27 -0
- package/dist/webhook-signing.js +119 -0
- package/dist/work-index.d.ts +29 -0
- package/dist/work-index.js +21 -5
- package/dist/workspace-revision.js +3 -1
- package/dist/worktree.d.ts +10 -4
- package/dist/worktree.js +70 -29
- package/package.json +13 -2
- package/scripts/provider-canary.mjs +210 -31
package/dist/chat-delivery.js
CHANGED
|
@@ -26,7 +26,33 @@ import { isShotsKind, resultShotsFor } from "./result-shots.js";
|
|
|
26
26
|
import { CHAT_APP_NAMES } from "./flow-triggers.js";
|
|
27
27
|
import { answersPrompt, applyDecideFeedback, applyDecideTap, decideFallbackLink, decideOffer, decideTargetOf, hasLiveDecideTokens, linksFor, mergedText, mintDecideButtons, offerFingerprint, openPromptFor, recordChatMerge } from "./chat-decide.js";
|
|
28
28
|
import { mergePullRequest } from "./pull-request-flow.js";
|
|
29
|
-
import { watchedPing } from "./agent-board.js";
|
|
29
|
+
import { attentionSubjectOf, neverHeld, watchedPing } from "./agent-board.js";
|
|
30
|
+
/**
|
|
31
|
+
* The watch at a chat app's final fence (cockpit §3, R11), right before the network: a notice about an agent its
|
|
32
|
+
* person has open now waits, as a held part, until the watch ends; then it is delivered as usual. The subject is the
|
|
33
|
+
* part's own run, or its task's live build. Only a part planned with `watch` (never a security alert or the digest)
|
|
34
|
+
* can be held. True: held, so nothing is sent now.
|
|
35
|
+
*/
|
|
36
|
+
export function heldForWatch(state, store, part, event, approver, content, now) {
|
|
37
|
+
// Fail closed: only a notice planned as holdable (a kind a watch may hold) is ever held here.
|
|
38
|
+
if (event.kind !== "notice" || content.watch !== true)
|
|
39
|
+
return false;
|
|
40
|
+
// One part may cover several tasks (a grouped card, a batch): it waits only while every one of them is watched,
|
|
41
|
+
// until the first of those watches ends.
|
|
42
|
+
const subjects = content.subjects ?? [{ ref: content.task ? store.lookupRef(content.task)?.id ?? null : null, run: content.run ?? null }];
|
|
43
|
+
if (subjects.length === 0)
|
|
44
|
+
return false;
|
|
45
|
+
let until = null;
|
|
46
|
+
for (const subject of subjects) {
|
|
47
|
+
const decided = watchedPing(store, { kind: "notice", run: subject.run, taskRef: subject.ref }, approver, now);
|
|
48
|
+
if (decided === "send" || decided === "skip")
|
|
49
|
+
return false;
|
|
50
|
+
if (until === null || decided.hold < until)
|
|
51
|
+
until = decided.hold;
|
|
52
|
+
}
|
|
53
|
+
state.holdPart(part, until);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
30
56
|
export const chatObject = (v) => v !== null && typeof v === "object" && !Array.isArray(v)
|
|
31
57
|
? v
|
|
32
58
|
: {};
|
|
@@ -397,8 +423,9 @@ export async function processChatEvent(options) {
|
|
|
397
423
|
return false;
|
|
398
424
|
if (!outcome.ok &&
|
|
399
425
|
"refused" in outcome &&
|
|
400
|
-
outcome.refused === "concurrent") {
|
|
401
|
-
|
|
426
|
+
(outcome.refused === "concurrent" || outcome.refused === "backlog")) {
|
|
427
|
+
// The lead is answering, or messages sent earlier wait in the person's lead queue: this one waits its turn.
|
|
428
|
+
state.defer(event.id, outcome.refused === "backlog" ? "Waiting behind earlier messages" : "Assistant is answering another message", new Date(nowOf(options).getTime() + 5000));
|
|
402
429
|
return true;
|
|
403
430
|
}
|
|
404
431
|
receipt = store.leadRequestReceipt(resolved.session.id, request);
|
|
@@ -422,7 +449,7 @@ export async function processChatEvent(options) {
|
|
|
422
449
|
if (turn?.state !== "answered") {
|
|
423
450
|
state.plan(event.id, [
|
|
424
451
|
{
|
|
425
|
-
text: leadFailureText(turn?.failureReason ?? null),
|
|
452
|
+
text: leadFailureText(turn?.failureReason ?? null, turn != null && store.leadTurnCostUnconfirmed(turn.id)),
|
|
426
453
|
},
|
|
427
454
|
], nowOf(options));
|
|
428
455
|
return true;
|
|
@@ -734,7 +761,12 @@ function planQuietCard(options, binding, notification, now) {
|
|
|
734
761
|
const view = quietCardView(store, card.tasks, now, options.evidenceRoot, binding.approver);
|
|
735
762
|
if (view === null)
|
|
736
763
|
return;
|
|
737
|
-
|
|
764
|
+
// A card about one task is that task's: its person's watch may hold it at the final fence (never a security kind).
|
|
765
|
+
// A card about its tasks is theirs: its person's watch may hold it at the final fence while every one of them is
|
|
766
|
+
// watched (never for a security kind).
|
|
767
|
+
const content = { text: view.text, link: view.link, ...(card.tasks.length === 1 ? { task: notification.taskId } : {}),
|
|
768
|
+
...(card.tasks.length > 0 && !neverHeld(notification.kind) ? { watch: true } : {}),
|
|
769
|
+
...(card.tasks.length > 1 && !neverHeld(notification.kind) ? { subjects: card.tasks.slice(0, 100).map(ref => ({ ref, run: null })) } : {}) };
|
|
738
770
|
const shown = chatHash(JSON.stringify(content));
|
|
739
771
|
if (card.message !== null) {
|
|
740
772
|
if (card.digest === shown)
|
|
@@ -763,7 +795,12 @@ function planFinished(options, binding, taskRef, run, now, notification = null,
|
|
|
763
795
|
// A single update keeps its task and run, so a reply to it names that work.
|
|
764
796
|
const single = batch.items.length === 1 ? batch.items[0] : null;
|
|
765
797
|
const task = single === null ? null : store.refById(single.taskRef)?.externalId ?? null;
|
|
766
|
-
|
|
798
|
+
// Holdable when its fact is a kind a watch may hold (the batch line for a finished run, or the notification joining
|
|
799
|
+
// it); a batch of several holds only while every one of them is watched.
|
|
800
|
+
const joining = notification === null ? null : options.store.notificationById(notification);
|
|
801
|
+
const holdable = batch.items.length > 0 && (joining === null || !neverHeld(joining.kind));
|
|
802
|
+
const about = { ...(task === null ? {} : { task }), ...(single?.run == null ? {} : { run: single.run }), ...(holdable ? { watch: true } : {}),
|
|
803
|
+
...(holdable && batch.items.length > 1 ? { subjects: batch.items.slice(0, 100).map(one => ({ ref: one.taskRef, run: one.run })) } : {}) };
|
|
767
804
|
// A message about one result, plan, failure or pull request acts in place (chat-decide.ts), as on Telegram: its
|
|
768
805
|
// buttons are minted only when what it offers changed, or its own were spent (Not now), so an unchanged repaint keeps them.
|
|
769
806
|
const offer = decideOfferFor(options, binding, view.target, repos, now);
|
|
@@ -962,6 +999,7 @@ function offerNotice(options, notification, binding, registry) {
|
|
|
962
999
|
task: notification.taskId,
|
|
963
1000
|
run: run.id,
|
|
964
1001
|
link: card.link,
|
|
1002
|
+
...(attentionSubjectOf(store, notification) !== null ? { watch: true } : {}),
|
|
965
1003
|
};
|
|
966
1004
|
const digest = chatHash(JSON.stringify(content));
|
|
967
1005
|
const prior = state
|
|
@@ -1044,6 +1082,7 @@ function offerNotice(options, notification, binding, registry) {
|
|
|
1044
1082
|
text: offer?.text ?? chatText(phoneText(notification.body === "" ? leadSubjectOf(store, notification, binding.approver) : `${leadSubjectOf(store, notification, binding.approver)}\n\n${notification.body}`, 2500), notification.taskId === null ? [] : [{ id: notification.taskId, title: chatTitle(store, notification.taskId) }]),
|
|
1045
1083
|
...(notification.taskId ? { task: notification.taskId } : {}),
|
|
1046
1084
|
...(run ? { run: run.id } : {}),
|
|
1085
|
+
...(attentionSubjectOf(store, notification) !== null ? { watch: true } : {}),
|
|
1047
1086
|
...(offer !== null ? { decide: mintDecide(options, binding, target, offer, now) }
|
|
1048
1087
|
: fallback !== null ? { link: fallback }
|
|
1049
1088
|
: notification.link
|
package/dist/chat-messages.d.ts
CHANGED
|
@@ -174,6 +174,8 @@ export declare class ChatMessages extends ChatCore {
|
|
|
174
174
|
taskId: string | null;
|
|
175
175
|
sourceRun: number | null;
|
|
176
176
|
}, now: Date): number;
|
|
177
|
+
/** How many of this chat's messages wait for, or are being answered by, the lead (the lead queue's bound per chat). */
|
|
178
|
+
messagesWaiting(binding: number): number;
|
|
177
179
|
/** Whether a message from this chat is already waiting on, or being answered from, this bot message (a tapped question). */
|
|
178
180
|
messageWaitingOn(binding: number, messageId: string): boolean;
|
|
179
181
|
getMessage(id: number): TelegramConversation | null;
|
|
@@ -294,6 +296,12 @@ export declare class ChatMessages extends ChatCore {
|
|
|
294
296
|
claimDeliveries(binding: TelegramBinding, owner: string, ttlMs: number, now: Date, only?: "all" | "urgent"): TelegramDelivery[];
|
|
295
297
|
/** Synchronous final fence, called AFTER each asynchronous enrollment read. */
|
|
296
298
|
deliveryProblem(row: TelegramDelivery, binding: TelegramBinding, owner: string, projects: readonly string[], now: Date, batch?: readonly number[]): string | null;
|
|
299
|
+
/**
|
|
300
|
+
* The watch at the final fence (cockpit §3, R11): right before the network, a person who has since opened the
|
|
301
|
+
* agent this ping is about holds it — the claim is released with the watch's end as its next attempt, no attempt
|
|
302
|
+
* spent — and news about a finished run they have now seen settles quietly. "send": nothing changed.
|
|
303
|
+
*/
|
|
304
|
+
watchFence(row: TelegramDelivery, binding: TelegramBinding, owner: string, now: Date): "send" | "held" | "skipped";
|
|
297
305
|
claimHeld(row: TelegramDelivery, owner: string, now: Date): boolean;
|
|
298
306
|
/** A confirmed network message remains history even if authority changed
|
|
299
307
|
* while it was in flight. It only binds the OLD destination, never new work. */
|
package/dist/chat-messages.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* Imports the store's types only (no runtime cycle): the store builds its chats on it.
|
|
10
10
|
*/
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
-
import { watchedPing } from "./agent-board.js";
|
|
12
|
+
import { neverHeld, watchedPing } from "./agent-board.js";
|
|
13
13
|
import { ChatCore } from "./chat-core.js";
|
|
14
14
|
import { readChatMessageBody, readChatPart, savedChatEventBody, savedChatPart } from "./contracts/chat-content.js";
|
|
15
15
|
import { isTelegramProgressNotification, proposalTaskOf, readNotification, readNotificationReceipt } from "./notification-rows.js";
|
|
@@ -381,6 +381,10 @@ export class ChatMessages extends ChatCore {
|
|
|
381
381
|
.run(`m${row.updateId}`, row.binding.botId, row.binding.id, row.binding.chatId, row.binding.userId, row.messageId, row.replyTo ?? "", savedChatEventBody(body), now.toISOString());
|
|
382
382
|
return row.updateId;
|
|
383
383
|
}
|
|
384
|
+
/** How many of this chat's messages wait for, or are being answered by, the lead (the lead queue's bound per chat). */
|
|
385
|
+
messagesWaiting(binding) {
|
|
386
|
+
return Number(this.prepare("SELECT COUNT(*) AS n FROM chat_event WHERE provider = :provider AND kind = 'message' AND binding = ? AND state IN ('queued', 'running')").get(binding)?.["n"] ?? 0);
|
|
387
|
+
}
|
|
384
388
|
/** Whether a message from this chat is already waiting on, or being answered from, this bot message (a tapped question). */
|
|
385
389
|
messageWaitingOn(binding, messageId) {
|
|
386
390
|
return this.prepare("SELECT 1 AS hit FROM chat_event WHERE provider = :provider AND kind = 'message' AND binding = ? AND ts = ? AND state IN ('queued', 'running') LIMIT 1").get(binding, messageId) !== undefined;
|
|
@@ -767,17 +771,45 @@ export class ChatMessages extends ChatCore {
|
|
|
767
771
|
return TELEGRAM_HOLD_REASONS.task;
|
|
768
772
|
}
|
|
769
773
|
}
|
|
770
|
-
|
|
774
|
+
// A security alert never waits behind its task's earlier pings (one may be held while its person watches the agent).
|
|
775
|
+
if (row.taskRef !== null && !neverHeld(String(current["kind"]))) {
|
|
771
776
|
// Skipped history never fences what follows it: it is settled.
|
|
772
|
-
const earlier = this.db.prepare(`SELECT n.
|
|
777
|
+
const earlier = this.db.prepare(`SELECT n.*, d.next_attempt_at AS held_until, d.last_error AS held_error FROM notification n
|
|
773
778
|
LEFT JOIN notification_delivery d ON d.notification = n.id AND d.destination = ?
|
|
774
779
|
WHERE n.task_ref = ? AND n.id < ? AND n.resolved_at IS NULL AND ${TELEGRAM_UNSETTLED}`)
|
|
775
780
|
.all(row.destination, row.taskRef, row.id);
|
|
776
|
-
|
|
781
|
+
// One a watch is holding (cockpit §3) keeps nothing behind it: it goes when the watch ends, and what followed
|
|
782
|
+
// it about another attempt is not that watch's to delay.
|
|
783
|
+
// Held: deferred by a watch (a future next attempt with no failure behind it), or watched right now.
|
|
784
|
+
const held = (one) => (one["held_until"] != null && String(one["held_until"]) > now.toISOString() && one["held_error"] == null)
|
|
785
|
+
|| typeof watchedPing(this.store, readNotification(one), binding.approver, now) === "object";
|
|
786
|
+
if (earlier.some(one => !batch.includes(Number(one["id"])) && !held(one)))
|
|
777
787
|
return TELEGRAM_HOLD_REASONS.order;
|
|
778
788
|
}
|
|
779
789
|
return null;
|
|
780
790
|
}
|
|
791
|
+
/**
|
|
792
|
+
* The watch at the final fence (cockpit §3, R11): right before the network, a person who has since opened the
|
|
793
|
+
* agent this ping is about holds it — the claim is released with the watch's end as its next attempt, no attempt
|
|
794
|
+
* spent — and news about a finished run they have now seen settles quietly. "send": nothing changed.
|
|
795
|
+
*/
|
|
796
|
+
watchFence(row, binding, owner, now) {
|
|
797
|
+
const current = this.db.prepare("SELECT * FROM notification WHERE id = ?").get(row.id);
|
|
798
|
+
if (current === undefined)
|
|
799
|
+
return "send";
|
|
800
|
+
const decided = watchedPing(this.store, readNotification(current), binding.approver, now);
|
|
801
|
+
if (decided === "send")
|
|
802
|
+
return "send";
|
|
803
|
+
const where = "WHERE notification = ? AND destination = ? AND claim_owner = ? AND claim_generation = ? AND delivered_at IS NULL";
|
|
804
|
+
if (decided === "skip") {
|
|
805
|
+
this.db.prepare(`UPDATE notification_delivery SET receipt = '${TELEGRAM_SKIPPED_QUIET}', claim_owner = NULL, claim_expires_at = NULL ${where}`)
|
|
806
|
+
.run(row.id, row.destination, owner, row.claimGeneration);
|
|
807
|
+
return "skipped";
|
|
808
|
+
}
|
|
809
|
+
this.db.prepare(`UPDATE notification_delivery SET next_attempt_at = ?, claim_owner = NULL, claim_expires_at = NULL ${where}`)
|
|
810
|
+
.run(decided.hold, row.id, row.destination, owner, row.claimGeneration);
|
|
811
|
+
return "held";
|
|
812
|
+
}
|
|
781
813
|
claimHeld(row, owner, now) {
|
|
782
814
|
return this.db.prepare(`SELECT 1 FROM notification_delivery WHERE notification = ? AND destination = ?
|
|
783
815
|
AND claim_owner = ? AND claim_generation = ? AND claim_expires_at > ? AND delivered_at IS NULL`)
|
package/dist/check-levels-ui.js
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* words. One choice (Quick, Full, Off) and the quick command beside the full
|
|
3
3
|
* one. Every change takes the password and lands in the ledger; approving a
|
|
4
4
|
* command restates exactly what will run, unattended, before the yes. */
|
|
5
|
-
import { html, postForm } from "./html.js";
|
|
5
|
+
import { html, postForm, reauth } from "./html.js";
|
|
6
6
|
import { CHECK_LEVELS, CHECK_LEVEL_HINTS, CHECK_LEVEL_WORDS } from "./check-levels.js";
|
|
7
7
|
import { BATCH_HINT } from "./batch-policy.js";
|
|
8
8
|
export function checkSettingsHtml(view) {
|
|
9
9
|
const post = (act, body, attrs) => postForm("/settings/checks", body, { attrs, hidden: { repo: view.repo, act } });
|
|
10
|
-
const password = html `<label>Password<input type="password" name="password" autocomplete="current-password" required></label
|
|
10
|
+
const password = html `${reauth(html `<label>Password<input type="password" name="password" autocomplete="current-password" required></label>`)}`;
|
|
11
11
|
const notes = [
|
|
12
12
|
view.said === null ? "" : html `<p class="notice" role="status">${view.said}</p>`,
|
|
13
13
|
view.problem === null ? "" : html `<p class="problem" role="alert">${view.problem}</p>`,
|
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)";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Store } from "./store.js";
|
|
2
|
+
export type CheckoutProof = {
|
|
3
|
+
ok: true;
|
|
4
|
+
path: string;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
reason: string;
|
|
8
|
+
};
|
|
9
|
+
export declare function localCheckoutOf(store: Store, runId: number, local: {
|
|
10
|
+
runner?: string | undefined;
|
|
11
|
+
poolRoot?: string | undefined;
|
|
12
|
+
}, now: Date): CheckoutProof;
|
|
13
|
+
/** How Finder is asked: the system's own `open` by absolute path (never found through PATH), from the root directory,
|
|
14
|
+
* with a minimal environment, one argument. True only when it answered success before the deadline: at the deadline
|
|
15
|
+
* the answer is "not shown", the child is killed outright, and anything it says later is ignored. */
|
|
16
|
+
export type RevealExec = (file: string, args: readonly string[], options: {
|
|
17
|
+
cwd: string;
|
|
18
|
+
env: Record<string, string>;
|
|
19
|
+
}, done: (error: Error | null) => void) => {
|
|
20
|
+
kill: (signal: NodeJS.Signals) => unknown;
|
|
21
|
+
} | undefined;
|
|
22
|
+
export declare const REVEAL_COMMAND = "/usr/bin/open";
|
|
23
|
+
export declare const REVEAL_DEADLINE_MS = 10000;
|
|
24
|
+
export declare function revealInFinder(path: string, run?: RevealExec, deadlineMs?: number, clock?: () => number): Promise<boolean>;
|
|
25
|
+
/** Whether this console can ask Finder at all (a Mac, or a test's own revealer). */
|
|
26
|
+
export declare const canReveal: (injected: boolean) => boolean;
|
|
27
|
+
/** The command to paste into a terminal: `cd` to the path as one single-quoted shell word (a quote inside escaped). */
|
|
28
|
+
export declare function cdCommandOf(path: string): string;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A running attempt's checkout, proved to be on this machine (docs/plans/agents-cockpit.md phase E): the run is live
|
|
3
|
+
* under the task's current claim, its worktree row is this machine's runner's and this task's, and the path is its own
|
|
4
|
+
* real path, a directory inside this machine's pool. Used to offer "copy the cd command" and "Show in Finder" — and
|
|
5
|
+
* proved again immediately before Finder is asked to reveal it. Nothing is ever launched in it.
|
|
6
|
+
*/
|
|
7
|
+
import { execFile } from "node:child_process";
|
|
8
|
+
import { lstatSync, realpathSync } from "node:fs";
|
|
9
|
+
export function localCheckoutOf(store, runId, local, now) {
|
|
10
|
+
if (local.runner === undefined || local.poolRoot === undefined)
|
|
11
|
+
return { ok: false, reason: "this console doesn't run builds on this machine" };
|
|
12
|
+
const run = store.getRun(runId);
|
|
13
|
+
if (run === null || run.outcome !== null || run.worktree === null)
|
|
14
|
+
return { ok: false, reason: "this attempt has no running checkout" };
|
|
15
|
+
if (store.currentLiveLease(run.taskRef, now) !== run.leaseId)
|
|
16
|
+
return { ok: false, reason: "this attempt isn't the one running now" };
|
|
17
|
+
const row = store.getWorktree(run.worktree);
|
|
18
|
+
if (row === null || row.taskRef !== run.taskRef)
|
|
19
|
+
return { ok: false, reason: "the checkout isn't where the record says" };
|
|
20
|
+
// The run, its worktree row and this machine all name the same runner: an inconsistent record fails closed.
|
|
21
|
+
if (run.runner !== local.runner || row.runner !== local.runner)
|
|
22
|
+
return { ok: false, reason: "this attempt runs on another computer" };
|
|
23
|
+
try {
|
|
24
|
+
const real = realpathSync(run.worktree), pool = realpathSync(local.poolRoot);
|
|
25
|
+
// Strictly inside: the pool directory itself is never a checkout.
|
|
26
|
+
if (real !== run.worktree || !real.startsWith(`${pool}/`) || !lstatSync(real).isDirectory())
|
|
27
|
+
return { ok: false, reason: "the checkout isn't inside this machine's pool" };
|
|
28
|
+
return { ok: true, path: real };
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return { ok: false, reason: "the checkout couldn't be found on this machine" };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export const REVEAL_COMMAND = "/usr/bin/open";
|
|
35
|
+
export const REVEAL_DEADLINE_MS = 10_000;
|
|
36
|
+
export function revealInFinder(path, run = (file, args, options, done) => execFile(file, [...args], options, error => done(error)), deadlineMs = REVEAL_DEADLINE_MS, clock = () => performance.now()) {
|
|
37
|
+
return new Promise(resolve => {
|
|
38
|
+
let settled = false;
|
|
39
|
+
let child;
|
|
40
|
+
// The deadline is a moment on a monotonic clock, not the timer having run: a success that arrives after it (even
|
|
41
|
+
// ahead of an overdue timer, in a stalled event loop) is "not shown".
|
|
42
|
+
const due = clock() + deadlineMs;
|
|
43
|
+
const finish = (shown) => { if (settled)
|
|
44
|
+
return; settled = true; clearTimeout(timer); resolve(shown); };
|
|
45
|
+
const timer = setTimeout(() => { finish(false); try {
|
|
46
|
+
child?.kill("SIGKILL");
|
|
47
|
+
}
|
|
48
|
+
catch { /* already gone */ } }, deadlineMs);
|
|
49
|
+
try {
|
|
50
|
+
child = run(REVEAL_COMMAND, ["-R", path], { cwd: "/", env: { PATH: "/usr/bin:/bin" } }, error => finish(error === null && clock() < due));
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
finish(false);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
/** Whether this console can ask Finder at all (a Mac, or a test's own revealer). */
|
|
58
|
+
export const canReveal = (injected) => injected || process.platform === "darwin";
|
|
59
|
+
/** The command to paste into a terminal: `cd` to the path as one single-quoted shell word (a quote inside escaped). */
|
|
60
|
+
export function cdCommandOf(path) {
|
|
61
|
+
return `cd '${path.replaceAll("'", `'\\''`)}'`;
|
|
62
|
+
}
|
package/dist/claim.d.ts
CHANGED
|
@@ -70,6 +70,13 @@ export type AcquireResult = {
|
|
|
70
70
|
reason: "mode-ended";
|
|
71
71
|
message: string;
|
|
72
72
|
}
|
|
73
|
+
/** A lead sweep's limit (docs/plans/lead-fan-out.md): `limit` of its families already hold a live claim. */
|
|
74
|
+
| {
|
|
75
|
+
ok: false;
|
|
76
|
+
reason: "sweep-limit";
|
|
77
|
+
sweep: number;
|
|
78
|
+
limit: number;
|
|
79
|
+
}
|
|
73
80
|
/** The credential presented with this acquisition did not verify against
|
|
74
81
|
* the runner row AT CLAIM TIME (MCP spec v6): stale incarnations never
|
|
75
82
|
* ride a successor's rotation. */
|
|
@@ -183,6 +190,11 @@ export { DEFAULT_MAX_OPEN_DECISIONS, missingCapability } from "./dispatch.js";
|
|
|
183
190
|
* it, which is a race being won; not-ready means nobody should have it, and
|
|
184
191
|
* says why.
|
|
185
192
|
*/
|
|
193
|
+
/**
|
|
194
|
+
* A conformance check's claim (acp-check.ts): its task has no scope to approve, so readiness is not asked — but a
|
|
195
|
+
* check runs real agents, so the runner's capacity is counted where the claim lands, as for dispatch.
|
|
196
|
+
*/
|
|
197
|
+
export declare function acquireForCheck(store: Store, taskRef: number, runner: string, options: AcquireOptions): AcquireResult | NotReady;
|
|
186
198
|
export declare function acquireIfReady(store: Store, taskRef: number, runner: string, options: AcquireOptions & {
|
|
187
199
|
repo?: string;
|
|
188
200
|
maxOpenDecisions?: number;
|
package/dist/claim.js
CHANGED
|
@@ -131,6 +131,12 @@ function acquireLocked(store, taskRef, runner, options) {
|
|
|
131
131
|
message: "the operating mode that approved this has ended — the approval falls back to a person",
|
|
132
132
|
};
|
|
133
133
|
}
|
|
134
|
+
// A LEAD SWEEP'S LIMIT, inside this write transaction so two runners can't both take the last slot: at most
|
|
135
|
+
// max_parallel of its families hold a live claim (claimed-but-not-started counts; a revision counts as its child).
|
|
136
|
+
const sweep = placedRef === null ? null : store.sweepOfTask(placedRef.externalId);
|
|
137
|
+
if (sweep !== null && store.sweepLiveClaims(sweep.id, stamp, taskRef) >= sweep.maxParallel) {
|
|
138
|
+
return { ok: false, reason: "sweep-limit", sweep: sweep.id, limit: sweep.maxParallel };
|
|
139
|
+
}
|
|
134
140
|
const existing = latest(db, taskRef);
|
|
135
141
|
if (existing !== undefined && isLive(existing, stamp)) {
|
|
136
142
|
return {
|
|
@@ -199,6 +205,19 @@ export { DEFAULT_MAX_OPEN_DECISIONS, missingCapability } from "./dispatch.js";
|
|
|
199
205
|
* it, which is a race being won; not-ready means nobody should have it, and
|
|
200
206
|
* says why.
|
|
201
207
|
*/
|
|
208
|
+
/**
|
|
209
|
+
* A conformance check's claim (acp-check.ts): its task has no scope to approve, so readiness is not asked — but a
|
|
210
|
+
* check runs real agents, so the runner's capacity is counted where the claim lands, as for dispatch.
|
|
211
|
+
*/
|
|
212
|
+
export function acquireForCheck(store, taskRef, runner, options) {
|
|
213
|
+
return inTransaction(store, () => {
|
|
214
|
+
const registered = store.getRunner(runner);
|
|
215
|
+
if (registered !== null && store.liveClaimCount(runner, options.now) >= registered.runner.capacity) {
|
|
216
|
+
return { ok: false, reason: "capacity", message: `${runner} has no free slot for a check` };
|
|
217
|
+
}
|
|
218
|
+
return acquireLocked(store, taskRef, runner, options);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
202
221
|
export function acquireIfReady(store, taskRef, runner, options) {
|
|
203
222
|
const role = options.dispatchRole ?? "builder";
|
|
204
223
|
return inTransaction(store, () => {
|
|
@@ -1116,7 +1135,7 @@ function sealPlan(store, leaseId, args) {
|
|
|
1116
1135
|
? ""
|
|
1117
1136
|
: "The filed goal, exclusions, touches, and acceptance criteria are reproduced exactly.\n"
|
|
1118
1137
|
: `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
|
|
1138
|
+
(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
1139
|
}, now);
|
|
1121
1140
|
return { ok: true, changes: contractChanges.length, amendment };
|
|
1122
1141
|
}
|
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...]
|
|
5
|
+
export declare const HELP = "toolroll \u2014 a control plane for unattended coding agents\n\nUsage\n toolroll in a repository: start Toolroll for it, or open the one already running\n (under an agent or with --json: only says what it would do)\n toolroll scan [path...] report what is in flight (also bare `toolroll` outside a repository)\n toolroll [path...] the same report for the paths given\n toolroll pulls report what is waiting on a person\n toolroll graph report which work graph is already here\n toolroll repos list connected repositories, and how to adjust\n toolroll repos add <path> add a project; it is in the console at once (no path: the repo you are in)\n toolroll repos remove <path>\n take it out of the console; its tasks and results stay\n toolroll repos add-from-github <owner/name> --root <dir>\n preview, then clone and connect (--yes)\n toolroll link put `toolroll` on your PATH\n toolroll unlink take it off again\n toolroll update update to the latest release: verified, drained, undoable\n (preview first; --yes, --now, --at HH:MM, --rollback)\n toolroll release <branch> release Toolroll itself: 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
|
@@ -6,7 +6,10 @@ import { DEFAULT_MAX_DEPTH } from "./scan.js";
|
|
|
6
6
|
export const HELP = `toolroll — a control plane for unattended coding agents
|
|
7
7
|
|
|
8
8
|
Usage
|
|
9
|
-
toolroll
|
|
9
|
+
toolroll in a repository: start Toolroll for it, or open the one already running
|
|
10
|
+
(under an agent or with --json: only says what it would do)
|
|
11
|
+
toolroll scan [path...] report what is in flight (also bare \`toolroll\` outside a repository)
|
|
12
|
+
toolroll [path...] the same report for the paths given
|
|
10
13
|
toolroll pulls report what is waiting on a person
|
|
11
14
|
toolroll graph report which work graph is already here
|
|
12
15
|
toolroll repos list connected repositories, and how to adjust
|
|
@@ -63,6 +66,7 @@ and any queue command + --help prints it too
|
|
|
63
66
|
(--editor vscode with --runner: file links
|
|
64
67
|
open in VS Code on the device you browse from)
|
|
65
68
|
toolroll watch / daemon install advanced: run only the builder
|
|
69
|
+
toolroll doctor check that Toolroll's service holds no key a build could read
|
|
66
70
|
toolroll reconcile recover what the last stretch left behind
|
|
67
71
|
|
|
68
72
|
With nothing connected it reports everything it can find below the working
|
package/dist/cli.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* should not have to parse a column layout.
|
|
12
12
|
*/
|
|
13
13
|
import { previewGithubRepo, cloneGithubRepo } from "./onboard.js";
|
|
14
|
+
import { type QuickStartSeams } from "./quick-start.js";
|
|
14
15
|
import { type SessionCliOptions } from "./session-cli.js";
|
|
15
16
|
import { type TeamCliOptions } from "./team-cli.js";
|
|
16
17
|
import { type RemoteExecOptions } from "./remote-exec.js";
|
|
@@ -69,6 +70,8 @@ export type MainOptions = {
|
|
|
69
70
|
};
|
|
70
71
|
update?: UpdateCliDeps;
|
|
71
72
|
release?: ReleaseCliDeps;
|
|
73
|
+
/** Tests: bare `toolroll` in a repository (quick-start.ts) — its repository, agent check, proofs and actions. */
|
|
74
|
+
quickStart?: Partial<QuickStartSeams>;
|
|
72
75
|
};
|
|
73
76
|
/**
|
|
74
77
|
* Strip `-o <file>` / `--output <file>` before dispatch. The flag belongs to
|
package/dist/cli.js
CHANGED
|
@@ -24,6 +24,7 @@ import { applyClaudeCodeInstall, applyInstall, contextBlock, defaultClaudeCodeSk
|
|
|
24
24
|
import { GUIDES, guideNamed } from "./guides.js";
|
|
25
25
|
import { parseGithubRepo, previewGithubRepo, cloneGithubRepo, isLargeRepo } from "./onboard.js";
|
|
26
26
|
import { run as execRun } from "./exec.js";
|
|
27
|
+
import { isBareInvocation, runQuickStart } from "./quick-start.js";
|
|
27
28
|
import { COMMAND_GUIDE, SURFACE_NOTES, SURFACE_SCHEMA_VERSION } from "./surface.js";
|
|
28
29
|
import { runSessionCommand, SESSION_CLI_ACTIONS } from "./session-cli.js";
|
|
29
30
|
import { maybeRunTeamCommand, TEAM_CLI_ACTIONS } from "./team-cli.js";
|
|
@@ -111,7 +112,7 @@ export function parseArgs(argv) {
|
|
|
111
112
|
* exact sets instead of regex-reading source. "" is the no-verb report,
|
|
112
113
|
* which answers as `scan`. */
|
|
113
114
|
export const TOP_LEVEL_COMMANDS = [
|
|
114
|
-
"", "pulls", "graph", "repos", "repos add", "repos remove", "repos add-from-github",
|
|
115
|
+
"", "scan", "pulls", "graph", "repos", "repos add", "repos remove", "repos add-from-github",
|
|
115
116
|
"link", "unlink", "update", "release", "contract", "skills list", "skills get", "skills install", "demo",
|
|
116
117
|
...SESSION_CLI_ACTIONS.map(action => `session ${action}`),
|
|
117
118
|
...TEAM_CLI_ACTIONS,
|
|
@@ -172,7 +173,7 @@ export const OPERATE_COMMANDS = new Set([
|
|
|
172
173
|
"reconcile",
|
|
173
174
|
"run",
|
|
174
175
|
"flows",
|
|
175
|
-
"config", "chat", "proposals", "mode", "chat-approval", "people", "tokens", "keys",
|
|
176
|
+
"config", "chat", "proposals", "mode", "chat-approval", "people", "tokens", "keys", "agents",
|
|
176
177
|
"setup",
|
|
177
178
|
"verify",
|
|
178
179
|
"intake",
|
|
@@ -313,6 +314,9 @@ async function dispatch(argv, write, mainOptions) {
|
|
|
313
314
|
// Command families below load when their command runs, so one command never pays for the others.
|
|
314
315
|
if (argv[0] === 'health')
|
|
315
316
|
return (await import("./health-cli.js")).runHealthCommand(argv.slice(1), write, mainOptions.team ?? mainOptions.remote);
|
|
317
|
+
// This computer's own service, read here and never on a central server.
|
|
318
|
+
if (argv[0] === 'doctor')
|
|
319
|
+
return (await import("./doctor.js")).runDoctorCommand(argv.slice(1), write);
|
|
316
320
|
const central = await maybeRunRemoteCommand(argv, mainOptions.remote ?? mainOptions.team);
|
|
317
321
|
if (typeof central === "number")
|
|
318
322
|
return central;
|
|
@@ -360,6 +364,24 @@ async function dispatch(argv, write, mainOptions) {
|
|
|
360
364
|
if (first !== undefined && OPERATE_COMMANDS.has(first)) {
|
|
361
365
|
return runOperate(first, rest, write, mainOptions.operate ?? {});
|
|
362
366
|
}
|
|
367
|
+
// Cockpit B1: the scan by name, and bare `toolroll` in a repository starting (or attaching to) its console.
|
|
368
|
+
if (first === "scan")
|
|
369
|
+
argv = rest;
|
|
370
|
+
else if (isBareInvocation(argv)) {
|
|
371
|
+
const databaseFile = mainOptions.operate?.databaseFile ?? databasePath(process.env, homedir());
|
|
372
|
+
const started = await runQuickStart(argv, write, {
|
|
373
|
+
databaseFile, cwd: process.cwd(), env: process.env, terminal: process.stdout.isTTY === true,
|
|
374
|
+
build: (await import("./monitoring.js")).packageVersion(),
|
|
375
|
+
findRepo: async (cwd) => { const top = await execRun("git", ["rev-parse", "--show-toplevel"], { cwd, timeoutMs: 10_000 }); return top.code === 0 ? top.stdout.trim() : null; },
|
|
376
|
+
enrolled: async (repo) => { const loaded = await loadRepos(registryBeside(databaseFile)); return !("error" in loaded) && loaded.repos.includes(repo); },
|
|
377
|
+
admit: repo => runReposCommand(["add", repo], write, mainOptions.onboard),
|
|
378
|
+
startUp: () => runOperate("up", [], write, mainOptions.operate ?? {}),
|
|
379
|
+
openBrowser: async (url) => { (await import("./operate.js")).openBrowser(url); },
|
|
380
|
+
...mainOptions.quickStart,
|
|
381
|
+
});
|
|
382
|
+
if (started !== null)
|
|
383
|
+
return started;
|
|
384
|
+
}
|
|
363
385
|
const parsed = parseArgs(argv);
|
|
364
386
|
if ("error" in parsed) {
|
|
365
387
|
write(parsed.error);
|
package/dist/coding-ui.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
|
-
import { html, joinHtml, postForm } from './html.js';
|
|
2
|
+
import { html, joinHtml, passwordlessRendering, postForm, reauth } from './html.js';
|
|
3
3
|
function linkedText(text) {
|
|
4
4
|
const pattern = /\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)|\x60([^\x60\n]+)\x60|\*\*([^*\n]+)\*\*|(https?:\/\/[^\s<>]+)/g;
|
|
5
5
|
let end = 0;
|
|
@@ -42,7 +42,7 @@ function itemHtml(item) {
|
|
|
42
42
|
}
|
|
43
43
|
function requestHtml(request, session) {
|
|
44
44
|
const questions = request.kind === 'questions';
|
|
45
|
-
const body = questions ? request.questions.map((q, index) => html `<label>${q.question}<input type="text" name="question:${q.id}"${q.options.length ? html ` list="coding-options-${request.id}-${index}"` : ''} required autocomplete="off">${q.options.length ? html `<datalist id="coding-options-${request.id}-${index}">${q.options.map(o => html `<option value="${o.label}">${o.description}</option>`)}</datalist>` : ''}</label>`) : html `<pre class="coding-permission-detail">${request.detail}</pre
|
|
45
|
+
const body = questions ? request.questions.map((q, index) => html `<label>${q.question}<input type="text" name="question:${q.id}"${q.options.length ? html ` list="coding-options-${request.id}-${index}"` : ''} required autocomplete="off">${q.options.length ? html `<datalist id="coding-options-${request.id}-${index}">${q.options.map(o => html `<option value="${o.label}">${o.description}</option>`)}</datalist>` : ''}</label>`) : html `<pre class="coding-permission-detail">${request.detail}</pre>${reauth(html `<label>Password to approve this request<input type="password" name="password" autocomplete="current-password" required></label>`)}`;
|
|
46
46
|
return html `<section class="coding-request" data-coding-request="${request.id}"><h3>${request.title || (questions ? 'Your answer' : 'Allow extra access?')}</h3>${postForm(`${sessionUrl(session)}/answer`, html `${body}<div class="coding-actions"><button type="submit" name="decision" value="accept">${questions ? 'Send answer' : 'Approve access'}</button><button type="submit" name="decision" value="decline" formnovalidate class="coding-secondary">${questions ? 'Cancel question' : 'Decline'}</button></div>`, { attrs: { 'data-coding-form': 'answer' }, hidden: { requestId: request.id } })}</section>`;
|
|
47
47
|
}
|
|
48
48
|
function controlsHtml(session) {
|
|
@@ -69,7 +69,7 @@ export function codingWorkspaceHtml(input) {
|
|
|
69
69
|
let main;
|
|
70
70
|
if (!selected) {
|
|
71
71
|
const options = input.projects.map(p => html `<option value="${p.path}"${selectedRepo === p.path ? html ` selected` : ''}>${p.name}</option>`);
|
|
72
|
-
main = html `<header class="coding-header"><h1>Build with Codex</h1></header>${warning}${input.projects.length ? postForm('/code/start', html `<label>Project<select name="repo" required>${options}</select></label><label>What would you like to change?<textarea name="prompt" rows="5" required maxlength="24000" placeholder="Describe the result you want…">${input.draft ?? ''}</textarea></label><details class="coding-options"><summary>Session options</summary><label>Title <span class="coding-meta">Optional</span><input name="title" maxlength="160" autocomplete="off"></label><label>Model <span class="coding-meta">Optional; uses the installed default</span><input name="model" maxlength="120" placeholder="Installed default" autocomplete="off"></label></details><p class="coding-terms">Codex uses this installation’s login and edits a separate copy of this project. It can run commands and read files outside the project. Connected tools (MCP servers) keep their own access to this computer and connected services. Additional command and file access requires your approval.</p
|
|
72
|
+
main = html `<header class="coding-header"><h1>Build with Codex</h1></header>${warning}${input.projects.length ? postForm('/code/start', html `<label>Project<select name="repo" required>${options}</select></label><label>What would you like to change?<textarea name="prompt" rows="5" required maxlength="24000" placeholder="Describe the result you want…">${input.draft ?? ''}</textarea></label><details class="coding-options"><summary>Session options</summary><label>Title <span class="coding-meta">Optional</span><input name="title" maxlength="160" autocomplete="off"></label><label>Model <span class="coding-meta">Optional; uses the installed default</span><input name="model" maxlength="120" placeholder="Installed default" autocomplete="off"></label></details><p class="coding-terms">Codex uses this installation’s login and edits a separate copy of this project. It can run commands and read files outside the project. Connected tools (MCP servers) keep their own access to this computer and connected services. Additional command and file access requires your approval.</p>${reauth(html `<label>Password to start<input type="password" name="password" autocomplete="current-password" required></label>`)}<button type="submit"${input.available ? '' : html ` disabled`}>Start coding</button>`, { attrs: { 'data-coding-form': 'start', class: 'coding-start' }, hidden: { requestId: randomUUID() } }) : html `<div class="coding-empty"><p>Add a project to start coding.</p><a class="coding-link-button" href="/projects">Open projects</a></div>`}`;
|
|
73
73
|
}
|
|
74
74
|
else {
|
|
75
75
|
const s = selected.session;
|
|
@@ -94,6 +94,8 @@ export const CODING_CSS = `
|
|
|
94
94
|
@media(prefers-reduced-motion:reduce){.coding-workspace *{scroll-behavior:auto!important}}
|
|
95
95
|
`;
|
|
96
96
|
export function codingWorkspaceScript() {
|
|
97
|
+
// Passwordless: the request form the script builds leaves its password field out (the server proves the session).
|
|
98
|
+
const approvePassword = passwordlessRendering() ? "" : '<label>Password to approve this request<input type="password" name="password" autocomplete="current-password" required></label>';
|
|
97
99
|
return String.raw `(() => {
|
|
98
100
|
const root = document.querySelector('.coding-workspace');
|
|
99
101
|
if (!root || root.dataset.codingMounted) return;
|
|
@@ -196,7 +198,7 @@ export function codingWorkspaceScript() {
|
|
|
196
198
|
};
|
|
197
199
|
const requestHtml = request => {
|
|
198
200
|
const questions = request.kind === 'questions';
|
|
199
|
-
const body = questions ? request.questions.map((q,index) => '<label>' + e(q.question) + '<input type="text" name="question:' + e(q.id) + '"' + (q.options.length ? ' list="coding-options-' + e(request.id) + '-' + index + '"' : '') + ' required autocomplete="off">' + (q.options.length ? '<datalist id="coding-options-' + e(request.id) + '-' + index + '">' + q.options.map(o => '<option value="' + e(o.label) + '">' + e(o.description) + '</option>').join('') + '</datalist>' : '') + '</label>').join('') : '<pre class="coding-permission-detail">' + e(request.detail) + '</pre
|
|
201
|
+
const body = questions ? request.questions.map((q,index) => '<label>' + e(q.question) + '<input type="text" name="question:' + e(q.id) + '"' + (q.options.length ? ' list="coding-options-' + e(request.id) + '-' + index + '"' : '') + ' required autocomplete="off">' + (q.options.length ? '<datalist id="coding-options-' + e(request.id) + '-' + index + '">' + q.options.map(o => '<option value="' + e(o.label) + '">' + e(o.description) + '</option>').join('') + '</datalist>' : '') + '</label>').join('') : '<pre class="coding-permission-detail">' + e(request.detail) + '</pre>' + ${JSON.stringify(approvePassword)};
|
|
200
202
|
return '<section class="coding-request" data-coding-request="' + e(request.id) + '"><h3>' + e(request.title || (questions ? 'Your answer' : 'Allow extra access?')) + '</h3><form method="post" action="' + base + '/answer" data-coding-form="answer">' + h({csrf,requestId:request.id}) + body + '<div class="coding-actions"><button type="submit" name="decision" value="accept">' + (questions ? 'Send answer' : 'Approve access') + '</button><button type="submit" name="decision" value="decline" formnovalidate class="coding-secondary">' + (questions ? 'Cancel question' : 'Decline') + '</button></div></form></section>';
|
|
201
203
|
};
|
|
202
204
|
const render = snapshot => {
|