toolroll 0.9.99 → 0.9.101
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 +18 -0
- package/dist/accent-colors.js +1 -1
- package/dist/acp-check.js +4 -2
- package/dist/activity-line.d.ts +1 -1
- package/dist/activity-line.js +1 -1
- package/dist/agent-state.js +2 -1
- package/dist/agents-board.js +3 -2
- package/dist/agents-live.js +3 -2
- package/dist/approval-authority.d.ts +2 -1
- package/dist/approval-authority.js +2 -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 +32 -32
- package/dist/browser-workspace.d.ts +31 -2
- package/dist/chat-ask.js +4 -2
- package/dist/chat-core.js +4 -2
- package/dist/chat-decide.js +1 -2
- package/dist/chat-delivery-state.d.ts +29 -6
- package/dist/chat-delivery-state.js +43 -13
- package/dist/chat-delivery.js +12 -5
- package/dist/chat-flow.js +15 -13
- package/dist/chat-live.js +5 -2
- package/dist/chat-message-states.d.ts +75 -0
- package/dist/chat-message-states.js +74 -0
- package/dist/chat-messages.d.ts +10 -4
- package/dist/chat-messages.js +12 -7
- package/dist/check-failure.js +2 -1
- package/dist/check-report.js +1 -1
- package/dist/claim.d.ts +40 -2
- package/dist/claim.js +92 -19
- package/dist/contracts/chat-tables.js +3 -0
- package/dist/coordinator.js +3 -3
- package/dist/demo.js +7 -2
- 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/discord-chat.js +2 -3
- package/dist/dispatch.js +2 -1
- package/dist/dispose.d.ts +4 -1
- package/dist/dispose.js +2 -1
- package/dist/fallback.js +3 -2
- package/dist/fast-start.js +2 -2
- package/dist/first-run.d.ts +10 -6
- package/dist/first-run.js +19 -18
- package/dist/guides.js +1 -1
- package/dist/monitoring.js +2 -1
- package/dist/operate.js +77 -36
- package/dist/plane-review.js +2 -1
- package/dist/project-knowledge.d.ts +3 -3
- package/dist/release-authority.d.ts +9 -0
- package/dist/release-authority.js +13 -0
- package/dist/release-auto.js +2 -3
- package/dist/release-gate.js +1 -1
- package/dist/release-view.js +2 -1
- package/dist/release.js +18 -30
- package/dist/result-shots.js +1 -1
- package/dist/route-results.js +2 -1
- package/dist/run-ending.d.ts +21 -0
- package/dist/run-ending.js +19 -0
- package/dist/scope.d.ts +5 -0
- package/dist/scope.js +10 -3
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +1 -1
- package/dist/server/console.css +46 -17
- package/dist/server/kit.d.ts +36 -3
- package/dist/server/kit.js +33 -4
- 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 +6 -5
- package/dist/server/render-tasks.d.ts +10 -0
- package/dist/server/render-tasks.js +234 -64
- package/dist/server/task-views.js +5 -1
- package/dist/settings-groups-ui.js +1 -1
- package/dist/setup-guide.d.ts +2 -2
- package/dist/slack-chat.js +2 -3
- package/dist/store.d.ts +61 -2
- package/dist/store.js +141 -80
- package/dist/subagent-question.js +8 -6
- package/dist/surface.js +2 -2
- package/dist/task-archive.d.ts +3 -0
- package/dist/task-archive.js +5 -0
- package/dist/task-moves.d.ts +90 -0
- package/dist/task-moves.js +68 -0
- package/dist/task-status.d.ts +2 -2
- package/dist/task-status.js +3 -3
- package/dist/teams-chat.js +2 -2
- package/dist/telegram-flow.d.ts +1 -1
- package/dist/telegram-flow.js +1 -1
- package/dist/telegram-lead.d.ts +1 -2
- package/dist/telegram-lead.js +18 -23
- package/dist/telegram-progress.js +2 -1
- package/dist/telegram.d.ts +0 -1
- package/dist/telegram.js +6 -7
- package/dist/tui.js +1 -1
- package/dist/vanished-retry.js +3 -2
- package/dist/workspace-ui.d.ts +69 -0
- package/dist/workspace-ui.js +105 -1
- package/dist/worktree.js +3 -2
- package/package.json +1 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/** Task pages: approval, agents, plans, controls, decisions and the task body. */
|
|
2
2
|
import { archivedWords } from "../tidy.js";
|
|
3
|
-
import { btn } from "./kit.js";
|
|
3
|
+
import { btn, stepRows } from "./kit.js";
|
|
4
4
|
import { whenUtc } from "../when-html.js";
|
|
5
5
|
import { networkWords } from "../egress.js";
|
|
6
|
-
import {
|
|
6
|
+
import { wasInterrupted } from "../run-ending.js";
|
|
7
|
+
import { chainTermsLines, liveChainFromJson, releaseAsksAgainWords, releaseCheckSentence, releaseTermsSentence } from "../scope.js";
|
|
7
8
|
import { SUBSCRIPTION_CAPABLE } from "../keys.js";
|
|
8
9
|
import { RELEASE_CI_RUNNING_WORDS } from "../release-ci-hold.js";
|
|
9
10
|
import { writesWords } from "../writes-mode.js";
|
|
@@ -46,10 +47,10 @@ import {} from "../store.js";
|
|
|
46
47
|
import { runCostWords } from "../summary.js";
|
|
47
48
|
import {} from "../task-control.js";
|
|
48
49
|
import { assignmentStageOf, demoChecksOf, stageOfCode, stageOfDispatch, STATUS_MORE, taskStatusOf } from "../task-status.js";
|
|
49
|
-
import { buildProgressOf, dispatchActionLabel, earlierAttemptsWords, receiptPublicationWords, resultStatusOf, REVIEW_TOKENS, workStatusOf } from "../workspace-ui.js";
|
|
50
|
+
import { buildProgressOf, dispatchActionLabel, earlierAttemptsWords, receiptPublicationWords, resultStatusOf, REVIEW_TOKENS, stepRowsOf, workStatusOf } from "../workspace-ui.js";
|
|
50
51
|
import { createHash, randomUUID } from "node:crypto";
|
|
51
52
|
import { TEXT_LIMITS } from "../text-limits.js";
|
|
52
|
-
import { isOverdue, oneLineOf, projectChip, regionScript, safePrUrl, screen, SENSITIVE_INPUT, sentenceCase, shortDigest,
|
|
53
|
+
import { isOverdue, oneLineOf, projectChip, regionScript, safePrUrl, screen, SENSITIVE_INPUT, sentenceCase, shortDigest, whenTime } from "./chrome.js";
|
|
53
54
|
import { steerDestinationWords, steerStandingParts } from "../steer-status.js";
|
|
54
55
|
import { reviewHref, taskChatHref, taskHref } from "./http.js";
|
|
55
56
|
import { coordinatorProposalsSection } from "./render-chat.js";
|
|
@@ -312,6 +313,9 @@ export function approvalSheetParts(input) {
|
|
|
312
313
|
// What the yes allows, in plain words, right above it: the agent's
|
|
313
314
|
// permissions, the per-attempt limit, and an earlier version still running.
|
|
314
315
|
const allowing = [];
|
|
316
|
+
// The clauses of `allowing` the plan sheet says under Worth knowing (a paid fallback, an earlier version still
|
|
317
|
+
// running): warnings, said first, never folded.
|
|
318
|
+
const warnings = [];
|
|
315
319
|
const allowed = permissionPlainWords(scope.profile);
|
|
316
320
|
if (allowed !== null)
|
|
317
321
|
allowing.push(allowed);
|
|
@@ -322,11 +326,16 @@ export function approvalSheetParts(input) {
|
|
|
322
326
|
if (fallbacks.length > 0) {
|
|
323
327
|
const paid = fallbacks.filter(one => one.authMode === "api-key" || SUBSCRIPTION_CAPABLE[one.profile.provider] !== true);
|
|
324
328
|
// Each agent named once, the paid one said where it stands in the order.
|
|
325
|
-
|
|
329
|
+
const clause = `if it runs out of its plan, the work moves to ${fallbacks.map(one => `${agentNameWords(one.profile.provider, one.profile.model)}${paid.includes(one) ? " on an API key, which spends money" : ""}`).join(", then ")}`;
|
|
330
|
+
allowing.push(clause);
|
|
331
|
+
if (paid.length > 0)
|
|
332
|
+
warnings.push(clause);
|
|
326
333
|
}
|
|
327
334
|
const earlier = earlierVersionsWords(input.earlier.active, input.earlier.running);
|
|
328
|
-
if (earlier !== null)
|
|
335
|
+
if (earlier !== null) {
|
|
329
336
|
allowing.push(earlier);
|
|
337
|
+
warnings.push(earlier);
|
|
338
|
+
}
|
|
330
339
|
const yoursToCheck = scope.acceptance.filter(one => one.evidence.includes("manual-review")).map(one => one.statement.trim().replace(/[.\s]+$/, ""));
|
|
331
340
|
const consent = joinHtml([
|
|
332
341
|
allowing.length === 0 ? "" : html `<p class="approval-allowing" data-approval-allowing>You’re allowing: ${allowing.join(" · ")}</p>`,
|
|
@@ -341,13 +350,14 @@ export function approvalSheetParts(input) {
|
|
|
341
350
|
const submit = rebuild !== null ? html `Approve and retry` : html `Approve`;
|
|
342
351
|
// Everything else the digest binds, one tap away.
|
|
343
352
|
const detail = (title, body, attrs = html ``) => isEmptyHtml(body) ? html `` : html `<section class="approval-detail"${attrs}><h3>${title}</h3>${body}</section>`;
|
|
344
|
-
const
|
|
353
|
+
const revisionBody = input.revision === null ? html `` : joinHtml([
|
|
345
354
|
html `<div class="revision-card" data-revision-feedback>`,
|
|
346
355
|
html `<p><a href="${input.revisionSourceHref}"${input.surface === "chat" ? html ` data-revision-source` : ""}>${input.surface === "chat" ? html `Original result: build #${input.revision.sourceRun} →` : html `build #${input.revision.sourceRun}`}</a></p>`,
|
|
347
356
|
input.revision.comments.length === 0 ? "" : html `<ul>${input.revision.comments.map(one => html `<li>${revisionImageLink(one.image)}${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : `:${one.line}`}</span> · `}${one.note} <span class="meta">— ${one.author}</span></li>`)}</ul>`,
|
|
348
357
|
revisionLineageHtml(input.revision.lineage),
|
|
349
358
|
html `</div>`,
|
|
350
|
-
])
|
|
359
|
+
]);
|
|
360
|
+
const revisionDetail = input.revision === null ? "" : detail("Earlier build", revisionBody);
|
|
351
361
|
const contract = input.planContract;
|
|
352
362
|
const amendment = approvalAmendmentHtml(contract);
|
|
353
363
|
const contractDetail = input.planDocument === null || !isEmptyHtml(amendment) ? html ``
|
|
@@ -374,7 +384,8 @@ export function approvalSheetParts(input) {
|
|
|
374
384
|
scope.budgetMicrousd === null ? "" : html `<p class="meta">Each build attempt has a ${money(scope.budgetMicrousd)} agent-reported usage cap. On a subscription this limits work; it is not an API charge.</p>`,
|
|
375
385
|
]);
|
|
376
386
|
const details = joinHtml([
|
|
377
|
-
|
|
387
|
+
// On the task page the details are open: the page without script shows every term before the yes. Chat keeps the fold.
|
|
388
|
+
html `<details class="approval-details"${input.surface === "task" ? html ` open` : ""}><summary>Plan details</summary>`,
|
|
378
389
|
revisionDetail,
|
|
379
390
|
repairText === null ? "" : detail("Full goal", html `<p>${scope.goal}</p>`),
|
|
380
391
|
detail("Plan record", contractDetail),
|
|
@@ -444,12 +455,21 @@ export function approvalSheetParts(input) {
|
|
|
444
455
|
input.deliverable === "report" ? html `<p class="approval-note">Read-only: it reports back and changes nothing in the repository.</p>` : "",
|
|
445
456
|
originNote(input.origin) === null ? "" : html `<p class="approval-note" data-approval-origin>${originNote(input.origin)}</p>`,
|
|
446
457
|
]);
|
|
458
|
+
// A release's terms are part of what the yes agrees to (the digest signs them): said whole on every approval
|
|
459
|
+
// surface (the plan sheet, the page without script, chat), before Approve, with nothing folded.
|
|
460
|
+
const releaseBody = scope.releaseTerms == null && scope.releaseCheck == null ? "" : joinHtml([
|
|
461
|
+
scope.releaseTerms == null ? "" : html `<p>${releaseTermsSentence(scope.releaseTerms)}</p>`,
|
|
462
|
+
scope.releaseCheck == null ? "" : html `<p>${releaseCheckSentence(scope.releaseCheck)}</p>`,
|
|
463
|
+
input.releaseCiRunning === true ? html `<p class="meta">${RELEASE_CI_RUNNING_WORDS}</p>` : "",
|
|
464
|
+
scope.releaseTerms?.policy.carry !== true ? "" : html `<p class="plan-sheet-sub">What asks again</p><ul class="plan-sheet-list">${releaseAsksAgainWords(scope.releaseTerms).map(line => html `<li>${line}</li>`)}</ul>`,
|
|
465
|
+
]);
|
|
447
466
|
const rows = joinHtml([
|
|
448
467
|
html `<dl class="approval-rows">`,
|
|
449
468
|
row("Goal", html `<p class="approval-goal">${goalShown}</p>`),
|
|
450
469
|
row("Changes", changes),
|
|
451
470
|
row("Won’t touch", html `<p>${scope.outOfScope === null ? "Nothing is ruled out." : scope.outOfScope}</p>`),
|
|
452
471
|
row("Done when", doneWhen),
|
|
472
|
+
releaseBody === "" ? "" : html `<div class="approval-row" data-approval-release><dt>Release</dt><dd>${releaseBody}</dd></div>`,
|
|
453
473
|
chain.length === 0 ? "" : row("If it runs out", html `<p>${chain[0]}</p><ol class="approval-steps" data-approval-fallback>${chain.slice(1).map(one => html `<li>${one.replace(/^\d+\. /, "")}</li>`)}</ol>`),
|
|
454
474
|
html `</dl>`,
|
|
455
475
|
amendment,
|
|
@@ -459,8 +479,74 @@ export function approvalSheetParts(input) {
|
|
|
459
479
|
routeEvidenceHtml(route, "approval-evidence"),
|
|
460
480
|
consent,
|
|
461
481
|
]);
|
|
462
|
-
|
|
482
|
+
// The plan sheet (docs/plans/task-page-steps.md): the same parts, re-ordered and re-labelled to read top to bottom —
|
|
483
|
+
// one lead sentence, Steps, Done when, Will change / Won't touch, Worth knowing, the agent — with nothing a person
|
|
484
|
+
// signs inside a fold. Never a second reading of the scope: every term below is one of the parts above.
|
|
485
|
+
const parsedPlan = plan !== null && plan.ok ? plan.document : null;
|
|
486
|
+
const approach = parsedPlan?.approach.trim() ?? "";
|
|
487
|
+
const firstSentence = (text) => /^[\s\S]+?[.!?](?=\s|$)/.exec(text)?.[0] ?? text;
|
|
488
|
+
const sheetLead = approach !== "" ? firstSentence(approach) : goalShown.trim();
|
|
489
|
+
const approachRest = approach !== "" ? approach.slice(sheetLead.length).trim() : "";
|
|
490
|
+
const part = (title, body, attrs = html ``) => body === "" || isEmptyHtml(body) ? html `` : html `<section class="plan-sheet-part"${attrs}><h3 class="plan-sheet-h">${title}</h3>${body}</section>`;
|
|
491
|
+
const statementKey = (text) => text.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
492
|
+
const signedKeys = scope.acceptance.map(one => statementKey(one.statement));
|
|
493
|
+
// The plan's proof lines a requirement doesn't already say, word for word: a line that adds a condition to one stays.
|
|
494
|
+
const extraProof = (parsedPlan?.proof ?? []).filter(line => { const key = statementKey(line); return key !== "" && !signedKeys.includes(key); });
|
|
495
|
+
const doneWhenSheet = joinHtml([
|
|
496
|
+
scope.acceptance.length === 0 ? html `<p>You decide when you review the result.</p>` : html `<ul class="plan-sheet-done">${scope.acceptance.map(one => html `<li><span>${one.statement}</span><span class="plan-sheet-how">${one.evidence.map(kind => EVIDENCE_SHEET[kind] ?? kind).join(" · ")}</span></li>`)}</ul>`,
|
|
497
|
+
extraProof.length === 0 ? "" : html `<ul class="plan-sheet-done plan-sheet-proof">${extraProof.map(line => html `<li><span>${line}</span><span class="plan-sheet-how">From the plan</span></li>`)}</ul>`,
|
|
498
|
+
]);
|
|
499
|
+
const reportNote = input.deliverable === "report" ? html `<p class="approval-note">Read-only: it reports back and changes nothing in the repository.</p>` : "";
|
|
500
|
+
const notAllowingWarnings = allowing.filter(one => !warnings.includes(one));
|
|
501
|
+
const sheet = joinHtml([
|
|
502
|
+
html `<article class="plan-sheet" data-plan-sheet aria-label="${input.deliverable === "report" ? "The investigation" : "The plan"}">`,
|
|
503
|
+
sentence === null ? "" : html `<p class="approval-revision">${sentence}</p>`,
|
|
504
|
+
input.coordinator === null ? "" : html `<p class="approval-note">An agent filed this: <span class="mono">${input.coordinator.label}</span>${input.coordinator.filedAgo === null ? "" : html `, ${input.coordinator.filedAgo}`}. Nothing runs until you approve, and approving runs its request.</p>`,
|
|
505
|
+
reportNote,
|
|
506
|
+
originNote(input.origin) === null ? "" : html `<p class="approval-note" data-plan-origin>${originNote(input.origin)}</p>`,
|
|
507
|
+
html `<p class="${sheetLead.length > 180 ? "plan-sheet-lead plan-sheet-lead-long" : "plan-sheet-lead"}">${sheetLead}</p>`,
|
|
508
|
+
approachRest === "" ? "" : html `<p class="plan-sheet-text">${approachRest}</p>`,
|
|
509
|
+
// An older plan that doesn't parse keeps its text, as written.
|
|
510
|
+
plan !== null && !plan.ok ? html `<pre class="recap plan-doc">${input.planDocument ?? ""}</pre>` : "",
|
|
511
|
+
goalShown.trim() === sheetLead ? "" : part("Goal", html `<p class="approval-goal">${goalShown}</p>`),
|
|
512
|
+
repairText === null ? "" : part("Full goal", html `<p>${scope.goal}</p>`),
|
|
513
|
+
input.revision === null ? "" : part("Asked for changes", revisionBody),
|
|
514
|
+
releaseBody === "" ? "" : part("Release", releaseBody, html ` data-plan-release`),
|
|
515
|
+
milestones.length === 0 ? "" : part("Steps", html `<ol class="plan-sheet-steps">${milestones.map(one => html `<li><span>${one}</span></li>`)}</ol>`),
|
|
516
|
+
part("Done when", doneWhenSheet),
|
|
517
|
+
html `<div class="plan-sheet-pair">`,
|
|
518
|
+
part("Will change", scope.touches.length > 0 ? approvalPathsHtml(scope.touches) : html `<p>Any file in the project.</p>`),
|
|
519
|
+
part("Won’t touch", html `<p>${scope.outOfScope === null ? "Nothing is ruled out." : scope.outOfScope}</p>`),
|
|
520
|
+
html `</div>`,
|
|
521
|
+
part("Worth knowing", joinHtml([
|
|
522
|
+
amendment,
|
|
523
|
+
...warnings.map(one => html `<p class="plan-sheet-warn" data-plan-warning>${one.charAt(0).toUpperCase()}${one.slice(1)}.</p>`),
|
|
524
|
+
(parsedPlan?.risks.length ?? 0) === 0 ? "" : html `<ul class="plan-sheet-list">${parsedPlan.risks.map(one => html `<li>${one}</li>`)}</ul>`,
|
|
525
|
+
(parsedPlan?.dependencies.length ?? 0) === 0 ? "" : html `<p class="plan-sheet-sub">Depends on</p><ul class="plan-sheet-list">${parsedPlan.dependencies.map(one => html `<li>${one}</li>`)}</ul>`,
|
|
526
|
+
])),
|
|
527
|
+
part("The agent", joinHtml([
|
|
528
|
+
projection === null ? "" : sizeLineHtml(projection),
|
|
529
|
+
who.length === 0 ? "" : html `<p class="approval-who">${who.join(" · ")}</p>`,
|
|
530
|
+
routeEvidenceHtml(route, "approval-evidence"),
|
|
531
|
+
agentsDetail,
|
|
532
|
+
limits,
|
|
533
|
+
notAllowingWarnings.length === 0 ? "" : html `<p class="approval-allowing" data-plan-allowing>You’re allowing: ${notAllowingWarnings.join(" · ")}</p>`,
|
|
534
|
+
yoursToCheck.length === 0 ? "" : html `<p class="approval-you-check" data-plan-you-check>You’ll check: ${yoursToCheck.join("; ")}</p>`,
|
|
535
|
+
chain.length === 0 ? "" : html `<p>${chain[0]}</p><ol class="plan-sheet-list" data-plan-fallback>${chain.slice(1).map(one => html `<li>${one.replace(/^\d+\. /, "")}</li>`)}</ol>`,
|
|
536
|
+
])),
|
|
537
|
+
part("Plan record", contractDetail),
|
|
538
|
+
scope.candidate ? part("Saved commit", html `<p><code class="approval-commit">${scope.candidate}</code></p>`) : "",
|
|
539
|
+
part("Plan version", html `<p class="meta">Approving agrees to exactly this plan. <span class="seal mono" title="${planVersionWords(scope.digest)}">${planVersionWords(scope.digest)}</span></p>`),
|
|
540
|
+
html `</article>`,
|
|
541
|
+
]);
|
|
542
|
+
return { lead, rows, editor, editorForm, details, submit, label: rebuild !== null ? "Approve and retry" : "Approve", after, allowing, passwordNote, sheet, sheetLead };
|
|
543
|
+
}
|
|
544
|
+
/** The plan sheet: what an approval signs, read top to bottom (approvalSheetParts' own parts, re-ordered). */
|
|
545
|
+
export function planSheetHtml(input) {
|
|
546
|
+
return approvalSheetParts(input).sheet;
|
|
463
547
|
}
|
|
548
|
+
/** Evidence kinds on the plan sheet: how each requirement is proven, in plain words. */
|
|
549
|
+
export const EVIDENCE_SHEET = { check: "Runs automatically", screenshot: "Shown in a screenshot", "changed-path": "Seen in the changed files", "manual-review": "You review it" };
|
|
464
550
|
/** Earlier versions of a task still in flight, in plain words: queued
|
|
465
551
|
* while they only wait, running once one has started; null when none. */
|
|
466
552
|
export function earlierVersionsWords(active, running) {
|
|
@@ -834,6 +920,8 @@ export function approveRefusalWords(reason) {
|
|
|
834
920
|
return "not approved: only a person can approve this. Approve it on the task page or from your paired chat";
|
|
835
921
|
if (reason === "archived")
|
|
836
922
|
return "not approved: this task is archived. Restore it first";
|
|
923
|
+
if (reason === "cancelled")
|
|
924
|
+
return "not approved: this task was cancelled";
|
|
837
925
|
if (reason === "unrouted")
|
|
838
926
|
return "not approved: this plan was made before agents were chosen per task, so its old approval no longer stands. Edit and save the plan to choose today’s agents, then approve it";
|
|
839
927
|
if (reason === "profile-unresolved")
|
|
@@ -1215,7 +1303,7 @@ export function planRevisionLedgerHtml(ledger, taskId, csrf) {
|
|
|
1215
1303
|
html `</details>`,
|
|
1216
1304
|
]);
|
|
1217
1305
|
return joinHtml([
|
|
1218
|
-
html `<div class="card plan-revision"><span class="eyebrow">plan updated</span><h2>Using plan revision ${current.revision}</h2>`,
|
|
1306
|
+
html `<div class="card plan-revision" id="plan-revisions"><span class="eyebrow">plan updated</span><h2>Using plan revision ${current.revision}</h2>`,
|
|
1219
1307
|
html `<p class="meta">${current.reason}${current.evidenceLink === null ? "" : ` · evidence: ${current.evidenceLink}`}</p>`,
|
|
1220
1308
|
pending,
|
|
1221
1309
|
history,
|
|
@@ -1339,10 +1427,8 @@ export function taskBodyParts(data) {
|
|
|
1339
1427
|
if (data.peekable !== true)
|
|
1340
1428
|
return "";
|
|
1341
1429
|
const peek = html `<p class="meta">What is changing right now</p><div id="run-peek"><p class="meta">Watching… the first look lands within 15 seconds</p></div><p class="meta" id="run-peek-stamp"></p>`;
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
: html `<p class="meta">What the agent is saying</p><pre id="live-transcript" class="mono" style="max-height:18rem;overflow:auto;white-space:pre-wrap"></pre><p class="meta" id="live-transcript-state"></p>`;
|
|
1345
|
-
return html `<div class="card attempt-live" data-live-run="${liveRun.id}">${head}${peek}${transcript}${door}</div>`;
|
|
1430
|
+
// What the agent is saying is the work panel's (Watch the work): the same records, read by the workspace.
|
|
1431
|
+
return html `<div class="card attempt-live" data-live-run="${liveRun.id}">${head}${peek}${door}</div>`;
|
|
1346
1432
|
})();
|
|
1347
1433
|
// One truthful answer to the first question on a queued task: "will this
|
|
1348
1434
|
// run?" The badge alone cannot distinguish approval, dependency,
|
|
@@ -1557,6 +1643,8 @@ export function taskBodyParts(data) {
|
|
|
1557
1643
|
// its draft; the full scope form below stays as it was.
|
|
1558
1644
|
const inlineScopeDraft = data.scopeDraft?.has("requirement-new") === true;
|
|
1559
1645
|
const scopeDraft = inlineScopeDraft ? undefined : data.scopeDraft;
|
|
1646
|
+
// A release's terms in the scope card (the page without script); the plan sheet says them whole (approvalSheetParts).
|
|
1647
|
+
const releaseTermsHtml = scope == null || (scope.releaseTerms == null && scope.releaseCheck == null) ? "" : html `<p><strong>Release</strong></p>${scope.releaseTerms == null ? "" : html `<p class="recap">${releaseTermsSentence(scope.releaseTerms)}</p>`}${scope.releaseCheck == null ? "" : html `<p class="recap">${releaseCheckSentence(scope.releaseCheck)}</p>`}${data.releaseCiRunning === true ? html `<p class="meta">${RELEASE_CI_RUNNING_WORDS}</p>` : ""}${scope.releaseTerms?.policy.carry === true ? html `<p><strong>What asks again</strong></p><ul>${joinHtml(releaseAsksAgainWords(scope.releaseTerms).map(line => html `<li>${line}</li>`), "")}</ul>` : ""}`;
|
|
1560
1648
|
const scopeCard = scope === null
|
|
1561
1649
|
? html `<p class="meta">No plan yet. Nothing builds until one is approved.</p>`
|
|
1562
1650
|
: joinHtml([
|
|
@@ -1566,7 +1654,7 @@ export function taskBodyParts(data) {
|
|
|
1566
1654
|
scope.touches.length === 0 ? "" : html `<p class="scope-paths"><strong>Files</strong> ${scope.touches.join(", ")}</p>`,
|
|
1567
1655
|
html `<p><strong>Quality</strong> ${qualityModeTitle(scope.qualityMode ?? "default")}</p>`,
|
|
1568
1656
|
acceptanceCeremonyHtml(scope.acceptance),
|
|
1569
|
-
|
|
1657
|
+
releaseTermsHtml,
|
|
1570
1658
|
approval.approved && data.autoRelease != null
|
|
1571
1659
|
? html `<p class="meta scope-seal">${autoReleaseWords(data.autoRelease)}</p>${planVersionDetailsHtml(scope.digest)}`
|
|
1572
1660
|
: approval.approved && data.release?.kind === "carried"
|
|
@@ -1628,6 +1716,14 @@ export function taskBodyParts(data) {
|
|
|
1628
1716
|
// card does not repeat it (one #contract-amendment on the page).
|
|
1629
1717
|
const sheetStatesContract = scope !== null && !approval.approved && data.plan === "drafted" && data.dispatch?.action !== "repair-dependency" &&
|
|
1630
1718
|
!(data.revision != null && "problem" in data.revision) && scope.profileState !== "unresolved" && consentDoorOf(scope, data.route).open;
|
|
1719
|
+
// The plan document's own editor (#plan-edit): the plan card holds it, and the task page's Plan row.
|
|
1720
|
+
const planEditorHtml = data.planDocument === null || data.csrf === "" || data.planSha == null || approval.approved
|
|
1721
|
+
? ""
|
|
1722
|
+
: html `<details class="plan-editor" id="plan-edit"><summary>Edit plan</summary>${postForm(`${taskHref(task.id)}/plan-edit`, joinHtml([
|
|
1723
|
+
html `<label>Plan details <span class="meta">Keep the five headings. Approval locks this version for the build.</span>`,
|
|
1724
|
+
html `<textarea name="plan-document" rows="14">${data.planDocument}</textarea></label>`,
|
|
1725
|
+
html `<button type="submit">Save plan</button>`,
|
|
1726
|
+
]), { hidden: { "saw-plan": data.planSha } })}</details>`;
|
|
1631
1727
|
const planCard = data.planDocument === null
|
|
1632
1728
|
? data.plan === "requested"
|
|
1633
1729
|
// A request the planner cannot be given waits on a person; the status card above already says what to shorten.
|
|
@@ -1644,13 +1740,7 @@ export function taskBodyParts(data) {
|
|
|
1644
1740
|
html `</div><span class="plan-lock">${planStanding}</span>${approval.approved ? html `</summary><div class="planner-plan-body">` : html `</div>`}`,
|
|
1645
1741
|
executionPlanHtml(displayedPlanDocument ?? data.planDocument),
|
|
1646
1742
|
approval.approved || sheetStatesContract ? "" : planContractHtml(data.planContract ?? null, "full"),
|
|
1647
|
-
|
|
1648
|
-
? ""
|
|
1649
|
-
: html `<details class="plan-editor" id="plan-edit"><summary>Edit plan</summary>${postForm(`${taskHref(task.id)}/plan-edit`, joinHtml([
|
|
1650
|
-
html `<label>Plan details <span class="meta">Keep the five headings. Approval locks this version for the build.</span>`,
|
|
1651
|
-
html `<textarea name="plan-document" rows="14">${data.planDocument}</textarea></label>`,
|
|
1652
|
-
html `<button type="submit">Save plan</button>`,
|
|
1653
|
-
]), { hidden: { "saw-plan": data.planSha } })}</details>`,
|
|
1743
|
+
planEditorHtml,
|
|
1654
1744
|
approval.approved ? html `</div></details>` : html `</section>`,
|
|
1655
1745
|
]);
|
|
1656
1746
|
const progressCard = milestoneProgressHtml(data.milestoneProgress);
|
|
@@ -1686,7 +1776,8 @@ export function taskBodyParts(data) {
|
|
|
1686
1776
|
(data.assignment?.primaryAction?.code === "approve-scope" && data.assignment.primaryAction.target.taskId === task.id);
|
|
1687
1777
|
// The sheet's terms, when this person can approve them here: the task page's kit approval box (its Plan tab shows
|
|
1688
1778
|
// the rows) and the legacy sheet both read these.
|
|
1689
|
-
|
|
1779
|
+
// A cancelled task asks for no approval: nothing would start, so its page offers no Approve (reopening asks again).
|
|
1780
|
+
const sheetInput = scope === null || approval.approved || task.state === "cancelled" || data.plan === "requested" || (data.revision != null && "problem" in data.revision) ||
|
|
1690
1781
|
scope.profileState === "unresolved" || !consentDoor.open ? null : {
|
|
1691
1782
|
surface: "task",
|
|
1692
1783
|
action: `${taskHref(task.id)}/approve`,
|
|
@@ -1706,6 +1797,7 @@ export function taskBodyParts(data) {
|
|
|
1706
1797
|
coordinator: data.coordinator ?? null,
|
|
1707
1798
|
origin: data.origin ?? null,
|
|
1708
1799
|
deliverable: data.deliverable ?? "branch",
|
|
1800
|
+
releaseCiRunning: data.releaseCiRunning === true,
|
|
1709
1801
|
rebuild: data.assignment?.need != null && "key" in data.assignment.need && data.assignment.need.key === "replan" ? data.assignment.need.build : null,
|
|
1710
1802
|
editHref: data.csrf !== "" && data.planSha != null && data.planDocument !== null ? "#plan-edit" : "#scope",
|
|
1711
1803
|
notNowHref: "/tasks",
|
|
@@ -1715,7 +1807,7 @@ export function taskBodyParts(data) {
|
|
|
1715
1807
|
: { action: `${taskHref(task.id)}/scope`, draft: inlineScopeDraft ? data.scopeDraft ?? null : null, problem: inlineScopeDraft ? data.problem : null,
|
|
1716
1808
|
open: data.editPlan === true, stepsHref: data.planSha != null && data.planDocument !== null ? "#plan-edit" : null },
|
|
1717
1809
|
};
|
|
1718
|
-
const approveForm = scope === null || approval.approved || data.plan === "requested"
|
|
1810
|
+
const approveForm = scope === null || approval.approved || task.state === "cancelled" || data.plan === "requested"
|
|
1719
1811
|
? ""
|
|
1720
1812
|
: data.revision !== null && data.revision !== undefined && "problem" in data.revision
|
|
1721
1813
|
? html `<div class="card approve-form" id="approve"><p><strong>This task is waiting on you: approval is blocked.</strong></p><p class="meta">${data.revision.problem}. A revision can be approved only when its saved notes can be read.</p></div>`
|
|
@@ -1862,7 +1954,7 @@ export function taskBodyParts(data) {
|
|
|
1862
1954
|
const workerRow = liveRun !== undefined
|
|
1863
1955
|
? prop("builder", html `${liveRun.runner} · <a href="/r/${liveRun.id}">build #${liveRun.id}</a> running`)
|
|
1864
1956
|
: data.runs[0] !== undefined
|
|
1865
|
-
? prop("last attempt", html `<a href="/r/${data.runs[0].id}">${runNoun(data.runs[0])} #${data.runs[0].id}</a> · ${data.runs[0].role === "planner" && data.runs[0].reason === "plan-drafted" ? "planned" : data.runs[0]
|
|
1957
|
+
? prop("last attempt", html `<a href="/r/${data.runs[0].id}">${runNoun(data.runs[0])} #${data.runs[0].id}</a> · ${data.runs[0].role === "planner" && data.runs[0].reason === "plan-drafted" ? "planned" : wasInterrupted(data.runs[0]) ? "interrupted" : data.runs[0].id === liveHistoryRunId ? "running" : data.runs[0].outcome ?? "never finished"} · ${data.runs[0].runner}`)
|
|
1866
1958
|
: "";
|
|
1867
1959
|
const queueRow = data.position !== null && data.position !== undefined && task.state === "queued"
|
|
1868
1960
|
? prop("queue", html `${data.position.position} of ${data.position.total}${data.position.column === null ? " in the shared queue" : html ` in ${data.position.column}'s queue`} · <a href="/tasks?view=order">reorder</a>`)
|
|
@@ -1885,7 +1977,7 @@ export function taskBodyParts(data) {
|
|
|
1885
1977
|
facts.push({ label: "Builder", parts: [`${liveRun.runner} · `, { label: `build #${liveRun.id}`, href: `/r/${liveRun.id}` }, " running"] });
|
|
1886
1978
|
else if (lastRun !== undefined)
|
|
1887
1979
|
facts.push({ label: "Last attempt", parts: [{ label: `${runNoun(lastRun)} #${lastRun.id}`, href: `/r/${lastRun.id}` },
|
|
1888
|
-
` · ${lastRun.role === "planner" && lastRun.reason === "plan-drafted" ? "planned" : lastRun
|
|
1980
|
+
` · ${lastRun.role === "planner" && lastRun.reason === "plan-drafted" ? "planned" : wasInterrupted(lastRun) ? "interrupted" : lastRun.id === liveHistoryRunId ? "running" : lastRun.outcome ?? "never finished"} · ${lastRun.runner}`] });
|
|
1889
1981
|
if (data.position !== null && data.position !== undefined && task.state === "queued") {
|
|
1890
1982
|
facts.push({ label: "Queue", parts: [`${data.position.position} of ${data.position.total}${data.position.column === null ? " in the shared queue" : ` in ${data.position.column}'s queue`} · `, { label: "Reorder", href: "/tasks?view=order" }] });
|
|
1891
1983
|
}
|
|
@@ -2132,30 +2224,10 @@ export function taskBodyParts(data) {
|
|
|
2132
2224
|
: html `<details class="task-secondary-approval"><summary>Updated approval terms</summary>${approveForm}</details>`;
|
|
2133
2225
|
const optionsHtml = html `${identity}${dispatchStatus}${dependencyChoiceNeeded ? "" : actsBar}`;
|
|
2134
2226
|
const optionsOpen = data.assignment?.primaryAction?.code === "unhold" || data.assignment?.primaryAction?.code === "retry-task";
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
data.archived == null ? "" : html `<div class="task-archived" data-archived><p>${archivedWords(data.archived)} · ${whenUtc(data.archived.at)}</p>${data.canPutAway === true && data.csrf !== ""
|
|
2140
|
-
? postForm(`${taskHref(task.id)}/restore`, html `<button type="submit" class="secondary">Restore</button>`, { hidden: { return: taskHref(task.id) }, attrs: { class: "inline" } }) : ""}</div>`,
|
|
2141
|
-
(data.history ?? ""),
|
|
2142
|
-
// A put-away task's banner is its status: no Needs you card, no act, no approval form (each would be refused).
|
|
2143
|
-
data.archived == null ? statusHtml : "",
|
|
2144
|
-
controlHtml,
|
|
2145
|
-
previousResult === null ? "" : html `<details class="task-previous-result"><summary>${previousResult.title}</summary>${previousResult.html}</details>`,
|
|
2146
|
-
progressCard,
|
|
2147
|
-
revisionLedgerCard,
|
|
2148
|
-
planCard,
|
|
2149
|
-
mirrorCard,
|
|
2150
|
-
problemHtml,
|
|
2151
|
-
needsScopeCard,
|
|
2152
|
-
data.archived == null ? approvalHtml : "",
|
|
2153
|
-
html `<details class="task-status-details" id="task-diagnostics"${optionsOpen ? html ` open` : ""}><summary>Task options</summary>${optionsHtml}</details>`,
|
|
2154
|
-
// Evidence-first (M5.5): what needs you, then what happened — decisions
|
|
2155
|
-
// and incidents above the attempt ledger and spend, the mechanics
|
|
2156
|
-
// (scope, holds, acts) after. Only trustworthy facts moved up. The rail
|
|
2157
|
-
// (slice 1c) rides beside the main column on wide screens and above it
|
|
2158
|
-
// on narrow ones.
|
|
2227
|
+
// The Plan section after its scope card: the release, the recipe and audit links, the agents, the edit form.
|
|
2228
|
+
const scopeRest = [releaseCardHtml(task.id, data.release ?? null, data.canStopRelease === true), html `<p class="meta">${data.repo !== null && data.scope !== null ? html `<a href="/recipes/from-task?task=${encodeURIComponent(task.id)}">Reuse this plan as a recipe →</a> · ` : ""}<a href="${taskHref(data.rootId ?? task.id)}/evidence">Audit record →</a></p>`, agentsCardHtml(task.id, data.route, data.canEditRoute === true), revisionInApproval ? "" : revisionCard, data.completion != null ? "" : repairChainHtml(data.repairChain ?? null), scopeForm];
|
|
2229
|
+
// The page's sections, built once and in order (each `section` also records its part for the rebuilt page).
|
|
2230
|
+
const layoutTail = joinHtml([
|
|
2159
2231
|
html `<div class="task-layout"><div class="task-main">`,
|
|
2160
2232
|
attemptPanel,
|
|
2161
2233
|
data.coordinatorProposals == null || data.coordinatorProposals.rows.length === 0
|
|
@@ -2181,12 +2253,41 @@ export function taskBodyParts(data) {
|
|
|
2181
2253
|
section("plan",
|
|
2182
2254
|
// The recipe road rides with the scope it reuses (UI polish
|
|
2183
2255
|
// 2026-09-13), off the title-to-action path.
|
|
2184
|
-
joinHtml([html `<h2>Plan</h2>`, scopeCard,
|
|
2256
|
+
joinHtml([html `<h2>Plan</h2>`, scopeCard, ...scopeRest], "\n"), scopeDraft !== undefined || (data.plan !== "requested" && approveForm === "" && !(scope === null && canPlan)), undefined, "scope"),
|
|
2185
2257
|
dependencyChoiceNeeded ? "" : section("waits for", waitsForCard, (data.waitsFor ?? []).length > 0, (data.waitsFor ?? []).length),
|
|
2186
2258
|
section("holds", holds, true, data.holds.length),
|
|
2187
2259
|
cancelAct,
|
|
2188
2260
|
html `</div><aside class="task-rail">${rail}</aside></div>`,
|
|
2189
2261
|
], "\n");
|
|
2262
|
+
// `rows`: the task's step rows, every one open (the page without script, docs/plans/task-page-steps.md): the status,
|
|
2263
|
+
// the approval, the plan, the build's progress and the previous result move into their rows; everything else stays.
|
|
2264
|
+
const nativeBody = (rows) => joinHtml([
|
|
2265
|
+
// The title is bare; the receipt or shared task status leads once.
|
|
2266
|
+
html `<div class="task-title-row"><h1 class="task-main-title">${data.rootTitle ?? task.title}</h1>${data.csrf === "" ? "" : taskViewSwitch(data.rootId ?? task.id, "overview")}</div>`,
|
|
2267
|
+
data.versionLabel == null ? "" : html `<p class="meta">${data.versionLabel} · <a href="${taskHref(data.rootId ?? task.id)}">Current work</a></p>`,
|
|
2268
|
+
data.archived == null ? "" : html `<div class="task-archived" data-archived><p>${archivedWords(data.archived)} · ${whenUtc(data.archived.at)}</p>${data.canPutAway === true && data.csrf !== ""
|
|
2269
|
+
? postForm(`${taskHref(task.id)}/restore`, html `<button type="submit" class="secondary">Restore</button>`, { hidden: { return: taskHref(task.id) }, attrs: { class: "inline" } }) : ""}</div>`,
|
|
2270
|
+
(data.history ?? ""),
|
|
2271
|
+
rows ?? "",
|
|
2272
|
+
// A put-away task's banner is its status: no Needs you card, no act, no approval form (each would be refused).
|
|
2273
|
+
rows === null && data.archived == null ? statusHtml : "",
|
|
2274
|
+
rows === null ? controlHtml : "",
|
|
2275
|
+
rows !== null || previousResult === null ? "" : html `<details class="task-previous-result"><summary>${previousResult.title}</summary>${previousResult.html}</details>`,
|
|
2276
|
+
rows === null ? progressCard : "",
|
|
2277
|
+
rows === null ? revisionLedgerCard : "",
|
|
2278
|
+
rows === null ? planCard : "",
|
|
2279
|
+
mirrorCard,
|
|
2280
|
+
problemHtml,
|
|
2281
|
+
needsScopeCard,
|
|
2282
|
+
rows === null && data.archived == null ? approvalHtml : "",
|
|
2283
|
+
html `<details class="task-status-details" id="task-diagnostics"${optionsOpen ? html ` open` : ""}><summary>Task options</summary>${optionsHtml}</details>`,
|
|
2284
|
+
// Evidence-first (M5.5): what needs you, then what happened — decisions
|
|
2285
|
+
// and incidents above the attempt ledger and spend, the mechanics
|
|
2286
|
+
// (scope, holds, acts) after. Only trustworthy facts moved up. The rail
|
|
2287
|
+
// (slice 1c) rides beside the main column on wide screens and above it
|
|
2288
|
+
// on narrow ones.
|
|
2289
|
+
layoutTail,
|
|
2290
|
+
], "\n");
|
|
2190
2291
|
// Console v2: one thread in time order (the plan, the agent's notes and
|
|
2191
2292
|
// results, its questions, the person's replies) and the metadata grouped
|
|
2192
2293
|
// for a Details panel. Every form below keeps its own route and fields.
|
|
@@ -2243,13 +2344,19 @@ export function taskBodyParts(data) {
|
|
|
2243
2344
|
}
|
|
2244
2345
|
// What the agent is working through now, and how its plan changed.
|
|
2245
2346
|
const liveAt = liveRun?.startedAt ?? task.updatedAt;
|
|
2347
|
+
// The plan itself is the page's Plan row: the thread keeps one line saying it was written.
|
|
2246
2348
|
if (planCard !== "")
|
|
2247
2349
|
thread.push({ key: "plan", at: familyRuns.filter(run => run.role === "planner").map(run => run.finishedAt ?? run.startedAt).sort().at(-1) ?? task.updatedAt,
|
|
2248
|
-
kind: "plan", who: "agent", author: "", title: "
|
|
2350
|
+
kind: "plan", who: "agent", author: "", title: data.planDocument !== null ? "Plan written" : data.revision != null && !("problem" in data.revision) ? "Updating the plan" : "Planning", text: null, link: null, html: "", more: null });
|
|
2249
2351
|
if (!isEmptyHtml(progressCard))
|
|
2250
2352
|
thread.push({ key: "progress", at: liveAt, kind: "progress", who: "agent", author: "", title: "Progress", text: null, link: null, html: htmlString(progressCard), more: null });
|
|
2251
|
-
|
|
2252
|
-
|
|
2353
|
+
// A plan change is decided on the page (the Build row holds its card): the thread, which the work panel shows, keeps
|
|
2354
|
+
// one line and a link there. Nothing in the thread takes a decision.
|
|
2355
|
+
if (!isEmptyHtml(revisionLedgerCard)) {
|
|
2356
|
+
const waiting = data.planRevisions?.pending ?? null;
|
|
2357
|
+
thread.push({ key: "plan-revisions", at: liveAt, kind: "progress", who: "agent", author: "", title: waiting === null ? "Plan changes" : "Recommends a plan change",
|
|
2358
|
+
text: waiting === null ? null : oneLineOf(waiting.reason, 280), link: { label: waiting === null ? "See the plan changes" : "Decide on the task page", href: "#plan-revisions" }, html: "", more: null });
|
|
2359
|
+
}
|
|
2253
2360
|
// The change a person asked for: the revision's own brief.
|
|
2254
2361
|
if (data.revision != null && !("problem" in data.revision)) {
|
|
2255
2362
|
const feedback = data.revision;
|
|
@@ -2273,10 +2380,12 @@ export function taskBodyParts(data) {
|
|
|
2273
2380
|
text: [chosen, decision.note].filter((one) => one !== null && one !== "").join(" — ") || null, link: null, html: "", more: null });
|
|
2274
2381
|
}
|
|
2275
2382
|
}
|
|
2276
|
-
// Open questions
|
|
2383
|
+
// Open questions are answered on the page (their cards sit in the step that waits, at #task-questions): the thread
|
|
2384
|
+
// says what was asked and links there.
|
|
2277
2385
|
if (decisionRail !== "")
|
|
2278
2386
|
thread.push({ key: "questions", at: openDecisions.map(one => one.createdAt).sort()[0] ?? task.updatedAt, kind: "question", who: "agent", author: "",
|
|
2279
|
-
title: openDecisions.length === 1 ? "Asked you a question" : `Asked you ${openDecisions.length} questions`, text:
|
|
2387
|
+
title: openDecisions.length === 1 ? "Asked you a question" : `Asked you ${openDecisions.length} questions`, text: openDecisions.map(one => oneLineOf(one.question, 280)).join("\n"),
|
|
2388
|
+
link: { label: openDecisions.length === 1 ? "Answer it on the task page" : "Answer them on the task page", href: "#task-questions" }, html: "", more: null });
|
|
2280
2389
|
for (const note of data.steering ?? []) {
|
|
2281
2390
|
thread.push({ key: `steer-${note.id}`, at: note.createdAt, kind: "reply", who: "person", author: note.author, title: "Note to the agent", text: note.note, link: null, html: "", more: null });
|
|
2282
2391
|
}
|
|
@@ -2402,6 +2511,29 @@ export function taskBodyParts(data) {
|
|
|
2402
2511
|
{ group: "share", label: "Copy link", kind: "copy" },
|
|
2403
2512
|
...(cancelForm !== "" ? [{ group: "danger", label: "Cancel task", kind: "goto", tab: "plan", target: "cancel-task" }] : []),
|
|
2404
2513
|
];
|
|
2514
|
+
// The Plan row (docs/plans/task-page-steps.md): the plan sheet of the terms on file, built from the same parts as
|
|
2515
|
+
// the approval's (approvalSheetParts), and the plan's own editor. While the approval waits here the sheet is the
|
|
2516
|
+
// approval's own (`approve.planHtml`), so this row carries only the editor: the plan is shown once.
|
|
2517
|
+
const plannerRun = familyRuns.filter(run => run.role === "planner" && run.outcome !== null).sort((a, b) => b.id - a.id)[0];
|
|
2518
|
+
const sheetShown = scope === null ? null : sheetInput ?? {
|
|
2519
|
+
surface: "task", action: "", csrf: "", nonce: "", digest: scope.digest, returnTo: null, scope,
|
|
2520
|
+
planDocument: data.planDocument ?? null, planContract: data.planContract ?? null,
|
|
2521
|
+
revision: data.revision == null || "problem" in data.revision ? null : data.revision,
|
|
2522
|
+
revisionSourceHref: data.revision == null || "problem" in data.revision ? "" : `/r/${data.revision.sourceRun}`,
|
|
2523
|
+
repairChain: data.repairChain ?? null, route: data.route, coordinator: data.coordinator ?? null, origin: data.origin ?? null,
|
|
2524
|
+
deliverable: data.deliverable ?? "branch", releaseCiRunning: data.releaseCiRunning === true, editHref: "#scope", notNowHref: "/tasks", sticky: false,
|
|
2525
|
+
earlier: { active: data.assignment?.earlierActive ?? 0, running: data.assignment?.earlierRunning ?? 0 }, edit: null,
|
|
2526
|
+
};
|
|
2527
|
+
const sheetParts = sheetShown === null ? null : approvalSheetParts(sheetShown);
|
|
2528
|
+
const approvalWaitsHere = data.archived == null && sheetInput !== null && !dependencyChoiceNeeded && data.csrf !== "";
|
|
2529
|
+
// A revision's plan being updated with the notes: the planner's card says so above the plan it starts from.
|
|
2530
|
+
const planUpdating = data.planDocument === null && data.plan === "requested" && data.revision != null && !("problem" in data.revision) && planCard !== "";
|
|
2531
|
+
const planView = scope === null
|
|
2532
|
+
? (planCard === "" ? null : { lead: null, by: null, at: null, html: htmlString(planCard), planning: data.plan === "requested", updating: planUpdating, key: "card" })
|
|
2533
|
+
: { lead: oneLineOf(sheetParts.sheetLead, 160), by: plannerRun === undefined ? null : agentNameWords(plannerRun.provider, plannerRun.model),
|
|
2534
|
+
at: plannerRun?.finishedAt ?? scope.proposedAt ?? null, planning: planUpdating, updating: planUpdating,
|
|
2535
|
+
key: `${planUpdating ? "updating:" : ""}${approvalWaitsHere ? "editor" : `sheet:${scope.digest}`}`,
|
|
2536
|
+
html: htmlString(joinHtml([planUpdating ? planCard : "", approvalWaitsHere ? "" : sheetParts.sheet, planEditorHtml])) };
|
|
2405
2537
|
const view = {
|
|
2406
2538
|
kind: "task",
|
|
2407
2539
|
id: task.id,
|
|
@@ -2422,9 +2554,24 @@ export function taskBodyParts(data) {
|
|
|
2422
2554
|
failure: failedCard && data.failure != null ? { line: data.failure.line, evidence: data.failure.evidence, suggestion: data.failure.suggestion, link: data.failure.link } : null,
|
|
2423
2555
|
retry,
|
|
2424
2556
|
runChecks,
|
|
2425
|
-
progress,
|
|
2557
|
+
progress: progress === null ? null : { ...progress, steps: (data.milestoneProgress ?? []).map(one => ({ title: one.description, state: one.state })) },
|
|
2558
|
+
plan: planView,
|
|
2426
2559
|
// One quiet line under the step: what the agent did last, and when.
|
|
2427
2560
|
activity: liveRun === undefined ? null : data.activity ?? null,
|
|
2561
|
+
// The work panel's run: the live build, else this version's latest build or report. Its records are read through the
|
|
2562
|
+
// run page's own guarded window; a page asking for a password names no window at all.
|
|
2563
|
+
work: (() => {
|
|
2564
|
+
const recorded = (run) => run.role === "builder" || run.role === "scout";
|
|
2565
|
+
// Running: unfinished under the task's current claim (liveRun is the one run proved live; a builder whose repair
|
|
2566
|
+
// child is the live run shares that claim and is still this attempt).
|
|
2567
|
+
const running = (run) => liveRun !== undefined && run.outcome === null && run.finishedAt === null && run.leaseId === liveRun.leaseId;
|
|
2568
|
+
const builds = data.runs.filter(recorded).sort((a, b) => b.id - a.id);
|
|
2569
|
+
const run = builds.find(running) ?? builds[0];
|
|
2570
|
+
if (run === undefined)
|
|
2571
|
+
return null;
|
|
2572
|
+
const live = running(run);
|
|
2573
|
+
return { run: run.id, live, agent: agentNameWords(run.provider, run.model), transcript: degraded ? null : `/r/${run.id}?fragment=transcript&shape=records` };
|
|
2574
|
+
})(),
|
|
2428
2575
|
stop,
|
|
2429
2576
|
pause,
|
|
2430
2577
|
steering: data.csrf !== "" ? data.steerComposer ?? null : null,
|
|
@@ -2441,22 +2588,30 @@ export function taskBodyParts(data) {
|
|
|
2441
2588
|
recheckStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "recheck-stopped" && data.assignment.primaryAction.target.runId !== null
|
|
2442
2589
|
? { action: `${taskHref(data.assignment.rootId)}/recheck-stopped`, run: data.assignment.primaryAction.target.runId } : null,
|
|
2443
2590
|
rebuild,
|
|
2444
|
-
//
|
|
2591
|
+
// What needs a person on the page itself. Open questions and a plan change are answered here, in their step's row:
|
|
2592
|
+
// the thread (the work panel) only links to them.
|
|
2445
2593
|
lead: [
|
|
2446
2594
|
{ key: "history", html: data.history === undefined ? "" : htmlString(data.history) }, { key: "control", html: htmlString(stop === null ? controlHtml : taskControlDetailsHtml(data.control, task.id, data.csrf, "task", false, true)) }, { key: "problem", html: fragmentString(problemHtml) },
|
|
2595
|
+
{ key: "questions", html: decisionRail === "" ? "" : htmlString(decisionRail) }, { key: "plan-revisions", html: isEmptyHtml(revisionLedgerCard) ? "" : htmlString(revisionLedgerCard) },
|
|
2447
2596
|
{ key: "route-change", html: data.routeChange == null ? "" : htmlString(html `<div class="card"><p>${data.routeChange}</p></div>`) },
|
|
2448
2597
|
{ key: "fast-grown", html: data.fastGrown == null || data.csrf === "" ? "" : htmlString(html `<div class="card"><p>${data.fastGrown}</p>${postForm(`${taskHref(task.id)}/keep-grown`, btn("Approve it", { variant: "primary", type: "submit" }))}</div>`) },
|
|
2449
2598
|
{ key: "pull-request", html: fragmentString(pullRequestCard) },
|
|
2450
2599
|
{ key: "needs-scope", html: fragmentString(needsScopeCard) },
|
|
2451
2600
|
{ key: "mirror", html: fragmentString(mirrorCard) }, { key: "attempt", html: fragmentString(attemptPanel) },
|
|
2452
2601
|
].filter(one => one.html !== ""),
|
|
2453
|
-
// Open questions are
|
|
2602
|
+
// Open questions are a lead block (key "questions"), so they sit in the step that waits.
|
|
2454
2603
|
questions: "",
|
|
2455
2604
|
facts,
|
|
2456
2605
|
sections: [
|
|
2457
2606
|
...(previousResult === null || thread.some(one => one.more !== null) ? [] : [{ id: "result", title: previousResult.title, html: htmlString(previousResult.html), open: false, count: null }]),
|
|
2458
2607
|
// Questions are in the thread; the scope and the ledgers fold here until asked.
|
|
2459
|
-
|
|
2608
|
+
// The Plan and Approval rows hold the plan sheet (a release's terms included), so this page's Plan section keeps
|
|
2609
|
+
// only what the sheet leaves out (the links, the agents, the edit form): the plan is never printed a second time.
|
|
2610
|
+
...sectionParts.filter(one => !MANAGE.has(one.id) && one.id !== "decisions").map(one => one.id !== "scope" ? one : {
|
|
2611
|
+
...one,
|
|
2612
|
+
...(scope === null ? {} : { html: htmlString(joinHtml(scopeRest, "\n")) }),
|
|
2613
|
+
...(scopeDraft === undefined && (finished || approval.approved) ? { open: false } : {}),
|
|
2614
|
+
}),
|
|
2460
2615
|
],
|
|
2461
2616
|
manage: [
|
|
2462
2617
|
...sectionParts.filter(one => MANAGE.has(one.id)),
|
|
@@ -2482,7 +2637,22 @@ export function taskBodyParts(data) {
|
|
|
2482
2637
|
return { href: `/settings/flows?repo=${encodeURIComponent(data.repo)}&starter=${starter.id}#starter-${starter.id}`, starter: starter.name };
|
|
2483
2638
|
})(),
|
|
2484
2639
|
};
|
|
2485
|
-
|
|
2640
|
+
// Without script the steps are the page too: every row open, in order, each holding its own part.
|
|
2641
|
+
const rowFacts = data.archived != null ? null : stepRowsOf(view);
|
|
2642
|
+
const rowsHtml = rowFacts === null ? null : (() => {
|
|
2643
|
+
const here = rowFacts.find(row => row.state === "current" || row.state === "stuck") ?? rowFacts.find(row => row.open) ?? rowFacts.at(-1);
|
|
2644
|
+
const bodyOf = (key) => joinHtml([
|
|
2645
|
+
key === here.key ? statusHtml : "",
|
|
2646
|
+
key === "plan" ? planCard : "",
|
|
2647
|
+
// The shared approval sheet, its Plan details open here: nothing the yes agrees to is folded on this page.
|
|
2648
|
+
key === "approve" ? (approvalHtml !== "" ? approvalHtml : view.approved == null ? "" : html `<p class="meta">${view.approved.automatic ? `Approved automatically by ${view.approved.rule ?? "a rule"}` : `Approved by ${view.approved.by}`} · ${whenTime(view.approved.at)}</p>`) : "",
|
|
2649
|
+
key === "build" ? joinHtml([controlHtml, progressCard, revisionLedgerCard]) : "",
|
|
2650
|
+
key === "ready" && previousResult !== null ? html `<section class="task-previous-result" aria-label="${previousResult.title}">${previousResult.html}</section>` : "",
|
|
2651
|
+
]);
|
|
2652
|
+
return stepRows(rowFacts.map(row => ({ key: row.key, label: row.label, mark: row.mark, sentence: row.sentence, needsYou: row.needsYou, current: row.state === "current" || row.state === "stuck",
|
|
2653
|
+
at: row.at, when: row.at === null ? "" : whenText(row.at), body: bodyOf(row.key), open: true })));
|
|
2654
|
+
})();
|
|
2655
|
+
return { html: nativeBody(rowsHtml), view };
|
|
2486
2656
|
}
|
|
2487
2657
|
export function taskBody(data) {
|
|
2488
2658
|
return taskBodyParts(data).html;
|
|
@@ -2504,7 +2674,6 @@ export function taskPage(chrome, data) {
|
|
|
2504
2674
|
const liveRun = liveRunId === null ? undefined : data.runs.find(one => one.id === liveRunId);
|
|
2505
2675
|
const script = (liveRun === undefined ? "" : regionScript("check-progress", "check", `/r/${liveRun.id}`)) +
|
|
2506
2676
|
(liveRun !== undefined && data.peekable === true ? regionScript("run-peek", "peek", `/r/${liveRun.id}`) : "") +
|
|
2507
|
-
(liveRun !== undefined && data.peekable === true && liveRun.provider === "claude" ? transcriptScript(`/r/${liveRun.id}`) : "") +
|
|
2508
2677
|
(data.csrf !== "" && data.decisions.some(one => one.state === "open" || one.state === "expired") ? decisionAnswerScript() : "");
|
|
2509
2678
|
return screen(`task \u00b7 ${data.task.id}`, first.html, {
|
|
2510
2679
|
chrome,
|
|
@@ -2609,7 +2778,8 @@ export function approveKitOf(input, freshSince) {
|
|
|
2609
2778
|
rememberedUntil: until,
|
|
2610
2779
|
// Easy-start C5: the owner approving on this computer needs no password, and the line says so (not "Passwordless").
|
|
2611
2780
|
...(input.localOwner?.eligible === true ? { termsWords: "No password needed on this computer." } : {}),
|
|
2612
|
-
|
|
2781
|
+
// What it signs: the plan sheet (the same parts as the chat's sheet, re-ordered), then the in-place editor.
|
|
2782
|
+
planHtml: htmlString(joinHtml([parts.sheet, parts.editor ?? "", parts.editorForm])),
|
|
2613
2783
|
};
|
|
2614
2784
|
}
|
|
2615
2785
|
/** How an approved plan was approved, for the status box's terms line: by whom and how, or the rule that approved it. */
|
|
@@ -37,7 +37,7 @@ import { runActivityOf } from "../task-activity.js";
|
|
|
37
37
|
import { taskFingerprint } from "../task-live.js";
|
|
38
38
|
import { pullRequestFactOf } from '../task-status.js';
|
|
39
39
|
import { openWorkDecisionOf } from "../work-summary.js";
|
|
40
|
-
import { learningHtml } from "../workspace-ui.js";
|
|
40
|
+
import { learningHtml, stepRowsOf } from "../workspace-ui.js";
|
|
41
41
|
import { createHash, randomBytes } from "node:crypto";
|
|
42
42
|
import {} from "node:http";
|
|
43
43
|
import { agentChoicesFor, routeOfTask } from "../agentconfig.js";
|
|
@@ -661,6 +661,7 @@ export function createTaskViews(runtime) {
|
|
|
661
661
|
coordinator: view.coordinator ?? null,
|
|
662
662
|
origin: view.origin ?? null,
|
|
663
663
|
repairChain: view.repairChain ?? null,
|
|
664
|
+
releaseCiRunning: view.releaseCiRunning === true,
|
|
664
665
|
},
|
|
665
666
|
decisions: (view?.decisions ?? runtime.store.decisionsForTask(ref.id))
|
|
666
667
|
.filter(one => one.state === "open" || one.state === "expired")
|
|
@@ -886,6 +887,9 @@ export function createTaskViews(runtime) {
|
|
|
886
887
|
if (oneTaskView.release != null)
|
|
887
888
|
oneTaskView.journey = null;
|
|
888
889
|
}
|
|
890
|
+
// The step rows read the view as the page will show it (its result, its release, its checks words).
|
|
891
|
+
if (oneTaskView?.kind === "task")
|
|
892
|
+
oneTaskView.steps = stepRowsOf(oneTaskView);
|
|
889
893
|
// The Result tab's no-script page, under the task's own (the same HTML its result page serves), and its script.
|
|
890
894
|
if (extras.result != null) {
|
|
891
895
|
page.body = html `${page.body}<section class="task-result-tab" id="task-result" aria-label="Result">${extras.result.html}</section>`;
|