toolroll 0.9.84 → 0.9.86
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 +66 -0
- package/README.md +94 -1193
- package/dist/agent-fence.d.ts +9 -5
- package/dist/agent-fence.js +6 -5
- package/dist/agent-onboard.d.ts +2 -0
- package/dist/agent-onboard.js +3 -0
- package/dist/assignment.js +4 -3
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +33 -31
- package/dist/browser-workspace.d.ts +45 -0
- package/dist/chat-actions.d.ts +11 -1
- package/dist/chat-actions.js +53 -1
- package/dist/chat-channel.d.ts +10 -1
- package/dist/chat-channel.js +36 -5
- package/dist/chat-decide.d.ts +16 -4
- package/dist/chat-decide.js +239 -31
- package/dist/chat-messages.d.ts +5 -0
- package/dist/chat-messages.js +1 -1
- package/dist/chat-quiet.js +6 -4
- package/dist/chat-review.d.ts +4 -0
- package/dist/chat-review.js +13 -4
- package/dist/chat-terms.d.ts +9 -0
- package/dist/chat-terms.js +46 -0
- package/dist/claim.d.ts +3 -0
- package/dist/claim.js +4 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +3 -0
- package/dist/contracts/console-api.d.ts +6 -2
- package/dist/contracts/console-api.js +6 -2
- package/dist/contracts/flow-propose.d.ts +4 -1
- package/dist/contracts/flow-propose.js +1 -1
- package/dist/contracts/flow.d.ts +41 -0
- package/dist/contracts/flow.js +4 -2
- package/dist/contracts/lead-tools.d.ts +51 -0
- package/dist/contracts/lead-tools.js +5 -0
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/trigger-payloads.d.ts +20 -0
- package/dist/contracts/trigger-payloads.js +4 -0
- package/dist/contracts/youiyoux-request.d.ts +248 -0
- package/dist/contracts/youiyoux-request.js +152 -0
- package/dist/coordinator.js +1 -0
- package/dist/deploy-gate.js +4 -0
- package/dist/dispatch.d.ts +3 -0
- package/dist/dispatch.js +4 -1
- package/dist/flow-browser.d.ts +103 -0
- package/dist/flow-browser.js +204 -0
- package/dist/flow-cadence.d.ts +8 -2
- package/dist/flow-cadence.js +16 -3
- package/dist/flow-card-task.d.ts +9 -0
- package/dist/flow-card-task.js +15 -6
- package/dist/flow-code.d.ts +5 -1
- package/dist/flow-code.js +4 -1
- package/dist/flow-engine.js +16 -0
- package/dist/flow-live.js +9 -1
- package/dist/flow-pull-request.js +15 -7
- package/dist/flow-share.js +3 -1
- package/dist/flow-steps.d.ts +9 -1
- package/dist/flow-steps.js +55 -5
- package/dist/flow-triggers.js +55 -0
- package/dist/flows-cli.d.ts +19 -0
- package/dist/flows-cli.js +47 -2
- package/dist/flows-ui.js +11 -2
- package/dist/flows.js +5 -2
- package/dist/integrations-ui.js +9 -4
- package/dist/integrations.d.ts +9 -1
- package/dist/integrations.js +43 -2
- package/dist/landed-cleanup.d.ts +4 -0
- package/dist/landed-cleanup.js +3 -2
- package/dist/lead-capabilities.js +2 -1
- package/dist/lead-cli.js +3 -1
- package/dist/lead-tools.d.ts +2 -0
- package/dist/lead-tools.js +65 -10
- package/dist/lead-voice.js +16 -5
- package/dist/lead.d.ts +2 -0
- package/dist/lead.js +3 -3
- package/dist/operate.d.ts +2 -3
- package/dist/operate.js +226 -21
- package/dist/project-activity.d.ts +20 -0
- package/dist/project-activity.js +23 -0
- package/dist/project-cli.d.ts +1 -0
- package/dist/project-cli.js +36 -19
- package/dist/project-delete.js +7 -3
- package/dist/project-knowledge.d.ts +3 -3
- package/dist/project-outcomes.d.ts +119 -0
- package/dist/project-outcomes.js +79 -0
- package/dist/pull-request-flow.js +35 -4
- package/dist/result-actions.d.ts +4 -0
- package/dist/result-actions.js +41 -9
- package/dist/result-follow-ups.js +4 -0
- package/dist/result-review.d.ts +9 -0
- package/dist/result-review.js +45 -5
- package/dist/scope.d.ts +5 -0
- package/dist/scope.js +7 -0
- package/dist/server/chat.js +4 -2
- package/dist/server/console.css +41 -0
- package/dist/server/flows.js +15 -0
- package/dist/server/pages.js +11 -1
- package/dist/server/render-chat.js +3 -2
- package/dist/server/render-pages.d.ts +15 -2
- package/dist/server/render-pages.js +52 -4
- package/dist/server/render-results.d.ts +2 -0
- package/dist/server/render-results.js +20 -7
- package/dist/server/render-tasks.d.ts +9 -0
- package/dist/server/render-tasks.js +32 -11
- package/dist/server/route-table.js +12 -0
- package/dist/server/settings.js +18 -1
- package/dist/server/task-views.js +11 -0
- package/dist/server/tasks.d.ts +10 -0
- package/dist/server/tasks.js +95 -7
- package/dist/settings-proofs.js +1 -0
- package/dist/store.d.ts +116 -5
- package/dist/store.js +385 -24
- package/dist/surface.js +18 -2
- package/dist/task-archive.d.ts +10 -0
- package/dist/task-archive.js +16 -0
- package/dist/task-control.d.ts +1 -1
- package/dist/task-source.d.ts +13 -1
- package/dist/task-source.js +17 -1
- package/dist/telegram-lead.js +7 -5
- package/dist/telegram.js +1 -1
- package/dist/tidy.d.ts +130 -0
- package/dist/tidy.js +503 -0
- package/dist/toolroll-update.js +2 -2
- package/dist/webhooks.d.ts +6 -0
- package/dist/webhooks.js +25 -0
- package/dist/work-index.d.ts +28 -0
- package/dist/work-index.js +82 -28
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +3 -2
- package/dist/youiyoux-intake.d.ts +234 -0
- package/dist/youiyoux-intake.js +908 -0
- package/package.json +11 -8
package/dist/server/console.css
CHANGED
|
@@ -2730,3 +2730,44 @@ html.so-swap::view-transition-old(*):only-child { animation: so-card-out 160ms e
|
|
|
2730
2730
|
html.so-swap::view-transition-group(*), html.so-swap::view-transition-new(*), html.so-swap::view-transition-old(*) { animation: none !important; }
|
|
2731
2731
|
.worker.flash { animation: none; }
|
|
2732
2732
|
}
|
|
2733
|
+
|
|
2734
|
+
/* Exact-image feedback shares the existing comment form. */
|
|
2735
|
+
.result-image { margin: 0; min-width: 0; }
|
|
2736
|
+
.result-image > button { margin-top: .5rem; min-height: 44px; white-space: nowrap; }
|
|
2737
|
+
.feedback-image { display: flex; align-items: center; flex-wrap: wrap; gap: .75rem; margin: .5rem 0; min-width: 0; }
|
|
2738
|
+
.feedback-image[hidden], .feedback-image img[hidden] { display: none; }
|
|
2739
|
+
.feedback-image img { width: 5rem; max-height: 5rem; object-fit: contain; border: 1px solid var(--border); border-radius: var(--radius); }
|
|
2740
|
+
.feedback-image figcaption { flex: 1 1 12rem; overflow-wrap: anywhere; font-size: .875rem; }
|
|
2741
|
+
.feedback-image button { min-height: 44px; white-space: nowrap; }
|
|
2742
|
+
|
|
2743
|
+
.result-feedback-actions { display: flex; flex-wrap: wrap; gap: .5rem; }
|
|
2744
|
+
.result-feedback-actions button { min-height: 44px; white-space: nowrap; }
|
|
2745
|
+
|
|
2746
|
+
/* Put away old tasks (docs/plans/task-tidy.md): grouped checklist on the kit's column. */
|
|
2747
|
+
.put-away { max-width: var(--so-settings-width, 760px); }
|
|
2748
|
+
.put-away h1 { margin: 0 0 4px; }
|
|
2749
|
+
.put-away-days { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 0 0 16px; }
|
|
2750
|
+
.put-away-days label { display: flex; align-items: center; gap: 8px; margin: 0; font-size: 14px; }
|
|
2751
|
+
.put-away-days input[type=number] { width: 5rem; margin: 0; }
|
|
2752
|
+
.put-away-days button { width: auto; }
|
|
2753
|
+
.put-away-group { margin: 0 0 20px; padding: 0; border: 0; }
|
|
2754
|
+
.put-away-group legend { display: flex; align-items: center; gap: 8px; padding: 0; margin: 0 0 6px; font-weight: 600; font-size: 15px; }
|
|
2755
|
+
.put-away-group legend .count { font-family: var(--so-mono, ui-monospace, monospace); font-size: 11px; font-weight: 500; color: var(--so-muted); }
|
|
2756
|
+
.put-away-group ul { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--so-line); }
|
|
2757
|
+
.put-away-group li { border-bottom: 1px solid var(--so-line); }
|
|
2758
|
+
.put-away-item { display: grid; grid-template-columns: auto minmax(0, 1fr); column-gap: 10px; align-items: start; margin: 0; padding: 10px 2px; cursor: pointer; }
|
|
2759
|
+
.put-away-item input { margin: 3px 0 0; width: auto; min-height: 0; align-self: start; }
|
|
2760
|
+
.put-away-title { font-size: 13.5px; font-weight: 500; overflow-wrap: anywhere; }
|
|
2761
|
+
.put-away-why { grid-column: 2; font-size: 12.5px; color: var(--so-muted); }
|
|
2762
|
+
.put-away-id { grid-column: 2; font-family: var(--so-mono, ui-monospace, monospace); font-size: 11.5px; color: var(--so-muted); overflow-wrap: anywhere; }
|
|
2763
|
+
.put-away-cli { margin: 12px 0 0; font-size: 13px; color: var(--so-muted); overflow-wrap: anywhere; }
|
|
2764
|
+
.put-away-cli code { font-size: 12px; }
|
|
2765
|
+
.task-archived { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 8px 12px; margin: 0 0 12px; padding: 10px 12px; border-radius: 8px; background: var(--so-raised); }
|
|
2766
|
+
.task-archived p { margin: 0; font-size: 13.5px; }
|
|
2767
|
+
.task-archived form { margin: 0; }
|
|
2768
|
+
.task-archived button { width: auto; }
|
|
2769
|
+
.work-row-status form.inline { margin: 0; }
|
|
2770
|
+
@media (max-width: 900px) {
|
|
2771
|
+
.put-away-item { min-height: 44px; }
|
|
2772
|
+
.put-away-days button, .task-archived button, .work-row-status form.inline button { min-height: 44px; }
|
|
2773
|
+
}
|
package/dist/server/flows.js
CHANGED
|
@@ -21,6 +21,7 @@ import { exportFlow, fetchFlowFile, FlowFileError, importFlow, parseFlowFile, pl
|
|
|
21
21
|
import { addFlowTriggerTo, checkFlowTriggerNow, pressFlowButton, removeFlowTrigger, removeLinearKey, setFlowTriggerOn, renewFlowHook, saveHooksBase, saveLinearKey, saveSigningSecret, setUpFlowHook, changeFlowHookSender, setFlowHookScope, shareFlowButton, stopSharingFlowButton, rawTakesDeliveries, rawAdmitsAnyone } from "../flow-triggers.js";
|
|
22
22
|
import { FLOW_IMPORT_SCRIPT, flowFallbackHtml, flowImportHtml, flowsListHtml, flowView } from "../flows-ui.js";
|
|
23
23
|
import { FLOW_TEMPLATES, FlowContractError, validateFlowDefinition, withZoneNames } from "../flows.js";
|
|
24
|
+
import { revokeFlowBrowser, signFlowBrowser } from "../flow-browser.js";
|
|
24
25
|
import { prepareUnattendedCandidates, parseUnattendedAsk, revokeUnattended, signUnattended, untilWords } from "../flow-unattended.js";
|
|
25
26
|
import { keyStatus } from "../keys.js";
|
|
26
27
|
import { kitPageHtml, kitsGalleryHtml } from "../kits-ui.js";
|
|
@@ -656,6 +657,18 @@ export function createFlowsHandlers(runtime) {
|
|
|
656
657
|
if (action === "unattended-off") {
|
|
657
658
|
return revokeUnattended(store, flow.id, who.name, now) ? settle("It no longer runs on its own. New cards wait for you.") : answer(409, { ok: false, said: "It wasn't running on its own." });
|
|
658
659
|
}
|
|
660
|
+
if (action === "browser") {
|
|
661
|
+
// A person's own step-up (a lead's or remote run's token never gets here: route table), then signFlowBrowser
|
|
662
|
+
// re-reads the flow, its zones and scripts in its one write and refuses unless they are the digest the panel showed.
|
|
663
|
+
const proved = authenticateApprover(who, body.get("password") ?? "", flow.repo);
|
|
664
|
+
if (!proved.ok)
|
|
665
|
+
return answer(403, { ok: false, said: "That password isn't right. Nothing was approved." });
|
|
666
|
+
const signed = signFlowBrowser(store, { flow: flow.id, signer: who.name, proof: proved.method, sawDigest: body.get("digest") ?? "", console: { generation: who.via === "cookie" ? who.session.generation : -1 } }, now);
|
|
667
|
+
return signed.ok ? settle(`Browser allowed. ${signed.grant.zones.length === 1 ? "Its script" : "Its scripts"} can start Chromium now.`) : answer(409, { ok: false, said: signed.message });
|
|
668
|
+
}
|
|
669
|
+
if (action === "browser-off") {
|
|
670
|
+
return revokeFlowBrowser(store, flow.id, who.name, now) ? settle("Browser turned off. Its browser zones wait until someone allows it again.") : answer(409, { ok: false, said: "Browser use wasn't allowed for this flow." });
|
|
671
|
+
}
|
|
659
672
|
if (action === "save") {
|
|
660
673
|
let saved, drawn;
|
|
661
674
|
try {
|
|
@@ -975,6 +988,8 @@ export function createFlowsHandlers(runtime) {
|
|
|
975
988
|
"flow.secrets": flowAct("secrets"),
|
|
976
989
|
"flow.unattended": flowAct("unattended"),
|
|
977
990
|
"flow.unattended-off": flowAct("unattended-off"),
|
|
991
|
+
"flow.browser": flowAct("browser"),
|
|
992
|
+
"flow.browser-off": flowAct("browser-off"),
|
|
978
993
|
"flow.triggers": flowAct("triggers"),
|
|
979
994
|
"flow.card.move": cardAct("move"),
|
|
980
995
|
"flow.card.decide": cardAct("decide"),
|
package/dist/server/pages.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { projectOutcomes } from '../project-outcomes.js';
|
|
1
2
|
import { steerControlsFor } from "../steer-controls.js";
|
|
2
3
|
import { canReveal, cdCommandOf, localCheckoutOf } from "../checkout-proof.js";
|
|
3
4
|
import { instanceHttpAnswer } from "../console-instance.js";
|
|
@@ -967,7 +968,16 @@ export function createPagesHandlers(runtime) {
|
|
|
967
968
|
peeks[one.path] = peekOf(one.path);
|
|
968
969
|
for (const path of candidates)
|
|
969
970
|
peeks[path] = peekOf(path);
|
|
970
|
-
|
|
971
|
+
let activity;
|
|
972
|
+
if (open !== null && !returnTo.startsWith("/tasks/new") && visible(open) && store.accountCanAccess(who.name, open)) {
|
|
973
|
+
try {
|
|
974
|
+
activity = { project: projectName(open), report: projectOutcomes(store, open, now, { principal: 'operator', repos: [open] }) };
|
|
975
|
+
}
|
|
976
|
+
catch {
|
|
977
|
+
activity = { project: projectName(open), report: null };
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return sendScreen(response, status, projectsPage(chromeFor(open, "projects"), recent, [...candidates], open, problem, !restricted() && unscopedMode, !restricted() && (ceiling.roots.length > 0 || unscopedMode), onboardState, peeks, returnTo, who.via === "cookie" ? (path) => publishingOf(store, path).on : undefined, who.via === "cookie" ? (path) => projectCheckLevel(store, path).level : undefined, activity));
|
|
971
981
|
}
|
|
972
982
|
async function instanceEdge(ctx) {
|
|
973
983
|
const { request, response, url } = ctx;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseFeedbackImage } from "../result-review.js";
|
|
1
2
|
import { assignmentSummaryHtml } from "../assignment-ui.js";
|
|
2
3
|
import {} from "../assignment.js";
|
|
3
4
|
import {} from "../browser-workspace.js";
|
|
@@ -610,8 +611,8 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
|
|
|
610
611
|
const snapshot = payload["snapshot"];
|
|
611
612
|
const ids = Array.isArray(payload["notes"]) ? payload["notes"] : [];
|
|
612
613
|
const selected = snapshot?.notes.filter(one => ids.includes(one.id)) ?? [];
|
|
613
|
-
const notes = [...selected, ...(text("note") === "" ? [] : [{ note: text("note"), path: text("path") || null, line: typeof payload["line"] === "number" ? payload["line"] : null }])];
|
|
614
|
-
what = html `<h3>${text("taskTitle") || task}</h3><ul class="proposal-review-notes">${notes.map(one => html `<li>${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : html `:${one.line}`}</span> · `}${one.note}</li>`)}</ul>${text("operation") === "revise" ? html `<p class="meta">Updates this task. Your approval settings apply.</p>` : html `<p class="meta">No work starts.</p>`}`;
|
|
614
|
+
const notes = [...selected, ...(text("note") === "" ? [] : [{ note: text("note"), path: text("path") || null, line: typeof payload["line"] === "number" ? payload["line"] : null, image: parseFeedbackImage(payload["image"]) || null }])];
|
|
615
|
+
what = html `<h3>${text("taskTitle") || task}</h3><ul class="proposal-review-notes">${notes.map(one => html `<li>${one.image == null ? "" : html `<a href="/r/${one.image.run}/evidence/${one.image.artifact}">Screenshot ${one.image.artifact} · result #${one.image.run}</a><br>`}${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : html `:${one.line}`}</span> · `}${one.note}</li>`)}</ul>${text("operation") === "revise" ? html `<p class="meta">Updates this task. Your approval settings apply.</p>` : html `<p class="meta">No work starts.</p>`}`;
|
|
615
616
|
}
|
|
616
617
|
else if (view.kind === "next") {
|
|
617
618
|
what = html `<h3>Move <a href="${taskHref(task)}">${task}</a> to the front</h3>${facts(["current position", html `${String(payload["position"] ?? "?")} of ${String(payload["of"] ?? "?")}`], ["project", mono(repoId)])}`;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/** Everyday console pages: home, inbox, board, work, projects, fleet and the queue. */
|
|
2
|
+
import { type TidyItem } from "../tidy.js";
|
|
2
3
|
import { type TaskOrigin } from "../task-source.js";
|
|
3
4
|
import { type Html } from "../html.js";
|
|
4
5
|
import { type AssignmentSnapshot } from "../assignment.js";
|
|
5
6
|
import { type BoardCard } from "../board.js";
|
|
6
|
-
import { type BrowserLimits } from "../browser-workspace.js";
|
|
7
|
+
import { type BrowserLimits, type BrowserProjectsView } from "../browser-workspace.js";
|
|
7
8
|
import { type CheckLevel } from "../check-levels.js";
|
|
8
9
|
import { type DispatchDiagnosis } from "../dispatch.js";
|
|
9
10
|
import { type FirstRunStep } from "../first-run.js";
|
|
@@ -295,6 +296,8 @@ export declare function workPage(chrome: Chrome, data: {
|
|
|
295
296
|
now: Date;
|
|
296
297
|
limits?: BrowserLimits | null;
|
|
297
298
|
state?: TaskState | null;
|
|
299
|
+
/** An approver in a browser session: rows offer Put away (or Restore, archived), and the page offers Put away old tasks. */
|
|
300
|
+
canPutAway?: boolean;
|
|
298
301
|
}): Screen;
|
|
299
302
|
export declare function browsePage(chrome: Chrome, data: {
|
|
300
303
|
at: string;
|
|
@@ -451,7 +454,7 @@ export declare function projectsPage(chrome: Chrome, recent: {
|
|
|
451
454
|
path: string;
|
|
452
455
|
name: string;
|
|
453
456
|
lastOpenedAt: string;
|
|
454
|
-
}[], candidates: string[], open: string | null, problem: string | null, _unscopedMode: boolean, browsable?: boolean, onboard?: OnboardCardState | null, peeks?: Record<string, ProjectPeek | null>, returnTo?: string, pullRequestsOn?: (path: string) => boolean, checkLevelOf?: (path: string) => CheckLevel): Screen;
|
|
457
|
+
}[], candidates: string[], open: string | null, problem: string | null, _unscopedMode: boolean, browsable?: boolean, onboard?: OnboardCardState | null, peeks?: Record<string, ProjectPeek | null>, returnTo?: string, pullRequestsOn?: (path: string) => boolean, checkLevelOf?: (path: string) => CheckLevel, activity?: BrowserProjectsView["activity"]): Screen;
|
|
455
458
|
/** The no-script "move to the front" sentinel: the form cannot name the
|
|
456
459
|
* front of a partition, so the handler resolves it (slice 1b, fix 1). */
|
|
457
460
|
export declare const QUEUE_FRONT = "__TOP__";
|
|
@@ -562,3 +565,13 @@ export type PeekAdmission = {
|
|
|
562
565
|
epoch: string;
|
|
563
566
|
entries: ReturnType<typeof parseBaseTreeSnapshot>;
|
|
564
567
|
};
|
|
568
|
+
/** Put away old tasks (docs/plans/task-tidy.md): exactly what would be put away, grouped, each with why, every one
|
|
569
|
+
* ticked; one step-up puts away the ticked ones, bound to this preview's digest. The CLI twin is named beside it. */
|
|
570
|
+
export declare function putAwayPage(chrome: Chrome, data: {
|
|
571
|
+
items: readonly TidyItem[];
|
|
572
|
+
days: number;
|
|
573
|
+
digest: string;
|
|
574
|
+
now: Date;
|
|
575
|
+
problem: string | null;
|
|
576
|
+
releasesSkipped: string | null;
|
|
577
|
+
}): Screen;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { activityMetrics, activityScope, activityLimit } from '../project-activity.js';
|
|
1
2
|
/** Everyday console pages: home, inbox, board, work, projects, fleet and the queue. */
|
|
3
|
+
import { archivedWords, tidyGroups, TIDY_DEFAULT_DAYS } from "../tidy.js";
|
|
4
|
+
import { stepUpForm } from "../settings-kit.js";
|
|
2
5
|
import { originNote } from "../task-source.js";
|
|
3
6
|
import { html, htmlString, joinHtml, postForm, replaceMarkup, reauth } from "../html.js";
|
|
4
7
|
import {} from "../assignment.js";
|
|
@@ -631,14 +634,25 @@ export function workPage(chrome, data) {
|
|
|
631
634
|
return `/work${query.size ? `?${query}` : ''}`;
|
|
632
635
|
};
|
|
633
636
|
const tabs = html `<nav class="work-views" aria-label="Task views">${WORK_VIEWS.map(one => html `<a href="${href(one.key)}"${one.key === data.view ? html ` class="active" aria-current="page"` : null}>${one.label}<span class="count">${data.work.totals[one.key]}</span></a>`)}</nav>`;
|
|
637
|
+
// Put away (or Restore, in Archived): one quiet act per row, never on a task with work going on.
|
|
638
|
+
const tidyAct = (row) => data.canPutAway !== true ? null : row.archived !== undefined ? { action: `${taskHref(row.rootId)}/restore`, label: 'Restore' }
|
|
639
|
+
: row.liveRunId !== null || row.unfinishedRunId !== null || row.status.views.includes('running') ? null : { action: `${taskHref(row.rootId)}/archive`, label: 'Put away' };
|
|
640
|
+
const returnTo = href(data.view, undefined);
|
|
634
641
|
const rowHtml = (row) => {
|
|
642
|
+
if (row.archived !== undefined) {
|
|
643
|
+
const act = tidyAct(row);
|
|
644
|
+
const project = data.multiProject || row.repo === null ? html `<span class="project-label">${row.repo === null ? 'Unplaced' : projectName(row.repo)}</span>` : null;
|
|
645
|
+
return html `<article class="work-row" data-task="${row.rootId}" data-work-status="archived" data-work-views="archived"><div class="work-row-main"><a class="work-title" href="${taskHref(row.rootId)}">${row.title}</a><p class="work-meta">${project}<span>${relativeAge(row.archived.at, data.now)}</span><span class="mono">${row.rootId}</span></p><p class="work-detail" data-archived-by>${archivedWords(row.archived)}</p></div><div class="work-row-status">${act === null ? null : postForm(act.action, html `<button type="submit" class="secondary">${act.label}</button>`, { hidden: { return: returnTo }, attrs: { class: 'inline', 'data-tidy-act': 'restore' } })}</div></article>`;
|
|
646
|
+
}
|
|
635
647
|
const target = row.primaryAction?.target;
|
|
636
648
|
const actionHref = row.primaryAction?.code === 'open-result' && target?.runId != null
|
|
637
649
|
? `/review?result=${encodeURIComponent(target.taskId)}&run=${target.runId}`
|
|
638
650
|
: browserWorkActionHref(row);
|
|
639
651
|
const action = actionHref === null || row.primaryAction === null ? null : html `<a class="work-action" data-primary-action href="${actionHref}">${row.primaryAction.label} →</a>`;
|
|
640
652
|
const project = data.multiProject || row.repo === null ? html `<span class="project-label">${row.repo === null ? 'Unplaced' : projectName(row.repo)}</span>` : null;
|
|
641
|
-
|
|
653
|
+
const act = tidyAct(row);
|
|
654
|
+
const putAway = act === null ? null : postForm(act.action, html `<button type="submit" class="quiet">${act.label}</button>`, { hidden: { return: returnTo }, attrs: { class: 'inline', 'data-tidy-act': 'put-away' } });
|
|
655
|
+
return html `<article class="work-row" data-task="${row.rootId}" data-work-status="${row.status.token}" data-work-views="${row.status.views.join(' ')}"><div class="work-row-main"><a class="work-title" href="${taskHref(row.rootId)}">${row.title}</a><p class="work-meta">${project}<span>${relativeAge(row.updatedAt, data.now)}</span><span class="mono">${row.rootId}</span></p></div><div class="work-row-status">${statusLineHtml(row.status)}${action}${putAway}${row.familyProblem === null ? null : html `<p class="problem">${row.familyProblem}</p>`}${row.status.views.includes('needs-you') && !['write-scope', 'approve-scope'].includes(row.primaryAction?.code ?? '') && row.status.detail !== row.status.label && row.status.detail !== row.familyProblem ? html `<p class="work-detail">${row.status.detail}</p>` : null}${workDiagnosticsHtml(row.status.diagnostics)}</div></article>`;
|
|
642
656
|
};
|
|
643
657
|
const current = WORK_VIEWS.find(one => one.key === data.view);
|
|
644
658
|
const filtered = (data.state ?? null) !== null;
|
|
@@ -653,6 +667,7 @@ export function workPage(chrome, data) {
|
|
|
653
667
|
const pages = !data.previous && data.work.nextCursor === null ? null : html `<nav class="row work-pagination" aria-label="Task pages">${data.previous ? html `<a class="button-link" href="${href(data.view)}">First page</a>` : null}${data.work.nextCursor === null ? null : html `<a class="button-link" rel="next" href="${href(data.view, data.work.nextCursor)}">Next page</a>`}</nav>`;
|
|
654
668
|
const tools = html `<details class="work-tools"><summary>Work tools${CHEVRON_ICON}</summary><nav class="work-tools-menu">${[['/inbox', 'Inbox'], ['/board', 'Board'], ['/board?view=order', 'Order'], ['/recipes', 'Recipes'], ...(chrome.projectScoped ? [] : [['/workbench', 'Portfolio']]), ['/ledger', 'Action ledger']]
|
|
655
669
|
.map(([path, label]) => html `<a href="${path}">${label}</a>`)}</nav></details>`;
|
|
670
|
+
const putAwayLink = data.canPutAway === true && data.view !== 'archived' ? { label: 'Put away old tasks', href: '/work/put-away' } : null;
|
|
656
671
|
const toolLinks = [['/inbox', 'Inbox'], ['/board', 'Board'], ['/board?view=order', 'Order'], ['/recipes', 'Recipes'], ...(chrome.projectScoped ? [] : [['/workbench', 'Portfolio']])];
|
|
657
672
|
const view = {
|
|
658
673
|
kind: 'tasks',
|
|
@@ -666,6 +681,15 @@ export function workPage(chrome, data) {
|
|
|
666
681
|
const actionHref = row.primaryAction?.code === 'open-result' && target?.runId != null
|
|
667
682
|
? `/review?result=${encodeURIComponent(target.taskId)}&run=${target.runId}`
|
|
668
683
|
: browserWorkActionHref(row);
|
|
684
|
+
const act = tidyAct(row);
|
|
685
|
+
const tidy = act === null ? null : { action: act.action, label: act.label, returnTo };
|
|
686
|
+
if (row.archived !== undefined)
|
|
687
|
+
return {
|
|
688
|
+
id: row.rootId, title: row.title, href: taskHref(row.rootId),
|
|
689
|
+
project: data.multiProject || row.repo === null ? (row.repo === null ? 'Unplaced' : projectName(row.repo)) : null,
|
|
690
|
+
age: relativeAge(row.archived.at, data.now), status: { label: 'Archived', tone: 'muted', token: 'archived' },
|
|
691
|
+
ask: null, chip: null, group: 'rest', action: null, detail: archivedWords(row.archived), problem: null, notes: [], tidy,
|
|
692
|
+
};
|
|
669
693
|
const needsYouDetail = (row.status.views.includes('needs-you') || row.status.label === 'Failed') && !['write-scope', 'approve-scope'].includes(row.primaryAction?.code ?? '') && row.status.label !== 'Ready for review' && row.status.detail !== row.status.label && row.status.detail !== row.familyProblem;
|
|
670
694
|
return {
|
|
671
695
|
id: row.rootId, title: row.title, href: taskHref(row.rootId),
|
|
@@ -677,6 +701,7 @@ export function workPage(chrome, data) {
|
|
|
677
701
|
detail: needsYouDetail ? row.status.detail : row.progress ?? null,
|
|
678
702
|
problem: row.familyProblem,
|
|
679
703
|
notes: (row.status.diagnostics ?? []).map(one => `${one.label} · ${one.detail}`),
|
|
704
|
+
tidy,
|
|
680
705
|
};
|
|
681
706
|
}),
|
|
682
707
|
empty: data.work.items.length > 0 ? null : filtered && !data.previous ? { text: `No tasks are ${data.state} in this view.`, action: { label: "Show every state", href: href(data.view, undefined, null) } } : {
|
|
@@ -686,11 +711,12 @@ export function workPage(chrome, data) {
|
|
|
686
711
|
pages: { first: data.previous ? href(data.view) : null, next: data.work.nextCursor === null ? null : href(data.view, data.work.nextCursor) },
|
|
687
712
|
tools: toolLinks.map(([path, label]) => ({ label: label, href: path })),
|
|
688
713
|
newTask: { label: 'New task', href: '/tasks/new' },
|
|
714
|
+
putAway: putAwayLink,
|
|
689
715
|
limits: data.limits ?? null,
|
|
690
716
|
states: [{ label: 'Any state', href: href(data.view, undefined, null), active: (data.state ?? null) === null },
|
|
691
717
|
...TASK_STATES.map(one => ({ label: one.charAt(0).toUpperCase() + one.slice(1), href: href(data.view, undefined, one), active: data.state === one }))],
|
|
692
718
|
};
|
|
693
|
-
return screen('work', html `<div class="work-head"><h1>Tasks</h1>${tools}</div>${limitsHtml(data.limits ?? null)}${tabs}${stateLinks}${list}${pages}`, { chrome, workspace: { view } });
|
|
719
|
+
return screen('work', html `<div class="work-head"><h1>Tasks</h1>${putAwayLink === null ? null : html `<a class="button-link" href="${putAwayLink.href}" data-put-away-old>${putAwayLink.label}</a>`}${tools}</div>${limitsHtml(data.limits ?? null)}${tabs}${stateLinks}${list}${pages}`, { chrome, workspace: { view } });
|
|
694
720
|
}
|
|
695
721
|
export function browsePage(chrome, data) {
|
|
696
722
|
const crumb = data.at === data.root ? projectName(data.root) : `${projectName(data.root)}${data.at.slice(data.root.length)}`;
|
|
@@ -921,7 +947,10 @@ export function githubReposPage(chrome, data) {
|
|
|
921
947
|
html `<p class="row" style="margin-top:.6rem"><a class="badge" href="/projects">← back to projects</a></p>`,
|
|
922
948
|
], "\n"), { chrome });
|
|
923
949
|
}
|
|
924
|
-
export function projectsPage(chrome, recent, candidates, open, problem, _unscopedMode, browsable = false, onboard = null, peeks = {}, returnTo = "/", pullRequestsOn, checkLevelOf) {
|
|
950
|
+
export function projectsPage(chrome, recent, candidates, open, problem, _unscopedMode, browsable = false, onboard = null, peeks = {}, returnTo = "/", pullRequestsOn, checkLevelOf, activity) {
|
|
951
|
+
const activityHtml = activity === undefined ? null : html `<details><summary>Activity · ${activity.project}</summary>${activity.report === null
|
|
952
|
+
? html `<p role="alert">Activity is unavailable.</p><a href="/projects">Reload</a>`
|
|
953
|
+
: html `<p>${activityScope(activity.report)}</p>${activity.report.truncated ? html `<p>${activityLimit(activity.report)}</p>` : null}${activityMetrics(activity.report).map(metric => html `<details><summary>${metric.label} · ${metric.value}</summary><p>${metric.detail}</p>${metric.rows.length === 0 ? html `<p>No tasks in this group.</p>` : html `<ul>${metric.rows.map(row => html `<li><a href="${row.href}">${row.title}</a> · Recorded checks: ${row.checks}</li>`)}</ul>`}</details>`)}`}</details>`;
|
|
925
954
|
// The onboarding card (repo onboarding, findings 1-39): preview first,
|
|
926
955
|
// then a password-confirmed clone into a configured root. Disabled
|
|
927
956
|
// states explain themselves in words (finding 28/39).
|
|
@@ -1047,11 +1076,12 @@ export function projectsPage(chrome, recent, candidates, open, problem, _unscope
|
|
|
1047
1076
|
recentItems.length === 0 && candidateItems.length === 0
|
|
1048
1077
|
? html `<div class="card"><p><strong>Nothing to open yet.</strong></p><p class="meta">Add one below — opening it registers it here for next time.</p></div>`
|
|
1049
1078
|
: null,
|
|
1079
|
+
activityHtml,
|
|
1050
1080
|
recentItems.length > 0 ? html `<h2>Recent</h2>${cards(recentItems)}` : null,
|
|
1051
1081
|
candidateItems.length > 0 ? html `<h2>Available</h2>${cards(candidateItems)}` : null,
|
|
1052
1082
|
addCard,
|
|
1053
1083
|
], "\n"), { chrome, workspace: { view: {
|
|
1054
|
-
kind: "projects", choosing, problem, returnTo,
|
|
1084
|
+
kind: "projects", choosing, problem, returnTo, ...(activity === undefined ? {} : { activity }),
|
|
1055
1085
|
recent: recent.map(one => rowOf(one.path, one.name, one.lastOpenedAt)),
|
|
1056
1086
|
available: candidates.map(path => rowOf(path, projectName(path), null)),
|
|
1057
1087
|
add: { browse: browsable ? "/projects/browse" : null, github: onboard === null ? null : "/projects/github", html: htmlString(addForms) },
|
|
@@ -1315,3 +1345,21 @@ export function nextPage(chrome, data) {
|
|
|
1315
1345
|
}
|
|
1316
1346
|
return screen("next", joinHtml([header, card], "\n"), { chrome });
|
|
1317
1347
|
}
|
|
1348
|
+
/** Put away old tasks (docs/plans/task-tidy.md): exactly what would be put away, grouped, each with why, every one
|
|
1349
|
+
* ticked; one step-up puts away the ticked ones, bound to this preview's digest. The CLI twin is named beside it. */
|
|
1350
|
+
export function putAwayPage(chrome, data) {
|
|
1351
|
+
const groups = tidyGroups(data.items, data.days);
|
|
1352
|
+
const count = data.items.length;
|
|
1353
|
+
const daysArg = data.days === TIDY_DEFAULT_DAYS ? "" : ` --days ${data.days}`;
|
|
1354
|
+
const crumbs = html `<nav class="kit-crumbs" aria-label="Breadcrumb"><ol><li><a href="/work">Tasks</a></li><li><span aria-current="page">Put away old tasks</span></li></ol></nav>`;
|
|
1355
|
+
const daysForm = html `<form method="get" action="/work/put-away" class="put-away-days"><label>Also finished tasks untouched for <input type="number" name="days" min="1" max="365" value="${String(data.days)}" inputmode="numeric"> days</label><button type="submit" class="secondary">Update list</button></form>`;
|
|
1356
|
+
const list = joinHtml(groups.map(group => html `<fieldset class="put-away-group" data-tidy-group="${group.rule}"><legend>${group.label}<span class="count">${String(group.items.length)}</span></legend><ul>${group.items.map(item => html `<li><label class="put-away-item" data-tidy-task="${item.taskId}"><input type="checkbox" name="task" value="${item.taskId}" checked><span class="put-away-title">${item.title}</span><span class="put-away-why">${item.reason}</span><span class="put-away-id">${item.taskId}${item.repo === null ? "" : ` · ${projectName(item.repo)}`}${item.members.length > 1 ? ` · ${item.members.length} versions` : ""}</span></label></li>`)}</ul></fieldset>`));
|
|
1357
|
+
const cli = html `<p class="put-away-cli" data-tidy-cli>Or run: <code>toolroll task tidy --apply --digest ${data.digest}${daysArg}</code></p>`;
|
|
1358
|
+
const body = count === 0
|
|
1359
|
+
? html `<p class="kit-state">Nothing to put away right now.</p><p><a href="/work?view=archived">See archived tasks →</a></p>`
|
|
1360
|
+
: stepUpForm({ proof: "tasks-put-away", action: "/work/put-away", id: "put-away", hidden: { digest: data.digest, days: data.days },
|
|
1361
|
+
body: html `${list}${cli}`, save: { label: "Put away selected", idle: `${count} ${count === 1 ? "task" : "tasks"} selected` }, confirm: `Put away the selected tasks. You can restore any of them from Archived.` });
|
|
1362
|
+
return screen("Put away old tasks", html `<div class="put-away">${crumbs}<h1>Put away old tasks</h1>\
|
|
1363
|
+
<p class="kit-state">${count === 0 ? "Old releases, routine runs and fixed CI notices are put away automatically." : `${count} ${count === 1 ? "task" : "tasks"} can be put away. They leave every list and keep their history.`}</p>\
|
|
1364
|
+
${data.problem ? html `<p class="kit-problem" role="alert">${data.problem}</p>` : ""}${data.releasesSkipped === null ? "" : html `<p class="kit-hint">Release tasks were left out: ${data.releasesSkipped}.</p>`}${daysForm}${body}</div>`, { chrome });
|
|
1365
|
+
}
|
|
@@ -614,6 +614,8 @@ export type ResultDetail = {
|
|
|
614
614
|
fileAnchors: ReadonlyMap<string, string>;
|
|
615
615
|
comments: DiffComment[];
|
|
616
616
|
pastComments?: DiffComment[];
|
|
617
|
+
/** Live saved notes whose screenshot would be refused now (result-actions.ts feedbackImageProblem). */
|
|
618
|
+
unusableImageNotes?: readonly number[];
|
|
617
619
|
reviewerFindings: DiffComment[];
|
|
618
620
|
/** The build's one automatic review, when its project had review on. */
|
|
619
621
|
automaticReview?: BuildReviewView | null;
|
|
@@ -1068,7 +1068,7 @@ export function proofBundleView(store, run, artifacts, root) {
|
|
|
1068
1068
|
catch {
|
|
1069
1069
|
problem = "the file could not be read";
|
|
1070
1070
|
}
|
|
1071
|
-
return [{ path, caption: captionFor(path), artifactId: artifact.id, problem }];
|
|
1071
|
+
return [{ path, caption: captionFor(path), artifactId: artifact.id, sha256: artifact.sha256, problem }];
|
|
1072
1072
|
});
|
|
1073
1073
|
const stored = new Set(screenshots.map(one => one.path));
|
|
1074
1074
|
const uncapturedScreenshots = (proof?.screenshots ?? []).map(one => one.path).filter(path => !stored.has(path));
|
|
@@ -1598,6 +1598,8 @@ export function resultPanelParts(detail, o) {
|
|
|
1598
1598
|
const physicalReasons = receipt.reasons.filter(reason => !assessmentReasons.has(reason));
|
|
1599
1599
|
const runId = run.id;
|
|
1600
1600
|
const shots = proof?.screenshots ?? [];
|
|
1601
|
+
const picture = (shot) => html `<figure class="result-image"><a class="receipt-shot" href="/r/${runId}/evidence/${shot.artifactId}"><img src="/r/${runId}/evidence/${shot.artifactId}" alt="${shot.caption}"><span>${shot.caption}</span></a>${detail.canAnnotate && o.csrf !== "" && shot.sha256 !== undefined
|
|
1602
|
+
? html `<button type="button" class="quiet" data-comment-image="${JSON.stringify({ run: runId, artifact: shot.artifactId, sha256: shot.sha256 })}" data-image-caption="${shot.caption}">Comment</button>` : ""}</figure>`;
|
|
1601
1603
|
const shown = shots.filter(one => one.problem === null);
|
|
1602
1604
|
const stat = terminal?.stat ?? null;
|
|
1603
1605
|
const statOk = stat !== null && !("problem" in stat);
|
|
@@ -1694,7 +1696,7 @@ export function resultPanelParts(detail, o) {
|
|
|
1694
1696
|
}
|
|
1695
1697
|
else if (lead === "screenshots") {
|
|
1696
1698
|
summaryParts.push(html `<div class="receipt-visuals result-visuals" aria-label="validated screenshots">${shown
|
|
1697
|
-
.map(
|
|
1699
|
+
.map(picture)}</div>`);
|
|
1698
1700
|
}
|
|
1699
1701
|
const unavailableShots = shots.filter(one => one.problem !== null);
|
|
1700
1702
|
if (lead === "changes") {
|
|
@@ -1848,7 +1850,7 @@ export function resultPanelParts(detail, o) {
|
|
|
1848
1850
|
? directAssessment && !screenshotRequired ? html `` : html `<p class="meta" data-cockpit-source="screenshots">No screenshots${screenshotRequired ? " were captured, although one was required" : " were needed"}.</p>`
|
|
1849
1851
|
: lead === "screenshots"
|
|
1850
1852
|
? html `<p class="meta" data-cockpit-source="screenshots">${shown.length} validated screenshot${shown.length === 1 ? "" : "s"} shown in Summary${unavailableShots.length > 0 ? `; ${unavailableShots.length} unavailable` : ""}.</p>`
|
|
1851
|
-
: html `<details class="cockpit-proof-group" data-cockpit-source="screenshots"><summary>Screenshots · ${shown.length}${unavailableShots.length > 0 ? ` (${unavailableShots.length} unavailable)` : ""}</summary><div class="receipt-visuals" aria-label="validated screenshots">${shown.map(
|
|
1853
|
+
: html `<details class="cockpit-proof-group" data-cockpit-source="screenshots"><summary>Screenshots · ${shown.length}${unavailableShots.length > 0 ? ` (${unavailableShots.length} unavailable)` : ""}</summary><div class="receipt-visuals" aria-label="validated screenshots">${shown.map(picture)}</div></details>`);
|
|
1852
1854
|
checkParts.push(receipt.caveats.length === 0
|
|
1853
1855
|
? directAssessment ? html `` : html `<p class="meta" data-cockpit-source="caveats">No caveats were reported.</p>`
|
|
1854
1856
|
: html `<details class="cockpit-proof-group" data-cockpit-source="caveats"><summary>Agent caveats · ${receipt.caveats.length}</summary><ul>${receipt.caveats.map(one => html `<li>${one}</li>`)}</ul></details>`);
|
|
@@ -1863,19 +1865,29 @@ export function resultPanelParts(detail, o) {
|
|
|
1863
1865
|
checkParts.push(o.extraChecks);
|
|
1864
1866
|
// ---- request changes: both feedback styles, one sealed road ------------
|
|
1865
1867
|
const requestParts = [html `<h3 class="so-sr-only">What should change?</h3>`];
|
|
1868
|
+
const imageNote = (one) => {
|
|
1869
|
+
if (one.image == null)
|
|
1870
|
+
return html ``;
|
|
1871
|
+
const shot = shots.find(shot => shot.artifactId === one.image.artifact && shot.sha256 === one.image.sha256 && one.image.run === runId && shot.problem === null);
|
|
1872
|
+
// A live saved note whose screenshot no longer verifies can be kept without it, so it never blocks a revision.
|
|
1873
|
+
const drop = o.csrf === "" || !(detail.unusableImageNotes ?? []).includes(one.id) ? html ``
|
|
1874
|
+
: postForm(`/r/${runId}/comment`, html `<input type="hidden" name="intent" value="drop-image"><input type="hidden" name="comment" value="${String(one.id)}"><button type="submit" class="quiet">Keep note without screenshot</button>`, { attrs: { class: "feedback-image-drop" }, returnTo: o.returnTo, hidden: { tab: o.tab } });
|
|
1875
|
+
return html `<figure class="feedback-image">${shot === undefined ? html `<figcaption>Screenshot ${one.image.artifact} is unavailable.</figcaption>`
|
|
1876
|
+
: html `<a href="/r/${runId}/evidence/${shot.artifactId}"><img src="/r/${runId}/evidence/${shot.artifactId}" alt="${shot.caption}"></a><figcaption>${shot.caption} · Screenshot ${shot.artifactId}</figcaption>`}${drop}</figure>`;
|
|
1877
|
+
};
|
|
1866
1878
|
const pathWords = (path, line) => {
|
|
1867
1879
|
const shownPath = `${path}${line === null ? "" : `:${line}`}`;
|
|
1868
1880
|
const href = detail.editor === null ? null : editorFileHref(detail.editor.worktree, path, line);
|
|
1869
1881
|
return href === null ? html `<span class="mono">${shownPath}</span> ` : html `<a class="mono" href="${href}">${shownPath}</a> `;
|
|
1870
1882
|
};
|
|
1871
1883
|
if (detail.comments.length > 0) {
|
|
1872
|
-
requestParts.push(html `<p class="meta">Saved for later · ${detail.comments.length}</p><div class="diff-comments" data-result-notes="${detail.comments.length}">${detail.comments.map(one => html `<div class="diff-comment"
|
|
1884
|
+
requestParts.push(html `<p class="meta">Saved for later · ${detail.comments.length}</p><div class="diff-comments" data-result-notes="${detail.comments.length}">${detail.comments.map(one => html `<div class="diff-comment">${imageNote(one)}<span class="diff-comment-pin" aria-hidden="true"></span><p>${one.path === null ? "" : pathWords(one.path, one.line)}${one.note}</p><span class="meta">${one.author} · ${whenTime(one.createdAt)}</span></div>`)}</div>`);
|
|
1873
1885
|
if (o.csrf !== "" && !detail.canAnnotate) {
|
|
1874
1886
|
requestParts.push(postForm(`/r/${runId}/revise`, html `${revisionSealFields(detail.comments, detail.sourceDigest)}<div><strong>${detail.comments.length} note${detail.comments.length === 1 ? "" : "s"} ready</strong></div><button type="submit">Request changes</button>`, { attrs: { class: "card revision-from-comments" }, returnTo: o.returnTo }));
|
|
1875
1887
|
}
|
|
1876
1888
|
}
|
|
1877
1889
|
if ((detail.pastComments?.length ?? 0) > 0) {
|
|
1878
|
-
requestParts.push(html `<details class="result-feedback-history"><summary>Earlier feedback</summary><div class="diff-comments" data-past-feedback>${detail.pastComments.map(one => html `<div class="diff-comment"
|
|
1890
|
+
requestParts.push(html `<details class="result-feedback-history"><summary>Earlier feedback</summary><div class="diff-comments" data-past-feedback>${detail.pastComments.map(one => html `<div class="diff-comment">${imageNote(one)}<p>${one.path === null ? "" : pathWords(one.path, one.line)}${one.note}</p><span class="meta">${one.author} · ${whenTime(one.createdAt)}</span></div>`)}</div></details>`);
|
|
1879
1891
|
}
|
|
1880
1892
|
for (const revision of detail.revisions) {
|
|
1881
1893
|
// The child's standing is the shared status projection's own words
|
|
@@ -1892,6 +1904,7 @@ export function resultPanelParts(detail, o) {
|
|
|
1892
1904
|
requestParts.push(html `<details class="result-request-open result-request-form"${detail.comments.length > 0 ? " open" : ""}><summary>What should change?</summary>${postForm(`/r/${runId}/comment`, joinHtml([
|
|
1893
1905
|
revisionSealFields(detail.comments, detail.sourceDigest),
|
|
1894
1906
|
html `<input type="hidden" data-recorded-requests value="${[...detail.comments, ...(detail.pastComments ?? [])].filter(one => one.sourceKey?.startsWith(`review:${o.user}:`)).map(one => one.sourceKey.slice(`review:${o.user}:`.length)).join(",")}">`,
|
|
1907
|
+
html `<input type="hidden" name="image" value=""><figure class="feedback-image" data-feedback-image hidden><img alt="Selected screenshot" hidden><figcaption></figcaption><button type="button" class="quiet" data-remove-image>Remove image</button></figure>`,
|
|
1895
1908
|
html `<textarea name="note" rows="2" maxlength="${LIMITS.note}" placeholder="Describe the change…" aria-label="review comment" aria-describedby="comment-note-limit"${o.noted ? " autofocus" : ""}></textarea>`,
|
|
1896
1909
|
html `<span class="meta diff-comment-limit" id="comment-note-limit">up to ${LIMITS.note} characters</span>`,
|
|
1897
1910
|
html `<details class="result-pin"><summary>Attach to a file or line</summary>`,
|
|
@@ -1980,7 +1993,7 @@ export function personCheckItems(proof, patchText, shots, runId) {
|
|
|
1980
1993
|
return {
|
|
1981
1994
|
id: row.id, statement: row.statement, words: personCheckWords(row.statement), note: notes.length === 0 ? null : notes.join(" "),
|
|
1982
1995
|
excerpts: files.filter(one => one.lines.length > 0).slice(0, 3),
|
|
1983
|
-
shots: pictures.slice(0, 4).map(shot => ({ src: `/r/${runId}/evidence/${shot.artifactId}`, href: `/r/${runId}/evidence/${shot.artifactId}`, caption: shot.caption })),
|
|
1996
|
+
shots: pictures.slice(0, 4).map(shot => ({ src: `/r/${runId}/evidence/${shot.artifactId}`, href: `/r/${runId}/evidence/${shot.artifactId}`, caption: shot.caption, ...(shot.sha256 === undefined ? {} : { image: { run: runId, artifact: shot.artifactId, sha256: shot.sha256 } }) })),
|
|
1984
1997
|
};
|
|
1985
1998
|
});
|
|
1986
1999
|
}
|
|
@@ -2215,7 +2228,7 @@ export function runPage(chrome, run, taskId, artifacts, terminal = null, notes =
|
|
|
2215
2228
|
noted,
|
|
2216
2229
|
requestToken: result.requestToken,
|
|
2217
2230
|
hrefFor: one => (one === "summary" ? `/r/${run.id}` : `/r/${run.id}?tab=${one}`),
|
|
2218
|
-
returnTo: `/r/${run.id}`,
|
|
2231
|
+
returnTo: `/r/${run.id}?record=1`,
|
|
2219
2232
|
back: null,
|
|
2220
2233
|
});
|
|
2221
2234
|
return screen(`build #${run.id}`, joinHtml([
|
|
@@ -398,6 +398,7 @@ export type RevisionView = {
|
|
|
398
398
|
line: number | null;
|
|
399
399
|
note: string;
|
|
400
400
|
author: string;
|
|
401
|
+
image?: import("../result-review.js").FeedbackImage | null;
|
|
401
402
|
}[];
|
|
402
403
|
/** The lineage and ACTUAL terms (contract handoff task 2), read from
|
|
403
404
|
* the child's own rows — the same projection chat and the approval
|
|
@@ -437,6 +438,14 @@ export declare function taskBodyParts(data: {
|
|
|
437
438
|
/** Markup the caller rendered (the version history). */
|
|
438
439
|
history?: Html;
|
|
439
440
|
versionLabel?: string | null;
|
|
441
|
+
/** Put away (v145): by whom and why; and whether the viewer can put it away or restore it (an approver's browser). */
|
|
442
|
+
archived?: {
|
|
443
|
+
by: string;
|
|
444
|
+
byKind: "person" | "tidy";
|
|
445
|
+
reason: string;
|
|
446
|
+
at: string;
|
|
447
|
+
} | null;
|
|
448
|
+
canPutAway?: boolean;
|
|
440
449
|
status?: DisplayStatus;
|
|
441
450
|
task: Task;
|
|
442
451
|
/** Shared read-side lifecycle answer; the atomic claim still re-proves it. */
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/** Task pages: approval, agents, plans, controls, decisions and the task body. */
|
|
2
|
+
import { archivedWords } from "../tidy.js";
|
|
3
|
+
import { whenUtc } from "../when-html.js";
|
|
2
4
|
import { networkWords } from "../egress.js";
|
|
3
5
|
import { releaseAsksAgainWords, releaseTermsSentence } from "../scope.js";
|
|
4
6
|
import { RELEASE_CI_RUNNING_WORDS } from "../release-ci-hold.js";
|
|
@@ -50,7 +52,7 @@ import { reviewHref, taskChatHref, taskHref } from "./http.js";
|
|
|
50
52
|
import { coordinatorProposalsSection } from "./render-chat.js";
|
|
51
53
|
import { completionReceiptCard, criterionMatrixHtml, evidenceWords, homePhaseWords, incidentWords, needsCheck, pullRequestCardHtml, reasonWords, receiptStatusOf, repairChainHtml, reportItemsHtml, reviewRetryPanel, runNoun, runOutcomeBadge, semanticCoverageHtml, verificationExplanation, verificationRecovered } from "./render-results.js";
|
|
52
54
|
import {} from "./session.js";
|
|
53
|
-
import { linkWords, originNote } from "../task-source.js";
|
|
55
|
+
import { linkWords, originNote, requestName, requestWords } from "../task-source.js";
|
|
54
56
|
export function contractChangeHtml(change) {
|
|
55
57
|
const tag = html `<span class="contract-change-kind contract-change-${change.kind}">${change.kind}</span>`;
|
|
56
58
|
switch (change.field) {
|
|
@@ -203,7 +205,8 @@ const fragmentString = (fragment) => fragment === "" ? "" : htmlString(fragment)
|
|
|
203
205
|
function originWords(origin) {
|
|
204
206
|
const flow = origin.flow === null ? null : origin.flow === "hidden" ? "from a flow" : origin.flow === "removed" ? "from a flow (removed)" : `from flow “${origin.flow.name}”`;
|
|
205
207
|
const source = origin.source === null ? null : `${origin.source.label} (${origin.source.proved})`;
|
|
206
|
-
|
|
208
|
+
const request = origin.request == null ? null : `from ${requestName(origin.request)}`;
|
|
209
|
+
return [flow, source, request].filter(one => one !== null).join(" · ");
|
|
207
210
|
}
|
|
208
211
|
/** v126: a task's origin as a fact: the flow, linked for those who may open it, and the outside event, linked to where
|
|
209
212
|
* it lives, with that link's host always shown. */
|
|
@@ -219,6 +222,12 @@ function originParts(origin) {
|
|
|
219
222
|
if (origin.source.url !== null)
|
|
220
223
|
parts.push(" · ", { label: linkWords(origin.source.url), href: origin.source.url });
|
|
221
224
|
}
|
|
225
|
+
if (origin.request != null) {
|
|
226
|
+
if (parts.length > 0)
|
|
227
|
+
parts.push(" · ");
|
|
228
|
+
const where = requestWords(origin.request);
|
|
229
|
+
parts.push(`${requestName(origin.request)}${where === "" ? "" : `: ${where}`}`);
|
|
230
|
+
}
|
|
222
231
|
return parts;
|
|
223
232
|
}
|
|
224
233
|
export function approvalSheetHtml(input) {
|
|
@@ -294,7 +303,7 @@ export function approvalSheetHtml(input) {
|
|
|
294
303
|
const revisionDetail = input.revision === null ? "" : detail("Earlier build", joinHtml([
|
|
295
304
|
html `<div class="revision-card" data-revision-feedback>`,
|
|
296
305
|
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>`,
|
|
297
|
-
input.revision.comments.length === 0 ? "" : html `<ul>${input.revision.comments.map(one => html `<li>${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>`,
|
|
306
|
+
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>`,
|
|
298
307
|
revisionLineageHtml(input.revision.lineage),
|
|
299
308
|
html `</div>`,
|
|
300
309
|
]));
|
|
@@ -767,6 +776,8 @@ export function approveRefusalWords(reason) {
|
|
|
767
776
|
return gateWords({ verdict: "refuse", reason });
|
|
768
777
|
if (reason === "machine-proof")
|
|
769
778
|
return "not approved: only a person can approve this. Approve it on the task page or from your paired chat";
|
|
779
|
+
if (reason === "archived")
|
|
780
|
+
return "not approved: this task is put away. Restore it first";
|
|
770
781
|
if (reason === "unrouted")
|
|
771
782
|
return "not approved: this scope predates agent routing and its old approval no longer stands — edit and re-file the scope so it is routed under today’s agents, then approve it";
|
|
772
783
|
if (reason === "profile-unresolved")
|
|
@@ -1172,6 +1183,9 @@ export function executionPlanHtml(document, compact = false) {
|
|
|
1172
1183
|
html `</div>`,
|
|
1173
1184
|
]);
|
|
1174
1185
|
}
|
|
1186
|
+
function revisionImageLink(image) {
|
|
1187
|
+
return image == null ? html `` : html `<a href="/r/${image.run}/evidence/${image.artifact}">Screenshot ${image.artifact} · result #${image.run}</a> · `;
|
|
1188
|
+
}
|
|
1175
1189
|
/**
|
|
1176
1190
|
* A revision's lineage and ACTUAL terms, in the same words on the task
|
|
1177
1191
|
* page, the approval card, and chat (contract handoff task 2): which
|
|
@@ -1594,7 +1608,7 @@ export function taskBodyParts(data) {
|
|
|
1594
1608
|
: joinHtml([
|
|
1595
1609
|
html `<div class="revision-card" data-revision-feedback>`,
|
|
1596
1610
|
html `<p><strong>${data.revision.kind === "ci-repair" ? "CI repair" : data.revision.kind === "criterion-repair" ? "Criterion repair" : "Revision feedback"}</strong> <span class="meta">from <a href="/r/${data.revision.sourceRun}">build #${data.revision.sourceRun}</a></span></p>`,
|
|
1597
|
-
...data.revision.comments.map(one => html `<p class="row"><span class="meta">${one.author}</span> ${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : `:${one.line}`}</span> `}${one.note}</p>`),
|
|
1611
|
+
...data.revision.comments.map(one => html `<p class="row"><span class="meta">${one.author}</span> ${revisionImageLink(one.image)}${one.path === null ? "" : html `<span class="mono">${one.path}${one.line === null ? "" : `:${one.line}`}</span> `}${one.note}</p>`),
|
|
1598
1612
|
revisionLineageHtml(data.revision.lineage),
|
|
1599
1613
|
html `</div>`,
|
|
1600
1614
|
], "\n");
|
|
@@ -2058,8 +2072,11 @@ export function taskBodyParts(data) {
|
|
|
2058
2072
|
// The title is bare; the receipt or shared task status leads once.
|
|
2059
2073
|
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>`,
|
|
2060
2074
|
data.versionLabel == null ? "" : html `<p class="meta">${data.versionLabel} · <a href="${taskHref(data.rootId ?? task.id)}">Current work</a></p>`,
|
|
2075
|
+
data.archived == null ? "" : html `<div class="task-archived" data-archived><p>${archivedWords(data.archived)} · ${whenUtc(data.archived.at)}</p>${data.canPutAway === true && data.csrf !== ""
|
|
2076
|
+
? postForm(`${taskHref(task.id)}/restore`, html `<button type="submit" class="secondary">Restore</button>`, { hidden: { return: taskHref(task.id) }, attrs: { class: "inline" } }) : ""}</div>`,
|
|
2061
2077
|
(data.history ?? ""),
|
|
2062
|
-
|
|
2078
|
+
// A put-away task's banner is its status: no Needs you card, no act, no approval form (each would be refused).
|
|
2079
|
+
data.archived == null ? statusHtml : "",
|
|
2063
2080
|
controlHtml,
|
|
2064
2081
|
previousResult === null ? "" : html `<details class="task-previous-result"><summary>${previousResult.title}</summary>${previousResult.html}</details>`,
|
|
2065
2082
|
progressCard,
|
|
@@ -2068,7 +2085,7 @@ export function taskBodyParts(data) {
|
|
|
2068
2085
|
mirrorCard,
|
|
2069
2086
|
problemHtml,
|
|
2070
2087
|
needsScopeCard,
|
|
2071
|
-
approvalHtml,
|
|
2088
|
+
data.archived == null ? approvalHtml : "",
|
|
2072
2089
|
html `<details class="task-status-details" id="task-diagnostics"${optionsOpen ? html ` open` : ""}><summary>Task options</summary>${optionsHtml}</details>`,
|
|
2073
2090
|
// Evidence-first (M5.5): what needs you, then what happened — decisions
|
|
2074
2091
|
// and incidents above the attempt ledger and spend, the mechanics
|
|
@@ -2170,7 +2187,7 @@ export function taskBodyParts(data) {
|
|
|
2170
2187
|
const feedback = data.revision;
|
|
2171
2188
|
thread.push({ key: "revision", at: task.createdAt, kind: "reply", who: "person", author: feedback.comments[0]?.author ?? "You",
|
|
2172
2189
|
title: feedback.kind === "ci-repair" ? "Asked for a CI repair" : feedback.kind === "criterion-repair" ? "Asked for a repair" : "Asked for changes",
|
|
2173
|
-
text: feedback.comments.map(one => `${one.path === null ? "" : `${one.path}${one.line === null ? "" : `:${one.line}`} — `}${one.note.trim()}`).filter(one => one !== "").join("\n") || null,
|
|
2190
|
+
text: feedback.comments.map(one => `${one.image ? `Screenshot ${one.image.artifact} · result #${one.image.run} — ` : ""}${one.path === null ? "" : `${one.path}${one.line === null ? "" : `:${one.line}`} — `}${one.note.trim()}`).filter(one => one !== "").join("\n") || null,
|
|
2174
2191
|
link: { label: `From build #${feedback.sourceRun}`, href: `/r/${feedback.sourceRun}` }, html: "", more: null });
|
|
2175
2192
|
}
|
|
2176
2193
|
for (const decision of data.decisions) {
|
|
@@ -2199,7 +2216,8 @@ export function taskBodyParts(data) {
|
|
|
2199
2216
|
// The Details panel: the same facts, grouped and said once.
|
|
2200
2217
|
const lastRunForAgent = liveRun ?? familyRuns.filter(run => run.role !== "reviewer").sort((a, b) => b.id - a.id)[0];
|
|
2201
2218
|
const work = [];
|
|
2202
|
-
|
|
2219
|
+
// A put-away task's one status is Archived (its banner says by whom and why).
|
|
2220
|
+
work.push({ label: "Status", parts: [data.archived != null ? "Archived" : data.assignment != null && assignmentOptions !== null ? assignmentCardOf(data.assignment, assignmentOptions).status.headline : status.label] });
|
|
2203
2221
|
if (data.repo !== null)
|
|
2204
2222
|
work.push({ label: "Project", parts: [projectName(data.repo)] });
|
|
2205
2223
|
if (data.base != null)
|
|
@@ -2310,6 +2328,7 @@ export function taskBodyParts(data) {
|
|
|
2310
2328
|
...(sectionIds.has("waits-for") ? [{ group: "work", label: "Dependencies", kind: "goto", tab: "plan", target: "waits-for" }] : []),
|
|
2311
2329
|
...(sectionIds.has("holds") ? [{ group: "work", label: "Holds", kind: "goto", tab: "plan", target: "holds" }] : []),
|
|
2312
2330
|
...(data.csrf !== "" ? [{ group: "work", label: "Task options", kind: "goto", tab: "plan", target: "task-diagnostics" }] : []),
|
|
2331
|
+
...(data.canPutAway === true && data.csrf !== "" && data.archived == null && !building ? [{ group: "work", label: "Put away", kind: "post", action: `${taskHref(task.id)}/archive`, fields: { return: taskHref(task.id) } }] : []),
|
|
2313
2332
|
{ group: "share", label: "Copy link", kind: "copy" },
|
|
2314
2333
|
...(facts.some(one => one.label === "Audit") ? [{ group: "share", label: "Evidence pack", kind: "link", href: `${taskHref(rootOf)}/evidence` }] : []),
|
|
2315
2334
|
...(data.repo !== null && data.scope !== null ? [{ group: "share", label: "Reuse as recipe", kind: "link", href: `/recipes/from-task?task=${encodeURIComponent(task.id)}` }] : []),
|
|
@@ -2326,10 +2345,12 @@ export function taskBodyParts(data) {
|
|
|
2326
2345
|
{ label: "Ask", href: taskChatHref(data.rootId ?? task.id), active: false },
|
|
2327
2346
|
],
|
|
2328
2347
|
version: data.versionLabel == null ? null : { label: data.versionLabel, current: { label: "Current work", href: taskHref(data.rootId ?? task.id) } },
|
|
2329
|
-
|
|
2348
|
+
archived: data.archived == null ? null : { words: archivedWords(data.archived), at: data.archived.at,
|
|
2349
|
+
restore: data.canPutAway === true && data.csrf !== "" ? { action: `${taskHref(task.id)}/restore`, returnTo: taskHref(task.id) } : null },
|
|
2350
|
+
status: statusCard === null || data.archived != null ? null : inPlaceChecks(failedCard ? { ...statusCard, action: null, status: { ...statusCard.status, sentence: data.failure?.line ?? statusCard.status.sentence } }
|
|
2330
2351
|
: progress !== null || record !== null ? { ...statusCard, action: statusCard.action?.href === record?.href ? null : statusCard.action, status: { ...statusCard.status, sentence: progress?.line ?? statusCard.status.sentence } }
|
|
2331
2352
|
: statusCard.action !== null && rebuild === null && bareSelfLink(statusCard.action.href) ? { ...statusCard, action: null } : statusCard),
|
|
2332
|
-
statusHtml: htmlString(statusHtml),
|
|
2353
|
+
statusHtml: data.archived != null ? "" : htmlString(statusHtml),
|
|
2333
2354
|
failure: failedCard && data.failure != null ? { line: data.failure.line, evidence: data.failure.evidence, suggestion: data.failure.suggestion, link: data.failure.link } : null,
|
|
2334
2355
|
retry,
|
|
2335
2356
|
runChecks,
|
|
@@ -2342,7 +2363,7 @@ export function taskBodyParts(data) {
|
|
|
2342
2363
|
...(data.csrf !== "" && (data.steerRecent?.length ?? 0) > 0 ? { steerRecent: data.steerRecent } : {}),
|
|
2343
2364
|
record,
|
|
2344
2365
|
earlier,
|
|
2345
|
-
approval: fragmentString(approvalHtml),
|
|
2366
|
+
approval: data.archived != null ? "" : fragmentString(approvalHtml),
|
|
2346
2367
|
approvalPlacement: approvalLeads ? "lead" : "plan",
|
|
2347
2368
|
more,
|
|
2348
2369
|
confirmStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "confirm-stopped" && data.assignment.primaryAction.target.runId !== null
|