toolroll 0.9.85 → 0.9.87
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 +83 -0
- package/dist/agent-fence.d.ts +6 -2
- package/dist/agent-fence.js +3 -2
- package/dist/agents-board.d.ts +12 -0
- package/dist/agents-board.js +26 -0
- package/dist/approval-rules-ui.d.ts +4 -1
- package/dist/approval-rules-ui.js +31 -15
- package/dist/assignment-ui.js +1 -1
- package/dist/assignment.js +4 -3
- package/dist/backup-ui.d.ts +5 -4
- package/dist/backup-ui.js +65 -32
- package/dist/browser/terminal.css +1 -1
- package/dist/browser/terminal.js +57 -54
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +35 -68
- package/dist/browser-crew.js +1 -1
- package/dist/browser-workspace.d.ts +363 -177
- package/dist/browser-workspace.js +10 -9
- package/dist/chat-actions.d.ts +11 -1
- package/dist/chat-actions.js +53 -1
- package/dist/chat-channel.js +1 -0
- package/dist/chat-controls.d.ts +1 -1
- package/dist/chat-controls.js +1 -1
- package/dist/chat-quiet.js +3 -2
- package/dist/chat-settings-page.d.ts +29 -9
- package/dist/chat-settings-page.js +59 -28
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +67 -58
- 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/cli.d.ts +43 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +4 -1
- package/dist/contracts/console-api.js +4 -1
- 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 +21 -0
- package/dist/contracts/lead-tools.js +2 -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/coordinator.js +1 -0
- package/dist/credentials-ui.d.ts +5 -3
- package/dist/credentials-ui.js +58 -31
- package/dist/deploy-gate.js +4 -0
- package/dist/design/kit.css +685 -0
- package/dist/design/kit.d.ts +208 -0
- package/dist/design/kit.js +177 -0
- package/dist/design/tokens.css +19 -1
- package/dist/discord-settings.js +8 -4
- package/dist/dispatch.d.ts +3 -0
- package/dist/dispatch.js +4 -1
- package/dist/export-ui.d.ts +6 -4
- package/dist/export-ui.js +18 -8
- package/dist/flow-browser.d.ts +103 -0
- package/dist/flow-browser.js +204 -0
- package/dist/flow-changed.d.ts +14 -0
- package/dist/flow-changed.js +53 -0
- 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-gallery-ui.d.ts +41 -4
- package/dist/flow-gallery-ui.js +95 -63
- 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-starters-ui.d.ts +3 -9
- package/dist/flow-starters-ui.js +12 -23
- package/dist/flow-steps.d.ts +9 -1
- package/dist/flow-steps.js +55 -5
- package/dist/flow-triggers.js +55 -0
- package/dist/flow-unattended-words.d.ts +20 -0
- package/dist/flow-unattended-words.js +24 -0
- package/dist/flow-unattended.d.ts +9 -5
- package/dist/flow-unattended.js +45 -23
- package/dist/flows-cli.d.ts +19 -0
- package/dist/flows-cli.js +54 -5
- package/dist/flows-ui.d.ts +12 -6
- package/dist/flows-ui.js +152 -32
- package/dist/flows.js +5 -2
- package/dist/integrations-ui.d.ts +6 -4
- package/dist/integrations-ui.js +34 -40
- package/dist/integrations.d.ts +2 -2
- package/dist/integrations.js +4 -4
- package/dist/kits-ui.d.ts +8 -10
- package/dist/kits-ui.js +29 -33
- package/dist/kits.js +1 -1
- package/dist/knowledge-ui.js +76 -19
- package/dist/lead-tools.js +35 -6
- package/dist/lead-voice.js +16 -5
- package/dist/ledger-view.d.ts +2 -0
- package/dist/ledger-view.js +41 -38
- package/dist/models-ui.d.ts +8 -3
- package/dist/models-ui.js +82 -59
- package/dist/monitoring-ui.d.ts +4 -2
- package/dist/monitoring-ui.js +44 -13
- package/dist/needs-you.d.ts +1 -1
- package/dist/needs-you.js +8 -0
- package/dist/operate-remote-arguments.js +1 -0
- package/dist/operate-remote.js +1 -1
- package/dist/operate.d.ts +1 -1
- package/dist/operate.js +249 -6
- package/dist/passwordless.d.ts +1 -1
- package/dist/passwordless.js +1 -1
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +25 -23
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +27 -21
- package/dist/policy.d.ts +3 -0
- package/dist/policy.js +14 -1
- package/dist/project-delete-ui.d.ts +4 -2
- package/dist/project-delete-ui.js +135 -126
- package/dist/project-delete.js +7 -3
- package/dist/project-knowledge.d.ts +10 -10
- package/dist/publish.d.ts +11 -0
- package/dist/publish.js +16 -3
- package/dist/pull-request-flow.js +35 -4
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +39 -28
- package/dist/recipe-ui.d.ts +4 -2
- package/dist/recipe-ui.js +14 -4
- package/dist/release-adapters.d.ts +3 -0
- package/dist/release-adapters.js +22 -2
- package/dist/release-cli.js +81 -9
- package/dist/release.d.ts +21 -0
- package/dist/release.js +40 -4
- package/dist/releases.js +1 -1
- package/dist/request-budget-ui.d.ts +4 -2
- package/dist/request-budget-ui.js +30 -19
- package/dist/result-follow-ups.js +4 -0
- package/dist/retention-ui.d.ts +4 -3
- package/dist/retention-ui.js +15 -12
- package/dist/scope.d.ts +8 -0
- package/dist/scope.js +13 -0
- package/dist/serve.d.ts +0 -1
- package/dist/serve.js +51 -7
- package/dist/server/agents-computers.d.ts +9 -0
- package/dist/server/agents-computers.js +67 -0
- package/dist/server/chat.js +20 -18
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +19 -9
- package/dist/server/console.css +31 -2
- package/dist/server/flows.js +79 -36
- package/dist/server/home.d.ts +35 -0
- package/dist/server/home.js +104 -0
- package/dist/server/http.d.ts +3 -0
- package/dist/server/http.js +5 -0
- package/dist/server/kit-standalone.d.ts +22 -0
- package/dist/server/kit-standalone.js +69 -0
- package/dist/server/kit.d.ts +405 -0
- package/dist/server/kit.js +361 -0
- package/dist/server/pages.d.ts +2 -1
- package/dist/server/pages.js +213 -174
- package/dist/server/people-tokens.js +26 -85
- package/dist/server/render-history.d.ts +51 -0
- package/dist/server/render-history.js +228 -0
- package/dist/server/render-new-task.d.ts +29 -0
- package/dist/server/render-new-task.js +96 -0
- package/dist/server/render-next.d.ts +63 -0
- package/dist/server/render-next.js +109 -0
- package/dist/server/render-pages.d.ts +35 -190
- package/dist/server/render-pages.js +154 -563
- package/dist/server/render-people.d.ts +24 -3
- package/dist/server/render-people.js +93 -72
- package/dist/server/render-projects.d.ts +103 -0
- package/dist/server/render-projects.js +247 -0
- package/dist/server/render-results.d.ts +5 -24
- package/dist/server/render-results.js +48 -185
- package/dist/server/render-settings.d.ts +5 -74
- package/dist/server/render-settings.js +102 -493
- package/dist/server/render-tasks.d.ts +43 -5
- package/dist/server/render-tasks.js +131 -91
- package/dist/server/route-table.js +31 -5
- package/dist/server/settings.js +276 -255
- package/dist/server/task-views.d.ts +8 -0
- package/dist/server/task-views.js +90 -2
- package/dist/server/tasks.d.ts +18 -0
- package/dist/server/tasks.js +148 -41
- package/dist/server/terminals.js +1 -1
- package/dist/server/welcome.d.ts +22 -0
- package/dist/server/welcome.js +69 -0
- package/dist/settings-groups-ui.d.ts +279 -0
- package/dist/settings-groups-ui.js +615 -0
- package/dist/settings-kit.d.ts +42 -6
- package/dist/settings-kit.js +69 -7
- package/dist/settings-proofs.js +1 -0
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills-ui.js +59 -7
- package/dist/slack-settings.js +12 -5
- package/dist/spend-ui.d.ts +45 -26
- package/dist/spend-ui.js +124 -56
- package/dist/sso-ui.d.ts +5 -2
- package/dist/sso-ui.js +63 -31
- package/dist/storage-ui.d.ts +3 -2
- package/dist/storage-ui.js +56 -50
- package/dist/store.d.ts +182 -5
- package/dist/store.js +512 -38
- package/dist/surface.js +23 -3
- 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/teams-settings.d.ts +2 -1
- package/dist/teams-settings.js +24 -41
- package/dist/telegram-settings.d.ts +0 -4
- package/dist/telegram-settings.js +52 -25
- package/dist/tidy.d.ts +130 -0
- package/dist/tidy.js +503 -0
- package/dist/toolroll-update-ui.d.ts +8 -4
- package/dist/toolroll-update-ui.js +55 -45
- package/dist/tools-ui.d.ts +5 -3
- package/dist/tools-ui.js +107 -42
- package/dist/work-index.d.ts +24 -0
- package/dist/work-index.js +56 -30
- package/dist/workspace-ui.d.ts +1 -1
- package/dist/workspace-ui.js +8 -4
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ import type { WritesMode } from "./writes-mode.js";
|
|
|
4
4
|
import { type CheckAccess } from "./check-access.js";
|
|
5
5
|
import { type Html } from "./html.js";
|
|
6
6
|
import { type ProjectHoldings } from "./project-delete.js";
|
|
7
|
+
/** Check access: a wider level's warning shows only under the option chosen. Everything else on the page is kit markup. */
|
|
7
8
|
export declare const PROJECT_DELETE_CSS: string;
|
|
8
9
|
export type ProjectSettingsView = {
|
|
9
10
|
remoteUrl?: string | null;
|
|
@@ -68,8 +69,9 @@ export type ProjectSettingsView = {
|
|
|
68
69
|
signInsUnavailable?: string | null;
|
|
69
70
|
};
|
|
70
71
|
};
|
|
71
|
-
/** The project's page: its path, what Toolroll holds for it, and (for an instance operator)
|
|
72
|
-
|
|
72
|
+
/** The project's page: its name and path, what Toolroll holds for it, its build settings, and (for an instance operator)
|
|
73
|
+
* Remove and Delete. */
|
|
74
|
+
export declare function projectSettingsHtml(view: ProjectSettingsView, said: {
|
|
73
75
|
said?: string | null;
|
|
74
76
|
problem?: string | null;
|
|
75
77
|
}): Html;
|
|
@@ -6,55 +6,34 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { sandboxRuntimeWords } from "./sandbox-runtime.js";
|
|
8
8
|
import { CHECK_ACCESS_WARNINGS, signInHostsMissing } from "./check-access.js";
|
|
9
|
-
import { html,
|
|
9
|
+
import { html, joinHtml, postForm } from "./html.js";
|
|
10
10
|
import { holdingsWords } from "./project-delete.js";
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
`.project-settings .build-access .tool-name{font:var(--so-typography-mono)}.project-settings .build-access details{margin:4px 0 0}.project-settings .build-access summary{min-height:44px;display:inline-flex;align-items:center;cursor:pointer;color:var(--so-muted);font:var(--so-typography-label)}` +
|
|
22
|
-
`.project-settings .build-access ul{margin:0 0 4px;padding-left:20px;font:var(--so-typography-body);color:var(--so-ink)}.project-settings .build-access li{margin:2px 0}.project-settings .build-access li .meta{color:var(--so-muted)}` +
|
|
23
|
-
`.project-settings .build-access a.manage-tools{display:inline-flex;align-items:center;justify-content:center;min-height:44px;margin-top:4px;padding:0 14px;white-space:nowrap;background:var(--so-paper);color:var(--so-ink);border:1px solid var(--so-input-line);border-radius:var(--so-radius-md);font:var(--so-typography-label);text-decoration:none}` +
|
|
24
|
-
// A radio or checkbox choice sits beside its words, at its natural size, not as a full-width grid row.
|
|
25
|
-
`.project-settings .build-access label.check{display:flex;align-items:center;gap:8px;min-height:44px;font:var(--so-typography-body);font-weight:500}` +
|
|
26
|
-
`.project-settings .build-access label.check input{width:auto;min-height:0;margin:0;flex:none}.project-settings .build-access form .meta{margin:-8px 0 0 26px}` +
|
|
27
|
-
// Check access: a warning shows only under the option chosen; the folds draw the console's own chevron (as a diff's).
|
|
28
|
-
`.project-settings .check-access-setting .option .warning{display:none;color:var(--so-ink)}.project-settings .check-access-setting .option:has(input:checked) .warning{display:block}` +
|
|
29
|
-
`.project-settings .check-access-setting form{margin-top:0;gap:10px}.project-settings .check-access-setting .option{display:grid;gap:2px}` +
|
|
30
|
-
`.project-settings .check-access-setting .option .meta{margin:0 0 0 26px}.project-settings .check-access-setting label.check{min-height:36px}` +
|
|
31
|
-
`.project-settings .check-access-setting details > summary{list-style:none;display:flex;align-items:center;gap:10px;min-height:40px;padding:0}` +
|
|
32
|
-
`.project-settings .check-access-setting details > summary::-webkit-details-marker{display:none}.project-settings .check-access-setting details > summary::marker{content:""}` +
|
|
33
|
-
`.project-settings .check-access-setting details > summary::before{content:"";flex:none;width:.35rem;height:.35rem;border-right:1.5px solid var(--so-muted);border-bottom:1.5px solid var(--so-muted);transform:rotate(-45deg);transition:transform .15s ease}` +
|
|
34
|
-
`.project-settings .check-access-setting details[open] > summary::before{transform:rotate(45deg) translateY(-.1rem)}` +
|
|
35
|
-
// Show hosts: a quiet fold in the line, not a card.
|
|
36
|
-
`.project-settings .check-access-setting details.hosts{border:0;background:none;padding:0;margin:0}.project-settings .check-access-setting details.hosts > summary{min-height:32px;font-size:.8125rem}` +
|
|
37
|
-
`.project-settings .check-access-setting details.hosts .tool-name{margin:0 0 4px;font-size:.75rem}` +
|
|
38
|
-
`.project-settings .check-access-setting button.primary{background:var(--so-signal);color:var(--so-on-signal);border-color:var(--so-signal)}` +
|
|
39
|
-
`.project-settings .build-access a.manage-tools:hover{background:var(--so-raised)}.project-settings .build-access a.manage-tools:focus-visible,.project-settings .build-access summary:focus-visible{outline:2px solid var(--so-ink);outline-offset:2px}`;
|
|
40
|
-
/** Where builds may write, said plainly; an approver changes it with their password. */
|
|
11
|
+
import { acts, btn, codeLine, disclose, formRow, infoLine, input, note, notice, radios, settingsSection, switchInput, textarea } from "./server/kit.js";
|
|
12
|
+
import { kitSectionForm } from "./settings-kit.js";
|
|
13
|
+
/** Check access: a wider level's warning shows only under the option chosen. Everything else on the page is kit markup. */
|
|
14
|
+
export const PROJECT_DELETE_CSS = ".k-opt [data-check-access-warning]{display:none;color:var(--so-ink)}.k-opt:has(input:checked) [data-check-access-warning]{display:block}" +
|
|
15
|
+
".k-opt:has(input:disabled){cursor:default;opacity:.6}";
|
|
16
|
+
const mono = (text) => html `<span class="k-mono">${text}</span>`;
|
|
17
|
+
const list = (items) => html `<ul class="k-note">${items.map(one => html `<li>${one}</li>`)}</ul>`;
|
|
18
|
+
const SCOPED = "Tasks scoped from now on use this; tasks already approved keep what they were approved with.";
|
|
19
|
+
const APPROVER_CHANGES = note("An approver for this project can change this.");
|
|
20
|
+
/** Where builds may write, said plainly; an approver changes it with their password (always typed: it widens). */
|
|
41
21
|
function writesHtml(view) {
|
|
42
22
|
const writes = view.writes;
|
|
43
23
|
if (writes === undefined)
|
|
44
24
|
return "";
|
|
45
25
|
const said = writes.setting === "workspace"
|
|
46
|
-
?
|
|
47
|
-
:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return html `<section class="build-access writes-setting" id="writes" aria-labelledby="writes-title"><h2 id="writes-title">Where builds write</h2>${said}<details><summary>Change</summary>${form}</details></section>`;
|
|
26
|
+
? "Builds write only their own checkout, its caches and its temp folder. Your shell profile, login items, agent settings and the repository's Git config and hooks stay as they are."
|
|
27
|
+
: "Builds may write anywhere outside Toolroll's own folders, your credentials and other projects.";
|
|
28
|
+
return settingsSection({ id: "writes", title: "Where builds write", state: writes.setting === "workspace" ? " · Only their workspace" : " · Anywhere", lead: said,
|
|
29
|
+
body: !writes.canChange ? APPROVER_CHANGES : disclose("Change", kitSectionForm({ proof: "project-writes", action: "/settings/project/writes", id: "writes-form", gate: true, hidden: { repo: view.repo },
|
|
30
|
+
body: joinHtml([
|
|
31
|
+
formRow("Builds may write", null, radios({ name: "writes", legend: "Where builds write", current: writes.setting, items: [
|
|
32
|
+
{ value: "workspace", label: "Only their workspace (recommended)", text: "The checkout and its Git data, the project's own package caches, the build's temp folder, and what its agent needs to keep its session." },
|
|
33
|
+
{ value: "anywhere", label: "Anywhere outside Toolroll's fenced folders", text: "For a project whose tools must write elsewhere in your home folder or the shared temp folder. Credentials, Toolroll's own folders and other projects stay fenced." }
|
|
34
|
+
] }), "wide"),
|
|
35
|
+
note(SCOPED),
|
|
36
|
+
]) })) });
|
|
58
37
|
}
|
|
59
38
|
/** Home folder (project-sandbox.ts): what builds and checks can read there, said once; the paths they can't, and the
|
|
60
39
|
* advanced list (macOS services), changed under Change with the approver's password. */
|
|
@@ -64,24 +43,20 @@ function sandboxHtml(view) {
|
|
|
64
43
|
return "";
|
|
65
44
|
const { readDeny, machLookup } = sandbox.setting;
|
|
66
45
|
const said = readDeny.length === 0
|
|
67
|
-
?
|
|
68
|
-
:
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<p class="meta">Tasks scoped from now on use this; tasks already approved keep what they were approved with.</p>
|
|
82
|
-
${reauth(html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label>`)}
|
|
83
|
-
<button type="submit">Save</button>`, { hidden: { repo: view.repo } });
|
|
84
|
-
return html `<section class="build-access sandbox-setting" id="sandbox" aria-labelledby="sandbox-title">${head}<details><summary>Change</summary>${form}</details></section>`;
|
|
46
|
+
? "Builds and checks can read your home folder, as in Anthropic's sandbox runtime. Toolroll's own folders stay out of reach; other tasks' work is read, never written."
|
|
47
|
+
: `Builds and checks can read your home folder except ${readDeny.length === 1 ? readDeny[0] : `${readDeny.length} paths`}.`;
|
|
48
|
+
const head = joinHtml([
|
|
49
|
+
machLookup.length === 0 ? "" : note(`${machLookup.length} macOS ${machLookup.length === 1 ? "service" : "services"} allowed`),
|
|
50
|
+
// The engine, named where the sandbox is set (sandbox-runtime.ts).
|
|
51
|
+
html `<p class="k-note" data-sandbox-engine>Builds, checks and flows run in ${sandboxRuntimeWords()}.</p>`,
|
|
52
|
+
]);
|
|
53
|
+
return settingsSection({ id: "sandbox", title: "Home folder", lead: said,
|
|
54
|
+
body: joinHtml([head, !sandbox.canChange ? APPROVER_CHANGES : disclose("Change", kitSectionForm({ proof: "project-sandbox", action: "/settings/project/sandbox", id: "sandbox-form", gate: true, hidden: { repo: view.repo },
|
|
55
|
+
body: joinHtml([
|
|
56
|
+
formRow("Paths builds can't read", "One per line: ~/… or a full path.", textarea({ id: "sandbox-read-deny", name: "read-deny", value: readDeny.join("\n"), rows: 4, attrs: { placeholder: "~/.ssh\n~/.aws", "aria-label": "Paths builds can't read" } }), "top"),
|
|
57
|
+
disclose("Advanced", formRow("macOS services builds may also use", null, textarea({ id: "sandbox-mach", name: "mach", value: machLookup.join("\n"), rows: 2, attrs: { "aria-label": "macOS services builds may also use" } }), "top")),
|
|
58
|
+
note(SCOPED),
|
|
59
|
+
]) }))]) });
|
|
85
60
|
}
|
|
86
61
|
/** Check access (check-access.ts): how far the project's checks reach, said once. Collapsed: the level, what it means
|
|
87
62
|
* when it isn't the default, and who changed it. Inside Change: a line per level, and each wider level's warning under it
|
|
@@ -99,32 +74,41 @@ function checkAccessHtml(view) {
|
|
|
99
74
|
const meaning = { sandboxed: null, "sign-ins": "Checks can read your Claude, Codex and GitHub sign-ins.", unsandboxed: "Checks run with your full access to this machine and to Toolroll." };
|
|
100
75
|
// The sign-ins never widen the network: how many hosts they'd still need, and where to add them (a change of its own).
|
|
101
76
|
const missing = setting === "sign-ins" && network !== null ? signInHostsMissing(network) : [];
|
|
102
|
-
const hosts = missing.length === 0 ? "" :
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
const
|
|
77
|
+
const hosts = missing.length === 0 ? "" : joinHtml([
|
|
78
|
+
html `<p class="k-note" data-sign-in-hosts>${missing.length} sign-in ${missing.length === 1 ? "host isn't" : "hosts aren't"} allowed yet · <a class="k-link" href="#network">Add them</a></p>`,
|
|
79
|
+
disclose("Show hosts", html `<p class="k-note k-mono">${missing.join(", ")}</p>`),
|
|
80
|
+
]);
|
|
81
|
+
const changed = access.changed === null ? "" : note(`Changed by ${access.changed.by}, ${new Date(access.changed.at).toLocaleDateString("en-GB", { day: "numeric", month: "short", timeZone: "UTC" })}`);
|
|
107
82
|
// No OS sandbox here, and unconfined running allowed: checks run without a sandbox whatever is saved, and that is
|
|
108
83
|
// what the page says first (never "sandboxed"); the saved level is named under it.
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
84
|
+
const unconfinedHere = access.unconfinedHere === true;
|
|
85
|
+
const shown = unconfinedHere ? "unsandboxed" : setting;
|
|
86
|
+
const head = unconfinedHere
|
|
87
|
+
? joinHtml([saved === "unsandboxed" ? "" : html `<p class="k-note" data-saved-level>Saved as ${names[saved]}.</p>`,
|
|
88
|
+
html `<p class="k-note" data-unconfined-here>This machine can't sandbox checks, so they run without a sandbox whatever is chosen here.</p>`, changed])
|
|
89
|
+
: joinHtml([setting !== saved ? html `<p class="k-note" data-sign-ins-unavailable>Saved with model and GitHub sign-ins, but checks run sandboxed: sign-ins can't be shared with checks safely yet.</p>` : "", hosts, changed]);
|
|
90
|
+
const section = (body) => settingsSection({ id: "check-access", title: "Check access", state: ` · ${names[shown]}`,
|
|
91
|
+
lead: unconfinedHere ? undefined : meaning[setting] ?? undefined, body });
|
|
112
92
|
if (!access.canNarrow && !access.canWiden)
|
|
113
|
-
return
|
|
93
|
+
return section(head);
|
|
114
94
|
const option = (value, detail, warning) => {
|
|
115
95
|
const allowed = (value === setting || access.canWiden || value === "sandboxed") && !(value === "sign-ins" && unavailable !== null);
|
|
116
|
-
return html `<
|
|
96
|
+
return html `<label class="k-opt"><input type="radio" name="level" value="${value}"${setting === value ? html ` checked` : ""}${allowed ? "" : html ` disabled`}><span><b>${names[value]}</b><span>${detail}</span>\
|
|
97
|
+
${warning === null ? "" : html `<span data-check-access-warning="${value}">${warning}</span>`}</span></label>`;
|
|
117
98
|
};
|
|
118
99
|
const open = network?.mode === "open";
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
100
|
+
const choices = html `<fieldset class="k-radios"><legend class="k-sr">Check access</legend>\
|
|
101
|
+
${option("sandboxed", "Reads your home folder, as in Anthropic's sandbox runtime, except the folders this project hides (Home folder, above). Never Toolroll's folders or other tasks' work.", null)}\
|
|
102
|
+
${unavailable !== null ? option("sign-ins", unavailable, null) : option("sign-ins", `Also reads the files your Claude, Codex and GitHub sign-ins are kept in, on this project's network (${open ? "any host" : network === null || network.hosts.length === 0 ? "registries only" : `registries and ${network.hosts.length} ${network.hosts.length === 1 ? "host" : "hosts"}`}).`, CHECK_ACCESS_WARNINGS["sign-ins"])}\
|
|
103
|
+
${option("unsandboxed", "For projects whose checks create sandboxes themselves, such as Toolroll's own.", CHECK_ACCESS_WARNINGS.unsandboxed)}</fieldset>`;
|
|
104
|
+
const confirmOpen = open && unavailable === null
|
|
105
|
+
? html `<label class="k-opt"><input type="checkbox" name="confirm-open" value="yes"><span><b>The network is open</b><span>With sign-ins, checks could send them to any host.</span></span></label>`
|
|
106
|
+
: "";
|
|
107
|
+
// A form that offers a wider level asks for an instance operator's password, typed now (the server decides).
|
|
108
|
+
const form = kitSectionForm({ proof: access.canWiden ? "project-check-access-widen" : "project-check-access", action: "/settings/project/check-access", id: "check-access-form", gate: true, hidden: { repo: view.repo },
|
|
109
|
+
body: joinHtml([formRow("How far checks reach", null, choices, "wide"), confirmOpen,
|
|
110
|
+
note(`${access.canWiden ? "" : "Only an instance operator can widen this. "}Flow script steps always run sandboxed. Tasks already approved keep their level.`)]) });
|
|
111
|
+
return section(joinHtml([head, disclose("Change", form)]));
|
|
128
112
|
}
|
|
129
113
|
/** Network: what the project's builds may reach, said plainly; an approver changes it with their password. */
|
|
130
114
|
function networkHtml(view) {
|
|
@@ -133,22 +117,20 @@ function networkHtml(view) {
|
|
|
133
117
|
return "";
|
|
134
118
|
const { setting } = network;
|
|
135
119
|
const reach = setting.mode === "open"
|
|
136
|
-
? html
|
|
137
|
-
: html
|
|
120
|
+
? html `Builds may reach any host.`
|
|
121
|
+
: html `Builds reach their agent's provider and the package registries this project uses${setting.hosts.length === 0 ? "" : html `, and ${mono(setting.hosts.join(", "))}`}. Everything else is refused.`;
|
|
138
122
|
const local = setting.loopback === "any"
|
|
139
|
-
?
|
|
140
|
-
:
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
<button type="submit">Save</button>`, { hidden: { repo: view.repo } });
|
|
151
|
-
return html `<section class="build-access network-setting" id="network" aria-labelledby="network-title"><h2 id="network-title">Network</h2>${reach}${local}<details><summary>Change</summary>${form}</details></section>`;
|
|
123
|
+
? note("On a Mac, builds may reach this Mac's local services: test servers, and anything else listening here.")
|
|
124
|
+
: note("On a Mac, builds reach no local services. A project whose tests start a local server needs them on.");
|
|
125
|
+
return settingsSection({ id: "network", title: "Network", state: setting.mode === "open" ? " · Open" : "", lead: reach,
|
|
126
|
+
body: joinHtml([local, !network.canChange ? APPROVER_CHANGES : disclose("Change", kitSectionForm({ proof: "project-network", action: "/settings/project/network", id: "network-form", gate: true, hidden: { repo: view.repo },
|
|
127
|
+
body: joinHtml([
|
|
128
|
+
formRow("This Mac's local services", "Local test servers work, and so does anything else listening on this Mac (a local database, a dev proxy). Linux builds always have local services of their own.", switchInput({ id: "network-local", name: "local", label: "Allowed", on: setting.loopback === "any", attrs: { value: "any", "aria-label": "Builds may reach this Mac's local services" } }), "inline"),
|
|
129
|
+
formRow("Other hosts builds may reach", "One per line.", textarea({ id: "network-hosts", name: "hosts", value: setting.hosts.join("\n"), rows: 3,
|
|
130
|
+
attrs: { autocomplete: "off", autocapitalize: "off", spellcheck: "false", placeholder: "api.stripe.com\n*.example.com", "aria-label": "Other hosts builds may reach" } }), "top"),
|
|
131
|
+
formRow("Open", "Builds may reach any host.", switchInput({ id: "network-open", name: "mode", label: "Open", on: setting.mode === "open", attrs: { value: "open", "aria-label": "Open: builds may reach any host" } }), "inline"),
|
|
132
|
+
note("Tasks scoped from now on use this; tasks already approved keep the network they were approved with."),
|
|
133
|
+
]) }))]) });
|
|
152
134
|
}
|
|
153
135
|
/** Live view: hidden by default; showing commands takes the password, hiding them is one step. */
|
|
154
136
|
function liveViewHtml(view) {
|
|
@@ -156,63 +138,79 @@ function liveViewHtml(view) {
|
|
|
156
138
|
if (live === undefined)
|
|
157
139
|
return "";
|
|
158
140
|
const said = live.commands
|
|
159
|
-
?
|
|
160
|
-
:
|
|
161
|
-
const change = !live.canChange ?
|
|
141
|
+
? "Commands and their output show in the live view of this project's agents. Secrets are hidden where they're recognised, which isn't guaranteed."
|
|
142
|
+
: "Commands are hidden in the live view. You see which files an agent reads and edits, but not the commands it runs or their output.";
|
|
143
|
+
const change = !live.canChange ? APPROVER_CHANGES
|
|
162
144
|
: live.commands
|
|
163
|
-
? postForm("/settings/project/live-view",
|
|
164
|
-
:
|
|
165
|
-
|
|
145
|
+
? postForm("/settings/project/live-view", joinHtml([note("From the next run."), acts(btn("Hide commands", { sm: true, type: "submit" }))]), { hidden: { repo: view.repo, act: "off" } })
|
|
146
|
+
: kitSectionForm({ proof: "project-live-view", action: "/settings/project/live-view", id: "live-view-form", hidden: { repo: view.repo, act: "on", nonce: live.nonce ?? "" },
|
|
147
|
+
save: { label: "Show commands", variant: "outline" }, confirm: "Show this project's commands in the live view",
|
|
148
|
+
body: note("Output can include secrets the filter misses. Turn this on only where you're happy for anyone who can see this project to read it. From the next run.") });
|
|
149
|
+
return settingsSection({ id: "live-view", title: "Live view", state: html `<span data-live-view-setting="${live.commands ? "commands" : "base"}"> · ${live.commands ? "Commands shown" : "Commands hidden"}</span>`,
|
|
150
|
+
lead: said, body: change });
|
|
166
151
|
}
|
|
167
152
|
/** Build access: one sentence for what a build gets, the detail on demand, and the way to change it. */
|
|
168
153
|
function accessHtml(view) {
|
|
169
154
|
const access = view.access;
|
|
170
155
|
if (access === undefined)
|
|
171
156
|
return "";
|
|
172
|
-
const manage =
|
|
157
|
+
const manage = acts(btn("Manage tools", { sm: true, href: `/settings/tools?repo=${encodeURIComponent(view.repo)}` }));
|
|
173
158
|
const always = [
|
|
174
159
|
"Your other projects, keys and sign-in files are out of reach.",
|
|
175
160
|
"Your own Claude and Codex hooks, plugins and MCP servers don't load.",
|
|
176
161
|
"Builds sign in with your existing Claude or Codex login.",
|
|
177
162
|
];
|
|
178
163
|
if ("problem" in access) {
|
|
179
|
-
return
|
|
164
|
+
return settingsSection({ id: "build-access", title: "Build access", body: joinHtml([notice(access.problem), disclose("Details", list(always)), manage]) });
|
|
180
165
|
}
|
|
181
166
|
const loading = access.tools.filter(one => one.loads);
|
|
182
|
-
const names = loading.map((one, at) => html `${at === 0 ? "" : ", "}
|
|
167
|
+
const names = loading.map((one, at) => html `${at === 0 ? "" : ", "}${mono(one.name)}`);
|
|
183
168
|
const outcome = loading.length === 0
|
|
184
|
-
? html
|
|
185
|
-
: html
|
|
186
|
-
const tools = access.tools.map(one => html
|
|
187
|
-
return
|
|
169
|
+
? html `Builds work in their task's folder, with no extra tools.`
|
|
170
|
+
: html `Builds work in their task's folder, with ${loading.length === 1 ? "one tool" : `${loading.length} tools`}: ${names}.`;
|
|
171
|
+
const tools = access.tools.map(one => html `${mono(one.name)} · ${one.words}`);
|
|
172
|
+
return settingsSection({ id: "build-access", title: "Build access", lead: outcome, body: joinHtml([disclose("Details", list([...always, ...tools])), manage]) });
|
|
188
173
|
}
|
|
189
174
|
/** Builds at once: one number, one Save. The worker's cap is said only when it lowers the number. */
|
|
190
175
|
function buildsHtml(view) {
|
|
191
176
|
const b = view.builds;
|
|
192
177
|
if (b === undefined)
|
|
193
178
|
return "";
|
|
194
|
-
const capped = b.capacity !== null && b.capacity < b.setting ? ` Its
|
|
179
|
+
const capped = b.capacity !== null && b.capacity < b.setting ? ` Its builder runs ${b.capacity} at once, so ${b.capacity} for now.` : "";
|
|
195
180
|
const now = `${b.building} building now.${capped}`;
|
|
196
181
|
if (!b.canChange)
|
|
197
|
-
return
|
|
198
|
-
return
|
|
182
|
+
return settingsSection({ id: "builds-at-once", title: "Builds at once", state: ` · Up to ${b.setting}`, body: note(now) });
|
|
183
|
+
return settingsSection({ id: "builds-at-once", title: "Builds at once",
|
|
184
|
+
body: postForm("/settings/project/concurrency", formRow("Builds at once", now, html `${input({ id: "builds-at-once-n", name: "concurrency", type: "number", value: String(b.setting),
|
|
185
|
+
attrs: { min: 1, max: 64, step: 1, inputmode: "numeric", "aria-label": "Builds at once" } })}${btn("Save", { sm: true, type: "submit" })}`, "inline"), { hidden: { repo: view.repo } }) });
|
|
199
186
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
export function projectSettingsHtml(view,
|
|
203
|
-
const
|
|
204
|
-
const
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
187
|
+
/** The project's page: its name and path, what Toolroll holds for it, its build settings, and (for an instance operator)
|
|
188
|
+
* Remove and Delete. */
|
|
189
|
+
export function projectSettingsHtml(view, said) {
|
|
190
|
+
const answer = said.problem ? notice(said.problem) : said.said ? infoLine(said.said) : "";
|
|
191
|
+
const project = settingsSection({ id: "project", title: view.name, lead: mono(view.repo),
|
|
192
|
+
body: joinHtml([
|
|
193
|
+
note(`Toolroll holds ${holdingsWords(view.holdings)} for this project.`),
|
|
194
|
+
formRow("Recorded remote", view.remoteUrl ? null : "None. Landed work waits for your review.", view.remoteUrl ? mono(view.remoteUrl) : html ``, view.remoteUrl ? "wide" : "inline"),
|
|
195
|
+
disclose("Change remote", joinHtml([note("An owner or instance operator can run:"), codeLine("toolroll repos add <project> --remote <url>")])),
|
|
196
|
+
]) });
|
|
197
|
+
const settings = joinHtml([buildsHtml(view), accessHtml(view), networkHtml(view), writesHtml(view), sandboxHtml(view), checkAccessHtml(view), liveViewHtml(view)]);
|
|
208
198
|
if (!view.canDelete)
|
|
209
|
-
return
|
|
210
|
-
const
|
|
211
|
-
?
|
|
212
|
-
:
|
|
199
|
+
return joinHtml([answer, project, settings, note("An instance operator can remove or delete a project.")]);
|
|
200
|
+
const deleteBody = view.running.length > 0
|
|
201
|
+
? notice(`Its work is running: ${view.running.join(", ")}. Stop it before deleting the project.`)
|
|
202
|
+
: joinHtml([note(`Removes everything Toolroll holds for ${view.name}. The repository and its own branches stay. There's no undo.`),
|
|
203
|
+
postForm("/settings/project/delete", joinHtml([
|
|
204
|
+
formRow(`Type ${view.name} to continue`, null, input({ id: "delete-name", name: "name", attrs: { autocomplete: "off", autocapitalize: "off", spellcheck: "false", required: true, "aria-label": `Type ${view.name} to continue` } })),
|
|
205
|
+
acts(btn("Continue", { sm: true, type: "submit", variant: "danger" })),
|
|
206
|
+
]), { hidden: { repo: view.repo } })]);
|
|
213
207
|
// Removing is the reversible step: off the lists and the builder, everything kept.
|
|
214
|
-
const
|
|
215
|
-
|
|
208
|
+
const leave = settingsSection({ id: "remove", title: "Remove or delete",
|
|
209
|
+
body: joinHtml([
|
|
210
|
+
formRow("Remove from Toolroll", "Its tasks and results stay saved. Add it again to bring it back.", postForm("/projects/remove", btn("Remove from Toolroll", { sm: true, type: "submit" }), { hidden: { repo: view.repo } }), "inline"),
|
|
211
|
+
disclose("Delete project", deleteBody, Boolean(said.problem)),
|
|
212
|
+
]) });
|
|
213
|
+
return joinHtml([answer, project, settings, leave]);
|
|
216
214
|
}
|
|
217
215
|
/** The second step: exactly what goes, and the password. */
|
|
218
216
|
export function projectDeleteConfirmHtml(view, problem) {
|
|
@@ -226,5 +224,16 @@ export function projectDeleteConfirmHtml(view, problem) {
|
|
|
226
224
|
...(h.subagents > 0 ? [count(h.subagents, "subagent")] : []),
|
|
227
225
|
"Its budgets, settings and knowledge",
|
|
228
226
|
];
|
|
229
|
-
return
|
|
227
|
+
return joinHtml([
|
|
228
|
+
problem === null ? "" : notice(problem),
|
|
229
|
+
settingsSection({ id: "delete", title: "This removes",
|
|
230
|
+
body: joinHtml([
|
|
231
|
+
list(items),
|
|
232
|
+
note(html `The repository at ${mono(view.repo)} and its own branches stay. The ledger keeps its history. There's no undo.`),
|
|
233
|
+
// The answer is a page of its own (this page again with a refusal, or the project page), so it posts as a navigation.
|
|
234
|
+
kitSectionForm({ proof: "project-delete", action: "/settings/project/delete", id: "delete-form", native: true, hidden: { repo: view.repo, name: view.name, step: "delete" },
|
|
235
|
+
save: { label: "Delete project", variant: "danger" }, confirm: `Delete ${view.name}`,
|
|
236
|
+
body: acts(btn("Cancel", { sm: true, variant: "ghost", href: `/settings/project?repo=${encodeURIComponent(view.repo)}` })) }),
|
|
237
|
+
]) }),
|
|
238
|
+
]);
|
|
230
239
|
}
|
package/dist/project-delete.js
CHANGED
|
@@ -86,7 +86,9 @@ export function projectRunning(store, repo, now) {
|
|
|
86
86
|
const chats = count("SELECT COUNT(*) AS n FROM mate_turn WHERE state IN ('queued','running') AND id IN (SELECT value FROM json_each(?))", json(d.turns));
|
|
87
87
|
const steps = count("SELECT COUNT(*) AS n FROM flow_step_run WHERE state = 'running' AND card IN (SELECT value FROM json_each(?))", json(d.cards));
|
|
88
88
|
const calls = count("SELECT COUNT(*) AS n FROM subagent_call WHERE state = 'running' AND subagent IN (SELECT value FROM json_each(?))", json(d.subagents));
|
|
89
|
-
const merges = count(`SELECT COUNT(*) AS n FROM merge_intent WHERE state IN ('claimed','firing') AND publication IN (SELECT id FROM publication WHERE task_ref IN (SELECT value FROM json_each(?)))`, json(d.refs))
|
|
89
|
+
const merges = count(`SELECT COUNT(*) AS n FROM merge_intent WHERE state IN ('claimed','firing') AND publication IN (SELECT id FROM publication WHERE task_ref IN (SELECT value FROM json_each(?)))`, json(d.refs))
|
|
90
|
+
// A person's merge between its last check and GitHub's answer (v145); a row older than 15 minutes is a crash.
|
|
91
|
+
+ count("SELECT COUNT(*) AS n FROM merge_in_flight WHERE at > ? AND task_ref IN (SELECT value FROM json_each(?))", new Date(now.getTime() - 15 * 60_000).toISOString(), json(d.refs));
|
|
90
92
|
const checkouts = count("SELECT COUNT(*) AS n FROM worktree WHERE repo = ? AND runner IS NOT NULL AND released_at IS NULL", repo);
|
|
91
93
|
return [
|
|
92
94
|
...(building > 0 ? [`${plural(building, "task")} ${building === 1 ? "is" : "are"} being built`] : []),
|
|
@@ -267,6 +269,8 @@ function deleteRows(store, repo, d, now) {
|
|
|
267
269
|
del("skill_test", `${IN("task_ref")} OR ${IN("source_run")}`, T, R);
|
|
268
270
|
del("merge_blocker", `${IN("publication")} OR ${IN("task_id")}`, publications, K);
|
|
269
271
|
del("merge_intent", IN("publication"), publications);
|
|
272
|
+
del("merge_in_flight", IN("publication"), publications);
|
|
273
|
+
del("deploy_in_flight", IN("task_ref"), T);
|
|
270
274
|
del("publication", IN("id"), publications);
|
|
271
275
|
del("artifact", IN("id"), A);
|
|
272
276
|
// Tasks and their versions.
|
|
@@ -282,7 +286,7 @@ function deleteRows(store, repo, d, now) {
|
|
|
282
286
|
del("workflow_preview", "repo = ?", repo);
|
|
283
287
|
del("coding_handoff_scope", `id IN (SELECT id FROM coding_handoff WHERE json_extract(payload, '$.repo') = ?)`, repo);
|
|
284
288
|
del("coding_handoff", "json_extract(payload, '$.repo') = ?", repo);
|
|
285
|
-
for (const table of ["task_act", "task_replacement"])
|
|
289
|
+
for (const table of ["task_act", "task_replacement", "task_archive"])
|
|
286
290
|
del(table, IN("task_ref"), T);
|
|
287
291
|
del("task_ref", IN("id"), T);
|
|
288
292
|
del("task_scope", IN("task_id"), K);
|
|
@@ -293,7 +297,7 @@ function deleteRows(store, repo, d, now) {
|
|
|
293
297
|
del("flow_trigger", IN("flow"), F);
|
|
294
298
|
del("flow_unattended_run", `grant_id IN (SELECT id FROM flow_unattended_grant WHERE ${IN("flow")})`, F);
|
|
295
299
|
del("flow_unattended_grant", IN("flow"), F);
|
|
296
|
-
for (const table of ["flow_card_watcher", "flow_comment", "flow_event", "flow_mail", "flow_step_run"])
|
|
300
|
+
for (const table of ["flow_card_watcher", "flow_comment", "flow_event", "flow_mail", "flow_step_run", "flow_step_task"])
|
|
297
301
|
del(table, IN("card"), C);
|
|
298
302
|
del("subagent_question", IN("id"), questions);
|
|
299
303
|
del("subagent_call", `${IN("subagent")} OR ${IN("card")}`, M, C);
|
|
@@ -22,19 +22,19 @@ export type KnowledgeView = ReturnType<typeof knowledgeView>;
|
|
|
22
22
|
export declare function knowledgeVersion(store: Store, repo: string, actor: string, revision: number): Knowledge;
|
|
23
23
|
/** Chat reads only an admitted project. Keep the full source in the project UI. */
|
|
24
24
|
export declare function conversationKnowledge(store: Store, repo: string, actor: string, reference?: string, decision?: number): {
|
|
25
|
-
|
|
26
|
-
content?: never;
|
|
25
|
+
history?: never;
|
|
27
26
|
revision: number;
|
|
28
27
|
stale: boolean;
|
|
29
28
|
notice: string;
|
|
30
29
|
decision?: never;
|
|
31
|
-
|
|
30
|
+
title?: never;
|
|
31
|
+
content?: never;
|
|
32
32
|
instructions?: never;
|
|
33
33
|
references?: never;
|
|
34
34
|
decisions?: never;
|
|
35
35
|
} | {
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
revision?: never;
|
|
37
|
+
history?: never;
|
|
38
38
|
stale?: never;
|
|
39
39
|
decision: {
|
|
40
40
|
repo: string;
|
|
@@ -51,27 +51,27 @@ export declare function conversationKnowledge(store: Store, repo: string, actor:
|
|
|
51
51
|
id: number;
|
|
52
52
|
};
|
|
53
53
|
notice: string;
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
title?: never;
|
|
55
|
+
content?: never;
|
|
56
56
|
instructions?: never;
|
|
57
57
|
references?: never;
|
|
58
58
|
decisions?: never;
|
|
59
59
|
} | {
|
|
60
|
+
history?: never;
|
|
60
61
|
stale?: never;
|
|
61
62
|
decision?: never;
|
|
62
63
|
revision: number;
|
|
63
64
|
title: string;
|
|
64
65
|
content: string;
|
|
65
66
|
notice: string;
|
|
66
|
-
history?: never;
|
|
67
67
|
instructions?: never;
|
|
68
68
|
references?: never;
|
|
69
69
|
decisions?: never;
|
|
70
70
|
} | {
|
|
71
|
-
title?: never;
|
|
72
|
-
content?: never;
|
|
73
71
|
stale?: never;
|
|
74
72
|
decision?: never;
|
|
73
|
+
title?: never;
|
|
74
|
+
content?: never;
|
|
75
75
|
revision: number;
|
|
76
76
|
history: {
|
|
77
77
|
revision: number;
|
package/dist/publish.d.ts
CHANGED
|
@@ -131,3 +131,14 @@ export declare function sweepMerges(store: Store, options: {
|
|
|
131
131
|
clock?: () => Date;
|
|
132
132
|
exec?: PublishExec;
|
|
133
133
|
}): Promise<MergeReport>;
|
|
134
|
+
/** A conditional settle (store.settleObservation) and, only when it won, that head's checks' own timing (v146 ci_check),
|
|
135
|
+
* in one transaction: a stale, losing answer neither settles nor records a check. */
|
|
136
|
+
export declare function settleWithChecks(store: Store, observation: {
|
|
137
|
+
githubRepo: string;
|
|
138
|
+
prNumber: number;
|
|
139
|
+
headSha: string;
|
|
140
|
+
state: "passing" | "failing" | "running" | "none";
|
|
141
|
+
generation: number;
|
|
142
|
+
}, rollup: unknown, now: Date): {
|
|
143
|
+
won: boolean;
|
|
144
|
+
};
|
package/dist/publish.js
CHANGED
|
@@ -26,7 +26,7 @@ import { tmpdir } from "node:os";
|
|
|
26
26
|
import { join } from "node:path";
|
|
27
27
|
import { run as execRun } from "./exec.js";
|
|
28
28
|
import { summarizeChecks } from "./pulls.js";
|
|
29
|
-
import { releasePublication } from "./store.js";
|
|
29
|
+
import { parseCiRollup, recordCiChecks, releasePublication } from "./store.js";
|
|
30
30
|
import { headWithin } from "./names.js";
|
|
31
31
|
export const BODY_TEMPLATE_VERSION = 2;
|
|
32
32
|
/** After this many failed attempts a publication stops retrying and pages instead. */
|
|
@@ -340,6 +340,8 @@ export async function observeChecks(store, options) {
|
|
|
340
340
|
if (!settled.won)
|
|
341
341
|
return;
|
|
342
342
|
won = true;
|
|
343
|
+
// The checks' own timing (v146), only with the observation that won.
|
|
344
|
+
recordCiChecks(store, publication.githubRepo, publication.prNumber, headOid, parseCiRollup(payload.statusCheckRollup), clock());
|
|
343
345
|
store.recordPublicationRemoteState(publication.id, remoteState, clock());
|
|
344
346
|
report.resolved += store.resolveCiEpisodes(publication.githubRepo, publication.prNumber, null, clock());
|
|
345
347
|
// A closed PR moots its merge blocker and supersedes its intent —
|
|
@@ -361,7 +363,8 @@ export async function observeChecks(store, options) {
|
|
|
361
363
|
continue;
|
|
362
364
|
}
|
|
363
365
|
const state = summarizeChecks(payload.statusCheckRollup);
|
|
364
|
-
|
|
366
|
+
// Settled and, when it won, the checks' own timing recorded (v146) in one transaction: a losing generation writes nothing.
|
|
367
|
+
const settled = settleWithChecks(store, { githubRepo: publication.githubRepo, prNumber: publication.prNumber, headSha: headOid, state, generation }, payload.statusCheckRollup, clock());
|
|
365
368
|
if (!settled.won) {
|
|
366
369
|
// An out-of-order response: a newer observation already spoke.
|
|
367
370
|
// Nothing persists from this one (round-4 findings 24/25).
|
|
@@ -465,7 +468,7 @@ async function observeOne(store, exec, clock, githubRepo, prNumber) {
|
|
|
465
468
|
if (String(payload.state ?? "").toUpperCase() !== "OPEN")
|
|
466
469
|
return { ok: false, why: "pr-" + String(payload.state ?? "gone").toLowerCase() };
|
|
467
470
|
const state = summarizeChecks(payload.statusCheckRollup);
|
|
468
|
-
const settled = store
|
|
471
|
+
const settled = settleWithChecks(store, { githubRepo, prNumber, headSha, state, generation }, payload.statusCheckRollup, clock());
|
|
469
472
|
// A losing settle is NO observation (round-4 finding 24).
|
|
470
473
|
if (!settled.won)
|
|
471
474
|
return { ok: false, why: "stale-observation" };
|
|
@@ -707,3 +710,13 @@ export async function sweepMerges(store, options) {
|
|
|
707
710
|
}
|
|
708
711
|
return report;
|
|
709
712
|
}
|
|
713
|
+
/** A conditional settle (store.settleObservation) and, only when it won, that head's checks' own timing (v146 ci_check),
|
|
714
|
+
* in one transaction: a stale, losing answer neither settles nor records a check. */
|
|
715
|
+
export function settleWithChecks(store, observation, rollup, now) {
|
|
716
|
+
return store.transact(() => {
|
|
717
|
+
const settled = store.settleObservation(observation, now);
|
|
718
|
+
if (settled.won)
|
|
719
|
+
recordCiChecks(store, observation.githubRepo, observation.prNumber, observation.headSha, parseCiRollup(rollup), now);
|
|
720
|
+
return settled;
|
|
721
|
+
});
|
|
722
|
+
}
|