toolroll 0.9.85 → 0.9.87
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 +83 -0
- package/dist/agent-fence.d.ts +6 -2
- package/dist/agent-fence.js +3 -2
- package/dist/agents-board.d.ts +12 -0
- package/dist/agents-board.js +26 -0
- package/dist/approval-rules-ui.d.ts +4 -1
- package/dist/approval-rules-ui.js +31 -15
- package/dist/assignment-ui.js +1 -1
- package/dist/assignment.js +4 -3
- package/dist/backup-ui.d.ts +5 -4
- package/dist/backup-ui.js +65 -32
- package/dist/browser/terminal.css +1 -1
- package/dist/browser/terminal.js +57 -54
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +35 -68
- package/dist/browser-crew.js +1 -1
- package/dist/browser-workspace.d.ts +363 -177
- package/dist/browser-workspace.js +10 -9
- package/dist/chat-actions.d.ts +11 -1
- package/dist/chat-actions.js +53 -1
- package/dist/chat-channel.js +1 -0
- package/dist/chat-controls.d.ts +1 -1
- package/dist/chat-controls.js +1 -1
- package/dist/chat-quiet.js +3 -2
- package/dist/chat-settings-page.d.ts +29 -9
- package/dist/chat-settings-page.js +59 -28
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +67 -58
- package/dist/claim.d.ts +3 -0
- package/dist/claim.js +4 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +3 -0
- package/dist/contracts/cli.d.ts +43 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +4 -1
- package/dist/contracts/console-api.js +4 -1
- package/dist/contracts/flow-propose.d.ts +4 -1
- package/dist/contracts/flow-propose.js +1 -1
- package/dist/contracts/flow.d.ts +41 -0
- package/dist/contracts/flow.js +4 -2
- package/dist/contracts/lead-tools.d.ts +21 -0
- package/dist/contracts/lead-tools.js +2 -0
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/trigger-payloads.d.ts +20 -0
- package/dist/contracts/trigger-payloads.js +4 -0
- package/dist/coordinator.js +1 -0
- package/dist/credentials-ui.d.ts +5 -3
- package/dist/credentials-ui.js +58 -31
- package/dist/deploy-gate.js +4 -0
- package/dist/design/kit.css +685 -0
- package/dist/design/kit.d.ts +208 -0
- package/dist/design/kit.js +177 -0
- package/dist/design/tokens.css +19 -1
- package/dist/discord-settings.js +8 -4
- package/dist/dispatch.d.ts +3 -0
- package/dist/dispatch.js +4 -1
- package/dist/export-ui.d.ts +6 -4
- package/dist/export-ui.js +18 -8
- package/dist/flow-browser.d.ts +103 -0
- package/dist/flow-browser.js +204 -0
- package/dist/flow-changed.d.ts +14 -0
- package/dist/flow-changed.js +53 -0
- package/dist/flow-code.d.ts +5 -1
- package/dist/flow-code.js +4 -1
- package/dist/flow-engine.js +16 -0
- package/dist/flow-gallery-ui.d.ts +41 -4
- package/dist/flow-gallery-ui.js +95 -63
- package/dist/flow-live.js +9 -1
- package/dist/flow-pull-request.js +15 -7
- package/dist/flow-share.js +3 -1
- package/dist/flow-starters-ui.d.ts +3 -9
- package/dist/flow-starters-ui.js +12 -23
- package/dist/flow-steps.d.ts +9 -1
- package/dist/flow-steps.js +55 -5
- package/dist/flow-triggers.js +55 -0
- package/dist/flow-unattended-words.d.ts +20 -0
- package/dist/flow-unattended-words.js +24 -0
- package/dist/flow-unattended.d.ts +9 -5
- package/dist/flow-unattended.js +45 -23
- package/dist/flows-cli.d.ts +19 -0
- package/dist/flows-cli.js +54 -5
- package/dist/flows-ui.d.ts +12 -6
- package/dist/flows-ui.js +152 -32
- package/dist/flows.js +5 -2
- package/dist/integrations-ui.d.ts +6 -4
- package/dist/integrations-ui.js +34 -40
- package/dist/integrations.d.ts +2 -2
- package/dist/integrations.js +4 -4
- package/dist/kits-ui.d.ts +8 -10
- package/dist/kits-ui.js +29 -33
- package/dist/kits.js +1 -1
- package/dist/knowledge-ui.js +76 -19
- package/dist/lead-tools.js +35 -6
- package/dist/lead-voice.js +16 -5
- package/dist/ledger-view.d.ts +2 -0
- package/dist/ledger-view.js +41 -38
- package/dist/models-ui.d.ts +8 -3
- package/dist/models-ui.js +82 -59
- package/dist/monitoring-ui.d.ts +4 -2
- package/dist/monitoring-ui.js +44 -13
- package/dist/needs-you.d.ts +1 -1
- package/dist/needs-you.js +8 -0
- 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 +249 -6
- package/dist/passwordless.d.ts +1 -1
- package/dist/passwordless.js +1 -1
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +25 -23
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +27 -21
- package/dist/policy.d.ts +3 -0
- package/dist/policy.js +14 -1
- package/dist/project-delete-ui.d.ts +4 -2
- package/dist/project-delete-ui.js +135 -126
- package/dist/project-delete.js +7 -3
- package/dist/project-knowledge.d.ts +10 -10
- package/dist/publish.d.ts +11 -0
- package/dist/publish.js +16 -3
- package/dist/pull-request-flow.js +35 -4
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +39 -28
- package/dist/recipe-ui.d.ts +4 -2
- package/dist/recipe-ui.js +14 -4
- package/dist/release-adapters.d.ts +3 -0
- package/dist/release-adapters.js +22 -2
- package/dist/release-cli.js +81 -9
- package/dist/release.d.ts +21 -0
- package/dist/release.js +40 -4
- package/dist/releases.js +1 -1
- package/dist/request-budget-ui.d.ts +4 -2
- package/dist/request-budget-ui.js +30 -19
- package/dist/result-follow-ups.js +4 -0
- package/dist/retention-ui.d.ts +4 -3
- package/dist/retention-ui.js +15 -12
- package/dist/scope.d.ts +8 -0
- package/dist/scope.js +13 -0
- package/dist/serve.d.ts +0 -1
- package/dist/serve.js +51 -7
- package/dist/server/agents-computers.d.ts +9 -0
- package/dist/server/agents-computers.js +67 -0
- package/dist/server/chat.js +20 -18
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +19 -9
- package/dist/server/console.css +31 -2
- package/dist/server/flows.js +79 -36
- package/dist/server/home.d.ts +35 -0
- package/dist/server/home.js +104 -0
- package/dist/server/http.d.ts +3 -0
- package/dist/server/http.js +5 -0
- package/dist/server/kit-standalone.d.ts +22 -0
- package/dist/server/kit-standalone.js +69 -0
- package/dist/server/kit.d.ts +405 -0
- package/dist/server/kit.js +361 -0
- package/dist/server/pages.d.ts +2 -1
- package/dist/server/pages.js +213 -174
- package/dist/server/people-tokens.js +26 -85
- package/dist/server/render-history.d.ts +51 -0
- package/dist/server/render-history.js +228 -0
- package/dist/server/render-new-task.d.ts +29 -0
- package/dist/server/render-new-task.js +96 -0
- package/dist/server/render-next.d.ts +63 -0
- package/dist/server/render-next.js +109 -0
- package/dist/server/render-pages.d.ts +35 -190
- package/dist/server/render-pages.js +154 -563
- package/dist/server/render-people.d.ts +24 -3
- package/dist/server/render-people.js +93 -72
- package/dist/server/render-projects.d.ts +103 -0
- package/dist/server/render-projects.js +247 -0
- package/dist/server/render-results.d.ts +5 -24
- package/dist/server/render-results.js +48 -185
- package/dist/server/render-settings.d.ts +5 -74
- package/dist/server/render-settings.js +102 -493
- package/dist/server/render-tasks.d.ts +43 -5
- package/dist/server/render-tasks.js +131 -91
- package/dist/server/route-table.js +31 -5
- package/dist/server/settings.js +276 -255
- package/dist/server/task-views.d.ts +8 -0
- package/dist/server/task-views.js +90 -2
- package/dist/server/tasks.d.ts +18 -0
- package/dist/server/tasks.js +148 -41
- package/dist/server/terminals.js +1 -1
- package/dist/server/welcome.d.ts +22 -0
- package/dist/server/welcome.js +69 -0
- package/dist/settings-groups-ui.d.ts +279 -0
- package/dist/settings-groups-ui.js +615 -0
- package/dist/settings-kit.d.ts +42 -6
- package/dist/settings-kit.js +69 -7
- package/dist/settings-proofs.js +1 -0
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills-ui.js +59 -7
- package/dist/slack-settings.js +12 -5
- package/dist/spend-ui.d.ts +45 -26
- package/dist/spend-ui.js +124 -56
- package/dist/sso-ui.d.ts +5 -2
- package/dist/sso-ui.js +63 -31
- package/dist/storage-ui.d.ts +3 -2
- package/dist/storage-ui.js +56 -50
- package/dist/store.d.ts +182 -5
- package/dist/store.js +512 -38
- package/dist/surface.js +23 -3
- package/dist/task-archive.d.ts +10 -0
- package/dist/task-archive.js +16 -0
- package/dist/task-control.d.ts +1 -1
- package/dist/teams-settings.d.ts +2 -1
- package/dist/teams-settings.js +24 -41
- package/dist/telegram-settings.d.ts +0 -4
- package/dist/telegram-settings.js +52 -25
- package/dist/tidy.d.ts +130 -0
- package/dist/tidy.js +503 -0
- package/dist/toolroll-update-ui.d.ts +8 -4
- package/dist/toolroll-update-ui.js +55 -45
- package/dist/tools-ui.d.ts +5 -3
- package/dist/tools-ui.js +107 -42
- package/dist/work-index.d.ts +24 -0
- package/dist/work-index.js +56 -30
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +8 -4
- package/package.json +1 -1
|
@@ -96,16 +96,17 @@ export function browserProjectsOf(projects) {
|
|
|
96
96
|
}
|
|
97
97
|
export function browserNavigationOf(path, project = null, needsYou = 0, tasksProject = project, needsYouLabel) {
|
|
98
98
|
const pathname = path.split('?')[0].split('#')[0];
|
|
99
|
-
const
|
|
99
|
+
const under = (...roots) => roots.some(root => pathname === root || pathname.startsWith(`${root}/`));
|
|
100
|
+
// The rail (UI revamp, wave B): Home carries the one Needs-you count; the lead chat and an agent's question live
|
|
101
|
+
// under Home, a task's pages under Tasks, every computer and terminal under Agents, every setting under Settings.
|
|
100
102
|
return [
|
|
101
|
-
{ label: '
|
|
102
|
-
{ label: 'Tasks', href: `/work${tasksProject === null ? '' : `?project=${encodeURIComponent(tasksProject)}`}`,
|
|
103
|
-
active: pathname === '/work' || pathname === '/tasks' || pathname.startsWith('/t/') || pathname.startsWith('/r/'),
|
|
103
|
+
{ label: 'Home', href: '/', active: pathname === '/' || pathname === '/chat' || under('/d', '/activity', '/morning'),
|
|
104
104
|
...(needsYou > 0 ? { count: needsYou, ...(needsYouLabel === undefined ? {} : { countLabel: needsYouLabel }) } : {}) },
|
|
105
|
-
{ label: '
|
|
106
|
-
|
|
107
|
-
{ label: '
|
|
108
|
-
{ label: '
|
|
109
|
-
{ label: '
|
|
105
|
+
{ label: 'Tasks', href: `/work${tasksProject === null ? '' : `?project=${encodeURIComponent(tasksProject)}`}`,
|
|
106
|
+
active: under('/work', '/tasks', '/t', '/r', '/done', '/board', '/queue', '/inbox', '/review') },
|
|
107
|
+
{ label: 'Flows', href: '/flows', active: under('/flows', '/recipes', '/kits', '/routines') },
|
|
108
|
+
{ label: 'Agents', href: '/agents', active: under('/agents', '/peek', '/term', '/fleet', '/system', '/runs') },
|
|
109
|
+
{ label: 'Projects', href: '/projects', active: under('/projects') },
|
|
110
|
+
{ label: 'Settings', href: '/settings', active: under('/settings', '/spend', '/mode', '/people', '/ledger', '/menu') },
|
|
110
111
|
];
|
|
111
112
|
}
|
package/dist/chat-actions.d.ts
CHANGED
|
@@ -153,6 +153,16 @@ export declare const CHAT_ACTIONS: {
|
|
|
153
153
|
readonly protected: false;
|
|
154
154
|
readonly password: false;
|
|
155
155
|
};
|
|
156
|
+
readonly flow_browser: {
|
|
157
|
+
readonly label: "Allow browser";
|
|
158
|
+
readonly protected: true;
|
|
159
|
+
readonly password: true;
|
|
160
|
+
};
|
|
161
|
+
readonly flow_browser_off: {
|
|
162
|
+
readonly label: "Turn off browser";
|
|
163
|
+
readonly protected: false;
|
|
164
|
+
readonly password: false;
|
|
165
|
+
};
|
|
156
166
|
readonly subagent_create: {
|
|
157
167
|
readonly label: "Add subagent";
|
|
158
168
|
readonly protected: false;
|
|
@@ -266,7 +276,7 @@ export declare function sharedActionReviewPath(id: number): string;
|
|
|
266
276
|
export declare function mintSharedActionReview(store: Store, who: VerifiedApprover, id: number, root: string, now: Date): {
|
|
267
277
|
payload: {
|
|
268
278
|
version: 1;
|
|
269
|
-
operation: "decision_record" | "decision_retire" | "flow_card_add" | "flow_card_approve" | "flow_card_assign" | "flow_card_cancel" | "flow_card_choose" | "flow_card_comment" | "flow_card_move" | "flow_card_send_back" | "flow_card_watch" | "flow_create" | "flow_edit" | "flow_script_save" | "flow_starter" | "flow_trigger_add" | "flow_trigger_pause" | "flow_trigger_remove" | "flow_trigger_resume" | "kit_setup" | "knowledge_instructions" | "knowledge_remove" | "knowledge_restore" | "knowledge_save" | "lead_about_you" | "result_accept" | "scope_approve" | "skill_disable" | "skill_enable" | "skill_import" | "skill_restore" | "skill_test" | "subagent_answer" | "subagent_ask" | "subagent_create" | "subagent_memory" | "subagent_note" | "subagent_routine" | "subagent_soul" | "subagent_state" | "subagent_tools" | "subagent_undo" | "sweep_cancel_rest" | "sweep_file" | "task_cancel" | "task_resume" | "tool_add" | "tool_remove";
|
|
279
|
+
operation: "decision_record" | "decision_retire" | "flow_browser" | "flow_browser_off" | "flow_card_add" | "flow_card_approve" | "flow_card_assign" | "flow_card_cancel" | "flow_card_choose" | "flow_card_comment" | "flow_card_move" | "flow_card_send_back" | "flow_card_watch" | "flow_create" | "flow_edit" | "flow_script_save" | "flow_starter" | "flow_trigger_add" | "flow_trigger_pause" | "flow_trigger_remove" | "flow_trigger_resume" | "kit_setup" | "knowledge_instructions" | "knowledge_remove" | "knowledge_restore" | "knowledge_save" | "lead_about_you" | "result_accept" | "scope_approve" | "skill_disable" | "skill_enable" | "skill_import" | "skill_restore" | "skill_test" | "subagent_answer" | "subagent_ask" | "subagent_create" | "subagent_memory" | "subagent_note" | "subagent_routine" | "subagent_soul" | "subagent_state" | "subagent_tools" | "subagent_undo" | "sweep_cancel_rest" | "sweep_file" | "task_cancel" | "task_resume" | "tool_add" | "tool_remove";
|
|
270
280
|
request: {
|
|
271
281
|
[x: string]: unknown;
|
|
272
282
|
};
|
package/dist/chat-actions.js
CHANGED
|
@@ -25,6 +25,7 @@ import { saveScript, scriptDigest, validateScript } from "./flow-scripts.js";
|
|
|
25
25
|
import { addCardToFlow, advanceFlows, cancelFlowCard, crossProjectProblem, decideFlowCard, flowCardHref, flowCardText, flowDefinitionOf, moveCardInFlow } from "./flow-engine.js";
|
|
26
26
|
import { chooseFlowCard, flowPersonOf } from "./flow-send.js";
|
|
27
27
|
import { assignFlowCard, commentOnFlowCard, flowPeople, mentionsIn, watchFlowCard } from "./flow-people.js";
|
|
28
|
+
import { browserViewOf, liveBrowserGrant, revokeFlowBrowser, signFlowBrowser } from "./flow-browser.js";
|
|
28
29
|
import { addFlowTriggerTo, describeTrigger, readLinearKey, removeFlowTrigger, takesDeliveries, triggerConfigOf, validateTriggerConfig } from "./flow-triggers.js";
|
|
29
30
|
import { describeSchedule, parseSchedule } from "./flow-schedule.js";
|
|
30
31
|
import { dirname } from "node:path";
|
|
@@ -109,6 +110,10 @@ export const CHAT_ACTIONS = {
|
|
|
109
110
|
flow_trigger_pause: { label: "Pause trigger", protected: false, password: false },
|
|
110
111
|
flow_trigger_resume: { label: "Turn trigger on", protected: false, password: false },
|
|
111
112
|
flow_trigger_remove: { label: "Remove trigger", protected: false, password: false },
|
|
113
|
+
// Browser use widens a flow's sandbox (flow-browser.ts): the lead may only propose it; a person allows it with their
|
|
114
|
+
// own password on the secure screen, never on a chat card or a paired phone's tap. Turning it off lowers authority.
|
|
115
|
+
flow_browser: { label: "Allow browser", protected: true, password: true },
|
|
116
|
+
flow_browser_off: { label: "Turn off browser", protected: false, password: false },
|
|
112
117
|
subagent_create: { label: "Add subagent", protected: false, password: false },
|
|
113
118
|
subagent_soul: { label: "Update soul file", protected: false, password: false },
|
|
114
119
|
subagent_state: { label: "Change subagent", protected: false, password: false },
|
|
@@ -710,6 +715,32 @@ export function prepareSharedAction(store, who, operation, input, root, now = ne
|
|
|
710
715
|
terms.push("Only what happens from now on counts; nothing already there is added.");
|
|
711
716
|
terms.push("Work a card starts still waits for your usual approvals.");
|
|
712
717
|
}
|
|
718
|
+
else if (operation === "flow_browser" || operation === "flow_browser_off") {
|
|
719
|
+
const { flow } = flowTarget;
|
|
720
|
+
const view = browserViewOf(store, flow);
|
|
721
|
+
if (view === null)
|
|
722
|
+
throw Error(`No zone in ${flow.name} says it starts a browser. Mark its script zone first (edit the flow), then propose this.`);
|
|
723
|
+
// As on the flow page (route table: instance access), a project-limited account can't allow browser use.
|
|
724
|
+
if (operation === "flow_browser" && store.accountOf(who.name)?.projects !== null)
|
|
725
|
+
throw Error("Allowing browser use needs an account with access to every project.");
|
|
726
|
+
if (operation === "flow_browser_off") {
|
|
727
|
+
if (liveBrowserGrant(store, flow.id) === null)
|
|
728
|
+
throw Error(`Browser use isn't allowed for ${flow.name}.`);
|
|
729
|
+
state = { flow: flow.id, grant: liveBrowserGrant(store, flow.id).id };
|
|
730
|
+
title = `Turn off browser use in ${flow.name}`;
|
|
731
|
+
terms.push(`${flow.name}'s browser zones stop and wait until a person allows browser use again.`);
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
if (view.status === "allowed")
|
|
735
|
+
throw Error(`${view.words}.`);
|
|
736
|
+
if (view.digest === null)
|
|
737
|
+
throw Error(view.problem ?? "This flow's browser zones can't be approved as they are.");
|
|
738
|
+
// The approval the person reads and the one their password signs: the digest is re-derived when they confirm.
|
|
739
|
+
state = { flow: flow.id, revision: flow.revision, digest: view.digest };
|
|
740
|
+
title = `Allow browser use in ${flow.name}`;
|
|
741
|
+
terms.push(`Flow: ${flow.name} · project ${project}`, ...view.zones.map(zone => `${zone.title} runs ${zone.script} v${zone.version}\nScript digest ${zone.digest}`), "Each run of these zones starts a headless Chromium.", ...view.terms, `Approval digest ${view.digest}`);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
713
744
|
else if (operation === "flow_trigger_pause" || operation === "flow_trigger_resume" || operation === "flow_trigger_remove") {
|
|
714
745
|
const { flow, trigger } = flowTarget;
|
|
715
746
|
const config = trigger === null ? null : triggerConfigOf(trigger);
|
|
@@ -1099,6 +1130,7 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1099
1130
|
}
|
|
1100
1131
|
// The review nonce was minted against the saved proposal's stamp: it is checked against that, whoever reviews.
|
|
1101
1132
|
const savedStamp = payload.stamp;
|
|
1133
|
+
let personProved = null;
|
|
1102
1134
|
payload = live;
|
|
1103
1135
|
const config = CHAT_ACTIONS[live.operation], req = live.request;
|
|
1104
1136
|
const challenged = options.via !== "web" &&
|
|
@@ -1113,10 +1145,14 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1113
1145
|
// A scope approval proves its person inside approve() (one transaction with the seal, under the same freshness
|
|
1114
1146
|
// rule as the task page); here an empty field is only looked at, never spent (an explicit re-check is one-use).
|
|
1115
1147
|
const emptyApproval = payload.operation === "scope_approve" && options.review.password === "";
|
|
1148
|
+
const proved = config.password && !emptyApproval ? authenticateApprover(store, who.name, options.review.password, payload.repo) : null;
|
|
1149
|
+
// How the person was proved, for an approval that records it (browser use: flow-browser.ts refuses any but a person's).
|
|
1150
|
+
if (proved?.ok === true)
|
|
1151
|
+
personProved = proved.method;
|
|
1116
1152
|
if (config.password &&
|
|
1117
1153
|
(emptyApproval
|
|
1118
1154
|
? !passwordlessSessionFor(store, who.name) && !ssoNowAvailable(who.name) && withFreshness("approve-standard", () => freshnessFor(who.name)) === null
|
|
1119
|
-
:
|
|
1155
|
+
: proved?.ok !== true))
|
|
1120
1156
|
return refuse("needs-confirm", "Confirm with your password in the secure screen.");
|
|
1121
1157
|
}
|
|
1122
1158
|
try {
|
|
@@ -1156,6 +1192,22 @@ export function executeSharedAction(store, who, id, payload, now, options) {
|
|
|
1156
1192
|
? {}
|
|
1157
1193
|
: { restore: Number(req["restore"]) }),
|
|
1158
1194
|
}, now);
|
|
1195
|
+
else if (payload.operation === "flow_browser") {
|
|
1196
|
+
// Only through the secure screen with the person's own password (protected + password above); signFlowBrowser
|
|
1197
|
+
// re-reads the flow, its zones and scripts in this transaction and refuses unless they are what the card showed.
|
|
1198
|
+
const flow = Number(payload.state["flow"]);
|
|
1199
|
+
// signFlowBrowser re-reads the account's generation and reach in its transaction (it may have narrowed since).
|
|
1200
|
+
const signed = signFlowBrowser(store, { flow, signer: actor, proof: personProved, sawDigest: String(payload.state["digest"] ?? ""), console: { generation: who.generation } }, now);
|
|
1201
|
+
if (!signed.ok)
|
|
1202
|
+
throw Error(signed.message);
|
|
1203
|
+
return { ok: true, taskId: null, said: "Browser allowed. Its browser zones can start Chromium now.", href: `/flows/${flow}` };
|
|
1204
|
+
}
|
|
1205
|
+
else if (payload.operation === "flow_browser_off") {
|
|
1206
|
+
const flow = Number(payload.state["flow"]);
|
|
1207
|
+
if (!revokeFlowBrowser(store, flow, actor, now))
|
|
1208
|
+
throw Error("Browser use was already off.");
|
|
1209
|
+
return { ok: true, taskId: null, said: "Browser turned off. Its browser zones wait until someone allows it again.", href: `/flows/${flow}` };
|
|
1210
|
+
}
|
|
1159
1211
|
else if (payload.operation.startsWith("flow_")) {
|
|
1160
1212
|
const done = runFlowAction(store, payload, actor, who.repos, options.root, now);
|
|
1161
1213
|
return { ok: true, taskId: null, said: done.said, href: done.href };
|
package/dist/chat-channel.js
CHANGED
|
@@ -578,6 +578,7 @@ export const CHAT_ACTION_PARITY = {
|
|
|
578
578
|
ask_owner: { support: "direct", how: "One question with 2-4 options and Something else, drawn as buttons under the reply; the tapped option is sent as the person's next message.", gap: null },
|
|
579
579
|
search_project_memory: { support: "direct", how: "Read during a turn: one search over decisions, references, lessons and the conversations the person may read.", gap: null },
|
|
580
580
|
list_tasks: { support: "direct", how: "Read during a turn.", gap: null },
|
|
581
|
+
tidy_preview: { support: "direct", how: "Read during a turn: what Put away old tasks would put away now and why. Only a person puts tasks away, on the console or with the CLI command it names.", gap: null },
|
|
581
582
|
get_task: {
|
|
582
583
|
support: "direct",
|
|
583
584
|
how: "Read during a turn; a reply to a result message pins the exact execution.",
|
package/dist/chat-controls.d.ts
CHANGED
|
@@ -76,7 +76,7 @@ export declare const CHAT_CONTROLS: {
|
|
|
76
76
|
};
|
|
77
77
|
readonly providers: {
|
|
78
78
|
readonly label: "Connect an agent";
|
|
79
|
-
readonly href: "/settings#providers";
|
|
79
|
+
readonly href: "/settings/agents#providers";
|
|
80
80
|
};
|
|
81
81
|
readonly skills: {
|
|
82
82
|
readonly label: "Manage skills";
|
package/dist/chat-controls.js
CHANGED
|
@@ -21,7 +21,7 @@ export const CHAT_CONTROLS = {
|
|
|
21
21
|
integrations: { label: "Set up integrations", href: "/settings/integrations" },
|
|
22
22
|
settings: { label: "Open settings", href: "/settings" },
|
|
23
23
|
permissions: { label: "Review permissions", href: "/settings" },
|
|
24
|
-
providers: { label: "Connect an agent", href: "/settings#providers" },
|
|
24
|
+
providers: { label: "Connect an agent", href: "/settings/agents#providers" },
|
|
25
25
|
skills: { label: "Manage skills", href: "/settings/skills" },
|
|
26
26
|
tools: { label: "Manage tools", href: "/settings/tools" },
|
|
27
27
|
knowledge: { label: "Edit project knowledge", href: "/settings/knowledge" },
|
package/dist/chat-quiet.js
CHANGED
|
@@ -10,7 +10,7 @@ import { telegramProgressCard } from "./telegram-progress.js";
|
|
|
10
10
|
import { assignmentStatusFacts, headlineEmoji, taskStatusOf } from "./task-status.js";
|
|
11
11
|
import { asksToFinish, batchLine, chatText, chatTitle, factLinkLabel, mentions, READY_ACTIONS } from "./chat-voice.js";
|
|
12
12
|
import { phoneText, projectLabel } from "./telegram-status.js";
|
|
13
|
-
import { isLifecycleNotification } from "./store.js";
|
|
13
|
+
import { isLifecycleNotification, taskArchived } from "./store.js";
|
|
14
14
|
import { leadSubjectOf } from "./lead-voice.js";
|
|
15
15
|
import { decideFallbackLink, decideTargetOf } from "./chat-decide.js";
|
|
16
16
|
import { YOUIYOUX_CARD_KIND } from "./contracts/youiyoux-request.js";
|
|
@@ -199,7 +199,8 @@ export function eveningDigestText(store, account, now, root) {
|
|
|
199
199
|
const visible = (row) => row.project !== null && store.accountCanAccess(account, row.project);
|
|
200
200
|
const title = (taskId) => chatTitle(store, taskId);
|
|
201
201
|
const finished = new Map(), failed = new Map(), waits = new Map();
|
|
202
|
-
|
|
202
|
+
// A put-away task (v145) is out of the digest too: nothing about it waits, finished or failed for anyone now.
|
|
203
|
+
const facts = store.taskFactsSince(recent).filter(visible).filter(row => row.taskId === null || !taskArchived(store, row.taskId));
|
|
203
204
|
for (const row of facts) {
|
|
204
205
|
if (row.createdAt < since || row.taskId === null)
|
|
205
206
|
continue;
|
|
@@ -7,17 +7,37 @@ export type ChatSettingsOptions = {
|
|
|
7
7
|
now?: Date;
|
|
8
8
|
who?: string;
|
|
9
9
|
};
|
|
10
|
-
/** What differs between the Slack and
|
|
10
|
+
/** What differs between the Slack, Discord and Teams settings pages; everything else is one page. */
|
|
11
11
|
export type ChatSettingsPage = {
|
|
12
|
-
provider: "slack" | "discord";
|
|
12
|
+
provider: "slack" | "discord" | "teams";
|
|
13
13
|
label: string;
|
|
14
14
|
credentials: ChatIdentity | null;
|
|
15
|
-
/**
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
|
|
19
|
-
/** The
|
|
20
|
-
|
|
15
|
+
/** One sentence on what connecting gives, shown before it is connected. */
|
|
16
|
+
intro: string;
|
|
17
|
+
/** How to create the app, as list items, shown before it is connected. */
|
|
18
|
+
steps: Html;
|
|
19
|
+
/** The connect form's own fields (the password is the kit's). */
|
|
20
|
+
fields: Html;
|
|
21
|
+
/** One line under the connect fields. */
|
|
22
|
+
fieldsNote: string;
|
|
23
|
+
/** Where the pairing message is sent ("in a direct message to your Toolroll Slack app"). */
|
|
24
|
+
pairWhere: string;
|
|
25
|
+
/** What to try once paired. */
|
|
26
|
+
tryLine: Html;
|
|
27
|
+
/** Lines shown with the connection (the Teams messaging endpoint). */
|
|
28
|
+
connection?: Html;
|
|
29
|
+
/** The status words while connected and while waiting. */
|
|
30
|
+
liveWords?: readonly [on: string, off: string];
|
|
31
|
+
/** The "Access and setup" paragraphs, if any. */
|
|
32
|
+
access?: Html;
|
|
21
33
|
};
|
|
22
|
-
/**
|
|
34
|
+
/** A secret the connect form posts (an app or bot token, a client secret): masked, never given a value, kept out of
|
|
35
|
+
* password managers. */
|
|
36
|
+
export declare function chatSecretRow(options: {
|
|
37
|
+
id: string;
|
|
38
|
+
name: string;
|
|
39
|
+
label: string;
|
|
40
|
+
placeholder?: string;
|
|
41
|
+
}): Html;
|
|
42
|
+
/** Settings → Slack, Discord or Teams: connect the app once, then each teammate pairs their own account. */
|
|
23
43
|
export declare function chatSettingsHtml(store: Store, dir: string, options: ChatSettingsOptions, page: ChatSettingsPage): Html;
|
|
@@ -1,41 +1,72 @@
|
|
|
1
|
+
import { settingsSubPage } from "./settings-groups-ui.js";
|
|
1
2
|
import { loadPrimary } from "./webhooks.js";
|
|
2
|
-
import { html,
|
|
3
|
+
import { html, joinHtml } from "./html.js";
|
|
3
4
|
import { ChatState } from "./chat-delivery-state.js";
|
|
4
|
-
|
|
5
|
+
import { acts, btn, chip, codeLine, disclose, formRow, infoLine, input, note, notice, settingsSection } from "./server/kit.js";
|
|
6
|
+
import { kitSectionForm, SECRET_OPT_OUTS } from "./settings-kit.js";
|
|
7
|
+
/** A secret the connect form posts (an app or bot token, a client secret): masked, never given a value, kept out of
|
|
8
|
+
* password managers. */
|
|
9
|
+
export function chatSecretRow(options) {
|
|
10
|
+
return formRow(options.label, null, input({ id: options.id, name: options.name, type: "password",
|
|
11
|
+
attrs: { autocomplete: "off", spellcheck: "false", "data-so-secret": true, ...SECRET_OPT_OUTS, required: true, placeholder: options.placeholder, "aria-label": options.label } }));
|
|
12
|
+
}
|
|
13
|
+
/** Settings → Slack, Discord or Teams: connect the app once, then each teammate pairs their own account. */
|
|
5
14
|
export function chatSettingsHtml(store, dir, options, page) {
|
|
6
15
|
const { provider, label, credentials } = page, state = new ChatState(store, provider), bindings = credentials ? state.bindings(credentials.installation).filter(one => state.live(one)) : [], binding = options.who === undefined ? null : bindings.find(one => one.approver === options.who) ?? null, others = bindings.length - (binding === null ? 0 : 1);
|
|
7
16
|
const runtime = credentials ? state.runtime(credentials.installation) : null;
|
|
8
|
-
const now = options.now ?? new Date(), live = runtime &&
|
|
17
|
+
const now = options.now ?? new Date(), live = runtime !== null &&
|
|
9
18
|
runtime.leaseUntil !== null &&
|
|
10
19
|
runtime.leaseUntil > now.toISOString() &&
|
|
11
20
|
runtime.connected;
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
const
|
|
21
|
+
const route = (action) => `/settings/${provider}/${action}`;
|
|
22
|
+
const problem = options.problem ? notice(options.problem) : "";
|
|
23
|
+
const access = page.access === undefined ? "" : disclose("Access and setup", page.access);
|
|
24
|
+
const sections = [];
|
|
15
25
|
if (!credentials) {
|
|
16
|
-
|
|
26
|
+
sections.push(settingsSection({ id: "setup", title: `1. Create the ${label} app`, body: page.steps }), settingsSection({ id: "connect", title: `2. Connect ${label}`,
|
|
27
|
+
body: kitSectionForm({ proof: `${provider}-open`, action: route("connect"), id: "connect-form", save: { label: `Connect ${label}` },
|
|
28
|
+
confirm: `Connect ${label}`, body: joinHtml([page.fields, note(page.fieldsNote)]) }) }), settingsSection({ id: "pair", title: "3. Pair your account", body: note(`Once ${label} is connected, pair your own account here.`) }));
|
|
29
|
+
return settingsSubPage("alerts", label, joinHtml([problem, ...sections, access]), { lead: page.intro });
|
|
30
|
+
}
|
|
31
|
+
const [onWords, offWords] = page.liveWords ?? ["Connected", "Waiting for connection"];
|
|
32
|
+
sections.push(settingsSection({ id: "connection", title: credentials.workspace, state: html ` ${chip(live ? onWords : offWords, live ? "done" : "")}`,
|
|
33
|
+
body: joinHtml([
|
|
34
|
+
page.connection ?? "",
|
|
35
|
+
runtime?.problem ? notice(String(runtime.problem)) : "",
|
|
36
|
+
!live && !runtime?.problem ? note(`Keep Toolroll's builder running to receive ${label} messages.`) : "",
|
|
37
|
+
disclose("Connection settings", kitSectionForm({ proof: `${provider}-close`, action: route("disconnect"), id: "disconnect-form",
|
|
38
|
+
save: { label: `Disconnect ${label}`, variant: "danger" }, confirm: `Disconnect ${label}`, body: html `` })),
|
|
39
|
+
]) }));
|
|
40
|
+
const teammates = others ? `${others} teammate${others === 1 ? " is" : "s are"}` : "";
|
|
41
|
+
if (options.code) {
|
|
42
|
+
// The code shows once, on this page only: selectable text, never in a link or an address.
|
|
43
|
+
sections.push(settingsSection({ id: "pair", title: "Pair your account", state: " · Code ready",
|
|
44
|
+
lead: `Send this ${page.pairWhere}. It expires in 10 minutes.`,
|
|
45
|
+
body: joinHtml([codeLine(`pair ${options.code}`), note("Once you've sent it, this page says your account is paired."),
|
|
46
|
+
acts(btn("Check connection", { sm: true, href: `/settings/${provider}` }))]) }));
|
|
47
|
+
}
|
|
48
|
+
else if (binding && state.live(binding)) {
|
|
49
|
+
const { pending, dropped: failed } = state.partCounts(binding.id);
|
|
50
|
+
sections.push(settingsSection({ id: "pair", title: "Your account", state: " · Paired",
|
|
51
|
+
lead: `Your ${label} account is paired.${teammates ? ` ${teammates} paired too.` : ""}`,
|
|
52
|
+
body: joinHtml([
|
|
53
|
+
pending ? infoLine(`${pending} ${pending === 1 ? "reply is" : "replies are"} waiting to send.`) : "",
|
|
54
|
+
failed ? notice(`${failed} ${failed === 1 ? "reply" : "replies"} could not be delivered. Open the saved chat to recover them.`) : "",
|
|
55
|
+
note(page.tryLine),
|
|
56
|
+
acts(btn("Open saved chat", { sm: true, href: "/chat" })),
|
|
57
|
+
loadPrimary(process.env, dir) === provider
|
|
58
|
+
? formRow("Task updates", null, html `<span>Sent here</span>`, "inline")
|
|
59
|
+
: kitSectionForm({ proof: `${provider}-alerts`, action: route("alerts"), id: "alerts-form", save: { label: "Send task updates here", variant: "outline" },
|
|
60
|
+
body: note(`Task updates aren't sent to ${label} now.`) }),
|
|
61
|
+
kitSectionForm({ proof: `${provider}-close`, action: route("unpair"), id: "unpair-form", save: { label: "Unpair my account", variant: "danger" },
|
|
62
|
+
confirm: `Unpair your ${label} account`, body: note("Unpairing ends every open button in your chat. Teammates are unaffected.") }),
|
|
63
|
+
]) }));
|
|
17
64
|
}
|
|
18
65
|
else {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
parts.push(html `<p>Keep the Toolroll worker running to receive ${label} messages.</p>`);
|
|
24
|
-
if (options.code) {
|
|
25
|
-
parts.push(html `<h2>Pair your account</h2><p>Send this in a direct message to your Toolroll ${label} app. It expires in 10 minutes.</p>`, html `<label>Pairing message<input type="text" readonly value="pair ${options.code}" autocomplete="off" style="width:100%;max-width:100%;font-size:14px;min-height:44px"></label>`, html `<a class="button-link" style="min-height:44px;white-space:nowrap" href="/settings/${provider}">Check connection</a>`);
|
|
26
|
-
}
|
|
27
|
-
else if (binding && state.live(binding)) {
|
|
28
|
-
const { pending, dropped: failed } = state.partCounts(binding.id);
|
|
29
|
-
parts.push(html `<p>Your ${label} account is paired.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} paired too.` : ""}${pending ? ` ${pending} replies waiting to send.` : ""}${failed ? ` ${failed} replies could not be delivered. Open the saved chat to recover them.` : ""}</p>`, html `<p>Try “What needs my attention?” or “Show the evidence for the latest result.”</p>`, html `<p><a href="/chat">Open saved chat</a></p>`, loadPrimary(process.env, dir) === provider
|
|
30
|
-
? html `<p>Task updates are sent here.</p>`
|
|
31
|
-
: post("alerts", html `<button type="submit">Send task updates here</button>`));
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
parts.push(post("pair", html `<h2>Pair your account</h2><p>Your paired ${label} account can read your connected projects and confirm proposed changes. Password approvals still open in Toolroll.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} already paired.` : ""}</p>${password}<button type="submit">Create pairing code</button>`));
|
|
35
|
-
}
|
|
36
|
-
if (binding && state.live(binding) && !options.code)
|
|
37
|
-
parts.push(post("unpair", html `${password}<p class="meta">Unpairing ends every open button in your chat. Teammates are unaffected.</p><button type="submit">Unpair my account</button>`));
|
|
38
|
-
parts.push(html `<details><summary>Connection settings</summary>${post("disconnect", html `${password}<button type="submit">Disconnect ${label}</button>`)}</details>`);
|
|
66
|
+
sections.push(settingsSection({ id: "pair", title: "Pair your account", state: " · Not paired",
|
|
67
|
+
lead: `Your paired ${label} account can read your connected projects and confirm proposed changes. Password approvals still open in Toolroll.${teammates ? ` ${teammates} already paired.` : ""}`,
|
|
68
|
+
body: kitSectionForm({ proof: `${provider}-open`, action: route("pair"), id: "pair-form", native: true, save: { label: "Create pairing code" },
|
|
69
|
+
confirm: `Pair your ${label} account`, body: note("You get a one-time message to send to the app.") }) }));
|
|
39
70
|
}
|
|
40
|
-
return
|
|
71
|
+
return settingsSubPage("alerts", label, joinHtml([problem, ...sections, access]));
|
|
41
72
|
}
|
|
@@ -28,4 +28,5 @@ export type CheckSettingsView = {
|
|
|
28
28
|
problem: string | null;
|
|
29
29
|
};
|
|
30
30
|
export declare function checkSettingsHtml(view: CheckSettingsView): Html;
|
|
31
|
-
|
|
31
|
+
/** The follow-up list's look (result-follow-ups.ts); the checks page itself is kit markup. */
|
|
32
|
+
export declare const CHECK_SETTINGS_CSS = "\n .follow-ups .follow-up-list { margin: .4rem 0; padding-left: 1.1rem; display: grid; gap: .25rem; }\n .follow-ups .follow-up-acts, .follow-ups .follow-up-act { display: flex; flex-wrap: wrap; gap: .5rem; margin: .5rem 0 0; }\n .follow-ups .follow-up-act { margin: 0; }\n .follow-ups button { min-height: 44px; }\n @media (max-width: 480px) { .follow-ups .follow-up-acts, .follow-ups .follow-up-act { display: grid; } .follow-ups .follow-up-act button { width: 100%; } }\n";
|
package/dist/check-levels-ui.js
CHANGED
|
@@ -2,72 +2,81 @@
|
|
|
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,
|
|
5
|
+
import { html, joinHtml } 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
|
+
import { codeLine, disclose, formRow, infoLine, input, note, notice, radios, settingsSection, textarea } from "./server/kit.js";
|
|
9
|
+
import { kitSectionForm } from "./settings-kit.js";
|
|
10
|
+
const list = (items) => html `<ul class="k-note">${items.map(one => html `<li>${one}</li>`)}</ul>`;
|
|
8
11
|
export function checkSettingsHtml(view) {
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
view.said === null ? "" :
|
|
13
|
-
view.
|
|
14
|
-
];
|
|
15
|
-
const
|
|
16
|
-
const command = (one) => html `<p class="mono check-command">${one.command}</p><p class="meta">Up to ${Math.round(one.timeoutMs / 1000)}s · approved by ${one.approvedBy}</p>`;
|
|
12
|
+
const form = (id, act, body, save, extra = {}) => kitSectionForm({ proof: "checks", action: "/settings/checks", id, hidden: { repo: view.repo, act, ...extra }, save, body });
|
|
13
|
+
const head = joinHtml([
|
|
14
|
+
view.problem === null ? "" : notice(view.problem),
|
|
15
|
+
view.said === null ? "" : infoLine(view.said),
|
|
16
|
+
note(html `${view.name} · <span class="k-mono">${view.repo}</span>`),
|
|
17
|
+
]);
|
|
18
|
+
const command = (one) => joinHtml([codeLine(one.command), note(`Up to ${Math.round(one.timeoutMs / 1000)}s · approved by ${one.approvedBy}`)]);
|
|
17
19
|
// Quick with no quick command runs the full check; say so where the choice is made.
|
|
18
20
|
const fallsBack = view.level === "quick" && view.quick === null;
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
const levelNotes = joinHtml([
|
|
22
|
+
fallsBack ? note("No quick command yet, so builds run the full check.") : "",
|
|
23
|
+
view.level !== "full" && view.full !== null ? note("The full check still runs when a pull request opens, and Merge waits for it.") : "",
|
|
24
|
+
]);
|
|
25
|
+
const level = settingsSection({ id: "check-level", title: "After each build", state: ` · ${CHECK_LEVEL_WORDS[view.level]}`, lead: CHECK_LEVEL_HINTS[view.level],
|
|
26
|
+
body: !view.canChange ? levelNotes : joinHtml([levelNotes, kitSectionForm({ proof: "checks", action: "/settings/checks", id: "check-level-form", gate: true,
|
|
27
|
+
hidden: { repo: view.repo, act: "level" }, confirm: "Change checks",
|
|
28
|
+
body: joinHtml([
|
|
29
|
+
formRow("After each build", "A task can choose its own checks when it's filed. With Quick or Off, the full check runs when a pull request opens, and Merge waits for it unless you merge anyway.", radios({ name: "level", legend: "After each build", current: view.level, items: CHECK_LEVELS.map(one => ({ value: one, label: CHECK_LEVEL_WORDS[one], text: CHECK_LEVEL_HINTS[one] })) }), "wide"),
|
|
30
|
+
]) })]) });
|
|
31
|
+
const commandForm = (id, value, submit) => form(id, "quick", joinHtml([
|
|
32
|
+
formRow("Command", null, textarea({ id: `${id}-command`, name: "command", value, rows: 2, attrs: { maxlength: 2000, spellcheck: "false", required: true, "aria-label": "Command" } }), "wide"),
|
|
33
|
+
formRow("Time limit", "Seconds, up to 3600.", input({ id: `${id}-timeout`, name: "timeout", type: "number", value: String(view.quick === null ? 180 : Math.round(view.quick.timeoutMs / 1000)),
|
|
34
|
+
attrs: { min: 1, max: 3600, required: true, "aria-label": "Time limit in seconds" } }), "inline"),
|
|
35
|
+
list(["Runs unattended right after every Quick build, in that build's checkout.", "Plain environment: no credentials.", "A failure shows on the result; nothing is blocked."]),
|
|
36
|
+
]), { label: submit, variant: "outline" });
|
|
37
|
+
const quick = settingsSection({ id: "quick-check", title: "Quick check", state: view.quick === null ? " · None yet" : "",
|
|
38
|
+
lead: view.quick === null && view.canChange
|
|
39
|
+
? view.suggestion === null ? "Typecheck and the tests near the change, ideally under 2 minutes." : "Suggested from the project's scripts. Edit it before approving."
|
|
40
|
+
: undefined,
|
|
41
|
+
body: view.quick === null
|
|
42
|
+
? view.canChange ? commandForm("quick-form", view.suggestion ?? "", "Approve quick check") : ""
|
|
43
|
+
: joinHtml([command(view.quick), !view.canChange ? "" : joinHtml([
|
|
44
|
+
disclose("Change quick check", commandForm("quick-change-form", view.quick.command, "Approve change")),
|
|
45
|
+
disclose("Remove quick check", form("quick-clear-form", "quick-clear", note("Quick builds run the full check instead."), { label: "Remove", variant: "danger" })),
|
|
46
|
+
])]) });
|
|
47
|
+
const full = settingsSection({ id: "full-check", title: "Full check", state: view.full === null ? " · None yet" : "",
|
|
48
|
+
body: view.full === null
|
|
49
|
+
? joinHtml([note("Approve one from the command line:"), codeLine(`toolroll verify set --repo … --command "…"`)])
|
|
50
|
+
: command(view.full) });
|
|
51
|
+
return joinHtml([head, level, batchHtml(view), reviewHtml(view), quick, full]);
|
|
52
|
+
/** Batch checks: one state and one sentence; turning it on restates exactly what happens before the password. */
|
|
53
|
+
function batchHtml(view) {
|
|
54
|
+
const batch = view.batch;
|
|
55
|
+
if (batch === undefined)
|
|
56
|
+
return "";
|
|
57
|
+
const act = batch.on ? "Turn off" : "Turn on";
|
|
58
|
+
const terms = batch.on ? note("Each result runs its own full check again. Results already waiting are checked now.")
|
|
59
|
+
: list(["When results finish within 10 minutes of each other, they're merged in a temporary checkout and the full check runs once.",
|
|
60
|
+
"If it fails, the batch is split until the result that breaks it is found. Results that conflict are checked on their own.",
|
|
61
|
+
"Nothing is merged into a real branch. Each result still lands on its own."]);
|
|
62
|
+
return settingsSection({ id: "batch-checks", title: "Batch checks", state: html `<span data-batch-checks="${batch.on ? "on" : "off"}"> · ${batch.on ? "On" : "Off"}</span>`, lead: BATCH_HINT,
|
|
63
|
+
body: joinHtml([batch.on && view.level !== "full" ? note("Only Full checks are batched.") : "",
|
|
64
|
+
view.canChange ? disclose(act, form("batch-form", "batch", terms, { label: act, variant: "outline" }, { on: batch.on ? "0" : "1" })) : ""]) });
|
|
65
|
+
}
|
|
66
|
+
/** Automatic review: one state, one sentence, and its one change behind a disclosure (the password). */
|
|
67
|
+
function reviewHtml(view) {
|
|
68
|
+
const review = view.review;
|
|
69
|
+
if (review === undefined)
|
|
70
|
+
return "";
|
|
71
|
+
const state = review.on ? (review.source === "hands-off" ? "On while hands-off lasts" : "On") : "Off";
|
|
72
|
+
const act = review.on ? "Turn off" : "Turn on";
|
|
73
|
+
return settingsSection({ id: "automatic-review", title: "Automatic review", state: html `<span data-review-switch="${review.on ? "on" : "off"}"> · ${state}</span>`,
|
|
74
|
+
lead: "One read-only review after each build passes its checks. Only high findings send it back, once.",
|
|
75
|
+
body: view.canChange ? disclose(act, form("review-form", "review", html ``, { label: act, variant: "outline" }, { on: review.on ? "0" : "1" })) : "" });
|
|
22
76
|
}
|
|
23
|
-
const choices = CHECK_LEVELS.map(level => html `<label class="check-choice"><input type="radio" name="level" value="${level}"${level === view.level ? html ` checked` : ""}><span><strong>${CHECK_LEVEL_WORDS[level]}</strong><span class="meta">${CHECK_LEVEL_HINTS[level]}</span></span></label>`);
|
|
24
|
-
const levelForm = post("level", html `<fieldset><legend>After each build</legend>${choices}</fieldset>${fallsBack ? html `<p class="meta">No quick command yet, so Quick runs the full check.</p>` : ""}<p class="meta">A task can choose its own checks when it's filed. With Quick or Off, the full check runs when a pull request opens, and Merge waits for it unless you merge anyway.</p>${password}<button type="submit">Save</button>`, { class: "card check-settings" });
|
|
25
|
-
const quickTerms = html `<ul class="check-terms"><li>Runs unattended right after every Quick build, in that build's checkout.</li><li>Plain environment: no credentials.</li><li>A failure shows on the result; nothing is blocked.</li></ul>`;
|
|
26
|
-
const quickForm = (value, submit) => post("quick", html `<label>Command<textarea name="command" rows="2" maxlength="2000" spellcheck="false" class="mono" required>${value}</textarea></label><label>Time limit (seconds)<input type="number" name="timeout" min="1" max="3600" value="${view.quick === null ? 180 : Math.round(view.quick.timeoutMs / 1000)}" required></label>${quickTerms}${password}<button type="submit">${submit}</button>`, { class: "check-settings" });
|
|
27
|
-
const quick = view.quick === null
|
|
28
|
-
? html `<section class="card check-settings"><h2>Quick check</h2>${view.suggestion === null ? html `<p class="meta">Typecheck and the tests near the change, ideally under 2 minutes.</p>` : html `<p class="meta">Suggested from the project's scripts. Edit it before approving.</p>`}${quickForm(view.suggestion ?? "", "Approve quick check")}</section>`
|
|
29
|
-
: html `<section class="card check-settings"><h2>Quick check</h2>${command(view.quick)}<details class="settings-more"><summary>Change quick check</summary>${quickForm(view.quick.command, "Approve change")}</details><details class="settings-more"><summary>Remove quick check</summary>${post("quick-clear", html `<p class="meta">Quick builds run the full check instead.</p>${password}<button type="submit" class="danger">Remove</button>`, { class: "check-settings" })}</details></section>`;
|
|
30
|
-
const full = html `<details class="settings-more"><summary>Full check</summary><section class="card check-settings">${view.full === null ? html `<p class="meta">None yet. Approve one with <span class="mono">toolroll verify set --repo … --command "…"</span>.</p>` : command(view.full)}</section></details>`;
|
|
31
|
-
return html `${head}${levelForm}${batchHtml(view, password)}${reviewHtml(view, password)}${quick}${full}`;
|
|
32
|
-
}
|
|
33
|
-
/** Batch checks: one state and one sentence; turning it on restates exactly what happens, behind a disclosure with the password. */
|
|
34
|
-
function batchHtml(view, password) {
|
|
35
|
-
const batch = view.batch;
|
|
36
|
-
if (batch === undefined)
|
|
37
|
-
return "";
|
|
38
|
-
const act = batch.on ? "Turn off" : "Turn on";
|
|
39
|
-
const terms = batch.on ? html `<p class="meta">Each result runs its own full check again. Results already waiting are checked now.</p>`
|
|
40
|
-
: html `<ul class="check-terms"><li>When results finish within 10 minutes of each other, they're merged in a temporary checkout and the full check runs once.</li><li>If it fails, the batch is split until the result that breaks it is found. Results that conflict are checked on their own.</li><li>Nothing is merged into a real branch. Each result still lands on its own.</li></ul>`;
|
|
41
|
-
const change = password === null ? ""
|
|
42
|
-
: html `<details class="settings-more"><summary>${act}</summary>${postForm("/settings/checks", html `${terms}${password}<button type="submit">${act}</button>`, { attrs: { class: "check-settings" }, hidden: { repo: view.repo, act: "batch", on: batch.on ? "0" : "1" } })}</details>`;
|
|
43
|
-
return html `<section class="card check-settings" data-batch-checks="${batch.on ? "on" : "off"}"><h2>Batch checks</h2><p><strong>${batch.on ? "On" : "Off"}</strong> · ${BATCH_HINT}</p>${batch.on && view.level !== "full" ? html `<p class="meta">Only Full checks are batched.</p>` : ""}${change}</section>`;
|
|
44
|
-
}
|
|
45
|
-
/** Automatic review: one state, one sentence, and its one change behind a disclosure (the password). */
|
|
46
|
-
function reviewHtml(view, password) {
|
|
47
|
-
const review = view.review;
|
|
48
|
-
if (review === undefined)
|
|
49
|
-
return "";
|
|
50
|
-
const state = review.on ? (review.source === "hands-off" ? "On while hands-off lasts" : "On") : "Off";
|
|
51
|
-
const act = review.on ? "Turn off" : "Turn on";
|
|
52
|
-
const change = password === null ? ""
|
|
53
|
-
: html `<details class="settings-more"><summary>${act}</summary>${postForm("/settings/checks", html `${password}<button type="submit">${act}</button>`, { attrs: { class: "check-settings" }, hidden: { repo: view.repo, act: "review", on: review.on ? "0" : "1" } })}</details>`;
|
|
54
|
-
return html `<section class="card check-settings" data-review-switch="${review.on ? "on" : "off"}"><h2>Automatic review</h2><p><strong>${state}</strong> · One read-only review after each build passes its checks. Only high findings send it back, once.</p>${change}</section>`;
|
|
55
77
|
}
|
|
78
|
+
/** The follow-up list's look (result-follow-ups.ts); the checks page itself is kit markup. */
|
|
56
79
|
export const CHECK_SETTINGS_CSS = `
|
|
57
|
-
.check-settings { display: grid; gap: .6rem; max-width: 40rem; }
|
|
58
|
-
.check-settings p, .check-settings h2 { margin: 0; }
|
|
59
|
-
.check-settings h2 { font-size: 1rem; }
|
|
60
|
-
.check-settings fieldset { border: 0; margin: 0; padding: 0; display: grid; gap: .25rem; }
|
|
61
|
-
.check-settings legend { font-weight: 600; margin-bottom: .25rem; }
|
|
62
|
-
.check-settings label { display: grid; gap: .25rem; }
|
|
63
|
-
.check-settings .check-choice { display: flex; align-items: flex-start; gap: .6rem; min-height: 44px; padding: .35rem 0; cursor: pointer; }
|
|
64
|
-
.check-settings .check-choice > span { display: grid; gap: .1rem; }
|
|
65
|
-
.check-settings .check-choice input { margin-top: .2rem; }
|
|
66
|
-
.check-settings input[type=password], .check-settings input[type=number], .check-settings textarea { min-height: 44px; box-sizing: border-box; width: 100%; }
|
|
67
|
-
.check-settings button { min-height: 44px; justify-self: start; }
|
|
68
|
-
.check-settings .check-command { overflow-wrap: anywhere; }
|
|
69
|
-
.check-terms { margin: 0; padding-left: 1.1rem; display: grid; gap: .25rem; }
|
|
70
|
-
@media (max-width: 480px) { .check-settings button { justify-self: stretch; } }
|
|
71
80
|
.follow-ups .follow-up-list { margin: .4rem 0; padding-left: 1.1rem; display: grid; gap: .25rem; }
|
|
72
81
|
.follow-ups .follow-up-acts, .follow-ups .follow-up-act { display: flex; flex-wrap: wrap; gap: .5rem; margin: .5rem 0 0; }
|
|
73
82
|
.follow-ups .follow-up-act { margin: 0; }
|
package/dist/claim.d.ts
CHANGED
package/dist/claim.js
CHANGED
|
@@ -31,7 +31,7 @@ import { randomUUID } from "node:crypto";
|
|
|
31
31
|
import { authenticate, drainOf } from "./runner.js";
|
|
32
32
|
import { pauseForAuth } from "./provider-auth.js";
|
|
33
33
|
import { DEFAULT_MAX_OPEN_DECISIONS, missingCapability, scopeApprovedForDispatch, taskReadinessBlocker, } from "./dispatch.js";
|
|
34
|
-
import { BUILT_IN, } from "./store.js";
|
|
34
|
+
import { BUILT_IN, refArchived, } from "./store.js";
|
|
35
35
|
import { digestOf } from "./scope.js";
|
|
36
36
|
import { parseExecutionPlanDocument } from "./plan.js";
|
|
37
37
|
import { sealUnchangedPlanUnderMode } from "./plan-auto.js";
|
|
@@ -109,6 +109,9 @@ function acquireLocked(store, taskRef, runner, options) {
|
|
|
109
109
|
if (placedRef !== null && placedRef.coordinatorCid !== null && !store.scopeSealed(placedRef.externalId)) {
|
|
110
110
|
return { ok: false, reason: "coordinator-filed" };
|
|
111
111
|
}
|
|
112
|
+
// A PUT-AWAY TASK (v145, task-tidy.md) claims for nobody, on every road: restore it first.
|
|
113
|
+
if (refArchived(store, taskRef))
|
|
114
|
+
return { ok: false, reason: "archived" };
|
|
112
115
|
// The reservation gate lives HERE, in the one primitive every
|
|
113
116
|
// acquisition path shares (queue-columns review, finding 1): tick's
|
|
114
117
|
// acquireIfReady and the raw CLI claim both
|
|
@@ -160,6 +160,13 @@ export declare const chatActionRequestSchemas: {
|
|
|
160
160
|
readonly flow_trigger_remove: z.ZodObject<{
|
|
161
161
|
trigger: z.ZodInt;
|
|
162
162
|
}, z.core.$strict>;
|
|
163
|
+
/** Browser use in the flow's script zones (flow-browser.ts): allowing it takes the person's own password in the secure screen. */
|
|
164
|
+
readonly flow_browser: z.ZodObject<{
|
|
165
|
+
flow: z.ZodInt;
|
|
166
|
+
}, z.core.$strict>;
|
|
167
|
+
readonly flow_browser_off: z.ZodObject<{
|
|
168
|
+
flow: z.ZodInt;
|
|
169
|
+
}, z.core.$strict>;
|
|
163
170
|
readonly subagent_create: z.ZodObject<{
|
|
164
171
|
repo: z.ZodString;
|
|
165
172
|
template: z.ZodOptional<z.ZodUnknown>;
|
|
@@ -282,6 +289,8 @@ export declare const sharedActionSchema: z.ZodObject<{
|
|
|
282
289
|
operation: z.ZodEnum<{
|
|
283
290
|
decision_record: "decision_record";
|
|
284
291
|
decision_retire: "decision_retire";
|
|
292
|
+
flow_browser: "flow_browser";
|
|
293
|
+
flow_browser_off: "flow_browser_off";
|
|
285
294
|
flow_card_add: "flow_card_add";
|
|
286
295
|
flow_card_approve: "flow_card_approve";
|
|
287
296
|
flow_card_assign: "flow_card_assign";
|
|
@@ -61,6 +61,9 @@ export const chatActionRequestSchemas = {
|
|
|
61
61
|
flow_trigger_pause: z.strictObject({ trigger: id }),
|
|
62
62
|
flow_trigger_resume: z.strictObject({ trigger: id }),
|
|
63
63
|
flow_trigger_remove: z.strictObject({ trigger: id }),
|
|
64
|
+
/** Browser use in the flow's script zones (flow-browser.ts): allowing it takes the person's own password in the secure screen. */
|
|
65
|
+
flow_browser: z.strictObject({ flow: id }),
|
|
66
|
+
flow_browser_off: z.strictObject({ flow: id }),
|
|
64
67
|
subagent_create: z.strictObject({ repo: words, template: z.unknown().optional(), name: z.unknown().optional(), soul: z.unknown().optional() }),
|
|
65
68
|
subagent_soul: z.strictObject({ subagent: id, soul: words }),
|
|
66
69
|
subagent_state: z.strictObject({ subagent: id, state: z.enum(["active", "paused", "removed"]) }),
|