toolroll 0.9.102 → 0.9.104
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 +13 -0
- package/README.md +10 -8
- package/dist/assignment.d.ts +32 -0
- package/dist/assignment.js +140 -4
- package/dist/browser/workspace.js +25 -25
- package/dist/browser-workspace.d.ts +21 -5
- package/dist/contracts/console-api.d.ts +3 -2
- package/dist/contracts/console-api.js +4 -2
- package/dist/desktop-update.d.ts +2 -2
- package/dist/flow-engine.d.ts +3 -0
- package/dist/flow-engine.js +4 -1
- package/dist/flows-ui.d.ts +31 -1
- package/dist/flows-ui.js +40 -12
- package/dist/keymap.d.ts +3 -1
- package/dist/keymap.js +3 -0
- package/dist/local-owner-acts.d.ts +83 -0
- package/dist/local-owner-acts.js +221 -0
- package/dist/local-owner.d.ts +12 -1
- package/dist/local-owner.js +26 -0
- package/dist/operate-remote-arguments.js +4 -2
- package/dist/operate.js +76 -1
- package/dist/plan-auto.js +5 -0
- package/dist/project-knowledge.d.ts +6 -6
- package/dist/public-access.d.ts +9 -0
- package/dist/public-access.js +15 -0
- package/dist/pull-request-flow.js +9 -0
- package/dist/release-approval.d.ts +9 -20
- package/dist/release-approval.js +14 -53
- package/dist/release-card.d.ts +15 -1
- package/dist/release-card.js +35 -12
- package/dist/release-refusal.d.ts +32 -0
- package/dist/release-refusal.js +73 -0
- package/dist/result-completion.d.ts +6 -2
- package/dist/result-completion.js +13 -7
- package/dist/scope.d.ts +32 -1
- package/dist/scope.js +116 -25
- package/dist/serve.js +13 -14
- package/dist/server/guards.d.ts +1 -0
- package/dist/server/guards.js +11 -1
- package/dist/server/home-asks.d.ts +45 -0
- package/dist/server/home-asks.js +108 -0
- package/dist/server/home.d.ts +15 -5
- package/dist/server/home.js +53 -18
- package/dist/server/pages.js +15 -3
- package/dist/server/remote.d.ts +1 -0
- package/dist/server/render-results.d.ts +4 -0
- package/dist/server/render-tasks.d.ts +9 -0
- package/dist/server/render-tasks.js +4 -4
- package/dist/server/route-table.js +4 -0
- package/dist/server/runtime.d.ts +1 -0
- package/dist/server/task-views.js +3 -0
- package/dist/server/tasks.d.ts +25 -0
- package/dist/server/tasks.js +46 -6
- package/dist/surface.js +12 -2
- package/dist/task-outcome-cli.d.ts +18 -0
- package/dist/task-outcome-cli.js +60 -2
- package/dist/tui-contract.d.ts +61 -3
- package/dist/tui-source.js +91 -20
- package/dist/tui.js +54 -54
- package/dist/work-index.d.ts +10 -5
- package/dist/work-index.js +23 -13
- package/package.json +2 -2
|
@@ -392,8 +392,13 @@ export type BrowserTaskView = {
|
|
|
392
392
|
run: number;
|
|
393
393
|
} | null;
|
|
394
394
|
/** Build again, in place, when the status asks for it (a result built to an earlier plan). */
|
|
395
|
+
/** `pins`: the build and approved plan the page read, which a local owner's Retry binds to (local-owner slice 3). */
|
|
395
396
|
rebuild?: {
|
|
396
397
|
action: string;
|
|
398
|
+
pins?: {
|
|
399
|
+
run: number;
|
|
400
|
+
digest: string;
|
|
401
|
+
} | null;
|
|
397
402
|
} | null;
|
|
398
403
|
/** What a failed task missed, in one line (the requirement it missed, the failing check's error line, or the stop
|
|
399
404
|
* reason), the evidence line behind it, one suggestion of what to change, and where to see it (its build's result
|
|
@@ -402,6 +407,10 @@ export type BrowserTaskView = {
|
|
|
402
407
|
retry?: {
|
|
403
408
|
action: string;
|
|
404
409
|
note?: string;
|
|
410
|
+
pins?: {
|
|
411
|
+
run: number;
|
|
412
|
+
digest: string;
|
|
413
|
+
} | null;
|
|
405
414
|
} | null;
|
|
406
415
|
/** Run checks in place, on a status row that offers it: posts and comes back to this page. */
|
|
407
416
|
runChecks?: BrowserRunChecks | null;
|
|
@@ -867,6 +876,10 @@ export type BrowserResultView = {
|
|
|
867
876
|
retry: {
|
|
868
877
|
action: string;
|
|
869
878
|
note: string;
|
|
879
|
+
pins?: {
|
|
880
|
+
run: number;
|
|
881
|
+
digest: string;
|
|
882
|
+
} | null;
|
|
870
883
|
} | null;
|
|
871
884
|
/** A failed task's delivered result, accepted only in outline with a reason (the task's accept-proof). */
|
|
872
885
|
acceptAnyway?: {
|
|
@@ -1725,8 +1738,9 @@ export type BrowserHomeView = {
|
|
|
1725
1738
|
value: number;
|
|
1726
1739
|
href: string;
|
|
1727
1740
|
}[];
|
|
1728
|
-
/** What needs you, by what is asked, in this order: approve, answer, wrong, review, other
|
|
1729
|
-
* fits). `count` is the exact total for that ask, past the
|
|
1741
|
+
/** What needs you, by what is asked, in this order: release, approve, answer, flow, wrong, review, setup, other
|
|
1742
|
+
* (anything none of the others fits; docs/plans/home-other-asks.md). `count` is the exact total for that ask, past the
|
|
1743
|
+
* rows listed. */
|
|
1730
1744
|
groups: {
|
|
1731
1745
|
key: BrowserHomeAsk;
|
|
1732
1746
|
label: string;
|
|
@@ -1761,9 +1775,10 @@ export type BrowserHomeView = {
|
|
|
1761
1775
|
/** While the first run is open (no first result yet): the welcome's address, linked as Finish setup. */
|
|
1762
1776
|
setup?: string | null;
|
|
1763
1777
|
};
|
|
1764
|
-
/** What a waiting row asks (work-index.ts WorkAskGroup). */
|
|
1765
|
-
export type BrowserHomeAsk = 'approve' | 'answer' | 'wrong' | 'review' | 'other';
|
|
1778
|
+
/** What a waiting row asks (work-index.ts WorkAskGroup), plus a flow card waiting on you and a first-run step. */
|
|
1779
|
+
export type BrowserHomeAsk = 'release' | 'approve' | 'answer' | 'flow' | 'wrong' | 'review' | 'setup' | 'other';
|
|
1766
1780
|
export type BrowserHomeRow = {
|
|
1781
|
+
/** A task row's family id; a row that isn't a task is namespaced (`release:<task>`, `flow:<card>`, `setup:<step>`). */
|
|
1767
1782
|
id: string;
|
|
1768
1783
|
title: string;
|
|
1769
1784
|
href: string;
|
|
@@ -1777,7 +1792,8 @@ export type BrowserHomeRow = {
|
|
|
1777
1792
|
reason: string | null;
|
|
1778
1793
|
/** One line: what it asks, or what the agent is doing now. */
|
|
1779
1794
|
line: string | null;
|
|
1780
|
-
/** The one act that resolves it (a link), or null.
|
|
1795
|
+
/** The one act that resolves it (a link), or null. A release, a flow card or a setup step is never acted on at
|
|
1796
|
+
* Home: its one link opens its own page. */
|
|
1781
1797
|
action: {
|
|
1782
1798
|
label: string;
|
|
1783
1799
|
href: string;
|
|
@@ -202,7 +202,8 @@ export declare const CONSOLE_FORMS: {
|
|
|
202
202
|
readonly taskRepairDependency: FormContract<"blocker" | "operation" | "replacement">;
|
|
203
203
|
readonly taskNext: FormContract<"undo">;
|
|
204
204
|
readonly taskReopen: FormContract<"token">;
|
|
205
|
-
readonly taskRequeue: FormContract<"note" | "return">;
|
|
205
|
+
readonly taskRequeue: FormContract<"digest" | "note" | "return" | "run">;
|
|
206
|
+
readonly taskPlan: FormContract<"source">;
|
|
206
207
|
readonly taskFollowUp: FormContract<"index">;
|
|
207
208
|
readonly taskPlanEdit: FormContract<"plan-document" | "saw-plan">;
|
|
208
209
|
readonly taskRevision: FormContract<"revision-id" | "token">;
|
|
@@ -218,7 +219,7 @@ export declare const CONSOLE_FORMS: {
|
|
|
218
219
|
};
|
|
219
220
|
export type ConsoleFormName = keyof typeof CONSOLE_FORMS;
|
|
220
221
|
/** The console POSTs whose handlers read no field (sign-out not even the shared guard): their contract is the empty form. */
|
|
221
|
-
export declare const BODILESS_POSTS: readonly ["POST /settings/models/<unknown action>", "POST /r/<run>/draft-repair", "POST /ledger/checkpoint", "POST /settings/backups/now", "POST /mode/revoke", "POST /settings/telegram/retry", "POST /providers/<provider>/resume", "POST /logout", "POST /t/<task>/unhold", "POST /t/<task>/stop-carrying", "POST /t/<task>/
|
|
222
|
+
export declare const BODILESS_POSTS: readonly ["POST /settings/models/<unknown action>", "POST /r/<run>/draft-repair", "POST /ledger/checkpoint", "POST /settings/backups/now", "POST /mode/revoke", "POST /settings/telegram/retry", "POST /providers/<provider>/resume", "POST /logout", "POST /t/<task>/unhold", "POST /t/<task>/stop-carrying", "POST /t/<task>/retry-review", "POST /settings/updates/check", "POST /settings/storage/check", "POST /projects/github", "POST /code/<session>/ship/preview"];
|
|
222
223
|
/** The field names a console form's handler may read. */
|
|
223
224
|
export type FormFieldOf<N extends ConsoleFormName> = (typeof CONSOLE_FORMS)[N] extends FormContract<infer K> ? K : never;
|
|
224
225
|
/** `GET /ledger?format=json`: the chain's state, a page of entries (newest first) and the cursor for the next. */
|
|
@@ -220,7 +220,10 @@ export const CONSOLE_FORMS = {
|
|
|
220
220
|
taskRepairDependency: formContract("POST /t/<task>/repair-dependency", ["blocker", "operation", "replacement"]),
|
|
221
221
|
taskNext: formContract("POST /t/<task>/next", ["undo"]),
|
|
222
222
|
taskReopen: formContract("POST /t/<task>/reopen", ["token"]),
|
|
223
|
-
|
|
223
|
+
// Local-owner slice 3: the build and approved plan the page read (`run`, `digest`), read only for the laptop's local owner.
|
|
224
|
+
taskRequeue: formContract("POST /t/<task>/requeue", ["note", "return", "run", "digest"]),
|
|
225
|
+
// Local-owner slice 2: the planning source the page read (planner-source.ts), read only for the laptop's local owner.
|
|
226
|
+
taskPlan: formContract("POST /t/<task>/plan", ["source"]),
|
|
224
227
|
taskFollowUp: formContract("POST /t/<task>/follow-up", ["index"]),
|
|
225
228
|
taskPlanEdit: formContract("POST /t/<task>/plan-edit", ["saw-plan", "plan-document"]),
|
|
226
229
|
taskRevision: formContract("POST /t/<task>/accept-revision|reject-revision", ["revision-id", "token"]),
|
|
@@ -248,7 +251,6 @@ export const BODILESS_POSTS = [
|
|
|
248
251
|
"POST /logout",
|
|
249
252
|
"POST /t/<task>/unhold",
|
|
250
253
|
"POST /t/<task>/stop-carrying",
|
|
251
|
-
"POST /t/<task>/plan",
|
|
252
254
|
"POST /t/<task>/retry-review",
|
|
253
255
|
// Check now and its kin (docs/plans/get-side-effects.md): the form token only, no fields read.
|
|
254
256
|
"POST /settings/updates/check",
|
package/dist/desktop-update.d.ts
CHANGED
|
@@ -97,20 +97,20 @@ export declare function sqliteLock(file: string, busyTimeoutMs?: number): Databa
|
|
|
97
97
|
export declare function lockHeld(file: string): boolean;
|
|
98
98
|
export declare const installationLock: (j: UpdateJournal) => DatabaseSync | null;
|
|
99
99
|
export declare function desktopUpdateStatus(stateDir: string): {
|
|
100
|
-
|
|
100
|
+
version?: never;
|
|
101
101
|
active: boolean;
|
|
102
102
|
phase: string;
|
|
103
103
|
detail: string;
|
|
104
104
|
running: boolean;
|
|
105
105
|
canResume: boolean;
|
|
106
106
|
canCancel: boolean;
|
|
107
|
+
error?: never;
|
|
107
108
|
automaticRecovery?: never;
|
|
108
109
|
recoveryAttempts?: never;
|
|
109
110
|
recoveryRepairs?: never;
|
|
110
111
|
wasRunning?: never;
|
|
111
112
|
backupPath?: never;
|
|
112
113
|
updateId?: never;
|
|
113
|
-
version?: never;
|
|
114
114
|
buildId?: never;
|
|
115
115
|
workDir?: never;
|
|
116
116
|
} | {
|
package/dist/flow-engine.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ export type FlowAdvance = {
|
|
|
9
9
|
export declare function flowDefinitionOf(flow: FlowRow): FlowDefinition | null;
|
|
10
10
|
/** Where a card's link opens: the flow's canvas with the card selected. */
|
|
11
11
|
export declare const flowCardHref: (flow: number, card: number) => string;
|
|
12
|
+
/** A reply wait that timed out with nowhere to send the card asks its owner (or the flow's owner) to move it on: the
|
|
13
|
+
* card's waiting words end with this (flows-ui.ts flowCardWaitsOn reads it). */
|
|
14
|
+
export declare const NO_REPLY_MOVE_ON = "Move the card on when you're ready.";
|
|
12
15
|
/** Where a confirmed flow action may point: a flow's canvas, maybe with one card open. */
|
|
13
16
|
export declare const FLOW_HREF: RegExp;
|
|
14
17
|
/** Advance every active card in one project's flows. */
|
package/dist/flow-engine.js
CHANGED
|
@@ -36,6 +36,9 @@ export function flowDefinitionOf(flow) {
|
|
|
36
36
|
}
|
|
37
37
|
/** Where a card's link opens: the flow's canvas with the card selected. */
|
|
38
38
|
export const flowCardHref = (flow, card) => `/flows/${flow}?card=${card}`;
|
|
39
|
+
/** A reply wait that timed out with nowhere to send the card asks its owner (or the flow's owner) to move it on: the
|
|
40
|
+
* card's waiting words end with this (flows-ui.ts flowCardWaitsOn reads it). */
|
|
41
|
+
export const NO_REPLY_MOVE_ON = "Move the card on when you're ready.";
|
|
39
42
|
/** Where a confirmed flow action may point: a flow's canvas, maybe with one card open. */
|
|
40
43
|
export const FLOW_HREF = /^\/flows\/[1-9][0-9]{0,9}(\?card=[1-9][0-9]{0,9})?$/;
|
|
41
44
|
const ACCEPTANCE = FLOW_ACCEPTANCE;
|
|
@@ -173,7 +176,7 @@ function advanceCard(store, flow, definition, card, now, options, outcome) {
|
|
|
173
176
|
outcome.moved++;
|
|
174
177
|
return;
|
|
175
178
|
}
|
|
176
|
-
const waiting = `No reply after ${words}.
|
|
179
|
+
const waiting = `No reply after ${words}. ${NO_REPLY_MOVE_ON}`;
|
|
177
180
|
if (card.waiting !== waiting) {
|
|
178
181
|
store.updateFlowCard(card.id, { waiting }, now);
|
|
179
182
|
notifyPeople(store, card, [card.owner ?? flow.owner], null, { key: `no-reply:${card.entry}`, subject: `No reply to “${card.title}” after ${words}`, body: `It's waiting in ${stage.title}.`, attention: true }, now);
|
package/dist/flows-ui.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BrowserFlowView } from "./browser-workspace.js";
|
|
2
|
-
import
|
|
2
|
+
import { type FlowStage } from "./flows.js";
|
|
3
|
+
import type { FlowCardRow, FlowRow, Store, SubagentQuestionRow } from "./store.js";
|
|
3
4
|
import { type FlowFile, type FlowImportPlan } from "./flow-share.js";
|
|
4
5
|
import { type Html } from "./html.js";
|
|
5
6
|
/** The flow page's fallback rules, plus the Flows list and the import preview (kit pieces; only layout here). */
|
|
@@ -24,6 +25,35 @@ export declare function flowImportHtml(input: {
|
|
|
24
25
|
previewed: string;
|
|
25
26
|
problem: string | null;
|
|
26
27
|
}): Html;
|
|
28
|
+
/** Why a card waits on this person now (docs/plans/home-other-asks.md part 4, v2.3): the one rule the flow page's
|
|
29
|
+
* Decide and Home's Flow decisions both read, so they never disagree. Each needs approver standing:
|
|
30
|
+
* - `decide`: an approval zone it is theirs to decide (the zone's decider, the flow's owner for "to owner", any approver
|
|
31
|
+
* when none is named), and no subagent of the zone deciding it first;
|
|
32
|
+
* - `choose`: a "Person chooses" zone waiting on them (flow-send.ts flowChoiceAt);
|
|
33
|
+
* - `answer`: its subagent's open question or tool-call request asked of them;
|
|
34
|
+
* - `browser`: a check zone waiting for browser use, which only an approver with every project may allow;
|
|
35
|
+
* - `reply`: a reply wait that ran out with nowhere to go, asking the card's owner (else the flow's) to move it on.
|
|
36
|
+
* `reads` passes what the caller already read (a missing key is read here). */
|
|
37
|
+
export type FlowCardWait = {
|
|
38
|
+
decide: boolean;
|
|
39
|
+
choose: boolean;
|
|
40
|
+
answer: boolean;
|
|
41
|
+
browser: boolean;
|
|
42
|
+
reply: boolean;
|
|
43
|
+
};
|
|
44
|
+
export declare function flowCardWaitsOn(store: Store, flow: FlowRow, card: FlowCardRow, viewer: {
|
|
45
|
+
name: string;
|
|
46
|
+
approver: boolean;
|
|
47
|
+
}, reads?: {
|
|
48
|
+
stage?: FlowStage | undefined;
|
|
49
|
+
asked?: SubagentQuestionRow | null;
|
|
50
|
+
choosing?: {
|
|
51
|
+
person: string;
|
|
52
|
+
} | null;
|
|
53
|
+
browser?: {
|
|
54
|
+
status: string;
|
|
55
|
+
} | null;
|
|
56
|
+
}): FlowCardWait;
|
|
27
57
|
/** One flow's canvas for one person. */
|
|
28
58
|
export declare function flowView(store: Store, flow: FlowRow, viewer: {
|
|
29
59
|
name: string;
|
package/dist/flows-ui.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Flows in the console: the list of a person's flows, and one flow's canvas
|
|
2
2
|
* (the React view's data, plus a plain fallback page the view replaces). */
|
|
3
3
|
import { liveAttemptOf, steerControlsFor } from "./steer-controls.js";
|
|
4
|
-
import { draftFor, flowDefinitionOf } from "./flow-engine.js";
|
|
4
|
+
import { draftFor, flowDefinitionOf, NO_REPLY_MOVE_ON } from "./flow-engine.js";
|
|
5
5
|
import { describeStandingOrder } from "./flow-schedule.js";
|
|
6
6
|
import { describeTrigger, triggerHeadline, FLOW_TRIGGER_KINDS, FLOW_TRIGGER_WORDS, githubRepoOf, HOOK_PATH, hookReady, planeStatusLink, readHooksBase, readLinearKey, senderOf, takesDeliveries, triggerConfigOf } from "./flow-triggers.js";
|
|
7
7
|
import { SENDER_WORDS } from "./webhook-signing.js";
|
|
@@ -14,7 +14,7 @@ import { parseSortDecision, sortChip } from "./flow-sort.js";
|
|
|
14
14
|
import { flowSecretNames, readEmailSettings, sendingReady } from "./flow-actions.js";
|
|
15
15
|
import { projectToolsOf, secretsSetFor, toolStanding } from "./project-tools.js";
|
|
16
16
|
import { flowFingerprint } from "./flow-live.js";
|
|
17
|
-
import { flowChoiceAt, readFlowSend } from "./flow-send.js";
|
|
17
|
+
import { flowChoiceAt, flowPersonOf, readFlowSend } from "./flow-send.js";
|
|
18
18
|
import { itemCaption, whyLines } from "./flow-items.js";
|
|
19
19
|
import { FLOW_FILE_MAX_BYTES } from "./flow-share.js";
|
|
20
20
|
import { googleConnected } from "./google-mail.js";
|
|
@@ -224,6 +224,37 @@ const historyText = (event, title, sorts = new Set()) => {
|
|
|
224
224
|
default: return `Moved to ${title(event.toStage)}${who}${note}`;
|
|
225
225
|
}
|
|
226
226
|
};
|
|
227
|
+
export function flowCardWaitsOn(store, flow, card, viewer, reads = {}) {
|
|
228
|
+
const none = { decide: false, choose: false, answer: false, browser: false, reply: false };
|
|
229
|
+
if (!viewer.approver || card.state !== "active")
|
|
230
|
+
return none;
|
|
231
|
+
const stage = "stage" in reads ? reads.stage : flowDefinitionOf(flow)?.stages.find(one => one.id === card.stage);
|
|
232
|
+
// Its subagent's open question is the person's to answer whatever zone it is in, even one since removed from the flow
|
|
233
|
+
// (the flow page shows it and takes the answer).
|
|
234
|
+
const asked = "asked" in reads ? reads.asked ?? null : store.openSubagentQuestionOn(card.id, card.entry);
|
|
235
|
+
const answer = asked !== null && asked.state === "open" && asked.askedOf === viewer.name && store.getSubagent(asked.subagent) !== null;
|
|
236
|
+
if (stage === undefined)
|
|
237
|
+
return { ...none, answer };
|
|
238
|
+
const decider = stage.kind === "approval" ? deciderOf(stage, flow) : null;
|
|
239
|
+
// While its subagent is deciding (v92), the card isn't anyone else's to decide yet.
|
|
240
|
+
const subagentDeciding = stage.subagent !== undefined && card.waiting !== null && card.waiting.endsWith(" is deciding");
|
|
241
|
+
const choosing = stage.kind !== "choose" ? null : "choosing" in reads ? reads.choosing ?? null : flowChoiceAt(store, card.id, card.entry);
|
|
242
|
+
const browser = stage.kind !== "check" || stage.browser !== true ? null : "browser" in reads ? reads.browser ?? null : browserViewOf(store, flow);
|
|
243
|
+
const wait = stage.wait ?? { for: "reply" };
|
|
244
|
+
return {
|
|
245
|
+
decide: stage.kind === "approval" && (decider === null || decider === viewer.name) && !subagentDeciding,
|
|
246
|
+
choose: choosing !== null && choosing.person === viewer.name,
|
|
247
|
+
answer,
|
|
248
|
+
browser: browserWaitOn(card, stage, browser) && store.accountOf(viewer.name)?.projects === null,
|
|
249
|
+
reply: stage.kind === "wait" && wait.for === "reply" && card.waiting !== null && card.waiting.startsWith("No reply after ") && card.waiting.endsWith(NO_REPLY_MOVE_ON)
|
|
250
|
+
&& flowPersonOf(card, flow) === viewer.name,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
/** A check zone's card waiting for browser use (flow-browser.ts): the flow's browser isn't allowed and the card says so. */
|
|
254
|
+
function browserWaitOn(card, stage, browser) {
|
|
255
|
+
return card.state === "active" && stage?.kind === "check" && stage.browser === true && browser !== null && browser.status !== "allowed"
|
|
256
|
+
&& card.waiting !== null && card.waiting.startsWith(`${stage.title} starts a browser`);
|
|
257
|
+
}
|
|
227
258
|
/** One flow's canvas for one person. */
|
|
228
259
|
export function flowView(store, flow, viewer, selectedCard, setup = { dir: null, repos: [] }) {
|
|
229
260
|
const definition = flowDefinitionOf(flow);
|
|
@@ -249,10 +280,12 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
249
280
|
const canReadTask = taskRepo !== null && (taskRepo === flow.repo || (setup.repos.includes(taskRepo) && store.accountCanAccess(viewer.name, taskRepo)));
|
|
250
281
|
const discussion = store.flowComments(card.id);
|
|
251
282
|
const watchers = store.flowCardWatchers(card.id);
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
const
|
|
283
|
+
// Its open question: one of its own, or (v94) a tool call waiting for approval.
|
|
284
|
+
const asked = card.state === "active" ? store.openSubagentQuestionOn(card.id, card.entry) : null;
|
|
285
|
+
// "Person chooses" (flow-send.ts): what was sent, and the options, for the person it waits on; the latest send otherwise.
|
|
286
|
+
const choosing = card.state === "active" && stage?.kind === "choose" ? flowChoiceAt(store, card.id, card.entry) : null;
|
|
287
|
+
// Who may decide it here: the one rule Home reads too (flowCardWaitsOn).
|
|
288
|
+
const canDecide = flowCardWaitsOn(store, flow, card, viewer, { stage, asked, choosing, browser: browserNow }).decide;
|
|
256
289
|
// History reads moves and ownership together, newest first.
|
|
257
290
|
const owned = discussion.filter(one => one.kind === "owner").map(one => ({ text: one.body === "" ? `${one.author} left it without an owner` : one.body === one.author ? `${one.author} took it on` : `${one.author} made ${one.body} the owner`, at: one.at }));
|
|
258
291
|
const moves = store.flowEvents(card.id).map(event => ({ text: historyText(event, title, sortZones), at: event.at }));
|
|
@@ -266,8 +299,6 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
266
299
|
at: until.toISOString(),
|
|
267
300
|
label: stage.wait !== undefined && stage.wait.for !== "hours" ? (stage.wait.for === "reply" ? "No reply by" : "Moves on at") : stage.limit.to !== null ? `Moves to ${title(stage.limit.to)} at` : "Reminder at",
|
|
268
301
|
};
|
|
269
|
-
// Its open question: one of its own, or (v94) a tool call waiting for approval.
|
|
270
|
-
const asked = card.state === "active" ? store.openSubagentQuestionOn(card.id, card.entry) : null;
|
|
271
302
|
const pending = asked?.toolCall == null ? null : store.subagentCall(asked.toolCall);
|
|
272
303
|
// A decision its subagent handed over carries what the subagent said (v92).
|
|
273
304
|
const turn = card.state === "active" && stage?.kind === "approval" && stage.subagent !== undefined ? store.flowStepRun(card.id, card.entry) : null;
|
|
@@ -284,8 +315,6 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
284
315
|
}
|
|
285
316
|
}
|
|
286
317
|
const asker = asked === null || asked.state !== "open" ? null : store.getSubagent(asked.subagent);
|
|
287
|
-
// "Person chooses" (flow-send.ts): what was sent, and the options, for the person it waits on; the latest send otherwise.
|
|
288
|
-
const choosing = card.state === "active" && stage?.kind === "choose" ? flowChoiceAt(store, card.id, card.entry) : null;
|
|
289
318
|
const latest = store.flowSends(card.id)[0] ?? null;
|
|
290
319
|
const sentContent = latest === null ? null : readFlowSend(latest.contentJson);
|
|
291
320
|
// On the card itself: where it came from (the card's title is right above), and only the links the panel doesn't already have.
|
|
@@ -295,8 +324,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
295
324
|
// After research: the same numbered items the message carried, each beside its screenshot.
|
|
296
325
|
...(content.items === undefined ? {} : { items: content.items.map((item, index) => ({ number: index + 1, title: item.title, lines: whyLines(item.why), source: item.source, url: item.url,
|
|
297
326
|
image: item.shot === null || content.shots === null ? null : { src: `/r/${content.shots.run}/evidence/${item.shot}`, caption: itemCaption(index + 1, item) } })) }) });
|
|
298
|
-
const browserWait = card
|
|
299
|
-
&& card.waiting !== null && card.waiting.startsWith(`${stage.title} starts a browser`);
|
|
327
|
+
const browserWait = browserWaitOn(card, stage, browser);
|
|
300
328
|
return {
|
|
301
329
|
id: card.id, title: card.title, description: card.description, stage: card.stage, state: card.state,
|
|
302
330
|
waiting: card.state === "active" && card.task !== null && (stage?.kind === "task" || stage?.kind === "report")
|
package/dist/keymap.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export type WebShortcut = {
|
|
|
15
15
|
kind: "help";
|
|
16
16
|
};
|
|
17
17
|
/** What a key does in the terminal UI, or null with the reason it has none there (a named gap). */
|
|
18
|
-
export type TuiShortcut = "new" | "ink" | "send-back" | "retry" | "list" | "help";
|
|
18
|
+
export type TuiShortcut = "new" | "ink" | "send-back" | "retry" | "plan" | "list" | "help";
|
|
19
19
|
export type Shortcut = {
|
|
20
20
|
keys: string;
|
|
21
21
|
says: string;
|
|
@@ -30,6 +30,8 @@ export declare const INK_ACT_SELECTORS: readonly ["[data-ink-approve]", '[data-i
|
|
|
30
30
|
export declare const SEND_BACK_SELECTORS: readonly ['[data-act="request-changes"]', '[data-act="revise"]', 'a.result-feedback-link[href="#request-changes"]'];
|
|
31
31
|
/** Retry: the result's Retry, or the failed box's own Retry form. */
|
|
32
32
|
export declare const RETRY_SELECTORS: readonly ['[data-act="retry"]', 'form[data-retry] button[type="submit"]', 'form[data-rebuild] button[type="submit"]'];
|
|
33
|
+
/** Plan it: the task page's own plan button (local-owner slice 2). */
|
|
34
|
+
export declare const PLAN_SELECTORS: readonly ['form[action$="/plan"] button[type="submit"]'];
|
|
33
35
|
export declare const SHORTCUTS: readonly Shortcut[];
|
|
34
36
|
/** The keys typed, as the help list shows them: "g h" reads "g then h". */
|
|
35
37
|
export declare const keyWords: (keys: string) => string;
|
package/dist/keymap.js
CHANGED
|
@@ -8,11 +8,14 @@ export const INK_ACT_SELECTORS = ["[data-ink-approve]", '[data-ink-act="accept"]
|
|
|
8
8
|
export const SEND_BACK_SELECTORS = ['[data-act="request-changes"]', '[data-act="revise"]', 'a.result-feedback-link[href="#request-changes"]'];
|
|
9
9
|
/** Retry: the result's Retry, or the failed box's own Retry form. */
|
|
10
10
|
export const RETRY_SELECTORS = ['[data-act="retry"]', 'form[data-retry] button[type="submit"]', 'form[data-rebuild] button[type="submit"]'];
|
|
11
|
+
/** Plan it: the task page's own plan button (local-owner slice 2). */
|
|
12
|
+
export const PLAN_SELECTORS = ['form[action$="/plan"] button[type="submit"]'];
|
|
11
13
|
export const SHORTCUTS = [
|
|
12
14
|
{ keys: "n", says: "new task", web: { kind: "go", href: "/tasks/new" }, tui: "new" },
|
|
13
15
|
{ keys: "a", says: "Approve or Accept (presses the button on the page)", web: { kind: "press", selectors: INK_ACT_SELECTORS }, tui: "ink" },
|
|
14
16
|
{ keys: "b", says: "Send back (opens its note)", web: { kind: "press", selectors: SEND_BACK_SELECTORS }, tui: "send-back" },
|
|
15
17
|
{ keys: "r", says: "Retry (presses the button on the page)", web: { kind: "press", selectors: RETRY_SELECTORS }, tui: "retry" },
|
|
18
|
+
{ keys: "p", says: "Ask for a plan (presses the button on the page)", web: { kind: "press", selectors: PLAN_SELECTORS }, tui: "plan" },
|
|
16
19
|
{ keys: "/", says: "search", web: { kind: "search" }, tui: { none: "The terminal has no search; press w to open the console." } },
|
|
17
20
|
{ keys: "g h", says: "go Home", web: { kind: "go", href: "/" }, tui: "list" },
|
|
18
21
|
{ keys: "g t", says: "go to Tasks", web: { kind: "go", href: "/tasks" }, tui: "list" },
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The laptop's local owner's password-free acts beyond approving and accepting (docs/plans/local-owner-acts.md, slices
|
|
3
|
+
* 2 and 3): asking for a plan, and retrying its own approved build. One writer per act, shared by the terminal (the
|
|
4
|
+
* CLI rows `task plan` and `task requeue`, run as the local owner) and the browser (the task page's plan button and
|
|
5
|
+
* Retry, posted by a local owner's session). Each writer proves everything inside its own transaction: the local owner
|
|
6
|
+
* acting now as exactly this act (localActNow, never `proveAccountNow` saying yes to any act), the owner's current reach
|
|
7
|
+
* into the project, the one work rule (scope.ts localWorkEligible), the exact thing the screen read, and the act's own
|
|
8
|
+
* guards. Neither is ever chosen for anyone else: a password session, an API token, the lead, a chat bot or a machine
|
|
9
|
+
* keeps the road it had.
|
|
10
|
+
*/
|
|
11
|
+
import type { Store } from "./store.js";
|
|
12
|
+
/** What a refused local act says, and a reason the surfaces branch on. */
|
|
13
|
+
export type LocalActRefusal = {
|
|
14
|
+
ok: false;
|
|
15
|
+
reason: string;
|
|
16
|
+
message: string;
|
|
17
|
+
};
|
|
18
|
+
/** The ledger act a local owner's plan request is kept under: the planning source it was bound to, and its transport. */
|
|
19
|
+
export declare const LOCAL_PLAN_ACTION = "task plan (local owner)";
|
|
20
|
+
/** What a stale screen hears: the task is not what it read. */
|
|
21
|
+
export declare const LOCAL_TASK_CHANGED = "This task changed. Look at it again.";
|
|
22
|
+
/**
|
|
23
|
+
* The planning source a screen binds a plan request to (planner-source.ts): the digest over everything the planner
|
|
24
|
+
* would be given, the filed scope when there is one and the title alone when there is none. Null when it can't be read.
|
|
25
|
+
*/
|
|
26
|
+
export declare function planSourceOf(store: Store, taskId: string): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Ask for a plan, as the laptop's local owner, with no password (local-owner slice 2). In one transaction: the owner
|
|
29
|
+
* acting now as exactly `request-plan`; its current reach into the task's project; the task not put away; the one work
|
|
30
|
+
* rule for planning (no release, protected, two-person, escalated, coordinator, imported or unsandboxed work); no plan
|
|
31
|
+
* pins saved on the task (a local request never plans with an agent someone chose; it refuses rather than ignoring or
|
|
32
|
+
* clearing them, so what plans is exactly what the screen's terms say); and the planning source still the one the
|
|
33
|
+
* screen read (`source`). A request already made, with that source unchanged, answers "already" and writes nothing.
|
|
34
|
+
* Otherwise any unchanged-plan pre-authorization saved on the task is withdrawn (with its audit row), so the plan this
|
|
35
|
+
* asks for can only be approved by a person's own yes; then requestPlan's own guards (queued, not planned, not
|
|
36
|
+
* approved, no live claim); then the request's ledger row. It approves nothing, pins nothing and pre-authorizes nothing.
|
|
37
|
+
*/
|
|
38
|
+
export declare function requestPlanAsLocalOwner(store: Store, taskId: string, source: string, now: Date): {
|
|
39
|
+
ok: true;
|
|
40
|
+
replayed: boolean;
|
|
41
|
+
} | LocalActRefusal;
|
|
42
|
+
/** The ledger act a local owner's retry is kept under: the build it retried, the exact seal it spent, and its transport. */
|
|
43
|
+
export declare const LOCAL_RETRY_ACTION = "task requeue (local owner)";
|
|
44
|
+
/** Local retries per task and exact seal: then a note (Send back) or a password. */
|
|
45
|
+
export declare const LOCAL_RETRY_LIMIT = 3;
|
|
46
|
+
/** The fourth local retry of one approval. */
|
|
47
|
+
export declare const LOCAL_RETRY_SPENT = "You've retried this 3 times. Send it back with a note, or sign in with your password to retry again.";
|
|
48
|
+
/** What a retry hears while Toolroll can't yet prove the last build stopped. */
|
|
49
|
+
export declare const LOCAL_RETRY_STOPPING = "The last build is still stopping. Try again in a moment.";
|
|
50
|
+
/**
|
|
51
|
+
* What a Retry binds to (local-owner slice 3): the task's latest finished build and its approved scope digest, when the
|
|
52
|
+
* task failed or finished and its scope is approved as it stands. What a screen offers only; the writer decides.
|
|
53
|
+
*/
|
|
54
|
+
export declare function retryPinsOf(store: Store, taskId: string): {
|
|
55
|
+
run: number;
|
|
56
|
+
digest: string;
|
|
57
|
+
} | null;
|
|
58
|
+
/**
|
|
59
|
+
* Retry the local owner's own approved build after it stopped (a console restart, a model error), with no password
|
|
60
|
+
* (local-owner slice 3). One transaction, in this order:
|
|
61
|
+
* - the owner acting now as exactly `retry-build`, and its current reach into the task's project;
|
|
62
|
+
* - the task's current approval is this owner's own person yes on the exact seal (personApprovalOf: the ceremony or a
|
|
63
|
+
* chat tap, a person's proof for those bytes — never a mode, grant, schedule, automatic or carried seal, the lead, or
|
|
64
|
+
* another person), still standing for dispatch, and on work the owner could approve again now (localWorkEligible);
|
|
65
|
+
* - bound to what the screen read: `digest` is the approved scope digest, and `run` the latest finished build;
|
|
66
|
+
* - the same request again (this owner's retry of `run` under this seal) answers "already" and writes nothing while
|
|
67
|
+
* that build is still the latest one;
|
|
68
|
+
* - its own stop proof: no unfinished run of the task, no run after `run`, Toolroll's durable stop facts settled for
|
|
69
|
+
* every run (an expired claim alone proves nothing), no final result (accepted or published) in any task state, and a
|
|
70
|
+
* failed task or a finished one whose result wasn't accepted (requeueEligibility re-reads the claim and archive);
|
|
71
|
+
* - at most three local retries of this task under this exact seal (a fresh approval, even of the same bytes, is a new
|
|
72
|
+
* seal), then the words that lead to a note or a password;
|
|
73
|
+
* - then the requeue (incidents resolved, strikes cleared, as the console's Retry) and its ledger row.
|
|
74
|
+
* Nothing about the scope, route, model, chain, budget or permissions is written.
|
|
75
|
+
*/
|
|
76
|
+
export declare function retryAsLocalOwner(store: Store, taskId: string, pins: {
|
|
77
|
+
run: number;
|
|
78
|
+
digest: string;
|
|
79
|
+
}, now: Date): {
|
|
80
|
+
ok: true;
|
|
81
|
+
replayed: boolean;
|
|
82
|
+
retries: number;
|
|
83
|
+
} | LocalActRefusal;
|