toolroll 0.9.93 → 0.9.95
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 +79 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +10 -0
- package/dist/agent-fence.js +6 -3
- package/dist/approval-authority.d.ts +7 -1
- package/dist/approval-authority.js +11 -2
- package/dist/approval-password.d.ts +31 -0
- package/dist/approval-password.js +26 -0
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +3 -1
- package/dist/assignment.js +5 -0
- package/dist/attest.d.ts +3 -0
- package/dist/attest.js +10 -0
- package/dist/boot-identity.d.ts +2 -1
- package/dist/boot-identity.js +6 -4
- package/dist/bounded-spawn.d.ts +57 -0
- package/dist/bounded-spawn.js +220 -0
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +32 -32
- package/dist/browser-crew.js +2 -0
- package/dist/browser-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +50 -2
- package/dist/browser-workspace.js +3 -3
- package/dist/build-review.js +2 -0
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +11 -2
- package/dist/builder.js +234 -101
- package/dist/card-catalog.d.ts +209 -0
- package/dist/card-catalog.js +133 -0
- package/dist/card-cli.d.ts +43 -0
- package/dist/card-cli.js +44 -0
- package/dist/card-render.d.ts +26 -0
- package/dist/card-render.js +223 -0
- package/dist/card-sources.d.ts +68 -0
- package/dist/card-sources.js +234 -0
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +106 -9
- package/dist/chat-actions.js +473 -46
- package/dist/chat-approval.d.ts +13 -0
- package/dist/chat-approval.js +39 -9
- package/dist/chat-card-part.d.ts +11 -0
- package/dist/chat-card-part.js +18 -0
- package/dist/chat-card.d.ts +46 -0
- package/dist/chat-card.js +99 -0
- package/dist/chat-channel.d.ts +2 -0
- package/dist/chat-channel.js +37 -14
- package/dist/chat-controls.d.ts +4 -0
- package/dist/chat-controls.js +4 -0
- package/dist/chat-decide.d.ts +10 -16
- package/dist/chat-decide.js +79 -62
- package/dist/chat-delivery.js +13 -4
- package/dist/chat-flow.d.ts +0 -27
- package/dist/chat-flow.js +31 -2
- package/dist/chat-quiet.js +4 -4
- package/dist/chat-task-actions.d.ts +3 -2
- package/dist/chat-task-actions.js +19 -5
- package/dist/chat-terms.d.ts +14 -2
- package/dist/chat-terms.js +35 -0
- package/dist/chat-voice.d.ts +1 -1
- package/dist/chat-voice.js +2 -2
- package/dist/check-facts.d.ts +3 -1
- package/dist/check-facts.js +57 -11
- package/dist/check-failure.d.ts +24 -3
- package/dist/check-failure.js +89 -13
- package/dist/check-levels-ui.d.ts +4 -0
- package/dist/check-levels-ui.js +10 -1
- package/dist/check-load.d.ts +85 -0
- package/dist/check-load.js +147 -0
- package/dist/check-only-standing.d.ts +36 -0
- package/dist/check-only-standing.js +39 -0
- package/dist/check-only.d.ts +15 -0
- package/dist/check-only.js +99 -0
- package/dist/check-repair.d.ts +47 -0
- package/dist/check-repair.js +240 -0
- package/dist/check-report.d.ts +2 -0
- package/dist/check-report.js +1 -1
- package/dist/check-stamp.d.ts +46 -0
- package/dist/check-stamp.js +93 -0
- package/dist/checkout-cleanup.d.ts +36 -5
- package/dist/checkout-cleanup.js +166 -14
- package/dist/claim.d.ts +6 -0
- package/dist/claim.js +13 -2
- package/dist/cli-help.d.ts +4 -1
- package/dist/cli-help.js +49 -30
- package/dist/cli.js +15 -1
- package/dist/code-extract.d.ts +44 -0
- package/dist/code-extract.js +88 -0
- package/dist/code-map-acts.d.ts +24 -0
- package/dist/code-map-acts.js +53 -0
- package/dist/code-map-admission.d.ts +52 -0
- package/dist/code-map-admission.js +295 -0
- package/dist/code-map-build.d.ts +63 -0
- package/dist/code-map-build.js +415 -0
- package/dist/code-map-cli.d.ts +153 -0
- package/dist/code-map-cli.js +103 -0
- package/dist/code-map-main.d.ts +1 -0
- package/dist/code-map-main.js +48 -0
- package/dist/code-map-read.d.ts +106 -0
- package/dist/code-map-read.js +171 -0
- package/dist/code-map-report.d.ts +37 -0
- package/dist/code-map-report.js +95 -0
- package/dist/code-map-shape.d.ts +172 -0
- package/dist/code-map-shape.js +327 -0
- package/dist/code-map-store.d.ts +136 -0
- package/dist/code-map-store.js +233 -0
- package/dist/coding-provider.d.ts +3 -1
- package/dist/coding-provider.js +12 -7
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.d.ts +1 -1
- package/dist/coding-workspace.js +8 -8
- package/dist/console-chrome.js +2 -2
- package/dist/containment.d.ts +5 -0
- package/dist/containment.js +14 -1
- package/dist/contracts/chat-actions.d.ts +92 -0
- package/dist/contracts/chat-actions.js +20 -0
- package/dist/contracts/chat-card.d.ts +91 -0
- package/dist/contracts/chat-card.js +54 -0
- package/dist/contracts/chat-content.d.ts +191 -0
- package/dist/contracts/chat-content.js +4 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +12 -3
- package/dist/contracts/console-api.js +12 -3
- package/dist/contracts/flow-propose.d.ts +8 -1
- package/dist/contracts/flow-propose.js +3 -1
- package/dist/contracts/flow-send.d.ts +126 -0
- package/dist/contracts/flow-send.js +3 -0
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +234 -0
- package/dist/contracts/lead-tools.js +25 -1
- package/dist/contracts/project-knowledge.d.ts +38 -5
- package/dist/contracts/project-knowledge.js +11 -5
- package/dist/contracts/project-memory.d.ts +3 -1
- package/dist/contracts/project-memory.js +3 -2
- package/dist/contracts/storage.d.ts +14 -0
- package/dist/contracts/storage.js +3 -1
- package/dist/contracts/store-json.d.ts +18 -0
- package/dist/contracts/youiyoux-request.d.ts +1 -1
- package/dist/contracts/youiyoux-request.js +1 -1
- package/dist/desktop-access.js +6 -2
- package/dist/discord-chat.js +14 -0
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +25 -16
- package/dist/dispose.d.ts +19 -0
- package/dist/dispose.js +55 -9
- package/dist/easy-start.js +2 -3
- package/dist/eval-oracle.js +4 -1
- package/dist/evals.js +2 -1
- package/dist/evidence-pair.d.ts +53 -0
- package/dist/evidence-pair.js +128 -0
- package/dist/exhaustion.d.ts +17 -4
- package/dist/exhaustion.js +44 -4
- package/dist/export.js +1 -1
- package/dist/failing-first.d.ts +31 -0
- package/dist/failing-first.js +44 -0
- package/dist/fallback-schema.d.ts +8 -0
- package/dist/fallback-schema.js +40 -0
- package/dist/fallback.d.ts +138 -0
- package/dist/fallback.js +605 -0
- package/dist/fast-start.d.ts +136 -0
- package/dist/fast-start.js +431 -0
- package/dist/fast-terms.d.ts +9 -0
- package/dist/fast-terms.js +23 -0
- package/dist/fenced-check-coverage.d.ts +62 -0
- package/dist/fenced-check-coverage.js +54 -0
- package/dist/findings.d.ts +224 -0
- package/dist/findings.js +449 -0
- package/dist/first-run.js +1 -1
- package/dist/flaky-ci.d.ts +31 -0
- package/dist/flaky-ci.js +149 -0
- package/dist/flaky-cli.d.ts +60 -0
- package/dist/flaky-cli.js +60 -0
- package/dist/flaky.d.ts +75 -9
- package/dist/flaky.js +255 -0
- package/dist/fleet-schema.d.ts +17 -0
- package/dist/fleet-schema.js +135 -0
- package/dist/flow-browser.js +2 -2
- package/dist/flow-card-task.js +3 -0
- package/dist/flow-code.d.ts +2 -2
- package/dist/flow-code.js +9 -5
- package/dist/flow-result-card.d.ts +22 -0
- package/dist/flow-result-card.js +55 -0
- package/dist/flow-send.js +12 -9
- package/dist/flow-steps.js +1 -1
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +2 -2
- package/dist/flow-unattended.d.ts +26 -0
- package/dist/flow-unattended.js +12 -3
- package/dist/flows-ui.js +10 -2
- package/dist/follow-through-brief.d.ts +12 -0
- package/dist/follow-through-brief.js +31 -0
- package/dist/follow-through-cli.d.ts +23 -0
- package/dist/follow-through-cli.js +153 -0
- package/dist/git-incident.d.ts +16 -0
- package/dist/git-incident.js +69 -6
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/handover-note.d.ts +33 -0
- package/dist/handover-note.js +106 -0
- package/dist/invoke.d.ts +25 -2
- package/dist/invoke.js +157 -58
- package/dist/job-object-helper.ps1 +10 -3
- package/dist/keymap.d.ts +41 -0
- package/dist/keymap.js +51 -0
- package/dist/knowledge-cli.js +1 -1
- package/dist/knowledge-ui.d.ts +21 -4
- package/dist/knowledge-ui.js +75 -7
- package/dist/laptop-background.d.ts +16 -0
- package/dist/laptop-background.js +108 -2
- package/dist/lead-cli.js +26 -24
- package/dist/lead-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +3 -2
- package/dist/lead-doors.d.ts +4 -0
- package/dist/lead-doors.js +2 -2
- package/dist/lead-progress.js +5 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +162 -34
- package/dist/lead.js +2 -2
- package/dist/lesson-notes.d.ts +81 -0
- package/dist/lesson-notes.js +336 -0
- package/dist/limits-ui.js +1 -1
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +16 -0
- package/dist/machine-check.d.ts +16 -0
- package/dist/machine-check.js +26 -0
- package/dist/machine-identity.d.ts +75 -0
- package/dist/machine-identity.js +170 -0
- package/dist/mcp-core.js +11 -1
- package/dist/memory-cli.d.ts +300 -1
- package/dist/memory-cli.js +62 -9
- package/dist/memory-pass.d.ts +16 -0
- package/dist/memory-pass.js +67 -3
- package/dist/memory-tidy.d.ts +181 -0
- package/dist/memory-tidy.js +523 -0
- package/dist/memory-use.d.ts +35 -0
- package/dist/memory-use.js +109 -0
- package/dist/merge-checklist.d.ts +55 -0
- package/dist/merge-checklist.js +182 -0
- package/dist/modes.d.ts +6 -6
- package/dist/modes.js +3 -1
- package/dist/needs-you.d.ts +31 -9
- package/dist/needs-you.js +110 -41
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +7 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +565 -64
- package/dist/phase-routing.js +2 -2
- package/dist/plain-schema.d.ts +23 -0
- package/dist/plain-schema.js +94 -0
- package/dist/plain-words.d.ts +20 -0
- package/dist/plain-words.js +66 -0
- package/dist/plan-login.d.ts +14 -0
- package/dist/plan-login.js +58 -0
- package/dist/planner.d.ts +1 -1
- package/dist/planner.js +4 -2
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +19 -2
- package/dist/project-delete-ui.js +29 -1
- package/dist/project-delete.js +14 -1
- package/dist/project-knowledge.d.ts +18 -11
- package/dist/project-knowledge.js +39 -9
- package/dist/project-learning.d.ts +2 -1
- package/dist/project-learning.js +15 -45
- package/dist/project-memory.d.ts +5 -0
- package/dist/project-memory.js +22 -2
- package/dist/project-tools.d.ts +4 -4
- package/dist/project-tools.js +23 -7
- package/dist/proof.js +30 -5
- package/dist/protected-storage-look.d.ts +1 -0
- package/dist/protected-storage-look.js +13 -0
- package/dist/protected-storage.d.ts +70 -39
- package/dist/protected-storage.js +196 -147
- package/dist/provider-auth.js +1 -1
- package/dist/provider-capture.d.ts +34 -0
- package/dist/provider-capture.js +53 -0
- package/dist/publish.js +9 -1
- package/dist/pull-request-flow.js +5 -0
- package/dist/push.d.ts +18 -2
- package/dist/push.js +40 -16
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +76 -0
- package/dist/release-auto.js +13 -13
- package/dist/release-card.d.ts +1 -2
- package/dist/release-card.js +11 -0
- package/dist/release-carry.d.ts +7 -2
- package/dist/release-carry.js +49 -10
- package/dist/release-cli.d.ts +4 -0
- package/dist/release-cli.js +29 -22
- package/dist/release-coverage.js +1 -1
- package/dist/release-next.d.ts +48 -0
- package/dist/release-next.js +161 -0
- package/dist/release.d.ts +20 -1
- package/dist/release.js +85 -9
- package/dist/remote-destination.d.ts +36 -0
- package/dist/remote-destination.js +217 -0
- package/dist/remote-main.d.ts +48 -0
- package/dist/remote-main.js +84 -5
- package/dist/repository-context.d.ts +3 -2
- package/dist/repository-context.js +112 -70
- package/dist/restart-certification.d.ts +1 -0
- package/dist/restart-certification.js +8 -6
- package/dist/result-acts.d.ts +27 -19
- package/dist/result-acts.js +36 -25
- package/dist/result-follow-ups.d.ts +2 -2
- package/dist/result-follow-ups.js +18 -4
- package/dist/result-review.d.ts +1 -1
- package/dist/route-change-check.d.ts +4 -0
- package/dist/route-change-check.js +13 -0
- package/dist/route-change.d.ts +22 -0
- package/dist/route-change.js +89 -0
- package/dist/route-hooks.js +3 -2
- package/dist/route-results.d.ts +6 -1
- package/dist/route-results.js +14 -5
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +7 -5
- package/dist/runner-process.js +3 -2
- package/dist/runner.js +2 -1
- package/dist/scope.d.ts +28 -4
- package/dist/scope.js +92 -28
- package/dist/scout.d.ts +1 -1
- package/dist/scout.js +2 -1
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +26 -18
- package/dist/server/chat.js +16 -14
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +73 -0
- package/dist/server/guards.js +3 -0
- package/dist/server/home.d.ts +6 -0
- package/dist/server/home.js +21 -13
- package/dist/server/http.js +1 -1
- package/dist/server/kit.js +1 -1
- package/dist/server/merge-ready-view.d.ts +10 -0
- package/dist/server/merge-ready-view.js +30 -0
- package/dist/server/pages-assets.d.ts +1 -0
- package/dist/server/pages-assets.js +8 -5
- package/dist/server/pages.js +103 -82
- package/dist/server/render-chat.js +16 -3
- package/dist/server/render-code-map.d.ts +30 -0
- package/dist/server/render-code-map.js +137 -0
- package/dist/server/render-flaky.d.ts +20 -0
- package/dist/server/render-flaky.js +59 -0
- package/dist/server/render-follow-through.d.ts +11 -0
- package/dist/server/render-follow-through.js +68 -0
- package/dist/server/render-history.js +1 -1
- package/dist/server/render-new-task.d.ts +26 -1
- package/dist/server/render-new-task.js +30 -5
- package/dist/server/render-pages.d.ts +1 -175
- package/dist/server/render-pages.js +38 -313
- package/dist/server/render-projects.d.ts +2 -1
- package/dist/server/render-projects.js +9 -7
- package/dist/server/render-release.d.ts +2 -0
- package/dist/server/render-release.js +3 -3
- package/dist/server/render-results.d.ts +2 -0
- package/dist/server/render-results.js +68 -68
- package/dist/server/render-tasks.d.ts +16 -2
- package/dist/server/render-tasks.js +177 -141
- package/dist/server/retired-routes.d.ts +17 -0
- package/dist/server/retired-routes.js +53 -0
- package/dist/server/route-table.d.ts +3 -0
- package/dist/server/route-table.js +48 -12
- package/dist/server/settings.js +166 -34
- package/dist/server/task-views.js +66 -26
- package/dist/server/tasks.d.ts +2 -6
- package/dist/server/tasks.js +250 -321
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.d.ts +1 -0
- package/dist/settings-groups-ui.js +4 -2
- package/dist/settings-kit.js +3 -2
- package/dist/settings-proofs.js +3 -1
- package/dist/slack-chat.js +14 -1
- package/dist/steer-mailbox.d.ts +8 -1
- package/dist/steer-mailbox.js +164 -6
- package/dist/stop-proof.js +6 -4
- package/dist/storage-sweep-process.js +1 -1
- package/dist/storage-sweep.d.ts +5 -3
- package/dist/storage-sweep.js +30 -9
- package/dist/storage-ui.js +28 -17
- package/dist/storage.d.ts +27 -2
- package/dist/storage.js +39 -2
- package/dist/store.d.ts +89 -7
- package/dist/store.js +386 -114
- package/dist/surface.js +67 -6
- package/dist/sweep-quarantine.d.ts +1 -0
- package/dist/sweep-quarantine.js +6 -4
- package/dist/tamper-scan.d.ts +4 -0
- package/dist/tamper-scan.js +90 -0
- package/dist/task-aliases.d.ts +5 -0
- package/dist/task-aliases.js +9 -0
- package/dist/task-control.d.ts +2 -1
- package/dist/task-control.js +4 -1
- package/dist/task-outcome-cli.d.ts +2 -0
- package/dist/task-outcome-cli.js +17 -17
- package/dist/task-sizing.d.ts +5 -0
- package/dist/task-sizing.js +2 -0
- package/dist/task-source.d.ts +7 -0
- package/dist/task-source.js +14 -0
- package/dist/task-status.d.ts +9 -0
- package/dist/task-status.js +45 -49
- package/dist/teardown-git.d.ts +18 -0
- package/dist/teardown-git.js +105 -21
- package/dist/telegram-lead.js +23 -6
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- package/dist/telegram.js +12 -1
- package/dist/test-infra.d.ts +8 -0
- package/dist/test-infra.js +18 -0
- package/dist/tidy.js +1 -1
- package/dist/toolroll-update.js +19 -5
- package/dist/tui-contract.d.ts +24 -0
- package/dist/tui-source.js +67 -8
- package/dist/tui.js +51 -51
- package/dist/vanished-retry.d.ts +30 -0
- package/dist/vanished-retry.js +174 -0
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +95 -66
- package/dist/work-summary.d.ts +1 -1
- package/dist/work-summary.js +7 -2
- package/dist/workspace-ui.d.ts +7 -3
- package/dist/workspace-ui.js +38 -23
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +91 -14
- package/package.json +2 -1
- package/dist/server/render-next.d.ts +0 -63
- package/dist/server/render-next.js +0 -109
package/dist/server/home.js
CHANGED
|
@@ -22,7 +22,7 @@ export function homeSentence(needs, perProject, scope, any) {
|
|
|
22
22
|
const waiting = perProject.filter(one => one.needs > 0);
|
|
23
23
|
return waiting.length === 1 ? `${needs} ${verb} you in ${waiting[0].name}.` : `${needs} ${verb} you across ${waiting.length} projects.`;
|
|
24
24
|
}
|
|
25
|
-
function rowOf(store, item, now, multi, canAnswer) {
|
|
25
|
+
function rowOf(store, item, now, multi, canAnswer, closedDoor) {
|
|
26
26
|
const action = item.primaryAction;
|
|
27
27
|
const href = action?.code === "open-result" && action.target.runId != null
|
|
28
28
|
? `/review?result=${encodeURIComponent(action.target.taskId)}&run=${action.target.runId}` : browserWorkActionHref(item);
|
|
@@ -41,11 +41,14 @@ function rowOf(store, item, now, multi, canAnswer) {
|
|
|
41
41
|
const line = waits
|
|
42
42
|
? (open ? decision.question : item.status.detail !== item.status.label ? item.status.detail : null)
|
|
43
43
|
: item.progress ?? (item.status.detail !== item.status.label ? item.status.detail : null);
|
|
44
|
+
// A plan that can't take a yes never offers one: the reason, and the one act that opens it, as the task page says.
|
|
45
|
+
const door = action?.code === "approve-scope" ? closedDoor(action.target.taskId) : null;
|
|
44
46
|
return {
|
|
45
47
|
id: item.rootId, title: item.title, href: taskHref(item.rootId),
|
|
46
48
|
project: multi || item.repo === null ? (item.repo === null ? "Unplaced" : projectName(item.repo)) : null,
|
|
47
|
-
age: relativeAge(item.updatedAt, now), chip: { word, tone }, line,
|
|
48
|
-
action:
|
|
49
|
+
age: relativeAge(item.updatedAt, now), chip: { word, tone }, line: door === null ? line : door.title,
|
|
50
|
+
action: door !== null ? { label: door.road === "agents" ? "Change the agents" : "Edit the plan", href: `${taskHref(action.target.taskId)}#${door.road === "agents" ? "agents" : "scope"}` }
|
|
51
|
+
: href === null || action === null ? null : { label: action.label, href }, question,
|
|
49
52
|
};
|
|
50
53
|
}
|
|
51
54
|
/** Home's view for one person and scope, from two reads of the work index (what waits, what runs). */
|
|
@@ -54,26 +57,28 @@ export function homeView(facts) {
|
|
|
54
57
|
const read = (view, limit) => workIndexPage(store, now, access, { view, limit, project, ...(facts.evidenceRoot === undefined ? {} : { root: facts.evidenceRoot }) });
|
|
55
58
|
const needs = read("needs-you", HOME_NEEDS_SHOWN);
|
|
56
59
|
const running = read("running", HOME_RUNNING_SHOWN);
|
|
60
|
+
// Waiting (docs/plans/plain-toolroll.md P1.4): what Toolroll or another task moves on, with no person asked.
|
|
61
|
+
const waiting = workIndexPage(store, now, access, { view: "queued", limit: HOME_RUNNING_SHOWN, project, ...(facts.evidenceRoot === undefined ? {} : { root: facts.evidenceRoot }) });
|
|
57
62
|
const totals = needs.totals;
|
|
58
63
|
// Queued: the Tasks list's own Queued count (the same predicate its view=queued filter lists).
|
|
59
64
|
const queued = totals.queued;
|
|
60
|
-
// Every project is said by name (all
|
|
61
|
-
const scopeQuery = project === null ? "all
|
|
65
|
+
// Every project is said by name (scope=all): /tasks alone would narrow to the session's open project.
|
|
66
|
+
const scopeQuery = project === null ? "scope=all&" : `project=${encodeURIComponent(project)}&`;
|
|
62
67
|
const counts = [
|
|
63
|
-
{ key: "needs", label: "Needs you", value: totals["needs-you"], href: `/
|
|
64
|
-
{ key: "running", label: "Running", value: totals.running, href: `/
|
|
65
|
-
{ key: "queued", label: "Queued", value: queued, href: `/
|
|
66
|
-
{ key: "finished", label: "Finished", value: totals.completed, href: `/
|
|
68
|
+
{ key: "needs", label: "Needs you", value: totals["needs-you"], href: `/tasks?${scopeQuery}view=needs-you` },
|
|
69
|
+
{ key: "running", label: "Running", value: totals.running, href: `/tasks?${scopeQuery}view=running` },
|
|
70
|
+
{ key: "queued", label: "Queued", value: queued, href: `/tasks?${scopeQuery}view=queued` },
|
|
71
|
+
{ key: "finished", label: "Finished", value: totals.completed, href: `/tasks?${scopeQuery}view=completed` },
|
|
67
72
|
];
|
|
68
73
|
const multi = project === null && new Set([...needs.items, ...running.items].map(one => one.repo ?? "")).size > 1;
|
|
69
74
|
const groups = [];
|
|
70
75
|
for (const ask of ASKS) {
|
|
71
|
-
const rows = needs.items.filter(one => one.ask === ask).map(one => rowOf(store, one, now, multi, facts.canAnswer));
|
|
76
|
+
const rows = needs.items.filter(one => one.ask === ask).map(one => rowOf(store, one, now, multi, facts.canAnswer, facts.closedDoor));
|
|
72
77
|
if (rows.length > 0)
|
|
73
78
|
groups.push({ key: ask, label: ASK_LABEL[ask], rows });
|
|
74
79
|
}
|
|
75
80
|
// A waiting row whose ask the index couldn't name still waits on this person: it goes under Decide, never lost.
|
|
76
|
-
const unnamed = needs.items.filter(one => one.ask === null).map(one => rowOf(store, one, now, multi, facts.canAnswer));
|
|
81
|
+
const unnamed = needs.items.filter(one => one.ask === null).map(one => rowOf(store, one, now, multi, facts.canAnswer, facts.closedDoor));
|
|
77
82
|
if (unnamed.length > 0) {
|
|
78
83
|
const decide = groups.find(one => one.key === "decide");
|
|
79
84
|
if (decide)
|
|
@@ -81,12 +86,15 @@ export function homeView(facts) {
|
|
|
81
86
|
else
|
|
82
87
|
groups.unshift({ key: "decide", label: ASK_LABEL.decide, rows: unnamed });
|
|
83
88
|
}
|
|
84
|
-
const live = running.items.filter(one => !one.status.views.includes("needs-you")).map(one => rowOf(store, one, now, multi, facts.canAnswer));
|
|
89
|
+
const live = running.items.filter(one => !one.status.views.includes("needs-you")).map(one => rowOf(store, one, now, multi, facts.canAnswer, facts.closedDoor));
|
|
85
90
|
if (live.length > 0)
|
|
86
91
|
groups.push({ key: "running", label: "Running", rows: live });
|
|
92
|
+
const waits = waiting.items.filter(one => one.status.label === "Waiting" && !one.status.views.includes("needs-you")).map(one => rowOf(store, one, now, multi, facts.canAnswer, facts.closedDoor));
|
|
93
|
+
if (waits.length > 0)
|
|
94
|
+
groups.push({ key: "waiting", label: "Waiting", rows: waits });
|
|
87
95
|
const perProject = needs.projects.map(one => ({ name: one.repo === null ? "Unplaced" : projectName(one.repo), needs: one.totals["needs-you"] }));
|
|
88
96
|
const any = totals.all > 0 || totals.completed > 0 || (totals.archived ?? 0) > 0;
|
|
89
|
-
const shown = groups.filter(one => one.key !== "running").reduce((sum, one) => sum + one.rows.length, 0);
|
|
97
|
+
const shown = groups.filter(one => one.key !== "running" && one.key !== "waiting").reduce((sum, one) => sum + one.rows.length, 0);
|
|
90
98
|
return {
|
|
91
99
|
kind: "home",
|
|
92
100
|
say: homeSentence(totals["needs-you"], perProject, project === null ? null : projectName(project), any),
|
package/dist/server/http.js
CHANGED
|
@@ -167,7 +167,7 @@ export function safeReturn(raw) {
|
|
|
167
167
|
* button can name. Nothing here is a second redirect framework: one
|
|
168
168
|
* same-site path, or "/".
|
|
169
169
|
*/
|
|
170
|
-
export const LOGIN_RETURN_PAGES = /^\/(oauth\/authorize|t\/[^/]+|r\/[1-9]\d*|code(?:\/[a-f0-9]{32}(?:\/ship)?)?|review|chat|work|board|projects|routines|recipes|fleet|settings(\/[a-z-]+)?|mode)$/;
|
|
170
|
+
export const LOGIN_RETURN_PAGES = /^\/(oauth\/authorize|t\/[^/]+|r\/[1-9]\d*|code(?:\/[a-f0-9]{32}(?:\/ship)?)?|review|chat|tasks|work|board|projects|routines|recipes|fleet|settings(\/[a-z-]+)?|mode)$/;
|
|
171
171
|
export function loginReturn(raw) {
|
|
172
172
|
const safe = safeReturn(raw);
|
|
173
173
|
if (safe === "/")
|
package/dist/server/kit.js
CHANGED
|
@@ -223,7 +223,7 @@ export const card = (options) => html `<a class="k-card" href="${options.href}">
|
|
|
223
223
|
export function notFound(path, options = {}) {
|
|
224
224
|
return html `${pageHeader("Not found")}<div class="k-nf">${empty({
|
|
225
225
|
title: options.title ?? NOT_FOUND.title(path), text: options.text ?? NOT_FOUND.text,
|
|
226
|
-
action: acts(btn("Go to Home", { sm: true, href: "/" }), btn("Open Tasks", { sm: true, variant: "ghost", href: "/
|
|
226
|
+
action: acts(btn("Go to Home", { sm: true, href: "/" }), btn("Open Tasks", { sm: true, variant: "ghost", href: "/tasks" })),
|
|
227
227
|
})}</div>`;
|
|
228
228
|
}
|
|
229
229
|
/** The Settings frame: Overview and the eight groups, a desk breadcrumb, a phone back header, one column. */
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { AssignmentSnapshot } from "../assignment.js";
|
|
2
|
+
import type { MergeReadyView } from "../browser-workspace.js";
|
|
3
|
+
import type { Store } from "../store.js";
|
|
4
|
+
import type { Who } from "./session.js";
|
|
5
|
+
/** The panel for a task page, or null when there is no result to merge yet. */
|
|
6
|
+
export declare function mergeReadyViewOf(store: Store, taskId: string, assignment: AssignmentSnapshot | null | undefined, who: Who, runChecks: {
|
|
7
|
+
action: string;
|
|
8
|
+
level: "quick" | "full";
|
|
9
|
+
returnTo: string;
|
|
10
|
+
} | null, returnTo: string): MergeReadyView | null;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** The task page's "Can I merge?" panel (docs/plans/plain-toolroll.md P1.6): the checklist (merge-checklist.ts) with
|
|
2
|
+
* each blocker's act as the form this page already posts for it. Run checks is the Checks row's own post; Retry is the
|
|
3
|
+
* failed box's own requeue (the page shows it only where that box would); Ask the agent to fix is Send back: the same
|
|
4
|
+
* post to /r/<build>/comment with intent=revise that the result's Send back makes, its note prefilled with the blocker
|
|
5
|
+
* and editable, sealing the same saved notes. Only a signed-in approver gets the forms; anyone else reads the rows. */
|
|
6
|
+
import { randomBytes } from "node:crypto";
|
|
7
|
+
import { checklistFromAssignment } from "../merge-checklist.js";
|
|
8
|
+
import { isRevisionFeedback, revisionBatchOf, revisionSourceOf } from "../result-review.js";
|
|
9
|
+
/** The panel for a task page, or null when there is no result to merge yet. */
|
|
10
|
+
export function mergeReadyViewOf(store, taskId, assignment, who, runChecks, returnTo) {
|
|
11
|
+
if (assignment == null || assignment.receipt === null)
|
|
12
|
+
return null;
|
|
13
|
+
const list = checklistFromAssignment(store, taskId, assignment);
|
|
14
|
+
const person = who.via === "cookie" && who.role === "approver" && !store.isDemo();
|
|
15
|
+
const receipt = assignment.receipt;
|
|
16
|
+
const notes = store.liveDiffComments(receipt.runId).filter(one => isRevisionFeedback(one));
|
|
17
|
+
return {
|
|
18
|
+
answer: list.answer, sentence: list.sentence,
|
|
19
|
+
rows: list.rows.filter(one => one.mark !== "skip").map(one => {
|
|
20
|
+
const act = one.act;
|
|
21
|
+
const form = !person || act === null ? null
|
|
22
|
+
: act.kind === "run-checks" ? runChecks === null || runChecks.action !== `/r/${act.run}/checks` ? null : { kind: "run-checks", ...runChecks }
|
|
23
|
+
: act.kind === "retry" ? { kind: "retry" }
|
|
24
|
+
: act.run !== receipt.runId ? null
|
|
25
|
+
: { kind: "fix", action: `/r/${act.run}/comment`, note: act.note, batch: revisionBatchOf(notes), source: revisionSourceOf(store.getScope(receipt.taskId)?.digest ?? null),
|
|
26
|
+
request: randomBytes(16).toString("hex"), returnTo };
|
|
27
|
+
return { key: one.key, label: one.label, mark: one.mark, say: one.say, act: form };
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* typefaces, declared in the route table's edge stage and answered before authentication.
|
|
4
4
|
*/
|
|
5
5
|
import type { ServerResponse } from "node:http";
|
|
6
|
+
export declare const PWA_WORKER = "// Toolroll \u2014 push only; deliberately NO fetch handler (no offline cache of an authenticated console).\n// Every shape an older worker took, kept for good, then Home, Tasks' Ready and Board views and a task by its number.\nconst SHAPES = [/^\\/next$/, /^\\/review$/, /^\\/system$/, /^\\/routines$/, /^\\/routines\\/[0-9]+$/, /^\\/d\\/[0-9]+$/, /^\\/r\\/[0-9]+$/, /^\\/approve\\/[0-9]+$/,\n /^\\/$/, /^\\/tasks\\?view=ready$/, /^\\/tasks\\?view=board$/, /^\\/n\\/[1-9][0-9]{0,14}$/];\nself.addEventListener(\"push\", function (event) {\n var data = {};\n try { data = event.data ? event.data.json() : {}; } catch (e) {}\n var body = typeof data.body === \"string\" ? data.body : \"the console needs you\";\n var tag = typeof data.tag === \"string\" ? data.tag : \"toolroll\";\n var url = typeof data.url === \"string\" && SHAPES.some(function (s) { return s.test(data.url); }) ? data.url : \"/\";\n if (typeof data.waiting === \"number\" && data.waiting >= 0 && self.registration.setAppBadge) {\n // A count, never content. Honest no-op where unsupported.\n event.waitUntil(self.registration.setAppBadge(Math.floor(data.waiting)).catch(function () {}));\n }\n event.waitUntil(self.registration.showNotification(\"Toolroll\", { body: body, tag: tag, data: { url: url }, actions: [{ action: \"open\", title: \"Open\" }] }));\n});\nself.addEventListener(\"message\", function (event) {\n // The page recomputes on load/focus and is authoritative over any stale\n // push: {badge: N} sets, {badge: 0} clears.\n var badge = event.data && typeof event.data.badge === \"number\" ? event.data.badge : null;\n if (badge === null || !self.registration.setAppBadge) return;\n if (badge <= 0 && self.registration.clearAppBadge) { self.registration.clearAppBadge().catch(function () {}); return; }\n self.registration.setAppBadge(Math.floor(badge)).catch(function () {});\n});\n// A tap on the ping and its one \"Open\" action land in the same place.\nself.addEventListener(\"notificationclick\", function (event) {\n event.notification.close();\n if (self.registration.clearAppBadge) self.registration.clearAppBadge().catch(function () {});\n var url = event.notification.data && event.notification.data.url;\n var target = new URL(SHAPES.some(function (s) { return s.test(url); }) ? url : \"/\", self.location.origin).href;\n event.waitUntil(clients.matchAll({ type: \"window\" }).then(function (open) {\n for (var i = 0; i < open.length; i++) { if (open[i].url === target && open[i].focus) return open[i].focus(); }\n return clients.openWindow(target);\n }));\n});\n";
|
|
6
7
|
/**
|
|
7
8
|
* Answers one install asset or typeface (GET only; the caller checks the method), with nosniff and its own
|
|
8
9
|
* conservative caching/CSP. False for any other path: unknown names fall through to the router's refusal.
|
|
@@ -38,19 +38,21 @@ const PWA_MANIFEST = JSON.stringify({
|
|
|
38
38
|
// and the tap, only. notificationclick resolves ONLY allow-listed relative
|
|
39
39
|
// paths — the payload URL is data, revalidated, never handed raw to the
|
|
40
40
|
// browser (arc 3 finding 5).
|
|
41
|
-
const PWA_WORKER = `// Toolroll — push only; deliberately NO fetch handler (no offline cache of an authenticated console).
|
|
42
|
-
|
|
41
|
+
export const PWA_WORKER = `// Toolroll — push only; deliberately NO fetch handler (no offline cache of an authenticated console).
|
|
42
|
+
// Every shape an older worker took, kept for good, then Home, Tasks' Ready and Board views and a task by its number.
|
|
43
|
+
const SHAPES = [/^\\/next$/, /^\\/review$/, /^\\/system$/, /^\\/routines$/, /^\\/routines\\/[0-9]+$/, /^\\/d\\/[0-9]+$/, /^\\/r\\/[0-9]+$/, /^\\/approve\\/[0-9]+$/,
|
|
44
|
+
/^\\/$/, /^\\/tasks\\?view=ready$/, /^\\/tasks\\?view=board$/, /^\\/n\\/[1-9][0-9]{0,14}$/];
|
|
43
45
|
self.addEventListener("push", function (event) {
|
|
44
46
|
var data = {};
|
|
45
47
|
try { data = event.data ? event.data.json() : {}; } catch (e) {}
|
|
46
48
|
var body = typeof data.body === "string" ? data.body : "the console needs you";
|
|
47
49
|
var tag = typeof data.tag === "string" ? data.tag : "toolroll";
|
|
48
|
-
var url = typeof data.url === "string" && SHAPES.some(function (s) { return s.test(data.url); }) ? data.url : "/
|
|
50
|
+
var url = typeof data.url === "string" && SHAPES.some(function (s) { return s.test(data.url); }) ? data.url : "/";
|
|
49
51
|
if (typeof data.waiting === "number" && data.waiting >= 0 && self.registration.setAppBadge) {
|
|
50
52
|
// A count, never content. Honest no-op where unsupported.
|
|
51
53
|
event.waitUntil(self.registration.setAppBadge(Math.floor(data.waiting)).catch(function () {}));
|
|
52
54
|
}
|
|
53
|
-
event.waitUntil(self.registration.showNotification("Toolroll", { body: body, tag: tag, data: { url: url } }));
|
|
55
|
+
event.waitUntil(self.registration.showNotification("Toolroll", { body: body, tag: tag, data: { url: url }, actions: [{ action: "open", title: "Open" }] }));
|
|
54
56
|
});
|
|
55
57
|
self.addEventListener("message", function (event) {
|
|
56
58
|
// The page recomputes on load/focus and is authoritative over any stale
|
|
@@ -60,11 +62,12 @@ self.addEventListener("message", function (event) {
|
|
|
60
62
|
if (badge <= 0 && self.registration.clearAppBadge) { self.registration.clearAppBadge().catch(function () {}); return; }
|
|
61
63
|
self.registration.setAppBadge(Math.floor(badge)).catch(function () {});
|
|
62
64
|
});
|
|
65
|
+
// A tap on the ping and its one "Open" action land in the same place.
|
|
63
66
|
self.addEventListener("notificationclick", function (event) {
|
|
64
67
|
event.notification.close();
|
|
65
68
|
if (self.registration.clearAppBadge) self.registration.clearAppBadge().catch(function () {});
|
|
66
69
|
var url = event.notification.data && event.notification.data.url;
|
|
67
|
-
var target = new URL(SHAPES.some(function (s) { return s.test(url); }) ? url : "/
|
|
70
|
+
var target = new URL(SHAPES.some(function (s) { return s.test(url); }) ? url : "/", self.location.origin).href;
|
|
68
71
|
event.waitUntil(clients.matchAll({ type: "window" }).then(function (open) {
|
|
69
72
|
for (var i = 0; i < open.length; i++) { if (open[i].url === target && open[i].focus) return open[i].focus(); }
|
|
70
73
|
return clients.openWindow(target);
|
package/dist/server/pages.js
CHANGED
|
@@ -52,14 +52,21 @@ import { refuse, regionScript, relativeAge as relativeAgeOf, screen, settingsRow
|
|
|
52
52
|
import { redirect, respond, safeReturn, SAFETY, taskHref } from "./http.js";
|
|
53
53
|
import { assignmentOf } from "../assignment.js";
|
|
54
54
|
import { assignmentTaskStatusOf } from "../assignment-presentation.js";
|
|
55
|
-
import { githubIdentityOf, homePage,
|
|
55
|
+
import { githubIdentityOf, homePage, startBuilderHtml } from "./render-pages.js";
|
|
56
|
+
import { retiredRedirect } from "./retired-routes.js";
|
|
57
|
+
import { codeMapStatus, openMap } from "../code-map-read.js";
|
|
58
|
+
import { requestRefresh } from "../code-map-store.js";
|
|
59
|
+
import { codeMapReport } from "../code-map-report.js";
|
|
60
|
+
import { codeMapPage, codeMapReportBlock } from "./render-code-map.js";
|
|
61
|
+
import { FLAKY_WINDOW_DAYS, flakyList, flakyShow } from "../flaky.js";
|
|
62
|
+
import { flakyPage, flakyRowPage } from "./render-flaky.js";
|
|
56
63
|
import { browsePage, githubReposPage, projectHref, projectPage, projectsPage, unknownProject } from "./render-projects.js";
|
|
57
64
|
import { notFound as kitNotFound } from "./kit.js";
|
|
58
65
|
import {} from "./render-pages.js";
|
|
59
66
|
import {} from "../project-outcomes.js";
|
|
60
67
|
import { CHECK_LEVEL_WORDS } from "../check-levels.js";
|
|
61
68
|
import { capsPage } from "./render-settings.js";
|
|
62
|
-
import { decisionAnswerScript, taskBody } from "./render-tasks.js";
|
|
69
|
+
import { consentDoorOf, decisionAnswerScript, taskBody } from "./render-tasks.js";
|
|
63
70
|
import {} from "./session.js";
|
|
64
71
|
import { rereadBrowser } from "./live.js";
|
|
65
72
|
import { homeView } from "./home.js";
|
|
@@ -185,16 +192,21 @@ export function createPagesHandlers(runtime) {
|
|
|
185
192
|
async function homeRedirect(ctx) {
|
|
186
193
|
const { who, response, now, project } = ctx;
|
|
187
194
|
if (who.via !== "cookie" || restricted())
|
|
188
|
-
return redirect(response, "/
|
|
195
|
+
return redirect(response, "/tasks");
|
|
189
196
|
if (firstRunStepsNow(now) !== null && !store.hasAnyWork())
|
|
190
197
|
return redirect(response, "/chat");
|
|
191
198
|
// One scope for the page and the chrome around it: the project its address names (proved by the router), else all.
|
|
192
199
|
const chrome = chromeFor(project, "none");
|
|
193
200
|
const projects = chrome.projects ?? [];
|
|
194
|
-
const
|
|
201
|
+
const closedDoor = (taskId) => {
|
|
202
|
+
const scope = store.getScope(taskId);
|
|
203
|
+
const door = consentDoorOf(scope, runtime.routeViewOf(taskId, store.lookupRef(taskId), scope, now, who));
|
|
204
|
+
return door.open ? null : door;
|
|
205
|
+
};
|
|
206
|
+
const view = { ...homeView({ store, now, access: workAccess(), evidenceRoot, project, projects, chat: chrome.chat === true, canAnswer: who.role === "approver", closedDoor }),
|
|
195
207
|
// Until the first result: the welcome's three steps stay one link away.
|
|
196
208
|
setup: firstRunStepsNow(now) !== null ? "/welcome" : null };
|
|
197
|
-
return sendScreen(response, 200, screen("Home", html `<div data-kit-page><h1>Home</h1><p>${view.say}</p><p><a href="/
|
|
209
|
+
return sendScreen(response, 200, screen("Home", html `<div data-kit-page><h1>Home</h1><p>${view.say}</p><p><a href="/tasks">Tasks</a></p></div>`, { chrome, workspace: { view } }));
|
|
198
210
|
}
|
|
199
211
|
// Search (UI revamp wave B): the ⌘K menu's "See all results". The places come from the page; the tasks here are the
|
|
200
212
|
// most recent ones in view (live and archived), matched on their title and id. Reads only.
|
|
@@ -227,76 +239,6 @@ export function createPagesHandlers(runtime) {
|
|
|
227
239
|
response.end();
|
|
228
240
|
return;
|
|
229
241
|
}
|
|
230
|
-
async function workbench(ctx) {
|
|
231
|
-
const { url, who, response, now, project } = ctx;
|
|
232
|
-
// The fleet under one gaze (attended A1): the rail rolls up the whole
|
|
233
|
-
// ceiling regardless of the open project — watching is cross-project
|
|
234
|
-
// by nature; acting happens on task screens that re-prove everything.
|
|
235
|
-
const admission = admissionList();
|
|
236
|
-
const snapshot = store.boardScoped(null, now, 200, admission);
|
|
237
|
-
const rows = snapshot.tasks.filter(facts => facts.repo === null || visible(facts.repo));
|
|
238
|
-
const cards = rows.map(facts => classify(facts.blockerRepo !== null && !visible(facts.blockerRepo) ? { ...facts, blockerState: null } : facts, now));
|
|
239
|
-
const attention = cards.filter(card => card.lane === "attention").slice(0, 100);
|
|
240
|
-
const building = cards.filter(card => card.lane === "building");
|
|
241
|
-
const waiting = cards.filter(card => card.lane === "waiting");
|
|
242
|
-
const queued = cards.filter(card => card.lane === "queued");
|
|
243
|
-
const done = snapshot.done
|
|
244
|
-
.filter(one => one.repo === null || visible(one.repo))
|
|
245
|
-
.slice(0, 5)
|
|
246
|
-
.map(one => ({ taskId: one.taskId, title: one.title, outcome: one.outcome, repo: one.repo }));
|
|
247
|
-
const selected = url.searchParams.get("t");
|
|
248
|
-
const rail = workbenchRail({ attention, building, waiting, queued, done, selected, saturated: snapshot.saturated });
|
|
249
|
-
if (url.searchParams.get("fragment") === "rail") {
|
|
250
|
-
// The rail alone: same auth, same ceiling, no shell, no scripts.
|
|
251
|
-
return respond(response, 200, "text/html; charset=utf-8", rail);
|
|
252
|
-
}
|
|
253
|
-
// The portfolio overview (arc slice 1a). All-scope hygiene throughout:
|
|
254
|
-
// admission binds inside every bounded SQL read, and every unlimited
|
|
255
|
-
// feed's rows pass visible() BEFORE they render or tally — a hidden
|
|
256
|
-
// project must not leak into a row, a count, or a dollar.
|
|
257
|
-
const sinceIso = new Date(now.getTime() - 24 * 3_600_000).toISOString();
|
|
258
|
-
const decisions = store
|
|
259
|
-
.listDecisionsScoped(null)
|
|
260
|
-
.filter(one => visible(one.repo ?? null))
|
|
261
|
-
.slice(0, 10);
|
|
262
|
-
const approvals = store.scopesAwaitingApproval(null, 10, admission).filter(one => visible(one.repo ?? null));
|
|
263
|
-
const requeueables = store.listRequeueablesScoped(null, now, 10, admission).filter(one => visible(one.repo ?? null));
|
|
264
|
-
const cancelledBlockers = store
|
|
265
|
-
.listCancelledBlockersScoped(null, 10, admission)
|
|
266
|
-
.filter(one => visible(one.repo ?? null) && visible(one.blockerRepo ?? null));
|
|
267
|
-
const runs24 = store.runsSinceScoped(sinceIso, null).filter(run => visible(taskRepoOf(run.taskRef)));
|
|
268
|
-
const live = store.liveClaims(null, now).filter(one => visible(one.repo));
|
|
269
|
-
const ledger = store
|
|
270
|
-
.portfolioLedgerScoped(null, sinceIso, 30, admission)
|
|
271
|
-
.filter(row => visible(row.repo));
|
|
272
|
-
// Gaps stay project-relative (the roll-up inbox's rule): computed for
|
|
273
|
-
// the OPEN project only — there is no scope-safe road to another
|
|
274
|
-
// project's /caps.
|
|
275
|
-
const gaps = project === null ? [] : computeGaps(store, project, now).filter(gap => gap.unblocks.length > 0).slice(0, 10);
|
|
276
|
-
const csrf = who.via === "cookie" ? who.session.csrf : "";
|
|
277
|
-
let detail = html `${portfolioOverview({
|
|
278
|
-
attention, building, waiting, queued, done, saturated: snapshot.saturated,
|
|
279
|
-
decisions, approvals, requeueables, cancelledBlockers, gaps,
|
|
280
|
-
gapsProject: project, runs24, live, ledger, csrf, now,
|
|
281
|
-
})}<div class="workbench-mobile-rail">${rail}</div>`;
|
|
282
|
-
if (selected !== null) {
|
|
283
|
-
const view = taskViewData(selected, who, null);
|
|
284
|
-
detail = view === null
|
|
285
|
-
? html `<p class="workbench-mobile-back"><a href="/workbench">← all work</a></p><div class="card"><p class="meta">No such task — it may have been outside this console's view</p></div>`
|
|
286
|
-
: html `<p class="workbench-mobile-back"><a href="/workbench">← all work</a></p>${taskBody({ ...view, degraded: "pane" })}`;
|
|
287
|
-
}
|
|
288
|
-
return sendScreen(response, 200, screen("portfolio", detail, {
|
|
289
|
-
chrome: chromeFor(project, "workbench", html `<div id="wb-rail">${rail}</div><p class="meta" id="wb-rail-stamp"></p>`, "all"),
|
|
290
|
-
functional: {
|
|
291
|
-
// The decision enhancement rides ONLY the overview: a selected
|
|
292
|
-
// task's pane may carry a password ceremony, and sensitive
|
|
293
|
-
// pages gain no new scripts (commit-1 review, finding 1).
|
|
294
|
-
script: regionScript("wb-rail", "rail") +
|
|
295
|
-
(selected === null && decisions.length > 0 ? decisionAnswerScript() : ""),
|
|
296
|
-
fetches: true,
|
|
297
|
-
},
|
|
298
|
-
}));
|
|
299
|
-
}
|
|
300
242
|
async function ledgerPage(ctx) {
|
|
301
243
|
const { url, who, response, now } = ctx;
|
|
302
244
|
const admitted = admissionList();
|
|
@@ -971,11 +913,12 @@ export function createPagesHandlers(runtime) {
|
|
|
971
913
|
* switches the session's project; its Open project button posts to /projects/select, which proves it again. The
|
|
972
914
|
* name is found among the projects this person may open (projectsSelect's rule: visible here and inside the
|
|
973
915
|
* ceiling); a name it can't see answers exactly like one that doesn't exist. */
|
|
974
|
-
async function
|
|
975
|
-
const { url, who, response
|
|
916
|
+
async function projectFound(ctx, page) {
|
|
917
|
+
const { url, who, response } = ctx;
|
|
976
918
|
let name = "";
|
|
919
|
+
const tail = page === "" ? "" : `/${page}`;
|
|
977
920
|
try {
|
|
978
|
-
name = decodeURIComponent(url.pathname.slice("/projects/".length));
|
|
921
|
+
name = decodeURIComponent(url.pathname.slice("/projects/".length, url.pathname.length - tail.length));
|
|
979
922
|
}
|
|
980
923
|
catch {
|
|
981
924
|
name = "";
|
|
@@ -988,8 +931,37 @@ export function createPagesHandlers(runtime) {
|
|
|
988
931
|
const open = who.via === "cookie" ? who.session.project : null;
|
|
989
932
|
if (found === null || !(await authorizedProject(liveCeiling(), found.path))) {
|
|
990
933
|
const words = unknownProject(name === "" ? "by that name" : name);
|
|
991
|
-
|
|
934
|
+
await sendScreen(response, 404, screen("Not found", kitNotFound(url.pathname, words), { chrome: chromeFor(open, "projects") }));
|
|
935
|
+
return null;
|
|
992
936
|
}
|
|
937
|
+
return { ...found, ambiguous: named.length > 1 };
|
|
938
|
+
}
|
|
939
|
+
/** A project's flaky tests list and one row's history. Reads only; found exactly as the project's page is. */
|
|
940
|
+
async function projectFlakyHandler(ctx) {
|
|
941
|
+
const { url, who, response, now } = ctx;
|
|
942
|
+
const found = await projectFound(ctx, "flaky");
|
|
943
|
+
if (found === null)
|
|
944
|
+
return;
|
|
945
|
+
const open = who.via === "cookie" ? who.session.project : null;
|
|
946
|
+
const chrome = chromeFor(open, "projects");
|
|
947
|
+
const back = projectHref(found.name, found.path, found.ambiguous);
|
|
948
|
+
const selfHref = projectHref(found.name, found.path, found.ambiguous, "flaky");
|
|
949
|
+
const key = url.searchParams.get("row");
|
|
950
|
+
if (key !== null) {
|
|
951
|
+
const shown = flakyShow(store, found.path, key, now);
|
|
952
|
+
if (shown === null)
|
|
953
|
+
return sendScreen(response, 404, screen("Not found", kitNotFound(url.pathname, { title: "Nothing is recorded for that test in this project." }), { chrome }));
|
|
954
|
+
return sendScreen(response, 200, flakyRowPage(chrome, { name: found.name, repo: found.path, back: selfHref, row: shown.row, history: shown.history, now }));
|
|
955
|
+
}
|
|
956
|
+
const days = Number(url.searchParams.get("days") ?? FLAKY_WINDOW_DAYS);
|
|
957
|
+
return sendScreen(response, 200, flakyPage(chrome, { name: found.name, repo: found.path, back, selfHref, list: flakyList(store, found.path, now, days), now }));
|
|
958
|
+
}
|
|
959
|
+
async function projectPageHandler(ctx) {
|
|
960
|
+
const { who, response, now } = ctx;
|
|
961
|
+
const found = await projectFound(ctx, "");
|
|
962
|
+
if (found === null)
|
|
963
|
+
return;
|
|
964
|
+
const open = who.via === "cookie" ? who.session.project : null;
|
|
993
965
|
const path = found.path;
|
|
994
966
|
// The tasks that wait on this person or run, from the board's own classification, scoped to this one project.
|
|
995
967
|
const snapshot = store.boardScoped(path, now, 200, null);
|
|
@@ -1009,15 +981,61 @@ export function createPagesHandlers(runtime) {
|
|
|
1009
981
|
}
|
|
1010
982
|
}
|
|
1011
983
|
return sendScreen(response, 200, projectPage(chromeFor(open, "projects"), {
|
|
1012
|
-
name: found.name, path, selfHref: projectHref(found.name, path,
|
|
984
|
+
name: found.name, path, selfHref: projectHref(found.name, path, found.ambiguous), open: open === path, openedAt: found.openedAt,
|
|
1013
985
|
peek: peekOf(path, now), tasks, moreTasks: Math.max(0, cards.length - tasks.length),
|
|
1014
986
|
checks: cookie ? { href: `/settings/checks?repo=${encodeURIComponent(path)}`, level: CHECK_LEVEL_WORDS[projectCheckLevel(store, path).level] } : null,
|
|
1015
987
|
pullRequests: cookie ? { href: `/settings/pull-requests?repo=${encodeURIComponent(path)}`, on: publishingOf(store, path).on } : null,
|
|
1016
988
|
knowledgeHref: `/settings/knowledge?repo=${encodeURIComponent(path)}`,
|
|
989
|
+
flakyHref: projectHref(found.name, path, found.ambiguous, "flaky"),
|
|
1017
990
|
settingsHref: restricted() ? null : `/settings/project?repo=${encodeURIComponent(path)}`,
|
|
1018
991
|
activity, now,
|
|
1019
992
|
}));
|
|
1020
993
|
}
|
|
994
|
+
/** The named project's code map address, for this account; null when it isn't one they can see. */
|
|
995
|
+
async function mapProject(ctx, suffix) {
|
|
996
|
+
let name = "";
|
|
997
|
+
try {
|
|
998
|
+
name = decodeURIComponent(ctx.url.pathname.slice("/projects/".length, -suffix.length));
|
|
999
|
+
}
|
|
1000
|
+
catch {
|
|
1001
|
+
return null;
|
|
1002
|
+
}
|
|
1003
|
+
const { recent, candidates } = visibleProjects();
|
|
1004
|
+
const named = [...recent.map(one => ({ path: one.path, name: one.name })), ...candidates.map(path => ({ path, name: projectName(path) }))].filter(one => one.name === name);
|
|
1005
|
+
const asked = ctx.url.searchParams.get("path") ?? ctx.posted.get("repo");
|
|
1006
|
+
const found = asked !== null ? named.find(one => one.path === asked) ?? null : named.length === 1 ? named[0] : null;
|
|
1007
|
+
if (found === null || !(await authorizedProject(liveCeiling(), found.path)))
|
|
1008
|
+
return null;
|
|
1009
|
+
return { name: found.name, path: found.path, base: `/projects/${encodeURIComponent(found.name)}/map`, selfHref: projectHref(found.name, found.path, named.length > 1) };
|
|
1010
|
+
}
|
|
1011
|
+
async function codeMapHandler(ctx) {
|
|
1012
|
+
const { url, who, response, now } = ctx;
|
|
1013
|
+
const open = who.via === "cookie" ? who.session.project : null;
|
|
1014
|
+
const found = await mapProject(ctx, "/map");
|
|
1015
|
+
if (found === null)
|
|
1016
|
+
return sendScreen(response, 404, screen("Not found", kitNotFound(url.pathname, unknownProject("by that name")), { chrome: chromeFor(open, "projects") }));
|
|
1017
|
+
const asked = url.searchParams.get("v");
|
|
1018
|
+
const pinned = asked !== null && /^[1-9][0-9]{0,14}$/.test(asked) ? Number(asked) : undefined;
|
|
1019
|
+
const status = codeMapStatus(store, found.path, now, iso => new Date(iso).toISOString().slice(11, 16));
|
|
1020
|
+
const opened = status.state === "off" || status.state === "not-available" ? null : openMap(store, found.path, pinned);
|
|
1021
|
+
const held = opened !== null && opened.ok ? opened : null;
|
|
1022
|
+
const repoed = opened !== null && !opened.ok && pinned !== undefined ? openMap(store, found.path) : null;
|
|
1023
|
+
const map = held ?? (repoed !== null && repoed.ok ? repoed : null);
|
|
1024
|
+
const canRefresh = who.via === "cookie" && who.authority !== "local-owner" && store.accountOf(who.name)?.role === "approver" && store.accountCanAccess(who.name, found.path);
|
|
1025
|
+
return sendScreen(response, 200, codeMapPage(chromeFor(open, "projects"), {
|
|
1026
|
+
name: found.name, repo: found.path, base: found.base, selfHref: found.selfHref, status, map: map?.map ?? null, versionId: map?.version.id ?? null, remoteUrl: store.projectRemote(found.path),
|
|
1027
|
+
q: url.searchParams.get("q"), area: url.searchParams.get("area"), file: url.searchParams.get("file"), canRefresh, now, report: codeMapReportBlock(codeMapReport(store, found.path, now)),
|
|
1028
|
+
said: url.searchParams.get("said"), problem: repoed !== null ? "The map changed. Showing the current one." : url.searchParams.get("problem"),
|
|
1029
|
+
}));
|
|
1030
|
+
}
|
|
1031
|
+
async function codeMapRefreshHandler(ctx) {
|
|
1032
|
+
const { response, who, now } = ctx;
|
|
1033
|
+
const found = await mapProject(ctx, "/map/refresh");
|
|
1034
|
+
if (found === null || !store.accountCanAccess(who.name, found.path))
|
|
1035
|
+
return refuse(response, who, 403, "That project is outside your access.", "/projects");
|
|
1036
|
+
const said = requestRefresh(store, found.path, who.name, now);
|
|
1037
|
+
return redirect(response, `${found.base}?said=${encodeURIComponent(said === "queued" ? "Queued. Main is checked on the next pass." : "Already queued.")}`);
|
|
1038
|
+
}
|
|
1021
1039
|
async function instanceEdge(ctx) {
|
|
1022
1040
|
const { request, response, url } = ctx;
|
|
1023
1041
|
const secret = options.instanceSecret?.() ?? null;
|
|
@@ -1135,7 +1153,7 @@ export function createPagesHandlers(runtime) {
|
|
|
1135
1153
|
const taskId = url.searchParams.get("task");
|
|
1136
1154
|
const ref = taskId === null ? null : store.lookupRef(taskId.slice(0, 200));
|
|
1137
1155
|
if (taskId !== null && (ref === null || ref.repo === null || !visible(ref.repo)))
|
|
1138
|
-
return refuse(response, who, 404, "That task is not available.", "/
|
|
1156
|
+
return refuse(response, who, 404, "That task is not available.", "/tasks");
|
|
1139
1157
|
const shown = ref !== null ? ref.repo : project !== null && visible(project) ? project : null;
|
|
1140
1158
|
const connected = shown === null ? null : buildersFor(store, shown, now, ref?.assignedRunner ?? null).alive.length > 0;
|
|
1141
1159
|
return sendScreen(response, 200, screen("Start a builder", startBuilderHtml({
|
|
@@ -1148,10 +1166,13 @@ export function createPagesHandlers(runtime) {
|
|
|
1148
1166
|
"projects.github-list": projectsGithubList,
|
|
1149
1167
|
"projects.page": projectsIndex,
|
|
1150
1168
|
"projects.project": projectPageHandler,
|
|
1169
|
+
"projects.map": codeMapHandler,
|
|
1170
|
+
"projects.map-refresh": codeMapRefreshHandler,
|
|
1171
|
+
"projects.flaky": projectFlakyHandler,
|
|
1151
1172
|
"home": homeRedirect,
|
|
1152
1173
|
"search": searchPage,
|
|
1153
1174
|
"morning": morningAlias,
|
|
1154
|
-
"workbench": workbench,
|
|
1175
|
+
"workbench": async (ctx) => retiredRedirect("workbench", ctx.url, ctx.response),
|
|
1155
1176
|
"ledger": ledgerPage,
|
|
1156
1177
|
"activity": activityPage,
|
|
1157
1178
|
"system": systemScreen,
|
|
@@ -2,6 +2,8 @@ import { parseFeedbackImage } from "../result-review.js";
|
|
|
2
2
|
import { assignmentSummaryHtml } from "../assignment-ui.js";
|
|
3
3
|
import {} from "../assignment.js";
|
|
4
4
|
import {} from "../browser-workspace.js";
|
|
5
|
+
import { webCardHtml } from "../card-render.js";
|
|
6
|
+
import { cardOfProposal, cardPlainOf, cardsFirst, isCardProposal } from "../chat-card.js";
|
|
5
7
|
import { closedQuestionReason } from "../decision.js";
|
|
6
8
|
import { CHAT_ACTIONS, sharedActionNeedsReview, sharedActionPayload, sharedActionReviewPath } from "../chat-actions.js";
|
|
7
9
|
import { CHAT_CONTINUITY_SCRIPT } from "../chat-continuity.js";
|
|
@@ -209,7 +211,7 @@ export function chatFleetOverview(snapshot, projects, interactive) {
|
|
|
209
211
|
const briefing = "Brief me on what needs my attention, what is building, and the highest-leverage next action across every project.";
|
|
210
212
|
return html `<section class="card chat-overview" aria-label="live portfolio overview" data-card-kind="fleet-overview"><div class="chat-overview-head"><h2>${projects.length} project${projects.length === 1 ? "" : "s"}</h2>${interactive
|
|
211
213
|
? postForm("/chat", html `<button type="submit" name="message" value="${briefing}" class="quiet">Brief me</button>`, { attrs: { class: "inline" } })
|
|
212
|
-
: html `<a href="/board
|
|
214
|
+
: html `<a href="/tasks?view=board&scope=all" class="chat-overview-link">open board</a>`}</div><div class="chat-overview-stats"><a href="/tasks?view=needs-you" class="chat-overview-stat attention"><b>${needsYou}${snapshot.attentionCount?.saturated ? "+" : ""}</b><span>need you</span></a><a href="/board?scope=all" class="chat-overview-stat live"><b>${running}</b><span>building</span></a><a href="/board?scope=all&view=order" class="chat-overview-stat"><b>${queued}</b><span>queued</span></a><a href="/tasks" class="chat-overview-stat"><b>${done}</b><span>finished today</span></a></div>${rows.length === 0 ? html `<p class="chat-overview-clear">${needsYou === 0 && running === 0 ? html `<span class="dot dot-ok"></span>No tasks are waiting and no builds are running.` : html `<a href="/tasks">Open Tasks to inspect current work.</a>`}</p>` : html `<div class="chat-overview-items">${rows}</div>`}${completedWorkHtml(snapshot, projects.map(project => project.label), snapshot.assignmentStates)}${saturated ? html `<p class="meta chat-overview-note">Some items aren’t shown. Open Work to see more.</p>` : ""}</section>`;
|
|
213
215
|
}
|
|
214
216
|
/**
|
|
215
217
|
* The lead's project rail: one bounded pulse per admitted project, plus
|
|
@@ -544,6 +546,17 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
|
|
|
544
546
|
card: { id: view.id, kind: view.kind, label: "Question closed", state: view.state, body: htmlString(body), said: null, links: [], primary: null, dismissable: false, note: null },
|
|
545
547
|
};
|
|
546
548
|
}
|
|
549
|
+
// A Toolroll card: data to read, with its fixed links. It confirms nothing, so it waits on nobody.
|
|
550
|
+
if (isCardProposal(view)) {
|
|
551
|
+
const shown = cardOfProposal(view);
|
|
552
|
+
const body = shown === null ? html `<p>${cardPlainOf(view)}</p>` : webCardHtml(shown);
|
|
553
|
+
const links = shown?.buttons.map(one => ({ label: one.label, href: one.path })) ?? [];
|
|
554
|
+
const label = shown?.title ?? "Card";
|
|
555
|
+
return {
|
|
556
|
+
html: html `<article class="card proposal proposal-data" data-card-kind="data"><div class="proposal-body"><h3>${label}</h3>${body}</div>${links.length === 0 ? "" : html `<footer class="proposal-actions">${joinHtml(links.map(one => html `<a class="button-link" href="${one.href}">${one.label}</a>`))}</footer>`}</article>`,
|
|
557
|
+
card: { id: view.id, kind: view.kind, label, state: "pending", body: htmlString(body), said: null, links, primary: null, dismissable: false, note: null, data: true },
|
|
558
|
+
};
|
|
559
|
+
}
|
|
547
560
|
const presentations = {
|
|
548
561
|
task: { label: payload["report"] === true ? "Scout investigation" : "New task", action: "file task", icon: html `<path d="M12 5v14"/><path d="M5 12h14"/>` },
|
|
549
562
|
next: { label: "Queue priority", action: "move to front", icon: html `<path d="M12 19V5"/><path d="m5 12 7-7 7 7"/>` },
|
|
@@ -560,7 +573,7 @@ export function proposalCardParts(view, inert, decision, returnTo = null) {
|
|
|
560
573
|
icon: html `<path d="M14.7 6.3a4 4 0 0 0-5 5L4 17l3 3 5.7-5.7a4 4 0 0 0 5-5l-2.4 2.4-3-3z"/>`,
|
|
561
574
|
},
|
|
562
575
|
agents: { label: "Agents change", action: "change agents", icon: html `<circle cx="12" cy="12" r="3"/><path d="M12 2v3"/><path d="M12 19v3"/><path d="m4.9 4.9 2.2 2.2"/><path d="m16.9 16.9 2.2 2.2"/><path d="M2 12h3"/><path d="M19 12h3"/><path d="m4.9 19.1 2.2-2.2"/><path d="m16.9 7.1 2.2-2.2"/>` },
|
|
563
|
-
review: { label: text("operation") === "revise" ? "Changes to make" : "Note for later", action: text("operation") === "revise" ? "
|
|
576
|
+
review: { label: text("operation") === "revise" ? "Changes to make" : "Note for later", action: text("operation") === "revise" ? "Send back" : "Save for later", icon: html `<path d="M4 5h16v12H8l-4 3z"/>` },
|
|
564
577
|
control: { label: "Open control", action: "Open", icon: html `<path d="M5 12h14m-6-6 6 6-6 6"/>` },
|
|
565
578
|
action: { label: text("title") || "Review action", action: sharedActionPayload(payload) ? CHAT_ACTIONS[sharedActionPayload(payload).operation].label : "Unavailable", icon: html `<path d="M5 12h14m-6-6 6 6-6 6"/>` },
|
|
566
579
|
task_action: { label: payload["operation"] === "stop" ? "Stop task?" : payload["operation"] === "resume" ? "Resume task?" : "Task update", action: isChatTaskAction(payload["operation"]) ? CHAT_TASK_ACTIONS[payload["operation"]].label : "Unavailable", icon: html `<path d="M5 12h14m-6-6 6 6-6 6"/>` },
|
|
@@ -855,7 +868,7 @@ export function leadBrowserMessages(rows, back, target = { task: null, project:
|
|
|
855
868
|
activity: message.activity, createdAt: message.createdAt,
|
|
856
869
|
...(() => {
|
|
857
870
|
// Under the lead's message only, as the server-rendered thread does: the person's message shares the turn.
|
|
858
|
-
const parts = message.turn === null || message.role === 'operator' ? [] : rows.proposals.filter(one => one.turn === message.turn)
|
|
871
|
+
const parts = message.turn === null || message.role === 'operator' ? [] : cardsFirst(rows.proposals.filter(one => one.turn === message.turn))
|
|
859
872
|
.map(one => leadProposalCardParts(one, rows.pending !== null, rows.decisions.get(typeof one.payload['decision'] === 'number' ? one.payload['decision'] : -1) ?? null, back));
|
|
860
873
|
return { cardsHtml: htmlString(joinHtml(parts.map(one => one.html))), cards: parts.map(one => one.card) };
|
|
861
874
|
})(),
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type MapStatus } from "../code-map-read.js";
|
|
2
|
+
import { type CodeMapReport } from "../code-map-report.js";
|
|
3
|
+
import type { CodeMap } from "../code-map-shape.js";
|
|
4
|
+
import { type Html } from "../html.js";
|
|
5
|
+
import { type Chrome, type Screen } from "./chrome.js";
|
|
6
|
+
export declare const LIMITS_WORDS: {
|
|
7
|
+
readonly system: "Limits enforced by the system.";
|
|
8
|
+
readonly checked: "Limits checked by Toolroll every 250 ms.";
|
|
9
|
+
};
|
|
10
|
+
export type CodeMapPage = {
|
|
11
|
+
name: string;
|
|
12
|
+
repo: string;
|
|
13
|
+
base: string;
|
|
14
|
+
selfHref: string;
|
|
15
|
+
status: MapStatus;
|
|
16
|
+
map: CodeMap | null;
|
|
17
|
+
versionId: number | null;
|
|
18
|
+
remoteUrl: string | null;
|
|
19
|
+
q: string | null;
|
|
20
|
+
area: string | null;
|
|
21
|
+
file: string | null;
|
|
22
|
+
canRefresh: boolean;
|
|
23
|
+
said: string | null;
|
|
24
|
+
problem: string | null;
|
|
25
|
+
now: Date;
|
|
26
|
+
report: Html | null;
|
|
27
|
+
};
|
|
28
|
+
/** "Builds with and without the map": read by people; nothing switches on it. */
|
|
29
|
+
export declare function codeMapReportBlock(report: CodeMapReport): Html | null;
|
|
30
|
+
export declare function codeMapPage(chrome: Chrome, data: CodeMapPage): Screen;
|