toolroll 0.9.100 → 0.9.102
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 +15 -0
- package/dist/accent-colors.js +1 -1
- package/dist/activity-line.d.ts +1 -1
- package/dist/activity-line.js +1 -1
- package/dist/browser/terminal.css +1 -1
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +40 -40
- package/dist/browser-workspace.d.ts +78 -15
- package/dist/browser-workspace.js +5 -3
- package/dist/design/kit.css +94 -2
- package/dist/design/kit.d.ts +12 -4
- package/dist/design/kit.js +8 -2
- package/dist/design/tokens.css +5 -5
- package/dist/desktop-update.d.ts +1 -1
- package/dist/first-run.d.ts +10 -6
- package/dist/first-run.js +19 -18
- package/dist/guides.js +1 -1
- package/dist/operate.js +2 -0
- package/dist/page-work-contract.d.ts +21 -0
- package/dist/page-work-contract.js +5 -0
- package/dist/project-knowledge.d.ts +6 -6
- package/dist/run-efficiency.d.ts +3 -2
- package/dist/run-efficiency.js +4 -3
- package/dist/scope.d.ts +5 -0
- package/dist/scope.js +8 -1
- package/dist/serve.js +51 -9
- package/dist/server/chrome.d.ts +11 -2
- package/dist/server/chrome.js +10 -1
- package/dist/server/console.css +65 -21
- package/dist/server/home.d.ts +27 -14
- package/dist/server/home.js +78 -63
- package/dist/server/http.d.ts +6 -0
- package/dist/server/http.js +8 -0
- package/dist/server/in-place.d.ts +15 -0
- package/dist/server/in-place.js +32 -0
- package/dist/server/kit.d.ts +36 -3
- package/dist/server/kit.js +33 -4
- package/dist/server/pages.js +23 -2
- package/dist/server/render-chat.d.ts +2 -0
- package/dist/server/render-chat.js +1 -0
- package/dist/server/render-pages.js +1 -1
- package/dist/server/render-results.js +3 -3
- package/dist/server/render-tasks.d.ts +10 -0
- package/dist/server/render-tasks.js +245 -65
- package/dist/server/request-context.d.ts +14 -0
- package/dist/server/request-context.js +3 -0
- package/dist/server/task-views.js +11 -4
- package/dist/server/tasks.d.ts +8 -2
- package/dist/server/tasks.js +31 -17
- package/dist/settings-groups-ui.js +1 -1
- package/dist/store.js +14 -3
- package/dist/task-archive.d.ts +3 -0
- package/dist/task-archive.js +5 -0
- package/dist/task-status.d.ts +2 -2
- package/dist/task-status.js +3 -3
- package/dist/tui.js +1 -1
- package/dist/work-index.d.ts +14 -1
- package/dist/work-index.js +24 -8
- package/dist/workspace-ui.d.ts +69 -0
- package/dist/workspace-ui.js +103 -0
- package/package.json +1 -1
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { requestContext } from "./request-context.js";
|
|
1
2
|
import { heldFindingCount } from "../findings.js";
|
|
2
3
|
import { familyInWebReach } from "../findings.js";
|
|
3
4
|
import { existsSync } from "node:fs";
|
|
@@ -37,7 +38,7 @@ import { runActivityOf } from "../task-activity.js";
|
|
|
37
38
|
import { taskFingerprint } from "../task-live.js";
|
|
38
39
|
import { pullRequestFactOf } from '../task-status.js';
|
|
39
40
|
import { openWorkDecisionOf } from "../work-summary.js";
|
|
40
|
-
import { learningHtml } from "../workspace-ui.js";
|
|
41
|
+
import { learningHtml, stepRowsOf } from "../workspace-ui.js";
|
|
41
42
|
import { createHash, randomBytes } from "node:crypto";
|
|
42
43
|
import {} from "node:http";
|
|
43
44
|
import { agentChoicesFor, routeOfTask } from "../agentconfig.js";
|
|
@@ -54,7 +55,7 @@ import { isRevisionFeedback } from "../result-review.js";
|
|
|
54
55
|
import { buildReviewOf } from "../review-switch.js";
|
|
55
56
|
import { isAlive as runnerAlive } from "../runner.js";
|
|
56
57
|
import { approvalOf } from "../scope.js";
|
|
57
|
-
import { oneLineOf, refuse, screen } from "./chrome.js";
|
|
58
|
+
import { oneLineOf, refuse, screen, withScript } from "./chrome.js";
|
|
58
59
|
import { chatResultHref, reviewHref, taskChatHref, taskHref } from "./http.js";
|
|
59
60
|
import { decisionsFor } from "./render-chat.js";
|
|
60
61
|
import {} from "./render-pages.js";
|
|
@@ -661,6 +662,7 @@ export function createTaskViews(runtime) {
|
|
|
661
662
|
coordinator: view.coordinator ?? null,
|
|
662
663
|
origin: view.origin ?? null,
|
|
663
664
|
repairChain: view.repairChain ?? null,
|
|
665
|
+
releaseCiRunning: view.releaseCiRunning === true,
|
|
664
666
|
},
|
|
665
667
|
decisions: (view?.decisions ?? runtime.store.decisionsForTask(ref.id))
|
|
666
668
|
.filter(one => one.state === "open" || one.state === "expired")
|
|
@@ -816,7 +818,8 @@ export function createTaskViews(runtime) {
|
|
|
816
818
|
const admittedFamily = runtime.familyOf(taskId);
|
|
817
819
|
if (admittedFamily?.problem != null)
|
|
818
820
|
return runtime.sendScreen(response, status, screen(admittedFamily.root.title, html `<h1>${admittedFamily.root.title}</h1><p>${admittedFamily.current.state}</p><p class="problem" data-history-problem>${admittedFamily.problem}</p>`, { chrome: runtime.chromeFor(admittedFamily.root.repo, "tasks") }));
|
|
819
|
-
|
|
821
|
+
// A read ahead (x-toolroll-read: prefetch, live-pages-and-home.md A1.4) mints nothing: its plan form says "unminted".
|
|
822
|
+
const data = taskViewData(taskId, who, problem, { mintNonce: requestContext.getStore()?.readPurpose !== "prefetch" });
|
|
820
823
|
if (data === null)
|
|
821
824
|
return refuse(response, who, 404, "no such task", "/tasks");
|
|
822
825
|
const family = runtime.familyOf(taskId);
|
|
@@ -886,12 +889,16 @@ export function createTaskViews(runtime) {
|
|
|
886
889
|
if (oneTaskView.release != null)
|
|
887
890
|
oneTaskView.journey = null;
|
|
888
891
|
}
|
|
892
|
+
// The step rows read the view as the page will show it (its result, its release, its checks words).
|
|
893
|
+
if (oneTaskView?.kind === "task")
|
|
894
|
+
oneTaskView.steps = stepRowsOf(oneTaskView);
|
|
889
895
|
// The Result tab's no-script page, under the task's own (the same HTML its result page serves), and its script.
|
|
890
896
|
if (extras.result != null) {
|
|
891
897
|
page.body = html `${page.body}<section class="task-result-tab" id="task-result" aria-label="Result">${extras.result.html}</section>`;
|
|
892
898
|
// The result's own script, as its result page ships it (a password form there keeps it too: Request changes and
|
|
893
899
|
// the line notes need it).
|
|
894
|
-
|
|
900
|
+
// The app runs its own version (browser/work/result.ts), so a task with a result can open in place.
|
|
901
|
+
page.functional = withScript(page.functional, { script: `${reviewEvidenceScript()}${RESULT_REVIEW_SCRIPT}`, app: [{ kind: "result" }] });
|
|
895
902
|
}
|
|
896
903
|
if (docked !== null)
|
|
897
904
|
page.workspace = { ...page.workspace, ...(oneTaskView === undefined ? {} : { view: oneTaskView }), conversation: docked, pageHtml: page.body };
|
package/dist/server/tasks.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** tasks handlers, moved without changing their route bodies. */
|
|
2
2
|
import { type Html } from "../html.js";
|
|
3
|
-
import { type ServerResponse } from "node:http";
|
|
3
|
+
import { type IncomingMessage, type ServerResponse } from "node:http";
|
|
4
4
|
import { type Artifact, type Run, type Store } from "../store.js";
|
|
5
5
|
import type { HandlerContext } from './handler-context.js';
|
|
6
6
|
import type { ServerRuntime } from './runtime.js';
|
|
@@ -15,7 +15,7 @@ export declare function createTasksHandlers(runtime: ServerRuntime): {
|
|
|
15
15
|
body: Html;
|
|
16
16
|
retryAfter?: number;
|
|
17
17
|
}>;
|
|
18
|
-
taskMutation: (response: ServerResponse, who: Who, taskId: string, verb: string, posted: URLSearchParams, now: Date) => void;
|
|
18
|
+
taskMutation: (response: ServerResponse, who: Who, taskId: string, verb: string, posted: URLSearchParams, now: Date, request?: IncomingMessage | null) => void;
|
|
19
19
|
queueRegionFor: (project: string | null, csrf: string, revision: number, now: Date) => Html;
|
|
20
20
|
ciFailingFor: (runId: number | null, prNumber: number | null) => boolean;
|
|
21
21
|
completedRowFor: (taskId: string, project: string | null) => CompletedWorkRow | null;
|
|
@@ -75,3 +75,9 @@ export declare function createReleaseSheetHandlers(runtime: ServerRuntime): {
|
|
|
75
75
|
sheetScreen: (ctx: Pick<HandlerContext, "who" | "response" | "project">, taskId: string, problem: string | null, status: number) => void;
|
|
76
76
|
approveFromSheet: (ctx: Pick<HandlerContext, "who" | "response" | "project">, taskId: string, cardDigest: string, token: string) => void;
|
|
77
77
|
};
|
|
78
|
+
/** What a plan approval asked in place came to (live-pages-and-home.md B2.3): approved; a first yes on protected work
|
|
79
|
+
* recorded; approved but building again did not start; or refused, with nothing changed. */
|
|
80
|
+
export type ApprovalOutcome = "approved" | "vote" | "approved-not-rebuilt" | "refused";
|
|
81
|
+
/** The answer to an approval asked in place: its named outcome and the same words the page would show, as JSON. Written
|
|
82
|
+
* only after the approve route's proofs, by the caller that knows the outcome. */
|
|
83
|
+
export declare function answerApprovalInPlace(response: ServerResponse, outcome: ApprovalOutcome, words: string, status: number): void;
|
package/dist/server/tasks.js
CHANGED
|
@@ -80,7 +80,7 @@ import { buildProgressOf, parseWorkView } from "../workspace-ui.js";
|
|
|
80
80
|
import { requestContext } from "./request-context.js";
|
|
81
81
|
import { boardTickScript, oneLineOf, QUEUE_VIEW, refuse, regionScript, screen, transcriptScript, whenTime } from "./chrome.js";
|
|
82
82
|
import { retiredRedirect } from "./retired-routes.js";
|
|
83
|
-
import { chatReturnWithSaid, matchTaskPath, redirect, respond, reviewHref, safeChatReturn, safeReturn, SAFETY, taskChatHref, taskHref } from "./http.js";
|
|
83
|
+
import { answersInPlace, chatReturnWithSaid, matchTaskPath, redirect, respond, reviewHref, safeChatReturn, safeReturn, SAFETY, taskChatHref, taskHref } from "./http.js";
|
|
84
84
|
import { putAwayPage, boardBody, boardWorkers, QUEUE_FRONT, queueBody, queueScript, TASK_STATES, workPage } from "./render-pages.js";
|
|
85
85
|
import { runEfficiencyOf } from "../run-efficiency.js";
|
|
86
86
|
import { timingFor } from "../latency-timeline.js";
|
|
@@ -1165,7 +1165,7 @@ export function createTasksHandlers(runtime) {
|
|
|
1165
1165
|
const act = matchTaskPath(url.pathname, `/${verb}$`);
|
|
1166
1166
|
if (act === null)
|
|
1167
1167
|
return noPage(who, response);
|
|
1168
|
-
return taskMutation(response, who, act.taskId, verb, posted, now);
|
|
1168
|
+
return taskMutation(response, who, act.taskId, verb, posted, now, ctx.request);
|
|
1169
1169
|
};
|
|
1170
1170
|
// Cockpit E: Show in Finder — the checkout proved again right now (live, this machine's runner, inside the pool),
|
|
1171
1171
|
// then Finder asked to reveal it with a fixed argv. Nothing runs in it; a Mac only.
|
|
@@ -1632,7 +1632,9 @@ export function createTasksHandlers(runtime) {
|
|
|
1632
1632
|
}
|
|
1633
1633
|
}
|
|
1634
1634
|
// ---- mutations -----------------------------------------------------------
|
|
1635
|
-
function taskMutation(response, who, taskId, verb, posted, now
|
|
1635
|
+
function taskMutation(response, who, taskId, verb, posted, now,
|
|
1636
|
+
/** Read only to shape an answer in place (answersInPlace), after the act's proofs. */
|
|
1637
|
+
request = null) {
|
|
1636
1638
|
const ref = store.lookupRef(taskId);
|
|
1637
1639
|
if (ref === null || store.getTask(taskId) === null) {
|
|
1638
1640
|
return refuse(response, who, 404, "no such task", "/tasks");
|
|
@@ -2277,9 +2279,13 @@ export function createTasksHandlers(runtime) {
|
|
|
2277
2279
|
: safeChatReturn(requestedReturn);
|
|
2278
2280
|
// The release sheet's Approve release (trusted-release-reports.md A7): the same door, its own page for words.
|
|
2279
2281
|
const sheet = body.get("sheet") === "release";
|
|
2280
|
-
|
|
2282
|
+
// Each caller names its outcome (live-pages-and-home.md B2.3); none is inferred from the status.
|
|
2283
|
+
const approvalProblem = (outcome, message, status) => {
|
|
2281
2284
|
if (sheet)
|
|
2282
2285
|
return releaseSheet.sheetScreen({ who, response, project: null }, taskId, message, status);
|
|
2286
|
+
// Asked in place: the same proofs ran, only the answer's shape differs.
|
|
2287
|
+
if (answersInPlace(request))
|
|
2288
|
+
return answerApprovalInPlace(response, outcome, message, status);
|
|
2283
2289
|
if (requestedReturn !== null && requestedReturn !== "next") {
|
|
2284
2290
|
return redirect(response, chatReturnWithSaid(approvalBack, message));
|
|
2285
2291
|
}
|
|
@@ -2287,7 +2293,7 @@ export function createTasksHandlers(runtime) {
|
|
|
2287
2293
|
};
|
|
2288
2294
|
const approvingRef = store.lookupRef(taskId);
|
|
2289
2295
|
if (approvingRef?.plan === "requested") {
|
|
2290
|
-
return approvalProblem("approval is blocked while planning is in progress — review the drafted plan first", 409);
|
|
2296
|
+
return approvalProblem("refused", "approval is blocked while planning is in progress — review the drafted plan first", 409);
|
|
2291
2297
|
}
|
|
2292
2298
|
// Step-up: the session got you here; only the token agrees. The
|
|
2293
2299
|
// digest names what was seen; the nonce proves this exact form was
|
|
@@ -2296,25 +2302,25 @@ export function createTasksHandlers(runtime) {
|
|
|
2296
2302
|
const token = body.get("token") ?? "";
|
|
2297
2303
|
const nonce = body.get("nonce") ?? "";
|
|
2298
2304
|
if (!consumeApprovalNonce(nonce, who.name, taskId, digest)) {
|
|
2299
|
-
return approvalProblem("that approval form is stale — read it again", 409);
|
|
2305
|
+
return approvalProblem("refused", "that approval form is stale — read it again", 409);
|
|
2300
2306
|
}
|
|
2301
2307
|
// An empty password stands for an explicit identity-provider re-check not yet spent, or (cockpit B2) a password
|
|
2302
2308
|
// remembered on this session; approve() decides whether these terms may stand on a remembered one.
|
|
2303
2309
|
// Easy-start C5: a laptop's local owner approves eligible work with no password (approve() proves it again).
|
|
2304
2310
|
if (token === "" && !passwordlessSessionFor(store, who.name) && !ssoNowAvailable(who.name) && withFreshness("approve-standard", () => freshnessFor(who.name)) === null
|
|
2305
2311
|
&& !(who.authority === "local-owner" && localApprovalEligible(store, who.name, taskId).ok)) {
|
|
2306
|
-
return approvalProblem("approval requires your password, typed again", 400);
|
|
2312
|
+
return approvalProblem("refused", "approval requires your password, typed again", 400);
|
|
2307
2313
|
}
|
|
2308
2314
|
// The sheet binds the release card's digest (the nonce was minted for it): the shared card wrapper decides.
|
|
2309
2315
|
if (sheet)
|
|
2310
2316
|
return releaseSheet.approveFromSheet({ who, response, project: null }, taskId, digest, token);
|
|
2311
2317
|
const scopeRow = store.getScope(taskId);
|
|
2312
2318
|
if (scopeRow === null)
|
|
2313
|
-
return approvalProblem("this task has no scope to approve", 409);
|
|
2319
|
+
return approvalProblem("refused", "this task has no scope to approve", 409);
|
|
2314
2320
|
const planView = planViewOf(ref.id);
|
|
2315
2321
|
const expectedDigest = approvalFormDigest(scopeRow.digest, planView?.sha256 ?? null);
|
|
2316
2322
|
if (digest !== expectedDigest) {
|
|
2317
|
-
return approvalProblem("the scope or plan changed while this form was open — read the latest version and approve again", 409);
|
|
2323
|
+
return approvalProblem("refused", "the scope or plan changed while this form was open — read the latest version and approve again", 409);
|
|
2318
2324
|
}
|
|
2319
2325
|
// A revision approves ONLY against a brief that still verifies
|
|
2320
2326
|
// (Codex M5-M8 audit, IV-3): the batch the screen restated must be
|
|
@@ -2324,7 +2330,7 @@ export function createTasksHandlers(runtime) {
|
|
|
2324
2330
|
if (approvingRef !== null && approvingRef.revisionBriefArtifact !== null) {
|
|
2325
2331
|
const view = revisionViewOf(approvingRef);
|
|
2326
2332
|
if (view !== null && "problem" in view) {
|
|
2327
|
-
return approvalProblem(`approval is blocked: ${view.problem}`, 409);
|
|
2333
|
+
return approvalProblem("refused", `approval is blocked: ${view.problem}`, 409);
|
|
2328
2334
|
}
|
|
2329
2335
|
}
|
|
2330
2336
|
// A re-check is spent only if this yes is kept (sealed, or a protected-work vote recorded).
|
|
@@ -2332,11 +2338,11 @@ export function createTasksHandlers(runtime) {
|
|
|
2332
2338
|
if (!approved.ok) {
|
|
2333
2339
|
// v102: a first yes on protected work is recorded, not refused — the page says who else is needed.
|
|
2334
2340
|
if (approved.reason === "second-approver")
|
|
2335
|
-
return approvalProblem(gateWords({ verdict: "vote", have: approved.have, need: 2, already: approved.already }), 200);
|
|
2341
|
+
return approvalProblem("vote", gateWords({ verdict: "vote", have: approved.have, need: 2, already: approved.already }), 200);
|
|
2336
2342
|
if (approved.reason === "policy")
|
|
2337
|
-
return approvalProblem(approved.message, 403);
|
|
2343
|
+
return approvalProblem("refused", approved.message, 403);
|
|
2338
2344
|
const status = approved.reason === "changed" || approved.reason === "unrouted" ? 409 : 403;
|
|
2339
|
-
return approvalProblem(approveRefusalWords(approved.reason), status);
|
|
2345
|
+
return approvalProblem("refused", approveRefusalWords(approved.reason), status);
|
|
2340
2346
|
}
|
|
2341
2347
|
// Approve & build again (the plan was rewritten after a build): the same yes requeues, only for the build the
|
|
2342
2348
|
// sheet named, and only while it is still the task's latest result, made to an earlier plan than this one.
|
|
@@ -2348,10 +2354,12 @@ export function createTasksHandlers(runtime) {
|
|
|
2348
2354
|
const offered = named != null && named.taskRef === ref.id && named.role === "builder" && !!named.scopeDigest && named.scopeDigest !== scopeRow.digest;
|
|
2349
2355
|
const requeued = offered ? store.requeueTask(taskId, who.name, now, { run: named.id, scopeDigest: scopeRow.digest }) : null;
|
|
2350
2356
|
if (requeued === null || (!requeued.ok && requeued.reason === "moved"))
|
|
2351
|
-
return approvalProblem("Approved. The task changed while this page was open, so it wasn't built again — check its status.", 200);
|
|
2357
|
+
return approvalProblem("approved-not-rebuilt", "Approved. The task changed while this page was open, so it wasn't built again — check its status.", 200);
|
|
2352
2358
|
if (!requeued.ok)
|
|
2353
|
-
return approvalProblem(`Approved, but building again didn't start (${requeued.reason}). Use Build again.`, 200);
|
|
2359
|
+
return approvalProblem("approved-not-rebuilt", `Approved, but building again didn't start (${requeued.reason}). Use Build again.`, 200);
|
|
2354
2360
|
}
|
|
2361
|
+
if (!sheet && answersInPlace(request))
|
|
2362
|
+
return answerApprovalInPlace(response, "approved", "Approved.", 200);
|
|
2355
2363
|
return redirect(response, approvalBack);
|
|
2356
2364
|
}
|
|
2357
2365
|
case "accept-proof": {
|
|
@@ -2759,9 +2767,10 @@ export function createTasksHandlers(runtime) {
|
|
|
2759
2767
|
summary: detail.handoff?.conclusion ?? run.handoff,
|
|
2760
2768
|
},
|
|
2761
2769
|
detail,
|
|
2762
|
-
// How it ran: derived from the run's own records once it has finished, display only.
|
|
2770
|
+
// How it ran: derived from the run's own records once it has finished, display only. A read ahead of the page's
|
|
2771
|
+
// link (x-toolroll-read: prefetch) derives it and saves nothing.
|
|
2763
2772
|
efficiency: (() => { try {
|
|
2764
|
-
return runEfficiencyOf(store, evidenceRoot, run.id, now);
|
|
2773
|
+
return runEfficiencyOf(store, evidenceRoot, run.id, now, requestContext.getStore()?.readPurpose !== "prefetch");
|
|
2765
2774
|
}
|
|
2766
2775
|
catch {
|
|
2767
2776
|
return null;
|
|
@@ -3325,3 +3334,8 @@ export function createReleaseSheetHandlers(runtime) {
|
|
|
3325
3334
|
}
|
|
3326
3335
|
return { routes: { "task.release": showSheet, "task.release.go-on": goOn, "release.locate": locate }, sheetScreen, approveFromSheet };
|
|
3327
3336
|
}
|
|
3337
|
+
/** The answer to an approval asked in place: its named outcome and the same words the page would show, as JSON. Written
|
|
3338
|
+
* only after the approve route's proofs, by the caller that knows the outcome. */
|
|
3339
|
+
export function answerApprovalInPlace(response, outcome, words, status) {
|
|
3340
|
+
respond(response, status, "application/json; charset=utf-8", JSON.stringify({ outcome, words }));
|
|
3341
|
+
}
|
|
@@ -148,7 +148,7 @@ export function generalGroup(facts, flags) {
|
|
|
148
148
|
formRow("Theme", "Light, dark, or whatever this device uses.", postForm("/settings/appearance", html `<div class="k-seg" role="group" aria-label="Theme">${themeButton("system", "Match device")}${themeButton("light", "Light")}${themeButton("dark", "Dark")}</div>`, { returnTo: "/settings/general" })),
|
|
149
149
|
kitSectionForm({ proof: "appearance", action: "/settings/appearance", id: "accent", gate: true, hidden: { return: "/settings/general" },
|
|
150
150
|
body: joinHtml([
|
|
151
|
-
formRow("Accent", "Buttons
|
|
151
|
+
formRow("Accent", "Buttons, focus and what needs you.", select({ name: "accent", label: "Accent", current: facts.accent, items: [...ACCENT_CHOICES.map(one => [one.hex, one.id === "ink" ? "Ink (default)" : one.name]), ...(custom === null ? [] : [[custom, `Your own colour (${custom})`]])] })),
|
|
152
152
|
accentNoteWords === null ? "" : note(accentNoteWords),
|
|
153
153
|
]) }),
|
|
154
154
|
disclose("Use your own colour", kitSectionForm({ proof: "appearance", action: "/settings/appearance", id: "accent-own", gate: true, hidden: { return: "/settings/general" },
|
package/dist/store.js
CHANGED
|
@@ -16,7 +16,7 @@ import { V156_SCHEMA, V156_STEPS } from "./fallback-schema.js";
|
|
|
16
16
|
import { V158_ADDED_COLUMNS, V158_SCHEMA, V158_STEPS, LEAD_FAILURE_DETAILS, LEAD_ROUTES } from "./lead-latency-schema.js";
|
|
17
17
|
import { V157_SCHEMA, V157_STEPS } from "./chat-live-schema.js";
|
|
18
18
|
import { V159_ADDED_COLUMNS, V159_SCHEMA, V159_STEPS } from "./one-step-chat-schema.js";
|
|
19
|
-
import { refArchived, taskArchived } from "./task-archive.js";
|
|
19
|
+
import { refArchived, taskArchived, taskCancelled } from "./task-archive.js";
|
|
20
20
|
export { refArchived, taskArchived } from "./task-archive.js";
|
|
21
21
|
import { CHAT_PROVIDERS, CHAT_SCHEMA } from "./contracts/chat-tables.js";
|
|
22
22
|
import {} from "./chat-core.js";
|
|
@@ -8073,7 +8073,11 @@ export class Store {
|
|
|
8073
8073
|
* exactly what the signed digest was bound to. Returns false when the
|
|
8074
8074
|
* scope vanished mid-ceremony. */
|
|
8075
8075
|
sealScopeApproval(taskId, by, now, mutation = {}, basis, approverKind) {
|
|
8076
|
-
|
|
8076
|
+
// ONE TRANSACTION, the primitive's own (Codex review of the steps page, round 2): every check below is read in
|
|
8077
|
+
// the same write transaction as the seal and the release authority it opens, so a task cancelled or put away on
|
|
8078
|
+
// another connection can't slip between the check and the write, and a seal whose release authority can't open
|
|
8079
|
+
// rolls back whole even when the caller opened no transaction. Reentrant: inside a caller's transaction it joins.
|
|
8080
|
+
const seal = () => this.once(mutation, "sealScopeApproval", () => {
|
|
8077
8081
|
// ONLY A PERSON'S SEAL HAS NO BASIS (approval policy, phase 1): with none given this is the person's
|
|
8078
8082
|
// ceremony (scope.ts approve), so a machine — the lead's token, a remote API run — never mints one, whatever
|
|
8079
8083
|
// road it came by.
|
|
@@ -8082,6 +8086,10 @@ export class Store {
|
|
|
8082
8086
|
// A PUT-AWAY TASK (v145, task-tidy.md) is never approved, by any road: restore it first.
|
|
8083
8087
|
if (taskArchived(this, taskId))
|
|
8084
8088
|
return false;
|
|
8089
|
+
// A CANCELLED TASK the same (Codex review of the steps page, finding 2): no seal, and so no release authority,
|
|
8090
|
+
// is written for work that was cancelled — whichever road asks (a stale form, the CLI, a rule, the schedule).
|
|
8091
|
+
if (taskCancelled(this, taskId))
|
|
8092
|
+
return false;
|
|
8085
8093
|
// A RELEASE WAITING FOR CI (Codex review round 1, finding 4): filed while its pull request's checks still ran,
|
|
8086
8094
|
// only the automatic road (which proves CI itself) seals it until the plane read exact-head CI passing.
|
|
8087
8095
|
if (basis?.kind !== "auto-check" && basis?.kind !== "auto-green" && releaseCiHoldOf(this, taskId) !== null)
|
|
@@ -8308,7 +8316,10 @@ export class Store {
|
|
|
8308
8316
|
}
|
|
8309
8317
|
}
|
|
8310
8318
|
return changed.changes > 0;
|
|
8311
|
-
}) === true
|
|
8319
|
+
}) === true;
|
|
8320
|
+
// Already inside a transaction (a caller's transact(), or a savepoint opened outside one): it joins that one,
|
|
8321
|
+
// which owns the commit and the rollback. Otherwise it opens its own.
|
|
8322
|
+
return this.db.isTransaction === true ? seal() : this.transact(seal);
|
|
8312
8323
|
}
|
|
8313
8324
|
/** Whether this task's scope carries a LIVE seal: approved, and the
|
|
8314
8325
|
* approved digest is the current one (a rewritten scope unseals). The
|
package/dist/task-archive.d.ts
CHANGED
|
@@ -8,3 +8,6 @@ export declare function taskArchived(store: Store, taskId: string): boolean;
|
|
|
8
8
|
/** Whether a flow card's work is put away: its current task or its first build task (a later stage keeps the first
|
|
9
9
|
* and may clear the current one). Read from the card's own row now, so a put away that committed first holds it. */
|
|
10
10
|
export declare function cardTaskArchived(store: Store, cardId: number): boolean;
|
|
11
|
+
/** Whether the built-in task with this id is cancelled. Every door that approves, seals or carries a release asks
|
|
12
|
+
* this beside the put-away check: a cancelled task takes no new yes (fail closed), by any road. */
|
|
13
|
+
export declare function taskCancelled(store: Store, taskId: string): boolean;
|
package/dist/task-archive.js
CHANGED
|
@@ -14,3 +14,8 @@ export function cardTaskArchived(store, cardId) {
|
|
|
14
14
|
return store.handle.prepare(`SELECT 1 FROM flow_card c JOIN task_ref r ON r.backend = 'built-in' AND r.external_id IN (c.task, c.primary_task)
|
|
15
15
|
JOIN task_archive a ON a.task_ref = r.id WHERE c.id = ?`).get(cardId) !== undefined;
|
|
16
16
|
}
|
|
17
|
+
/** Whether the built-in task with this id is cancelled. Every door that approves, seals or carries a release asks
|
|
18
|
+
* this beside the put-away check: a cancelled task takes no new yes (fail closed), by any road. */
|
|
19
|
+
export function taskCancelled(store, taskId) {
|
|
20
|
+
return store.handle.prepare("SELECT 1 FROM task WHERE id = ? AND state = 'cancelled'").get(taskId) !== undefined;
|
|
21
|
+
}
|
package/dist/task-status.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ export declare const OPEN_RESULT = "Open result";
|
|
|
18
18
|
export declare const MACHINE_CHECK_RUNNING = "Running the check on the prepared version.";
|
|
19
19
|
export type DetailKey = "checks" | "pull-request" | "requirements" | "evidence";
|
|
20
20
|
/** The row's small icon: the only coloured part of a detail. `failed` is red
|
|
21
|
-
* and only ever appears under the Failed headline; `note` is
|
|
21
|
+
* and only ever appears under the Failed headline; `note` is ink, with a caution mark. */
|
|
22
22
|
export type DetailMark = "ok" | "running" | "none" | "note" | "failed";
|
|
23
23
|
export type StatusAction = {
|
|
24
24
|
label: string;
|
|
@@ -302,7 +302,7 @@ export declare function statusWhyHtml(status: TaskStatus, extra?: readonly strin
|
|
|
302
302
|
detail: string;
|
|
303
303
|
}[]): Html;
|
|
304
304
|
/** Shared CSS for the server-rendered status: neutral rows, coloured icons. */
|
|
305
|
-
export declare const TASK_STATUS_CSS = ".status-headline{display:flex;align-items:center;gap:.6rem;margin:0;font-size:1.125rem;font-weight:600;color:var(--foreground)}.status-headline i{width:.625rem;height:.625rem;border-radius:999px;flex-shrink:0;background:var(--muted-foreground)}[data-headline-tone=live] .status-headline i{background:var(--so-info,#0d74ce)}[data-headline-tone=attention] .status-headline i{background:var(--so-attention,var(--attention))}[data-headline-tone=ready] .status-headline i{background:transparent;box-shadow:inset 0 0 0 2px var(--so-success,#218358)}[data-headline-tone=success] .status-headline i{background:var(--so-success,#218358)}[data-headline-tone=danger] .status-headline i{background:var(--so-danger,#c4320a)}.status-sentence{margin:.35rem 0 0;color:var(--muted-foreground)}.status-details{list-style:none;margin:.75rem 0 0;padding:.25rem 0 0;border-top:1px solid var(--so-line,var(--border))}.status-detail{display:grid;grid-template-columns:14px 7.5rem minmax(0,1fr);align-items:center;gap:.6rem;padding:.4rem 0;font-size:.8125rem;color:var(--foreground)}.status-detail .status-icon{align-self:center;color:var(--muted-foreground)}.status-detail--ok .status-icon{color:var(--so-success,#218358)}.status-detail--running .status-icon{color:var(--so-info,#0d74ce)}.status-detail--note .status-icon{color:var(--so-
|
|
305
|
+
export declare const TASK_STATUS_CSS = ".status-headline{display:flex;align-items:center;gap:.6rem;margin:0;font-size:1.125rem;font-weight:600;color:var(--foreground)}.status-headline i{width:.625rem;height:.625rem;border-radius:999px;flex-shrink:0;background:var(--muted-foreground)}[data-headline-tone=live] .status-headline i{background:var(--so-info,#0d74ce)}[data-headline-tone=attention] .status-headline i{background:var(--so-attention,var(--attention))}[data-headline-tone=ready] .status-headline i{background:transparent;box-shadow:inset 0 0 0 2px var(--so-success,#218358)}[data-headline-tone=success] .status-headline i{background:var(--so-success,#218358)}[data-headline-tone=danger] .status-headline i{background:var(--so-danger,#c4320a)}.status-sentence{margin:.35rem 0 0;color:var(--muted-foreground)}.status-details{list-style:none;margin:.75rem 0 0;padding:.25rem 0 0;border-top:1px solid var(--so-line,var(--border))}.status-detail{display:grid;grid-template-columns:14px 7.5rem minmax(0,1fr);align-items:center;gap:.6rem;padding:.4rem 0;font-size:.8125rem;color:var(--foreground)}.status-detail .status-icon{align-self:center;color:var(--muted-foreground)}.status-detail--ok .status-icon{color:var(--so-success,#218358)}.status-detail--running .status-icon{color:var(--so-info,#0d74ce)}.status-detail--note .status-icon{color:var(--so-ink)}.status-detail--failed .status-icon{color:var(--so-danger,#c4320a)}.status-detail-label{color:var(--muted-foreground)}.status-detail-text{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 .6rem;min-width:0;overflow-wrap:anywhere}.status-detail-text a{color:inherit}.status-detail-act{font-weight:600;white-space:nowrap}.status-detail--note .status-detail-act{color:var(--so-ink)}@media(max-width:480px){.status-detail{grid-template-columns:14px 6.5rem minmax(0,1fr);min-height:2.75rem;padding:.25rem 0}.status-detail-text a{display:inline-block;padding:.75rem 0;margin:-.75rem 0}}.status-why{border:0;padding:0;margin:.25rem 0 0;background:transparent;box-shadow:none}.status-why>summary{cursor:pointer;min-height:2.75rem;display:list-item;align-content:center;font-size:.8125rem;color:var(--muted-foreground)}.status-why p{margin:.25rem 0 .5rem}.receipt-note{display:flex;align-items:center;gap:.45rem;font-size:.8125rem;color:var(--foreground)}.receipt-note .status-icon{color:var(--so-ink)}";
|
|
306
306
|
/** A dispatch diagnosis as its stage, for the phone and CLI reads that hold
|
|
307
307
|
* no assignment. A done task is Complete once a person marked its result;
|
|
308
308
|
* a refuted result whose project check failed is Failed. */
|
package/dist/task-status.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Severity: red belongs to the Failed headline alone. A detail problem that
|
|
10
10
|
* doesn't undo the outcome (a pull request that couldn't open, shortened
|
|
11
|
-
* output) is an
|
|
11
|
+
* output) is an ink note on its own row with one action; the exact
|
|
12
12
|
* technical reason stays one tap away in `why`. */
|
|
13
13
|
import { DEFAULT_LEAD_NAME } from "./lead-identity.js";
|
|
14
14
|
import { html } from "./html.js";
|
|
@@ -209,7 +209,7 @@ function sentenceOf(headline, facts) {
|
|
|
209
209
|
case "Stopped": return reason ?? "Stopped by a person. The work so far is kept.";
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
|
-
/** A detail problem: red only under Failed, otherwise an
|
|
212
|
+
/** A detail problem: red only under Failed, otherwise an ink note. */
|
|
213
213
|
const problem = (headline) => headline === "Failed" ? "failed" : "note";
|
|
214
214
|
function detailsOf(headline, facts) {
|
|
215
215
|
const rows = [];
|
|
@@ -627,7 +627,7 @@ export function statusWhyHtml(status, extra = [], diagnostics = []) {
|
|
|
627
627
|
return html `<details class="status-why"><summary>${STATUS_MORE}</summary>${lines.map(one => html `<p class="meta">${one}</p>`)}${diagnostics.map(one => html `<p class="meta" data-work-diagnostic="${one.token}">${one.label} · ${one.detail}</p>`)}</details>`;
|
|
628
628
|
}
|
|
629
629
|
/** Shared CSS for the server-rendered status: neutral rows, coloured icons. */
|
|
630
|
-
export const TASK_STATUS_CSS = `.status-headline{display:flex;align-items:center;gap:.6rem;margin:0;font-size:1.125rem;font-weight:600;color:var(--foreground)}.status-headline i{width:.625rem;height:.625rem;border-radius:999px;flex-shrink:0;background:var(--muted-foreground)}[data-headline-tone=live] .status-headline i{background:var(--so-info,#0d74ce)}[data-headline-tone=attention] .status-headline i{background:var(--so-attention,var(--attention))}[data-headline-tone=ready] .status-headline i{background:transparent;box-shadow:inset 0 0 0 2px var(--so-success,#218358)}[data-headline-tone=success] .status-headline i{background:var(--so-success,#218358)}[data-headline-tone=danger] .status-headline i{background:var(--so-danger,#c4320a)}.status-sentence{margin:.35rem 0 0;color:var(--muted-foreground)}.status-details{list-style:none;margin:.75rem 0 0;padding:.25rem 0 0;border-top:1px solid var(--so-line,var(--border))}.status-detail{display:grid;grid-template-columns:14px 7.5rem minmax(0,1fr);align-items:center;gap:.6rem;padding:.4rem 0;font-size:.8125rem;color:var(--foreground)}.status-detail .status-icon{align-self:center;color:var(--muted-foreground)}.status-detail--ok .status-icon{color:var(--so-success,#218358)}.status-detail--running .status-icon{color:var(--so-info,#0d74ce)}.status-detail--note .status-icon{color:var(--so-
|
|
630
|
+
export const TASK_STATUS_CSS = `.status-headline{display:flex;align-items:center;gap:.6rem;margin:0;font-size:1.125rem;font-weight:600;color:var(--foreground)}.status-headline i{width:.625rem;height:.625rem;border-radius:999px;flex-shrink:0;background:var(--muted-foreground)}[data-headline-tone=live] .status-headline i{background:var(--so-info,#0d74ce)}[data-headline-tone=attention] .status-headline i{background:var(--so-attention,var(--attention))}[data-headline-tone=ready] .status-headline i{background:transparent;box-shadow:inset 0 0 0 2px var(--so-success,#218358)}[data-headline-tone=success] .status-headline i{background:var(--so-success,#218358)}[data-headline-tone=danger] .status-headline i{background:var(--so-danger,#c4320a)}.status-sentence{margin:.35rem 0 0;color:var(--muted-foreground)}.status-details{list-style:none;margin:.75rem 0 0;padding:.25rem 0 0;border-top:1px solid var(--so-line,var(--border))}.status-detail{display:grid;grid-template-columns:14px 7.5rem minmax(0,1fr);align-items:center;gap:.6rem;padding:.4rem 0;font-size:.8125rem;color:var(--foreground)}.status-detail .status-icon{align-self:center;color:var(--muted-foreground)}.status-detail--ok .status-icon{color:var(--so-success,#218358)}.status-detail--running .status-icon{color:var(--so-info,#0d74ce)}.status-detail--note .status-icon{color:var(--so-ink)}.status-detail--failed .status-icon{color:var(--so-danger,#c4320a)}.status-detail-label{color:var(--muted-foreground)}.status-detail-text{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 .6rem;min-width:0;overflow-wrap:anywhere}.status-detail-text a{color:inherit}.status-detail-act{font-weight:600;white-space:nowrap}.status-detail--note .status-detail-act{color:var(--so-ink)}@media(max-width:480px){.status-detail{grid-template-columns:14px 6.5rem minmax(0,1fr);min-height:2.75rem;padding:.25rem 0}.status-detail-text a{display:inline-block;padding:.75rem 0;margin:-.75rem 0}}.status-why{border:0;padding:0;margin:.25rem 0 0;background:transparent;box-shadow:none}.status-why>summary{cursor:pointer;min-height:2.75rem;display:list-item;align-content:center;font-size:.8125rem;color:var(--muted-foreground)}.status-why p{margin:.25rem 0 .5rem}.receipt-note{display:flex;align-items:center;gap:.45rem;font-size:.8125rem;color:var(--foreground)}.receipt-note .status-icon{color:var(--so-ink)}`;
|
|
631
631
|
/** A dispatch diagnosis as its stage, for the phone and CLI reads that hold
|
|
632
632
|
* no assignment. A done task is Complete once a person marked its result;
|
|
633
633
|
* a refuted result whose project check failed is Failed. */
|
package/dist/tui.js
CHANGED
|
@@ -67,7 +67,7 @@ Read about how to prevent this error on https://github.com/vadimdemedes/ink/#isr
|
|
|
67
67
|
`))}patchConsole(){this.options.debug||(this.restoreConsole=_C((u,a)=>{u==="stdout"&&this.writeToStdout(a),u==="stderr"&&(a.startsWith("The above error occurred")||this.writeToStderr(a))}))}initKittyKeyboard(){if(!this.options.kittyKeyboard)return;let u=this.options.kittyKeyboard,a=u.mode??"auto";if(a==="disabled"||!this.options.stdin.isTTY||!this.options.stdout.isTTY)return;let l=u.flags??["disambiguateEscapeCodes"];if(a==="enabled"){this.enableKittyProtocol(l);return}let r=xi.env.TERM??"",E=xi.env.TERM_PROGRAM??"",B="KITTY_WINDOW_ID"in xi.env||r==="xterm-kitty"||E==="WezTerm"||E==="ghostty";!qe&&B&&this.confirmKittySupport(l)}confirmKittySupport(u){let{stdin:a,stdout:l}=this.options,r=[],E=()=>{this.cancelKittyDetection=void 0,clearTimeout(c),a.removeListener("data",B);let Q=Cy(r);r=[],Q.length>0&&a.unshift(Buffer.from(Q))},B=Q=>{let y=typeof Q=="string"?Buffer.from(Q):Q;for(let N of y)r.push(N);sy(r)&&(E(),this.isUnmounted||this.enableKittyProtocol(u))};a.on("data",B);let c=setTimeout(E,200);this.cancelKittyDetection=E,l.write("\x1B[?u")}enableKittyProtocol(u){this.options.stdout.write(`\x1B[>${_Q(u)}u`),this.kittyProtocolEnabled=!0}};var cy=(e,u)=>{let a={stdout:xo.stdout,stdin:xo.stdin,stderr:xo.stderr,debug:!1,exitOnCtrlC:!0,patchConsole:!0,maxFps:30,incrementalRendering:!1,concurrent:!1,...Qy(u)},l=fy(a.stdout,()=>new za(a),a.concurrent??!1);return l.render(e),{rerender:l.render,unmount(){l.unmount()},waitUntilExit:l.waitUntilExit,cleanup:()=>Ui.delete(a.stdout),clear:l.clear}},TE=cy,Qy=(e={})=>e instanceof Iy?{stdout:e,stdin:xo.stdin}:e,fy=(e,u,a)=>{let l=Ui.get(e);return l?l.isConcurrent!==a&&console.warn(`Warning: render() was called with concurrent: ${a}, but the existing instance for this stdout uses concurrent: ${l.isConcurrent}. The concurrent option only takes effect on the first render. Call unmount() first if you need to change the rendering mode.`):(l=u(),Ui.set(e,l)),l};var Dy=aA(Zl(),1);var To=aA(SA(),1);var XQ=aA(SA(),1);var dy=aA(SA(),1);var py=aA(SA(),1);var LE=aA(SA(),1);import{Buffer as my}from"node:buffer";var hy=/^(?:\x1b)([a-zA-Z0-9])$/,yy=/^(?:\x1b+)(O|N|\[|\[\[)(?:(\d+)(?:;(\d+))?([~^$])|(?:1;)?(\d+)?([a-zA-Z]))/,jQ={OP:"f1",OQ:"f2",OR:"f3",OS:"f4","[11~":"f1","[12~":"f2","[13~":"f3","[14~":"f4","[[A":"f1","[[B":"f2","[[C":"f3","[[D":"f4","[[E":"f5","[15~":"f5","[17~":"f6","[18~":"f7","[19~":"f8","[20~":"f9","[21~":"f10","[23~":"f11","[24~":"f12","[A":"up","[B":"down","[C":"right","[D":"left","[E":"clear","[F":"end","[H":"home",OA:"up",OB:"down",OC:"right",OD:"left",OE:"clear",OF:"end",OH:"home","[1~":"home","[2~":"insert","[3~":"delete","[4~":"end","[5~":"pageup","[6~":"pagedown","[[5~":"pageup","[[6~":"pagedown","[7~":"home","[8~":"end","[a":"up","[b":"down","[c":"right","[d":"left","[e":"clear","[2$":"insert","[3$":"delete","[5$":"pageup","[6$":"pagedown","[7$":"home","[8$":"end",Oa:"up",Ob:"down",Oc:"right",Od:"left",Oe:"clear","[2^":"insert","[3^":"delete","[5^":"pageup","[6^":"pagedown","[7^":"home","[8^":"end","[Z":"tab"},VQ=[...Object.values(jQ),"backspace"],Sy=e=>["[a","[b","[c","[d","[e","[2$","[3$","[5$","[6$","[7$","[8$","[Z"].includes(e),Fy=e=>["Oa","Ob","Oc","Od","Oe","[2^","[3^","[5^","[6^","[7^","[8^"].includes(e),WQ=/^\x1b\[(\d+)(?:;(\d+)(?::(\d+))?(?:;([\d:]+))?)?u$/,Ny=/^\x1b\[(\d+);(\d+):(\d+)([A-Za-z~])$/,Ry={A:"up",B:"down",C:"right",D:"left",E:"clear",F:"end",H:"home",P:"f1",Q:"f2",R:"f3",S:"f4"},vy={2:"insert",3:"delete",5:"pageup",6:"pagedown",7:"home",8:"end",11:"f1",12:"f2",13:"f3",14:"f4",15:"f5",17:"f6",18:"f7",19:"f8",20:"f9",21:"f10",23:"f11",24:"f12"},ZQ={27:"escape",9:"tab",127:"delete",8:"backspace",57358:"capslock",57359:"scrolllock",57360:"numlock",57361:"printscreen",57362:"pause",57363:"menu",57376:"f13",57377:"f14",57378:"f15",57379:"f16",57380:"f17",57381:"f18",57382:"f19",57383:"f20",57384:"f21",57385:"f22",57386:"f23",57387:"f24",57388:"f25",57389:"f26",57390:"f27",57391:"f28",57392:"f29",57393:"f30",57394:"f31",57395:"f32",57396:"f33",57397:"f34",57398:"f35",57399:"kp0",57400:"kp1",57401:"kp2",57402:"kp3",57403:"kp4",57404:"kp5",57405:"kp6",57406:"kp7",57407:"kp8",57408:"kp9",57409:"kpdecimal",57410:"kpdivide",57411:"kpmultiply",57412:"kpsubtract",57413:"kpadd",57414:"kpenter",57415:"kpequal",57416:"kpseparator",57417:"kpleft",57418:"kpright",57419:"kpup",57420:"kpdown",57421:"kppageup",57422:"kppagedown",57423:"kphome",57424:"kpend",57425:"kpinsert",57426:"kpdelete",57427:"kpbegin",57428:"mediaplay",57429:"mediapause",57430:"mediaplaypause",57431:"mediareverse",57432:"mediastop",57433:"mediafastforward",57434:"mediarewind",57435:"mediatracknext",57436:"mediatrackprevious",57437:"mediarecord",57438:"lowervolume",57439:"raisevolume",57440:"mutevolume",57441:"leftshift",57442:"leftcontrol",57443:"leftalt",57444:"leftsuper",57445:"lefthyper",57446:"leftmeta",57447:"rightshift",57448:"rightcontrol",57449:"rightalt",57450:"rightsuper",57451:"righthyper",57452:"rightmeta",57453:"isoLevel3Shift",57454:"isoLevel5Shift"},PQ=e=>e>=0&&e<=1114111&&!(e>=55296&&e<=57343),bE=e=>PQ(e)?String.fromCodePoint(e):"?";function $Q(e){return e===3?"release":e===2?"repeat":"press"}function Af(e){return{ctrl:!!(e&We.ctrl),shift:!!(e&We.shift),meta:!!(e&We.meta),option:!!(e&We.alt),super:!!(e&We.super),hyper:!!(e&We.hyper),capsLock:!!(e&We.capsLock),numLock:!!(e&We.numLock)}}var wy=e=>{let u=WQ.exec(e);if(!u)return null;let a=parseInt(u[1],10),l=u[2]?Math.max(0,parseInt(u[2],10)-1):0,r=u[3]?parseInt(u[3],10):1,E=u[4];if(!PQ(a))return null;let B;E&&(B=E.split(":").map(y=>bE(parseInt(y,10))).join(""));let c,Q;return a===32?(c="space",Q=!0):a===13?(c="return",Q=!0):ZQ[a]?(c=ZQ[a],Q=!1):a>=1&&a<=26?(c=String.fromCodePoint(a+96),Q=!1):(c=bE(a).toLowerCase(),Q=!0),Q&&!B&&(B=bE(a)),{name:c,...Af(l),eventType:$Q(r),sequence:e,raw:e,isKittyProtocol:!0,isPrintable:Q,text:B}},Gy=e=>{let u=Ny.exec(e);if(!u)return null;let a=parseInt(u[1],10),l=Math.max(0,parseInt(u[2],10)-1),r=parseInt(u[3],10),E=u[4],B=E==="~"?vy[a]:Ry[E];return B?{name:B,...Af(l),eventType:$Q(r),sequence:e,raw:e,isKittyProtocol:!0,isPrintable:!1}:null},My=(e="")=>{let u;my.isBuffer(e)?e[0]>127&&e[1]===void 0?(e[0]-=128,e="\x1B"+String(e)):e=String(e):e!==void 0&&typeof e!="string"?e=String(e):e||(e="");let a=wy(e);if(a)return a;let l=Gy(e);if(l)return l;if(WQ.test(e))return{name:"",ctrl:!1,meta:!1,shift:!1,option:!1,sequence:e,raw:e,isKittyProtocol:!0,isPrintable:!1};let r={name:"",ctrl:!1,meta:!1,shift:!1,option:!1,sequence:e,raw:e};if(r.sequence=r.sequence||e||r.name,e==="\r"||e==="\x1B\r")r.raw=void 0,r.name="return",r.option=e.length===2;else if(e===`
|
|
68
68
|
`)r.name="enter";else if(e===" ")r.name="tab";else if(e==="\b"||e==="\x1B\b")r.name="backspace",r.meta=e.charAt(0)==="\x1B";else if(e===""||e==="\x1B")r.name="delete",r.meta=e.charAt(0)==="\x1B";else if(e==="\x1B"||e==="\x1B\x1B")r.name="escape",r.meta=e.length===2;else if(e===" "||e==="\x1B ")r.name="space",r.meta=e.length===2;else if(e.length===1&&e<="")r.name=String.fromCharCode(e.charCodeAt(0)+97-1),r.ctrl=!0;else if(e.length===1&&e>="0"&&e<="9")r.name="number";else if(e.length===1&&e>="a"&&e<="z")r.name=e;else if(e.length===1&&e>="A"&&e<="Z")r.name=e.toLowerCase(),r.shift=!0;else if(u=hy.exec(e))r.meta=!0,r.shift=/^[A-Z]$/.test(u[1]);else if(u=yy.exec(e)){let E=[...e];E[0]==="\x1B"&&E[1]==="\x1B"&&(r.option=!0);let B=[u[1],u[2],u[4],u[6]].filter(Boolean).join(""),c=(u[3]||u[5]||1)-1;r.ctrl=!!(c&4),r.meta=!!(c&10),r.shift=!!(c&1),r.code=B,r.name=jQ[B],r.shift=Sy(B)||r.shift,r.ctrl=Fy(B)||r.ctrl}return r},tf=My;var ef=aA(SA(),1);var Hy=()=>(0,ef.useContext)(Ro),bo=Hy;var Uy=(e,u={})=>{let{stdin:a,setRawMode:l,internal_exitOnCtrlC:r,internal_eventEmitter:E}=bo();(0,LE.useEffect)(()=>{if(u.isActive!==!1)return l(!0),()=>{l(!1)}},[u.isActive,l]),(0,LE.useEffect)(()=>{if(u.isActive===!1)return;let B=c=>{let Q=tf(c),y={upArrow:Q.name==="up",downArrow:Q.name==="down",leftArrow:Q.name==="left",rightArrow:Q.name==="right",pageDown:Q.name==="pagedown",pageUp:Q.name==="pageup",home:Q.name==="home",end:Q.name==="end",return:Q.name==="return",escape:Q.name==="escape",ctrl:Q.ctrl,shift:Q.shift,tab:Q.name==="tab",backspace:Q.name==="backspace",delete:Q.name==="delete",meta:Q.meta||Q.name==="escape"||Q.option,super:Q.super??!1,hyper:Q.hyper??!1,capsLock:Q.capsLock??!1,numLock:Q.numLock??!1,eventType:Q.eventType},N;Q.isKittyProtocol?Q.isPrintable?N=Q.text??Q.name:Q.ctrl&&Q.name.length===1?N=Q.name:N="":Q.ctrl?N=Q.name:N=Q.sequence,!Q.isKittyProtocol&&VQ.includes(Q.name)&&(N=""),N.startsWith("\x1B")&&(N=N.slice(1)),N.length===1&&typeof N[0]=="string"&&/[A-Z]/.test(N[0])&&(y.shift=!0),(!(N==="c"&&y.ctrl)||!r)&&fe.batchedUpdates(()=>{e(N,y)})};return E?.on("input",B),()=>{E?.removeListener("input",B)}},[u.isActive,a,r,e])},OE=Uy;var nf=aA(SA(),1);var xy=()=>(0,nf.useContext)(No),kE=xy;var Ty=aA(SA(),1);var by=aA(SA(),1);var JE=aA(SA(),1);var Ly=aA(SA(),1);var Oy=aA(SA(),1);var Lo=aA(SA(),1);import{pathToFileURL as Py}from"node:url";var ky=["[data-ink-approve]",'[data-ink-act="accept"]',"[data-accept-result][data-primary-action]"],Jy=['[data-act="request-changes"]','[data-act="revise"]','a.result-feedback-link[href="#request-changes"]'],Ky=['[data-act="retry"]','form[data-retry] button[type="submit"]','form[data-rebuild] button[type="submit"]'],KE=[{keys:"n",says:"new task",web:{kind:"go",href:"/tasks/new"},tui:"new"},{keys:"a",says:"Approve or Accept (presses the button on the page)",web:{kind:"press",selectors:ky},tui:"ink"},{keys:"b",says:"Send back (opens its note)",web:{kind:"press",selectors:Jy},tui:"send-back"},{keys:"r",says:"Retry (presses the button on the page)",web:{kind:"press",selectors:Ky},tui:"retry"},{keys:"/",says:"search",web:{kind:"search"},tui:{none:"The terminal has no search; press w to open the console."}},{keys:"g h",says:"go Home",web:{kind:"go",href:"/"},tui:"list"},{keys:"g t",says:"go to Tasks",web:{kind:"go",href:"/tasks"},tui:"list"},{keys:"?",says:"these shortcuts",web:{kind:"help"},tui:"help"}],uf=e=>e.split(" ").join(" then ");var PA=aA(SA(),1);var Yy=/[--]/g;function _y(e){return e.replace(/(?:\x1b\[|\u009b)[0-?]*[ -/]*[@-~]/g,"").replace(/(?:\x1b\]|\u009d)[^\x07\x1b\u009c]*(?:\x07|\x1b\\|\u009c)?/g,"").replace(/\x1b[ -/]*[0-~]/g,"").replace(/\t/g," ").replace(/[\x00-\x1f\x7f-\x9f]/g,"").replace(new RegExp("\\p{Cf}","gu"),"").replace(Yy,"")}function af(e){return e.split(`
|
|
69
69
|
`).map(_y).join(`
|
|
70
|
-
`)}var YE=["you","working","done"],lf={you:"Waiting on you",working:"Working",done:"Done"};var X=aA(qa(),1);function at({children:e,...u}){return(0,X.jsx)(EA,{...u,children:af(e)})}var Xy={new:"new task",ink:"approve the plan (shows it in full first), or accept the result","send-back":"send the result back",retry:"retry",list:"back to the task list",help:"these keys"},Zy=["j / k or ↑ / ↓ move","Enter open the task",...KE.filter(e=>typeof e.tui=="string").map(e=>`${uf(e.keys).padEnd(17)}${Xy[e.tui]}`),"c accept the result","s stop the running attempt","1–9 answer the task's question","w open in the browser","Esc back","q quit"];function jy(e){let u=a=>a.group===null?YE.length:YE.indexOf(a.group);return e.map((a,l)=>({row:a,index:l})).sort((a,l)=>u(a.row)-u(l.row)||a.index-l.index).map(a=>a.row)}var Vy=e=>e.group===null?"Other":lf[e.group],Wy=e=>[...e].filter(u=>u>=" "&&u!=="").join("");function Ef({source:e,session:u,onExit:a,pollMs:l=1e3}){let{exit:r}=kE(),[E,B]=(0,PA.useState)(null),[c,Q]=(0,PA.useState)(null),[y,N]=(0,PA.useState)(0),[w,h]=(0,PA.useState)({kind:"main"}),[v,H]=(0,PA.useState)(null),[L,M]=(0,PA.useState)(null),[W,FA]=(0,PA.useState)([]),NA=(0,PA.useRef)(null),gA=(0,PA.useRef)(0),HA=(0,PA.useRef)(!1),vA=jy(E?.rows??[]),QA=vA[Math.min(y,Math.max(vA.length-1,0))],XA=(0,PA.useCallback)(async()=>{let T=await e.snapshot();B(T),T.rows.length===0&&Q(await e.setup())},[e]);(0,PA.useEffect)(()=>{XA()},[XA]),(0,PA.useEffect)(()=>{let T=setInterval(()=>{gA.current+=1;let _=e.changed();(_!==NA.current||gA.current%5===0)&&(NA.current=_,XA())},l);return()=>clearInterval(T)},[e,XA,l]);let St=QA?.id??null;(0,PA.useEffect)(()=>{if(St===null){FA([]);return}FA(e.live(St));let T=setInterval(()=>FA(e.live(St)),l);return()=>clearInterval(T)},[e,St,l]);let Ut=T=>{HA.current||(HA.current=!0,r(),a?.(T))},zt=(0,PA.useRef)(!1),TA=async T=>{M("Working…");try{let _=await e.act(T);return _.ok?(H(_.message),await XA(),T.kind==="new"&&u.shouldAskBackground()&&h({kind:"background"}),!0):(_.needsPassword?h({kind:"password",message:_.message}):H(_.message),!1)}finally{M(null)}},Ft=async(T,_)=>{let P=await e.task(T);P===null?H("That task can't be read now."):_(P)},xt=T=>Ft(T,_=>{_.plan===null?H("No plan is waiting for your approval on this task."):h({kind:"approve",task:_})}),Ot=T=>Ft(T,_=>{_.plan!==null?h({kind:"approve",task:_}):_.hasResult?TA({kind:"complete",id:T}):H("No plan or result is waiting for you on this task.")}),Tt=(T,_)=>{if(_===null){H("Nothing is running on this task.");return}TA({kind:"stop",id:T,runId:_})},qt=(T,_)=>{let P=T.decisions[0],V=P?.options[_];P===void 0||V===void 0||TA({kind:"answer",decision:P.id,option:V.id}).then(At=>{At&&h({kind:"main"})})},GA=()=>{M("Opening the browser…"),u.openBrowser().then(T=>{H(T),M(null)})},It=()=>{u.owned()?h({kind:"quit"}):Ut("leave")},de=(T,_,P)=>{if(zt.current&&(zt.current=!1,T==="h"||T==="t"))return h({kind:"main"}),!0;if(T==="g")return zt.current=!0,!0;if(T==="/"){let V=KE.find(At=>At.keys==="/").tui;return H(typeof V=="string"?null:V.none),!0}return T==="n"?(h({kind:"compose",purpose:"new",id:null,text:"",project:0}),!0):T==="w"?(GA(),!0):T==="?"?(h({kind:"help"}),!0):T==="q"?(It(),!0):_===null?!1:T==="a"?(Ot(_),!0):T==="s"?(Tt(_,P),!0):T==="b"?(h({kind:"compose",purpose:"revise",id:_,text:"",project:0}),!0):T==="r"?(TA({kind:"retry",id:_}),!0):T==="c"?(TA({kind:"complete",id:_}),!0):!1},pe=(T,_)=>{if(L===null){if(_.ctrl&&T==="c"){It();return}switch(w.kind){case"main":{if(T==="j"||_.downArrow){N(P=>Math.min(P+1,Math.max(vA.length-1,0)));return}if(T==="k"||_.upArrow){N(P=>Math.max(P-1,0));return}if(_.return&&QA!==void 0){Ft(QA.id,P=>h({kind:"task",task:P}));return}if(_.escape){H(null);return}de(T,QA?.id??null,QA?.liveRunId??null);return}case"task":{if(_.escape||_.leftArrow){h({kind:"main"});return}if(/^[1-9]$/.test(T)){qt(w.task,Number(T)-1);return}de(T,w.task.id,w.task.liveRunId);return}case"approve":{if(_.escape||T==="n"){h({kind:"main"}),H("Nothing approved.");return}if(T==="y"){let{task:P}=w;TA({kind:"approve",id:P.id,digest:P.plan.digest}).then(V=>{V&&h({kind:"main"})})}return}case"build":{if(_.escape){h({kind:"main"}),H("Nothing started.");return}if(T==="p"){let{text:P,repo:V}=w;h({kind:"main"}),TA({kind:"new",text:P,repo:V});return}if(_.return){let{preview:P,digest:V}=w.terms;TA({kind:"build",preview:P,digest:V}).then(At=>{At&&h({kind:"main"})})}return}case"compose":{if(_.escape){h({kind:"main"});return}let P=E?.projects??[];if(_.tab&&w.purpose==="new"&&P.length>1){h({...w,project:(w.project+1)%P.length});return}if(_.backspace||_.delete){h({...w,text:w.text.slice(0,-1)});return}if(_.return){let At=w.text.trim();if(At==="")return;let Z=w.purpose==="new"?{kind:"new",text:At,repo:P[w.project]??null}:{kind:"revise",id:w.id,text:At};if(h({kind:"main"}),Z.kind==="new"){M("Reading it…"),e.fastTerms(At,Z.repo).catch(()=>null).then(uA=>{M(null),uA===null?TA(Z):h({kind:"build",terms:uA,text:At,repo:Z.repo})});return}TA(Z);return}let V=Wy(T);V!==""&&h({...w,text:w.text+V});return}case"quit":{T==="s"?Ut("stop"):T==="b"?Ut("keep"):_.escape&&h({kind:"main"});return}case"background":{let P=T==="y"||T==="Y"||_.return;if(!P&&T!=="n"&&T!=="N")return;h({kind:"main"}),M(P?"Moving Toolroll to the background…":"Saving…"),u.answerBackground(P).then(V=>{H(V),M(null)});return}default:(_.escape||_.return||T==="q")&&h({kind:"main"})}}},Xt=(0,PA.useRef)(pe);Xt.current=pe,OE((0,PA.useCallback)((T,_)=>Xt.current(T,_),[]));let me=L??v;return(0,X.jsxs)(RA,{flexDirection:"column",children:[(0,X.jsxs)(RA,{justifyContent:"space-between",children:[(0,X.jsx)(EA,{bold:!0,children:"Toolroll"}),E?.needsYou?(0,X.jsx)(at,{color:"yellow",children:E.needsYou}):(0,X.jsx)(EA,{dimColor:!0,children:"Nothing needs you"}),E?.learning?(0,X.jsx)(at,{dimColor:!0,children:E.learning}):null]}),$A(),me===null?null:(0,X.jsx)(at,{children:me}),(0,X.jsx)(EA,{dimColor:!0,children:Zt()})]});function Zt(){switch(w.kind){case"main":return vA.length===0?"n new task · w browser · ? help · q quit":"j/k move · Enter open · n new · a approve/accept · b send back · r retry · s stop · w browser · ? help · q quit";case"task":return"a approve/accept · b send back · r retry · s stop · 1–9 answer · w browser · Esc back";case"approve":return"y approve · Esc cancel";case"build":return"Enter Build · p plan it first · Esc cancel";case"compose":return w.purpose==="new"&&(E?.projects.length??0)>1?"Enter send · Tab project · Esc cancel":"Enter send · Esc cancel";case"quit":return"s stop · b background · Esc cancel";case"background":return"y yes · n no";default:return"Esc back"}}function $A(){switch(w.kind){case"help":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Keys"}),Zy.map(T=>(0,X.jsx)(EA,{children:T},T))]});case"password":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"This needs your password"}),(0,X.jsx)(at,{children:w.message}),E?.passwordSet?(0,X.jsx)(EA,{children:"Sign in in the browser, then do it there."}):(0,X.jsx)(EA,{children:"Set a password: run `toolroll password set`"})]});case"quit":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Quit Toolroll?"}),(0,X.jsx)(EA,{children:"s Stop agents and quit (running work is saved)"}),(0,X.jsx)(EA,{children:"b Keep running in the background"})]});case"background":return(0,X.jsx)(RA,{borderStyle:"single",paddingX:1,children:(0,X.jsx)(EA,{bold:!0,children:"Keep Toolroll running in the background? (Y/n)"})});case"build":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Build approves exactly this:"}),w.terms.terms.map((T,_)=>(0,X.jsx)(at,{children:`${T.label}: ${T.words}`},_))]});case"compose":{let T=E?.projects[w.project]??null;return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"round",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:w.purpose==="new"?"New task":"What should change?"}),w.purpose==="new"&&T!==null?(0,X.jsx)(at,{dimColor:!0,children:`In ${T}`}):null,(0,X.jsxs)(EA,{children:[(0,X.jsx)(at,{children:w.text}),(0,X.jsx)(EA,{inverse:!0,children:" "})]})]})}case"approve":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(at,{bold:!0,children:`Approve this plan? ${w.task.title}`}),w.task.plan.lines.map((T,_)=>(0,X.jsx)(at,{children:T},_))]});case"task":{let{task:T}=w,_=T.decisions[0];return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(at,{bold:!0,children:T.title}),(0,X.jsx)(at,{children:`${T.status}${T.detail===""?"":` · ${T.detail}`}`}),T.plan===null?null:(0,X.jsx)(EA,{children:"A plan waits for your approval (a)."}),(T.ready?.length??0)===0?null:(0,X.jsx)(RA,{flexDirection:"column",marginTop:1,children:T.ready.map((P,V)=>(0,X.jsx)(at,{...V===0?{bold:!0}:{},children:P},V))}),_===void 0?null:(0,X.jsxs)(RA,{flexDirection:"column",marginTop:1,children:[(0,X.jsx)(at,{bold:!0,children:_.question}),_.options.map((P,V)=>(0,X.jsx)(at,{children:`${V+1} ${P.label}`},P.id))]}),W.length===0?null:(0,X.jsx)(RA,{flexDirection:"column",marginTop:1,children:W.slice(-12).map((P,V)=>(0,X.jsx)(at,{dimColor:!0,children:P},V))})]})}default:return E===null?(0,X.jsx)(EA,{dimColor:!0,children:"Loading…"}):vA.length===0?(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Get to your first result"}),(c?.lines??["No tasks yet. Press n to write one."]).map((T,_)=>(0,X.jsx)(at,{children:T},_))]}):(0,X.jsxs)(RA,{children:[(0,X.jsx)(RA,{flexDirection:"column",width:"45%",borderStyle:"single",paddingX:1,children:vA.map((T,_)=>(0,X.jsxs)(PA.Fragment,{children:[_===0||vA[_-1].group!==T.group?(0,X.jsx)(EA,{bold:!0,children:Vy(T)}):null,(0,X.jsxs)(EA,{inverse:T===QA,wrap:"truncate-end",children:[T===QA?"> ":" ",(0,X.jsx)(at,{children:T.title})," ",(0,X.jsx)(at,{...T.needsYou?{color:"yellow"}:{dimColor:!0},children:T.status})]})]},T.id))}),(0,X.jsx)(RA,{flexDirection:"column",flexGrow:1,borderStyle:"single",paddingX:1,children:QA===void 0?(0,X.jsx)(EA,{dimColor:!0,children:"Nothing selected"}):(0,X.jsxs)(X.Fragment,{children:[(0,X.jsx)(at,{bold:!0,wrap:"truncate-end",children:QA.title}),(0,X.jsx)(at,{children:`${QA.status}${QA.detail===""?"":` · ${QA.detail}`}`}),W.length===0?(0,X.jsx)(EA,{dimColor:!0,children:QA.liveRunId===null?"No agent is working on it now.":"Waiting for output…"}):W.slice(-12).map((T,_)=>(0,X.jsx)(at,{dimColor:!0,wrap:"truncate-end",children:T},_))]})})]})}}}function Bf(){let e=[{id:"t3",title:"Sample: upgrade the test runner",status:"Needs you",detail:"Plan ready to approve",needsYou:!0,liveRunId:null,repo:null,group:"you"},{id:"t1",title:"Sample: fix the login redirect",status:"Running",detail:"Sample task (not real)",needsYou:!1,liveRunId:1,repo:null,group:"working"},{id:"t2",title:"Sample: add CSV export",status:"Complete",detail:"Sample task (not real)",needsYou:!1,liveRunId:null,repo:null,group:"done"}];return{fastTerms:async()=>null,snapshot:async()=>({rows:e,needsYou:"1 task needs you: Sample: upgrade the test runner",projects:[],passwordSet:!1}),task:async u=>{let a=e.find(r=>r.id===u);return a===void 0?null:{id:u,title:a.title,status:a.status,detail:a.detail,liveRunId:a.liveRunId,hasResult:a.status==="Complete",decisions:[],plan:a.needsYou?{digest:"sample",lines:["Goal: upgrade the test runner (sample)"]}:null}},live:u=>u==="t1"?["Editing src/auth.ts (sample)"]:[],changed:()=>0,act:async()=>({ok:!1,message:"This is a sample: nothing changes.",needsPassword:!1}),setup:async()=>null}}function sf(){return{owned:()=>!1,openBrowser:async()=>"This is a sample.",shouldAskBackground:()=>!1,answerBackground:async()=>"This is a sample."}}var If=aA(qa(),1);async function $y(e=Bf(),u=sf()){if(!process.stdin.isTTY||!process.stdout.isTTY)throw new Error("The terminal UI needs an interactive terminal.");let a="leave";return await TE((0,If.jsx)(Ef,{source:e,session:u,onExit:r=>{a=r}}),{exitOnCtrlC:!1}).waitUntilExit(),a}var Cf=process.argv[1];Cf!==void 0&&import.meta.url===Py(Cf).href&&$y().catch(e=>{process.stderr.write(`${e instanceof Error?e.message:String(e)}
|
|
70
|
+
`)}var YE=["you","working","done"],lf={you:"Waiting on you",working:"Working",done:"Done"};var X=aA(qa(),1);function at({children:e,...u}){return(0,X.jsx)(EA,{...u,children:af(e)})}var Xy={new:"new task",ink:"approve the plan (shows it in full first), or accept the result","send-back":"send the result back",retry:"retry",list:"back to the task list",help:"these keys"},Zy=["j / k or ↑ / ↓ move","Enter open the task",...KE.filter(e=>typeof e.tui=="string").map(e=>`${uf(e.keys).padEnd(17)}${Xy[e.tui]}`),"c accept the result","s stop the running attempt","1–9 answer the task's question","w open in the browser","Esc back","q quit"];function jy(e){let u=a=>a.group===null?YE.length:YE.indexOf(a.group);return e.map((a,l)=>({row:a,index:l})).sort((a,l)=>u(a.row)-u(l.row)||a.index-l.index).map(a=>a.row)}var Vy=e=>e.group===null?"Other":lf[e.group],Wy=e=>[...e].filter(u=>u>=" "&&u!=="").join("");function Ef({source:e,session:u,onExit:a,pollMs:l=1e3}){let{exit:r}=kE(),[E,B]=(0,PA.useState)(null),[c,Q]=(0,PA.useState)(null),[y,N]=(0,PA.useState)(0),[w,h]=(0,PA.useState)({kind:"main"}),[v,H]=(0,PA.useState)(null),[L,M]=(0,PA.useState)(null),[W,FA]=(0,PA.useState)([]),NA=(0,PA.useRef)(null),gA=(0,PA.useRef)(0),HA=(0,PA.useRef)(!1),vA=jy(E?.rows??[]),QA=vA[Math.min(y,Math.max(vA.length-1,0))],XA=(0,PA.useCallback)(async()=>{let T=await e.snapshot();B(T),T.rows.length===0&&Q(await e.setup())},[e]);(0,PA.useEffect)(()=>{XA()},[XA]),(0,PA.useEffect)(()=>{let T=setInterval(()=>{gA.current+=1;let _=e.changed();(_!==NA.current||gA.current%5===0)&&(NA.current=_,XA())},l);return()=>clearInterval(T)},[e,XA,l]);let St=QA?.id??null;(0,PA.useEffect)(()=>{if(St===null){FA([]);return}FA(e.live(St));let T=setInterval(()=>FA(e.live(St)),l);return()=>clearInterval(T)},[e,St,l]);let Ut=T=>{HA.current||(HA.current=!0,r(),a?.(T))},zt=(0,PA.useRef)(!1),TA=async T=>{M("Working…");try{let _=await e.act(T);return _.ok?(H(_.message),await XA(),T.kind==="new"&&u.shouldAskBackground()&&h({kind:"background"}),!0):(_.needsPassword?h({kind:"password",message:_.message}):H(_.message),!1)}finally{M(null)}},Ft=async(T,_)=>{let P=await e.task(T);P===null?H("That task can't be read now."):_(P)},xt=T=>Ft(T,_=>{_.plan===null?H("No plan is waiting for your approval on this task."):h({kind:"approve",task:_})}),Ot=T=>Ft(T,_=>{_.plan!==null?h({kind:"approve",task:_}):_.hasResult?TA({kind:"complete",id:T}):H("No plan or result is waiting for you on this task.")}),Tt=(T,_)=>{if(_===null){H("Nothing is running on this task.");return}TA({kind:"stop",id:T,runId:_})},qt=(T,_)=>{let P=T.decisions[0],V=P?.options[_];P===void 0||V===void 0||TA({kind:"answer",decision:P.id,option:V.id}).then(At=>{At&&h({kind:"main"})})},GA=()=>{M("Opening the browser…"),u.openBrowser().then(T=>{H(T),M(null)})},It=()=>{u.owned()?h({kind:"quit"}):Ut("leave")},de=(T,_,P)=>{if(zt.current&&(zt.current=!1,T==="h"||T==="t"))return h({kind:"main"}),!0;if(T==="g")return zt.current=!0,!0;if(T==="/"){let V=KE.find(At=>At.keys==="/").tui;return H(typeof V=="string"?null:V.none),!0}return T==="n"?(h({kind:"compose",purpose:"new",id:null,text:"",project:0}),!0):T==="w"?(GA(),!0):T==="?"?(h({kind:"help"}),!0):T==="q"?(It(),!0):_===null?!1:T==="a"?(Ot(_),!0):T==="s"?(Tt(_,P),!0):T==="b"?(h({kind:"compose",purpose:"revise",id:_,text:"",project:0}),!0):T==="r"?(TA({kind:"retry",id:_}),!0):T==="c"?(TA({kind:"complete",id:_}),!0):!1},pe=(T,_)=>{if(L===null){if(_.ctrl&&T==="c"){It();return}switch(w.kind){case"main":{if(T==="j"||_.downArrow){N(P=>Math.min(P+1,Math.max(vA.length-1,0)));return}if(T==="k"||_.upArrow){N(P=>Math.max(P-1,0));return}if(_.return&&QA!==void 0){Ft(QA.id,P=>h({kind:"task",task:P}));return}if(_.escape){H(null);return}de(T,QA?.id??null,QA?.liveRunId??null);return}case"task":{if(_.escape||_.leftArrow){h({kind:"main"});return}if(/^[1-9]$/.test(T)){qt(w.task,Number(T)-1);return}de(T,w.task.id,w.task.liveRunId);return}case"approve":{if(_.escape||T==="n"){h({kind:"main"}),H("Nothing approved.");return}if(T==="y"){let{task:P}=w;TA({kind:"approve",id:P.id,digest:P.plan.digest}).then(V=>{V&&h({kind:"main"})})}return}case"build":{if(_.escape){h({kind:"main"}),H("Nothing started.");return}if(T==="p"){let{text:P,repo:V}=w;h({kind:"main"}),TA({kind:"new",text:P,repo:V});return}if(_.return){let{preview:P,digest:V}=w.terms;TA({kind:"build",preview:P,digest:V}).then(At=>{At&&h({kind:"main"})})}return}case"compose":{if(_.escape){h({kind:"main"});return}let P=E?.projects??[];if(_.tab&&w.purpose==="new"&&P.length>1){h({...w,project:(w.project+1)%P.length});return}if(_.backspace||_.delete){h({...w,text:w.text.slice(0,-1)});return}if(_.return){let At=w.text.trim();if(At==="")return;let Z=w.purpose==="new"?{kind:"new",text:At,repo:P[w.project]??null}:{kind:"revise",id:w.id,text:At};if(h({kind:"main"}),Z.kind==="new"){M("Reading it…"),e.fastTerms(At,Z.repo).catch(()=>null).then(uA=>{M(null),uA===null?TA(Z):h({kind:"build",terms:uA,text:At,repo:Z.repo})});return}TA(Z);return}let V=Wy(T);V!==""&&h({...w,text:w.text+V});return}case"quit":{T==="s"?Ut("stop"):T==="b"?Ut("keep"):_.escape&&h({kind:"main"});return}case"background":{let P=T==="y"||T==="Y"||_.return;if(!P&&T!=="n"&&T!=="N")return;h({kind:"main"}),M(P?"Moving Toolroll to the background…":"Saving…"),u.answerBackground(P).then(V=>{H(V),M(null)});return}default:(_.escape||_.return||T==="q")&&h({kind:"main"})}}},Xt=(0,PA.useRef)(pe);Xt.current=pe,OE((0,PA.useCallback)((T,_)=>Xt.current(T,_),[]));let me=L??v;return(0,X.jsxs)(RA,{flexDirection:"column",children:[(0,X.jsxs)(RA,{justifyContent:"space-between",children:[(0,X.jsx)(EA,{bold:!0,children:"Toolroll"}),E?.needsYou?(0,X.jsx)(at,{bold:!0,children:E.needsYou}):(0,X.jsx)(EA,{dimColor:!0,children:"Nothing needs you"}),E?.learning?(0,X.jsx)(at,{dimColor:!0,children:E.learning}):null]}),$A(),me===null?null:(0,X.jsx)(at,{children:me}),(0,X.jsx)(EA,{dimColor:!0,children:Zt()})]});function Zt(){switch(w.kind){case"main":return vA.length===0?"n new task · w browser · ? help · q quit":"j/k move · Enter open · n new · a approve/accept · b send back · r retry · s stop · w browser · ? help · q quit";case"task":return"a approve/accept · b send back · r retry · s stop · 1–9 answer · w browser · Esc back";case"approve":return"y approve · Esc cancel";case"build":return"Enter Build · p plan it first · Esc cancel";case"compose":return w.purpose==="new"&&(E?.projects.length??0)>1?"Enter send · Tab project · Esc cancel":"Enter send · Esc cancel";case"quit":return"s stop · b background · Esc cancel";case"background":return"y yes · n no";default:return"Esc back"}}function $A(){switch(w.kind){case"help":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Keys"}),Zy.map(T=>(0,X.jsx)(EA,{children:T},T))]});case"password":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"This needs your password"}),(0,X.jsx)(at,{children:w.message}),E?.passwordSet?(0,X.jsx)(EA,{children:"Sign in in the browser, then do it there."}):(0,X.jsx)(EA,{children:"Set a password: run `toolroll password set`"})]});case"quit":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Quit Toolroll?"}),(0,X.jsx)(EA,{children:"s Stop agents and quit (running work is saved)"}),(0,X.jsx)(EA,{children:"b Keep running in the background"})]});case"background":return(0,X.jsx)(RA,{borderStyle:"single",paddingX:1,children:(0,X.jsx)(EA,{bold:!0,children:"Keep Toolroll running in the background? (Y/n)"})});case"build":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Build approves exactly this:"}),w.terms.terms.map((T,_)=>(0,X.jsx)(at,{children:`${T.label}: ${T.words}`},_))]});case"compose":{let T=E?.projects[w.project]??null;return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"round",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:w.purpose==="new"?"New task":"What should change?"}),w.purpose==="new"&&T!==null?(0,X.jsx)(at,{dimColor:!0,children:`In ${T}`}):null,(0,X.jsxs)(EA,{children:[(0,X.jsx)(at,{children:w.text}),(0,X.jsx)(EA,{inverse:!0,children:" "})]})]})}case"approve":return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(at,{bold:!0,children:`Approve this plan? ${w.task.title}`}),w.task.plan.lines.map((T,_)=>(0,X.jsx)(at,{children:T},_))]});case"task":{let{task:T}=w,_=T.decisions[0];return(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(at,{bold:!0,children:T.title}),(0,X.jsx)(at,{children:`${T.status}${T.detail===""?"":` · ${T.detail}`}`}),T.plan===null?null:(0,X.jsx)(EA,{children:"A plan waits for your approval (a)."}),(T.ready?.length??0)===0?null:(0,X.jsx)(RA,{flexDirection:"column",marginTop:1,children:T.ready.map((P,V)=>(0,X.jsx)(at,{...V===0?{bold:!0}:{},children:P},V))}),_===void 0?null:(0,X.jsxs)(RA,{flexDirection:"column",marginTop:1,children:[(0,X.jsx)(at,{bold:!0,children:_.question}),_.options.map((P,V)=>(0,X.jsx)(at,{children:`${V+1} ${P.label}`},P.id))]}),W.length===0?null:(0,X.jsx)(RA,{flexDirection:"column",marginTop:1,children:W.slice(-12).map((P,V)=>(0,X.jsx)(at,{dimColor:!0,children:P},V))})]})}default:return E===null?(0,X.jsx)(EA,{dimColor:!0,children:"Loading…"}):vA.length===0?(0,X.jsxs)(RA,{flexDirection:"column",borderStyle:"single",paddingX:1,children:[(0,X.jsx)(EA,{bold:!0,children:"Get to your first result"}),(c?.lines??["No tasks yet. Press n to write one."]).map((T,_)=>(0,X.jsx)(at,{children:T},_))]}):(0,X.jsxs)(RA,{children:[(0,X.jsx)(RA,{flexDirection:"column",width:"45%",borderStyle:"single",paddingX:1,children:vA.map((T,_)=>(0,X.jsxs)(PA.Fragment,{children:[_===0||vA[_-1].group!==T.group?(0,X.jsx)(EA,{bold:!0,children:Vy(T)}):null,(0,X.jsxs)(EA,{inverse:T===QA,wrap:"truncate-end",children:[T===QA?"> ":" ",(0,X.jsx)(at,{children:T.title})," ",(0,X.jsx)(at,{...T.needsYou?{bold:!0}:{dimColor:!0},children:T.status})]})]},T.id))}),(0,X.jsx)(RA,{flexDirection:"column",flexGrow:1,borderStyle:"single",paddingX:1,children:QA===void 0?(0,X.jsx)(EA,{dimColor:!0,children:"Nothing selected"}):(0,X.jsxs)(X.Fragment,{children:[(0,X.jsx)(at,{bold:!0,wrap:"truncate-end",children:QA.title}),(0,X.jsx)(at,{children:`${QA.status}${QA.detail===""?"":` · ${QA.detail}`}`}),W.length===0?(0,X.jsx)(EA,{dimColor:!0,children:QA.liveRunId===null?"No agent is working on it now.":"Waiting for output…"}):W.slice(-12).map((T,_)=>(0,X.jsx)(at,{dimColor:!0,wrap:"truncate-end",children:T},_))]})})]})}}}function Bf(){let e=[{id:"t3",title:"Sample: upgrade the test runner",status:"Needs you",detail:"Plan ready to approve",needsYou:!0,liveRunId:null,repo:null,group:"you"},{id:"t1",title:"Sample: fix the login redirect",status:"Running",detail:"Sample task (not real)",needsYou:!1,liveRunId:1,repo:null,group:"working"},{id:"t2",title:"Sample: add CSV export",status:"Complete",detail:"Sample task (not real)",needsYou:!1,liveRunId:null,repo:null,group:"done"}];return{fastTerms:async()=>null,snapshot:async()=>({rows:e,needsYou:"1 task needs you: Sample: upgrade the test runner",projects:[],passwordSet:!1}),task:async u=>{let a=e.find(r=>r.id===u);return a===void 0?null:{id:u,title:a.title,status:a.status,detail:a.detail,liveRunId:a.liveRunId,hasResult:a.status==="Complete",decisions:[],plan:a.needsYou?{digest:"sample",lines:["Goal: upgrade the test runner (sample)"]}:null}},live:u=>u==="t1"?["Editing src/auth.ts (sample)"]:[],changed:()=>0,act:async()=>({ok:!1,message:"This is a sample: nothing changes.",needsPassword:!1}),setup:async()=>null}}function sf(){return{owned:()=>!1,openBrowser:async()=>"This is a sample.",shouldAskBackground:()=>!1,answerBackground:async()=>"This is a sample."}}var If=aA(qa(),1);async function $y(e=Bf(),u=sf()){if(!process.stdin.isTTY||!process.stdout.isTTY)throw new Error("The terminal UI needs an interactive terminal.");let a="leave";return await TE((0,If.jsx)(Ef,{source:e,session:u,onExit:r=>{a=r}}),{exitOnCtrlC:!1}).waitUntilExit(),a}var Cf=process.argv[1];Cf!==void 0&&import.meta.url===Py(Cf).href&&$y().catch(e=>{process.stderr.write(`${e instanceof Error?e.message:String(e)}
|
|
71
71
|
`),process.exitCode=1});export{$y as startTui};
|
|
72
72
|
/*! Bundled license information:
|
|
73
73
|
|
package/dist/work-index.d.ts
CHANGED
|
@@ -27,6 +27,8 @@ export type WorkIndexItem = {
|
|
|
27
27
|
ask: Ask | null;
|
|
28
28
|
/** The row's chip under its group: the specific ask (needs-you.ts askChipOf), or none. */
|
|
29
29
|
chip: AskChip | null;
|
|
30
|
+
/** What a Needs you row asks, in Home's five groups (ASK_GROUP_SQL); null for every other row. */
|
|
31
|
+
askGroup: WorkAskGroup | null;
|
|
30
32
|
primaryAction: WorkAction | null;
|
|
31
33
|
completion: {
|
|
32
34
|
actor: string;
|
|
@@ -45,7 +47,9 @@ export type WorkIndexItem = {
|
|
|
45
47
|
at: string;
|
|
46
48
|
};
|
|
47
49
|
};
|
|
48
|
-
/** `root`: the evidence root, so a result whose checks were Off can read the release check that covers it.
|
|
50
|
+
/** `root`: the evidence root, so a result whose checks were Off can read the release check that covers it.
|
|
51
|
+
* `completedSince` (an ISO time): the Completed view lists only what was completed at or after it, filtered before the
|
|
52
|
+
* page limit, and the page's `completedSince` counts them all. */
|
|
49
53
|
export type WorkIndexOptions = {
|
|
50
54
|
view?: WorkView;
|
|
51
55
|
limit?: number;
|
|
@@ -54,16 +58,25 @@ export type WorkIndexOptions = {
|
|
|
54
58
|
state?: TaskState;
|
|
55
59
|
leadId?: string;
|
|
56
60
|
root?: string;
|
|
61
|
+
completedSince?: string;
|
|
57
62
|
/** Only these families (by root task id): the sidebar reads its rows' own words this way. */
|
|
58
63
|
roots?: readonly string[];
|
|
59
64
|
};
|
|
60
65
|
/** How the list is grouped: the Needs you rows by their ask, then building, then the rest. */
|
|
61
66
|
export type WorkIndexGroup = Ask | 'building' | 'rest';
|
|
62
67
|
export type WorkIndexGroupCounts = Record<WorkIndexGroup, number>;
|
|
68
|
+
/** What a Needs you row asks, as Home groups it (docs/plans/live-pages-and-home.md B1.2): a plan to approve, a question
|
|
69
|
+
* to answer, something that went wrong, a result to review, or anything else that needs the person. */
|
|
70
|
+
export type WorkAskGroup = 'approve' | 'answer' | 'wrong' | 'review' | 'other';
|
|
71
|
+
export declare const WORK_ASK_GROUPS: readonly WorkAskGroup[];
|
|
72
|
+
/** `asks`: the exact Needs you totals per ask group, over every row in view (never only the page). `completedSince`:
|
|
73
|
+
* how many the Completed view lists from `options.completedSince` on (0 without it). */
|
|
63
74
|
export type WorkIndexPage = {
|
|
64
75
|
items: WorkIndexItem[];
|
|
65
76
|
totals: WorkIndexCounts;
|
|
66
77
|
groups: WorkIndexGroupCounts;
|
|
78
|
+
asks: Record<WorkAskGroup, number>;
|
|
79
|
+
completedSince: number;
|
|
67
80
|
projects: WorkProjectCounts[];
|
|
68
81
|
nextCursor: string | null;
|
|
69
82
|
limit: number;
|