toolroll 0.9.53 → 0.9.55
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 +17 -0
- package/README.md +5 -4
- package/dist/agent-onboard.js +4 -1
- package/dist/approval-rules-ui.d.ts +3 -2
- package/dist/approval-rules-ui.js +15 -20
- package/dist/assignment-ui.d.ts +3 -2
- package/dist/assignment-ui.js +4 -11
- package/dist/backup-ui.d.ts +2 -1
- package/dist/backup-ui.js +16 -27
- package/dist/brand-mark.d.ts +3 -2
- package/dist/brand-mark.js +4 -4
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +25 -25
- package/dist/browser-shell.d.ts +2 -1
- package/dist/browser-shell.js +9 -9
- package/dist/browser-workspace.d.ts +2 -3
- package/dist/browser-workspace.js +0 -3
- package/dist/chat-ask.js +8 -8
- package/dist/chat-core.d.ts +171 -0
- package/dist/chat-core.js +288 -0
- package/dist/chat-decide.d.ts +10 -8
- package/dist/chat-decide.js +26 -21
- package/dist/chat-delivery-state.d.ts +51 -75
- package/dist/chat-delivery-state.js +140 -170
- package/dist/chat-delivery.d.ts +21 -2
- package/dist/chat-delivery.js +313 -194
- package/dist/chat-flow.d.ts +29 -1
- package/dist/chat-flow.js +24 -50
- package/dist/chat-messages.d.ts +319 -0
- package/dist/chat-messages.js +841 -0
- package/dist/chat-migration.d.ts +30 -0
- package/dist/chat-migration.js +802 -0
- package/dist/chat-polish.d.ts +5 -4
- package/dist/chat-polish.js +9 -10
- package/dist/chat-quiet.d.ts +3 -0
- package/dist/chat-quiet.js +5 -0
- package/dist/check-levels-ui.d.ts +2 -1
- package/dist/check-levels-ui.js +26 -53
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +9 -3
- package/dist/coding-shipping-ui.d.ts +2 -1
- package/dist/coding-shipping-ui.js +6 -6
- package/dist/coding-ui.d.ts +3 -1
- package/dist/coding-ui.js +30 -29
- package/dist/contracts/chat-content.d.ts +71 -2
- package/dist/contracts/chat-content.js +22 -3
- package/dist/contracts/chat-tables.d.ts +11 -4
- package/dist/contracts/chat-tables.js +165 -87
- package/dist/contracts/cli.d.ts +1 -3
- package/dist/contracts/console-api.d.ts +1 -1
- package/dist/contracts/console-api.js +6 -5
- package/dist/contracts/discord-callback.js +2 -1
- package/dist/contracts/health.d.ts +1 -3
- package/dist/contracts/slack-callback.js +3 -2
- package/dist/contracts/store-json.d.ts +1 -5
- package/dist/contracts/store-json.js +1 -4
- package/dist/contracts/teams-callback.js +2 -1
- package/dist/control-ui.d.ts +5 -4
- package/dist/control-ui.js +9 -34
- package/dist/credentials-ui.d.ts +5 -4
- package/dist/credentials-ui.js +23 -24
- package/dist/demo-chat.d.ts +3 -3
- package/dist/demo-chat.js +29 -76
- package/dist/discord-chat.js +19 -38
- package/dist/discord-settings.d.ts +3 -2
- package/dist/discord-settings.js +21 -55
- package/dist/discord.js +4 -14
- package/dist/envelope.js +10 -0
- package/dist/evidence-pack.d.ts +2 -1
- package/dist/evidence-pack.js +45 -49
- package/dist/export-ui.d.ts +7 -2
- package/dist/export-ui.js +4 -10
- package/dist/export.js +4 -4
- package/dist/flow-gallery-ui.d.ts +4 -3
- package/dist/flow-gallery-ui.js +28 -43
- package/dist/flow-starters-ui.d.ts +2 -1
- package/dist/flow-starters-ui.js +12 -15
- package/dist/flow-triggers.d.ts +1 -0
- package/dist/flow-triggers.js +16 -14
- package/dist/flows-ui.d.ts +4 -3
- package/dist/flows-ui.js +19 -30
- package/dist/flows.js +2 -1
- package/dist/guides.js +108 -3
- package/dist/html.d.ts +76 -0
- package/dist/html.js +173 -0
- package/dist/integrations-ui.d.ts +2 -1
- package/dist/integrations-ui.js +19 -23
- package/dist/kits-ui.d.ts +5 -4
- package/dist/kits-ui.js +18 -20
- package/dist/knowledge-ui.d.ts +6 -5
- package/dist/knowledge-ui.js +26 -25
- package/dist/lead-context.d.ts +11 -1
- package/dist/lead-context.js +6 -8
- package/dist/ledger-view.d.ts +2 -1
- package/dist/ledger-view.js +27 -29
- package/dist/limits-ui.d.ts +2 -1
- package/dist/limits-ui.js +7 -7
- package/dist/live-bus.d.ts +50 -8
- package/dist/live-bus.js +133 -50
- package/dist/mate-tools.js +3 -2
- package/dist/mcp-oauth.js +35 -36
- package/dist/models-ui.d.ts +2 -1
- package/dist/models-ui.js +25 -35
- package/dist/modes.d.ts +13 -1
- package/dist/modes.js +17 -1
- package/dist/monitoring-ui.d.ts +4 -3
- package/dist/monitoring-ui.js +13 -26
- package/dist/notification-rows.d.ts +30 -0
- package/dist/notification-rows.js +102 -0
- package/dist/openrouter-models.d.ts +2 -1
- package/dist/openrouter-models.js +5 -9
- package/dist/operate.js +16 -9
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +13 -13
- package/dist/plane-review.js +5 -11
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +17 -23
- package/dist/project-delete-ui.d.ts +4 -3
- package/dist/project-delete-ui.js +15 -27
- package/dist/project-delete.js +21 -26
- package/dist/pull-request-ui.d.ts +2 -1
- package/dist/pull-request-ui.js +17 -32
- package/dist/recipe-ui.d.ts +8 -7
- package/dist/recipe-ui.js +39 -32
- package/dist/release-adapters.d.ts +60 -0
- package/dist/release-adapters.js +435 -0
- package/dist/release-cli.d.ts +24 -0
- package/dist/release-cli.js +181 -0
- package/dist/release-gate.d.ts +80 -0
- package/dist/release-gate.js +150 -0
- package/dist/release-links.d.ts +29 -0
- package/dist/release-links.js +102 -0
- package/dist/release.d.ts +331 -0
- package/dist/release.js +550 -0
- package/dist/reply-shape.d.ts +2 -1
- package/dist/reply-shape.js +5 -5
- package/dist/repository-context-ui.d.ts +2 -1
- package/dist/repository-context-ui.js +2 -7
- package/dist/request-budget-ui.d.ts +2 -1
- package/dist/request-budget-ui.js +16 -16
- package/dist/result-review.d.ts +2 -3
- package/dist/result-review.js +3 -9
- package/dist/result-shots.js +2 -2
- package/dist/retention-ui.d.ts +7 -1
- package/dist/retention-ui.js +7 -11
- package/dist/retention.js +1 -1
- package/dist/serve.d.ts +11 -2
- package/dist/serve.js +69 -58
- package/dist/server/chat.d.ts +3 -6
- package/dist/server/chat.js +969 -1006
- package/dist/server/chrome.d.ts +291 -0
- package/dist/server/chrome.js +747 -0
- package/dist/server/console.css +2819 -0
- package/dist/server/flows.d.ts +2 -6
- package/dist/server/flows.js +840 -766
- package/dist/server/guards.d.ts +1 -1
- package/dist/server/guards.js +5 -2
- package/dist/server/handler-context.d.ts +1 -1
- package/dist/server/handler-context.js +1 -1
- package/dist/server/handler-registry.d.ts +8 -6
- package/dist/server/handler-registry.js +14 -2
- package/dist/server/http.d.ts +274 -0
- package/dist/server/http.js +239 -0
- package/dist/server/live.d.ts +34 -0
- package/dist/server/live.js +309 -0
- package/dist/server/pages.d.ts +2 -6
- package/dist/server/pages.js +808 -789
- package/dist/server/people-tokens.d.ts +1 -6
- package/dist/server/people-tokens.js +580 -572
- package/dist/server/remote.d.ts +4 -7
- package/dist/server/remote.js +37 -70
- package/dist/server/render-chat.d.ts +402 -0
- package/dist/server/render-chat.js +927 -0
- package/dist/server/render-pages.d.ts +535 -0
- package/dist/server/render-pages.js +1277 -0
- package/dist/server/render-people.d.ts +32 -0
- package/dist/server/render-people.js +121 -0
- package/dist/server/render-results.d.ts +744 -0
- package/dist/server/render-results.js +2237 -0
- package/dist/server/render-settings.d.ts +147 -0
- package/dist/server/render-settings.js +577 -0
- package/dist/server/render-tasks.d.ts +632 -0
- package/dist/server/render-tasks.js +2345 -0
- package/dist/server/request-context.d.ts +46 -0
- package/dist/server/request-context.js +17 -0
- package/dist/server/route-table.d.ts +25 -3
- package/dist/server/route-table.js +163 -75
- package/dist/server/runtime.d.ts +15 -7
- package/dist/server/runtime.js +7 -1
- package/dist/server/session.d.ts +138 -0
- package/dist/server/session.js +94 -0
- package/dist/server/settings.d.ts +1 -5
- package/dist/server/settings.js +2182 -2117
- package/dist/server/task-views.d.ts +11 -4
- package/dist/server/task-views.js +26 -22
- package/dist/server/tasks.d.ts +9 -9
- package/dist/server/tasks.js +1486 -1418
- package/dist/server-telemetry.d.ts +4 -3
- package/dist/server-telemetry.js +4 -8
- package/dist/skills-ui.d.ts +4 -3
- package/dist/skills-ui.js +14 -17
- package/dist/skills.d.ts +1 -1
- package/dist/skills.js +2 -1
- package/dist/slack-api.d.ts +5 -1
- package/dist/slack-api.js +2 -2
- package/dist/slack-chat.d.ts +9 -4
- package/dist/slack-chat.js +29 -52
- package/dist/slack-settings.d.ts +3 -2
- package/dist/slack-settings.js +23 -57
- package/dist/slack.js +6 -16
- package/dist/spend-ui.d.ts +2 -1
- package/dist/spend-ui.js +9 -32
- package/dist/sso-ui.d.ts +3 -2
- package/dist/sso-ui.js +26 -27
- package/dist/storage-ui.d.ts +2 -1
- package/dist/storage-ui.js +14 -33
- package/dist/store.d.ts +97 -364
- package/dist/store.js +207 -1531
- package/dist/surface.js +14 -3
- package/dist/task-composer.d.ts +2 -1
- package/dist/task-composer.js +7 -7
- package/dist/task-status.d.ts +4 -3
- package/dist/task-status.js +15 -16
- package/dist/team-http.d.ts +5 -9
- package/dist/team-http.js +5 -63
- package/dist/team-ui.d.ts +2 -1
- package/dist/team-ui.js +2 -5
- package/dist/teammate-question.js +12 -12
- package/dist/teammates-ui.d.ts +5 -4
- package/dist/teammates-ui.js +61 -90
- package/dist/teams-chat.js +12 -9
- package/dist/teams-settings.d.ts +3 -2
- package/dist/teams-settings.js +22 -33
- package/dist/teams.js +4 -5
- package/dist/telegram-flow.js +5 -5
- package/dist/telegram-settings.d.ts +3 -2
- package/dist/telegram-settings.js +14 -25
- package/dist/telegram.js +4 -4
- package/dist/toolroll-update-ui.d.ts +3 -2
- package/dist/toolroll-update-ui.js +35 -39
- package/dist/toolroll-update.d.ts +10 -0
- package/dist/toolroll-update.js +50 -3
- package/dist/tools-ui.d.ts +5 -2
- package/dist/tools-ui.js +31 -31
- package/dist/when-html.d.ts +11 -3
- package/dist/when-html.js +5 -6
- package/dist/workspace-revision.js +3 -1
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +10 -10
- package/package.json +1 -1
- package/dist/server/shared.d.ts +0 -3151
- package/dist/server/shared.js +0 -12001
- package/dist/slack-state.d.ts +0 -13
- package/dist/slack-state.js +0 -10
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Live discovery for the UI. Prices are USD, separate from rounded budget pins. */
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
+
import { html } from "./html.js";
|
|
3
4
|
import { readCappedBody } from "./converse.js";
|
|
4
5
|
import { validModelId } from "./provider.js";
|
|
5
6
|
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
@@ -76,7 +77,6 @@ export function openRouterModelsCache(fetcher = fetch) {
|
|
|
76
77
|
return value;
|
|
77
78
|
};
|
|
78
79
|
}
|
|
79
|
-
const escape = (s) => s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
80
80
|
export function modelDollars(amount) {
|
|
81
81
|
if (amount === null)
|
|
82
82
|
return "Not reported";
|
|
@@ -96,14 +96,10 @@ function details(model) {
|
|
|
96
96
|
export function openRouterPicker(catalog, selected, refreshUrl) {
|
|
97
97
|
const rows = catalog.ok ? catalog.models : [];
|
|
98
98
|
const current = rows.find(row => row.id === selected);
|
|
99
|
-
const manual = selected && !current ? `<option selected value="${
|
|
100
|
-
return `<div data-openrouter-picker><p class="meta">${catalog.ok
|
|
101
|
-
? `${rows.length} text models · ${catalog.source === "account" ? "Filtered for your OpenRouter account" : "Public catalog · Connect a key to check your account"} · Updated ${
|
|
102
|
-
:
|
|
103
|
-
`<label data-model-search hidden>Search models<input type="search" placeholder="Search by name or provider" autocomplete="off"></label>` +
|
|
104
|
-
`<label>Model<select name="model" aria-describedby="openrouter-price"><option value="__custom__"${selected === null ? " selected" : ""}>Choose a model</option>${manual}${rows.map(row => `<option value="${escape(row.id)}" data-detail="${escape(details(row))}"${row.id === selected ? " selected" : ""}>${escape(row.name)} · ${modelDollars(row.input)} in / ${modelDollars(row.output)} out per 1M</option>`).join("")}</select></label>` +
|
|
105
|
-
`<p id="openrouter-price" class="meta" aria-live="polite">${current ? escape(details(current)) : selected ? "This saved model is not in the loaded catalog. Availability and pricing are unverified." : "Choose a model to see its token prices and context size."}</p>` +
|
|
106
|
-
`<p data-model-count class="meta" aria-live="polite"></p><p class="meta"><a href="https://openrouter.ai/models" target="_blank" rel="noreferrer">Compare models on OpenRouter ↗</a> · Prices in USD; catalog browsing uses no model tokens.</p></div>`;
|
|
99
|
+
const manual = selected && !current ? html `<option selected value="${selected}" data-detail="This saved model is not in the loaded catalog. Availability and pricing are unverified.">${selected} · Saved choice</option>` : "";
|
|
100
|
+
return html `<div data-openrouter-picker><p class="meta">${catalog.ok
|
|
101
|
+
? `${rows.length} text models · ${catalog.source === "account" ? "Filtered for your OpenRouter account" : "Public catalog · Connect a key to check your account"} · Updated ${catalog.checkedAt.slice(11, 16)} UTC`
|
|
102
|
+
: catalog.problem} <a href="${refreshUrl}">Refresh models</a></p><label data-model-search hidden>Search models<input type="search" placeholder="Search by name or provider" autocomplete="off"></label><label>Model<select name="model" aria-describedby="openrouter-price"><option value="__custom__"${selected === null ? html ` selected` : ""}>Choose a model</option>${manual}${rows.map(row => html `<option value="${row.id}" data-detail="${details(row)}"${row.id === selected ? html ` selected` : ""}>${row.name} · ${modelDollars(row.input)} in / ${modelDollars(row.output)} out per 1M</option>`)}</select></label><p id="openrouter-price" class="meta" aria-live="polite">${current ? details(current) : selected ? "This saved model is not in the loaded catalog. Availability and pricing are unverified." : "Choose a model to see its token prices and context size."}</p><p data-model-count class="meta" aria-live="polite"></p><p class="meta"><a href="https://openrouter.ai/models" target="_blank" rel="noreferrer">Compare models on OpenRouter ↗</a> · Prices in USD; catalog browsing uses no model tokens.</p></div>`;
|
|
107
103
|
}
|
|
108
104
|
/** Only filters already-rendered options; credentials never enter this script. */
|
|
109
105
|
export function openRouterPickerScript() {
|
package/dist/operate.js
CHANGED
|
@@ -175,7 +175,7 @@ import { mintCoordinator, revokeCoordinator, listCoordinators } from "./coordina
|
|
|
175
175
|
import { serveMcp } from "./mcp.js";
|
|
176
176
|
import { createInterface } from "node:readline";
|
|
177
177
|
import { propose, approve, addApprover, authenticateAccount, authenticateApprover as passwordApprover, describeScope, approvalOf, hashToken as hashApproverToken, fileAndSealUnderMode, modeFilingCoverage, acceptanceLinesToInput, parseAcceptanceCriteria, splitAcceptanceRubric, rubricIsPlaceholder, isCommitSha } from "./scope.js";
|
|
178
|
-
import { presetTerms, modeTermsJson, modeDigestOf, modeTermsFromJson, modeWords, MODE_MAX_DAYS } from "./modes.js";
|
|
178
|
+
import { CHAT_APPROVE_ALL, presetTerms, modeTermsJson, modeDigestOf, modeTermsFromJson, modeWords, MODE_MAX_DAYS } from "./modes.js";
|
|
179
179
|
import { WorktreePool } from "./worktree.js";
|
|
180
180
|
import { requestTaskStop, resumeTaskStop, taskControlOf } from "./task-control.js";
|
|
181
181
|
import { worktreeAdoptionNotice } from "./worktree-notices.js";
|
|
@@ -370,7 +370,7 @@ External trackers — build what a tracker nominates, under local approvals
|
|
|
370
370
|
|
|
371
371
|
toolroll claim <id> --runner <name> [--ttl <seconds>]
|
|
372
372
|
toolroll heartbeat <lease> still working; extends the lease
|
|
373
|
-
toolroll release <lease>
|
|
373
|
+
toolroll worker release <lease> done with it; fenced if superseded
|
|
374
374
|
toolroll reap release every lease that ran out
|
|
375
375
|
|
|
376
376
|
toolroll tick --runner <name> --token <t> --repo <path>
|
|
@@ -1143,8 +1143,10 @@ async function dispatch(command, positional, flags, context) {
|
|
|
1143
1143
|
return claimCommand(positional, flags, context);
|
|
1144
1144
|
case "heartbeat":
|
|
1145
1145
|
return leaseCommand("heartbeat", positional, flags, context);
|
|
1146
|
-
case "
|
|
1147
|
-
|
|
1146
|
+
case "worker":
|
|
1147
|
+
if (positional[0] === "release")
|
|
1148
|
+
return leaseCommand("release", positional.slice(1), flags, context);
|
|
1149
|
+
return fail(context.write, context.json, "worker", "usage", "`toolroll worker release <lease>` returns a lease.", EXIT.usage);
|
|
1148
1150
|
case "reap":
|
|
1149
1151
|
return reapCommand(context);
|
|
1150
1152
|
case "runner":
|
|
@@ -1476,18 +1478,20 @@ function claimCommand(positional, flags, context) {
|
|
|
1476
1478
|
*/
|
|
1477
1479
|
function leaseCommand(command, positional, flags, context) {
|
|
1478
1480
|
const { store, write, json, now } = context;
|
|
1481
|
+
// `release` answers as the worker verb it now is (the bare `toolroll release <lease>` form is deprecated).
|
|
1482
|
+
const name = command === "release" ? "worker release" : command;
|
|
1479
1483
|
const lease = positional[0];
|
|
1480
1484
|
if (lease === undefined)
|
|
1481
|
-
return fail(write, json,
|
|
1485
|
+
return fail(write, json, name, "usage", `which lease? \`toolroll ${name} <lease>\``, EXIT.usage);
|
|
1482
1486
|
const ttl = readTtl(flags);
|
|
1483
1487
|
if (ttl === null)
|
|
1484
|
-
return fail(write, json,
|
|
1488
|
+
return fail(write, json, name, "usage", "--ttl takes whole seconds", EXIT.usage);
|
|
1485
1489
|
const result = command === "heartbeat" ? heartbeat(store, lease, now, ttl) : release(store, lease, now);
|
|
1486
1490
|
if (!result.ok) {
|
|
1487
1491
|
const message = result.reason === "fenced"
|
|
1488
1492
|
? "superseded — another runner holds this task now; stop rather than retry"
|
|
1489
1493
|
: "no such lease";
|
|
1490
|
-
return fail(write, json,
|
|
1494
|
+
return fail(write, json, name, result.reason, message, EXIT.refused);
|
|
1491
1495
|
}
|
|
1492
1496
|
if (command === "release") {
|
|
1493
1497
|
const task = store.getTask(String(refExternalId(store, result.claim.taskRef)));
|
|
@@ -1496,7 +1500,7 @@ function leaseCommand(command, positional, flags, context) {
|
|
|
1496
1500
|
if (task !== null && task.state === "running")
|
|
1497
1501
|
store.setTaskState(task.id, "queued", now);
|
|
1498
1502
|
}
|
|
1499
|
-
return succeed(write, json,
|
|
1503
|
+
return succeed(write, json, name, { lease: result.claim }, () => [
|
|
1500
1504
|
command === "heartbeat"
|
|
1501
1505
|
? `Still yours until ${result.claim.expiresAt}.`
|
|
1502
1506
|
: `Released ${result.claim.leaseId}.`,
|
|
@@ -4659,8 +4663,11 @@ async function modeCommand(positional, flags, context) {
|
|
|
4659
4663
|
return fail(write, json, "mode set", "invalid", "--plan-auto requires automatic filing approval", EXIT.refused);
|
|
4660
4664
|
}
|
|
4661
4665
|
// Approving plans and merges from the signer's paired chat: never a preset default, only this flag.
|
|
4662
|
-
|
|
4666
|
+
// The signature names every chat app it covers: a grant signed before that named Telegram alone, and stays so.
|
|
4667
|
+
if (flag(flags, "chat-approve")) {
|
|
4663
4668
|
terms.chatApprove = true;
|
|
4669
|
+
terms.chatApproveChats = CHAT_APPROVE_ALL;
|
|
4670
|
+
}
|
|
4664
4671
|
// Automerge requires a live merge-capable grant on the repo (D1).
|
|
4665
4672
|
if (terms.publication === "automerge" && !store.hasMergeCapableGrant(repo, now)) {
|
|
4666
4673
|
return fail(write, json, "mode set", "no-grant", "automerge needs a merge-capable publication grant on this repo — file one first", EXIT.refused);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AuditAction } from "./remote-audit.js";
|
|
2
2
|
import type { ApiTokenRow } from "./store.js";
|
|
3
|
+
import { type Html } from "./html.js";
|
|
3
4
|
export declare const PEOPLE_AUDIT_CSS: string;
|
|
4
5
|
export type PersonAuditView = {
|
|
5
6
|
name: string;
|
|
@@ -11,4 +12,4 @@ export type PersonAuditView = {
|
|
|
11
12
|
now: Date;
|
|
12
13
|
};
|
|
13
14
|
export declare const personHref: (name: string, more?: Record<string, string>) => string;
|
|
14
|
-
export declare function personAuditHtml(view: PersonAuditView):
|
|
15
|
+
export declare function personAuditHtml(view: PersonAuditView): Html;
|
package/dist/people-audit-ui.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { basename } from "node:path";
|
|
7
7
|
import { tokenStatus } from "./remote-audit.js";
|
|
8
|
-
|
|
8
|
+
import { html } from "./html.js";
|
|
9
9
|
const stamp = (value) => `${value.slice(0, 16).replace("T", " ")} UTC`;
|
|
10
10
|
export const PEOPLE_AUDIT_CSS = `.person-audit{max-width:820px;min-width:0;overflow-wrap:anywhere}.person-audit h2{font-size:1.05rem;margin:1.25rem 0 8px}` +
|
|
11
11
|
`.person-audit .rows{display:grid;border:1px solid var(--so-line);border-radius:10px;overflow:hidden}.person-audit .row-item{padding:10px 14px;border-bottom:1px solid var(--so-line);min-width:0}` +
|
|
@@ -17,22 +17,22 @@ export const PEOPLE_AUDIT_CSS = `.person-audit{max-width:820px;min-width:0;overf
|
|
|
17
17
|
`@media(max-width:600px){.person-audit .row-item a{min-height:44px}}`;
|
|
18
18
|
export const personHref = (name, more = {}) => `/people?${new URLSearchParams({ person: name, ...more }).toString()}`;
|
|
19
19
|
export function personAuditHtml(view) {
|
|
20
|
-
const tokens = view.tokens.length === 0 ? `<p class="meta">No API tokens.</p>` : `<div class="rows">${view.tokens.map(one => {
|
|
20
|
+
const tokens = view.tokens.length === 0 ? html `<p class="meta">No API tokens.</p>` : html `<div class="rows">${view.tokens.map(one => {
|
|
21
21
|
const status = tokenStatus(one, view.now);
|
|
22
|
-
return `<div class="row-item" data-token-name="${
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
})
|
|
26
|
-
const filtered = view.token === null ? "" : `<p class="meta">With ${
|
|
27
|
-
const actions = view.actions.length === 0 ? `<p class="meta">No remote actions${view.token === null ? "" : " with this token"}.</p>` : `<div class="rows">${view.actions.map(actionHtml)
|
|
28
|
-
const older = view.nextCursor === null ? "" : `<p><a href="${
|
|
29
|
-
return `<p><a href="/people">People</a></p><h1>${
|
|
30
|
-
|
|
22
|
+
return html `<div class="row-item" data-token-name="${one.name}"><p><a href="${personHref(view.name, { "token-name": one.name })}">${one.name}</a></p>\
|
|
23
|
+
<p class="meta">${status === "active" ? "Active" : status === "expired" ? "Expired" : "Revoked"} · ${one.access === "act" ? "Can act" : "Read only"} · \
|
|
24
|
+
last used ${one.lastUsedAt === null ? "never" : stamp(one.lastUsedAt)}</p></div>`;
|
|
25
|
+
})}</div>`;
|
|
26
|
+
const filtered = view.token === null ? "" : html `<p class="meta">With ${view.token}</p><p><a href="${personHref(view.name)}">All tokens</a></p>`;
|
|
27
|
+
const actions = view.actions.length === 0 ? html `<p class="meta">No remote actions${view.token === null ? "" : " with this token"}.</p>` : html `<div class="rows">${view.actions.map(actionHtml)}</div>`;
|
|
28
|
+
const older = view.nextCursor === null ? "" : html `<p><a href="${personHref(view.name, { ...(view.token === null ? {} : { "token-name": view.token }), before: view.nextCursor })}">Older actions</a></p>`;
|
|
29
|
+
return html `<p><a href="/people">People</a></p><h1>${view.name}</h1><section class="person-audit"><h2>API tokens</h2>${tokens}\
|
|
30
|
+
<h2>Remote activity</h2>${filtered}${actions}${older}</section>`;
|
|
31
31
|
}
|
|
32
32
|
function actionHtml(action) {
|
|
33
33
|
const label = action.outcome === "ok" ? "OK" : action.outcome === "refused" ? "Refused" : "Error";
|
|
34
34
|
const detail = [stamp(action.at), action.token ?? "unknown token", action.source === "mcp" ? "Agent (MCP)" : "CLI",
|
|
35
35
|
...(action.repo === null ? [] : [basename(action.repo)]), ...(action.reason === null ? [] : [action.reason])];
|
|
36
|
-
return `<div class="row-item" data-action="${action.id}"><p><span class="outcome outcome-${action.outcome}">${label}</span>${
|
|
37
|
-
|
|
36
|
+
return html `<div class="row-item" data-action="${action.id}"><p><span class="outcome outcome-${action.outcome}">${label}</span>${action.name}\
|
|
37
|
+
${action.taskId === null ? "" : html ` · <a href="/t/${encodeURIComponent(action.taskId)}">${action.taskId}</a>`}</p><p class="meta">${detail.join(" · ")}</p></div>`;
|
|
38
38
|
}
|
package/dist/plane-review.js
CHANGED
|
@@ -175,17 +175,11 @@ export function reviewPlane(store, now, canSee = () => true) {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
// Chat replies that couldn't be delivered.
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
add(`chat/${app}`, `${name} replies weren't delivered`, ["reply", "replies"], { evidence: `After ${plural(Number(row["attempts"] ?? 0), "try", "tries")}: ${excerpt(str(row["problem"])) || "dropped"}` });
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (tableExists(store, "telegram_conversation_part")) {
|
|
187
|
-
for (const row of store.handle.prepare("SELECT last_error, attempts FROM telegram_conversation_part WHERE state = 'dropped' AND created_at >= ?").all(since)) {
|
|
188
|
-
add("chat/telegram", "Telegram replies weren't delivered", ["reply", "replies"], { evidence: `After ${plural(Number(row["attempts"] ?? 0), "try", "tries")}: ${excerpt(str(row["last_error"])) || "dropped"}` });
|
|
178
|
+
if (tableExists(store, "chat_part")) {
|
|
179
|
+
const names = { telegram: "Telegram", slack: "Slack", discord: "Discord", teams: "Teams" };
|
|
180
|
+
for (const row of store.handle.prepare("SELECT provider, problem, attempts FROM chat_part WHERE state = 'dropped' AND created >= ? ORDER BY provider = 'telegram' DESC, provider, id").all(since)) {
|
|
181
|
+
const app = String(row["provider"]);
|
|
182
|
+
add(`chat/${app}`, `${names[app] ?? app} replies weren't delivered`, ["reply", "replies"], { evidence: `After ${plural(Number(row["attempts"] ?? 0), "try", "tries")}: ${excerpt(str(row["problem"])) || "dropped"}` });
|
|
189
183
|
}
|
|
190
184
|
}
|
|
191
185
|
if (tableExists(store, "notification_delivery")) {
|
package/dist/policy-ui.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* action ledger. Anyone signed in reads it; an instance operator changes it
|
|
5
5
|
* with their password.
|
|
6
6
|
*/
|
|
7
|
+
import { type Html } from "./html.js";
|
|
7
8
|
import type { LedgerEntry } from "./action-ledger.js";
|
|
8
9
|
import { type SavedPolicy } from "./policy.js";
|
|
9
10
|
export declare const POLICY_CSS: string;
|
|
@@ -14,7 +15,7 @@ export type PolicyView = {
|
|
|
14
15
|
/** The project tools here, as a hint for the tools list. */
|
|
15
16
|
toolNames: string[];
|
|
16
17
|
};
|
|
17
|
-
export declare function policyHtml(view: PolicyView,
|
|
18
|
+
export declare function policyHtml(view: PolicyView, notice: {
|
|
18
19
|
said?: string | null;
|
|
19
20
|
problem?: string | null;
|
|
20
|
-
}):
|
|
21
|
+
}): Html;
|
package/dist/policy-ui.js
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings → Policy (sprint 8): the organisation policy — allowed providers,
|
|
3
|
+
* models and tools, and the permission ceiling — and its history from the
|
|
4
|
+
* action ledger. Anyone signed in reads it; an instance operator changes it
|
|
5
|
+
* with their password.
|
|
6
|
+
*/
|
|
7
|
+
import { html, postForm } from "./html.js";
|
|
1
8
|
import { whenUtc } from "./when-html.js";
|
|
2
9
|
import { LEVEL_HINTS, LEVEL_NAMES, PERMISSION_LEVELS, POLICY_PROVIDERS, PROVIDER_NAMES, policyParts } from "./policy.js";
|
|
3
|
-
const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
4
10
|
export const POLICY_CSS = `.policy{max-width:720px;min-width:0}.policy dl{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:6px 16px;margin:12px 0 0;font-size:.875rem}` +
|
|
5
11
|
`.policy dt{color:var(--muted-foreground)}.policy dd{margin:0;overflow-wrap:anywhere}.policy fieldset{border:0;padding:0;margin:20px 0 0;min-width:0}` +
|
|
6
12
|
`.policy legend{font-weight:600;font-size:.875rem;margin-bottom:8px}.policy legend{padding:0}.policy .providers{display:grid;grid-template-columns:repeat(auto-fill,minmax(132px,1fr));gap:0 16px}.policy .providers label{display:flex;gap:8px;align-items:center;min-height:44px;margin:0;padding:0;font-weight:400}` +
|
|
@@ -13,30 +19,18 @@ export const POLICY_CSS = `.policy{max-width:720px;min-width:0}.policy dl{displa
|
|
|
13
19
|
function historyItem(entry) {
|
|
14
20
|
const what = entry.action.replace(/^organisation policy: /, "");
|
|
15
21
|
const [before, after] = (entry.detail ?? "").split(" → ");
|
|
16
|
-
return `<li><span class="what">${
|
|
17
|
-
`<span class="meta">${e(entry.actor)} · ${whenUtc(entry.at)}</span></li>`;
|
|
22
|
+
return html `<li><span class="what">${what.charAt(0).toUpperCase() + what.slice(1)}: ${before ?? ""} → <strong>${after ?? ""}</strong></span><span class="meta">${entry.actor} · ${whenUtc(entry.at)}</span></li>`;
|
|
18
23
|
}
|
|
19
|
-
export function policyHtml(view,
|
|
20
|
-
const note = notice.problem ? `<p class="problem" role="alert">${
|
|
24
|
+
export function policyHtml(view, notice) {
|
|
25
|
+
const note = notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : notice.said ? html `<p role="status">${notice.said}</p>` : "";
|
|
21
26
|
const parts = policyParts(view.policy);
|
|
22
|
-
const changed = view.policy.updatedBy === null ? "" : `<p class="meta">Changed by ${
|
|
23
|
-
const current = `<dl><dt>Providers</dt><dd>${
|
|
24
|
-
|
|
25
|
-
const history = view.history.length === 0 ? "" : `<details><summary>History (${view.history.length})</summary><ol>${view.history.map(historyItem).join("")}</ol></details>`;
|
|
27
|
+
const changed = view.policy.updatedBy === null ? "" : html `<p class="meta">Changed by ${view.policy.updatedBy} on ${(view.policy.updatedAt ?? "").slice(0, 10)}.</p>`;
|
|
28
|
+
const current = html `<dl><dt>Providers</dt><dd>${parts.providers === "any" ? "Any" : parts.providers}</dd><dt>Models</dt><dd>${parts.models === "any" ? "Any" : parts.models}</dd><dt>Tools</dt><dd>${parts.tools === "any" ? "Any" : parts.tools}</dd><dt>Ceiling</dt><dd>${parts.ceiling}</dd></dl>${changed}`;
|
|
29
|
+
const history = view.history.length === 0 ? "" : html `<details><summary>History (${view.history.length})</summary><ol>${view.history.map(historyItem)}</ol></details>`;
|
|
26
30
|
if (!view.canChange)
|
|
27
|
-
return `<section class="policy">${note}${current}<p class="meta">An instance operator sets the policy.</p>${history}</section>`;
|
|
28
|
-
const providers = POLICY_PROVIDERS.map(one => `<label><input type="checkbox" name="provider" value="${one}"${view.policy.providers === null || view.policy.providers.includes(one) ?
|
|
29
|
-
const levels = [...PERMISSION_LEVELS].reverse().map(level => `<label class="choice"><input type="radio" name="ceiling" value="${level}"${view.policy.ceiling === level ?
|
|
30
|
-
`<span><strong>${LEVEL_NAMES[level]}</strong><small>${e(LEVEL_HINTS[level])}</small></span></label>`).join("");
|
|
31
|
+
return html `<section class="policy">${note}${current}<p class="meta">An instance operator sets the policy.</p>${history}</section>`;
|
|
32
|
+
const providers = POLICY_PROVIDERS.map(one => html `<label><input type="checkbox" name="provider" value="${one}"${view.policy.providers === null || view.policy.providers.includes(one) ? html ` checked` : ""}> ${PROVIDER_NAMES[one]}</label>`);
|
|
33
|
+
const levels = [...PERMISSION_LEVELS].reverse().map(level => html `<label class="choice"><input type="radio" name="ceiling" value="${level}"${view.policy.ceiling === level ? html ` checked` : ""}><span><strong>${LEVEL_NAMES[level]}</strong><small>${LEVEL_HINTS[level]}</small></span></label>`);
|
|
31
34
|
const toolHint = view.toolNames.length === 0 ? "" : ` Tools here: ${view.toolNames.slice(0, 12).join(", ")}${view.toolNames.length > 12 ? "…" : ""}.`;
|
|
32
|
-
return `<section class="policy">${note}${changed}
|
|
33
|
-
`<form method="post" action="/settings/policy"><input type="hidden" name="csrf" value="${e(csrf)}">` +
|
|
34
|
-
`<fieldset><legend>Providers</legend><div class="providers">${providers}</div></fieldset>` +
|
|
35
|
-
`<fieldset><legend><label for="policy-models">Models</label></legend><textarea id="policy-models" name="models" spellcheck="false" placeholder="Any model" aria-describedby="policy-models-hint">${e((view.policy.models ?? []).join("\n"))}</textarea>` +
|
|
36
|
-
`<span class="hint" id="policy-models-hint">One per line; end with * to allow a family. Blank allows any.</span></fieldset>` +
|
|
37
|
-
`<fieldset><legend><label for="policy-tools">Project tools</label></legend><textarea id="policy-tools" name="tools" spellcheck="false" placeholder="Any tool" aria-describedby="policy-tools-hint">${e((view.policy.tools ?? []).join("\n"))}</textarea>` +
|
|
38
|
-
`<span class="hint" id="policy-tools-hint">One per line. Blank allows any.${e(toolHint)}</span></fieldset>` +
|
|
39
|
-
`<fieldset><legend>Permission ceiling</legend>${levels}</fieldset>` +
|
|
40
|
-
`<div class="step-up"><label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label></div>` +
|
|
41
|
-
`<button type="submit">Save policy</button></form>${history}</section>`;
|
|
35
|
+
return html `<section class="policy">${note}${changed}${postForm("/settings/policy", html `<fieldset><legend>Providers</legend><div class="providers">${providers}</div></fieldset><fieldset><legend><label for="policy-models">Models</label></legend><textarea id="policy-models" name="models" spellcheck="false" placeholder="Any model" aria-describedby="policy-models-hint">${(view.policy.models ?? []).join("\n")}</textarea><span class="hint" id="policy-models-hint">One per line; end with * to allow a family. Blank allows any.</span></fieldset><fieldset><legend><label for="policy-tools">Project tools</label></legend><textarea id="policy-tools" name="tools" spellcheck="false" placeholder="Any tool" aria-describedby="policy-tools-hint">${(view.policy.tools ?? []).join("\n")}</textarea><span class="hint" id="policy-tools-hint">One per line. Blank allows any.${toolHint}</span></fieldset><fieldset><legend>Permission ceiling</legend>${levels}</fieldset><div class="step-up"><label>Your Toolroll password<input type="password" name="password" autocomplete="current-password" required></label></div><button type="submit">Save policy</button>`)}${history}</section>`;
|
|
42
36
|
}
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* then read exactly what goes and confirm with their password (or a fresh
|
|
5
5
|
* sign-in with the identity provider). The repository itself stays.
|
|
6
6
|
*/
|
|
7
|
+
import { type Html } from "./html.js";
|
|
7
8
|
import { type ProjectHoldings } from "./project-delete.js";
|
|
8
9
|
export declare const PROJECT_DELETE_CSS: string;
|
|
9
10
|
export type ProjectSettingsView = {
|
|
@@ -21,9 +22,9 @@ export type ProjectSettingsView = {
|
|
|
21
22
|
};
|
|
22
23
|
};
|
|
23
24
|
/** The project's page: its path, what Toolroll holds for it, and (for an instance operator) Delete project. */
|
|
24
|
-
export declare function projectSettingsHtml(view: ProjectSettingsView,
|
|
25
|
+
export declare function projectSettingsHtml(view: ProjectSettingsView, notice: {
|
|
25
26
|
said?: string | null;
|
|
26
27
|
problem?: string | null;
|
|
27
|
-
}):
|
|
28
|
+
}): Html;
|
|
28
29
|
/** The second step: exactly what goes, and the password. */
|
|
29
|
-
export declare function projectDeleteConfirmHtml(view: ProjectSettingsView,
|
|
30
|
+
export declare function projectDeleteConfirmHtml(view: ProjectSettingsView, problem: string | null): Html;
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* then read exactly what goes and confirm with their password (or a fresh
|
|
5
5
|
* sign-in with the identity provider). The repository itself stays.
|
|
6
6
|
*/
|
|
7
|
+
import { html, postForm } from "./html.js";
|
|
7
8
|
import { holdingsWords } from "./project-delete.js";
|
|
8
|
-
const e = (value) => String(value ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[c]);
|
|
9
9
|
export const PROJECT_DELETE_CSS = `.project-settings,.project-delete{max-width:720px;min-width:0}.project-settings .path,.project-delete .path{font-family:var(--font-mono);font-size:.8125rem;overflow-wrap:anywhere;word-break:break-all}` +
|
|
10
10
|
`.project-settings details.danger-zone{margin-top:24px;border:1px solid var(--border);border-radius:10px;padding:0 14px}.project-settings details.danger-zone summary{min-height:44px;display:flex;align-items:center;cursor:pointer;font-weight:500;color:var(--danger)}` +
|
|
11
11
|
`.project-settings details.danger-zone[open]{padding-bottom:14px}.project-settings form,.project-delete form{display:grid;gap:12px;margin-top:12px}.project-settings label,.project-delete label{display:grid;gap:4px;font-size:.8125rem;font-weight:500}` +
|
|
@@ -16,39 +16,32 @@ export const PROJECT_DELETE_CSS = `.project-settings,.project-delete{max-width:7
|
|
|
16
16
|
`.project-settings .builds-at-once .builds-row{display:flex;gap:8px;align-items:stretch}.project-settings .builds-at-once input{width:5.5rem;min-height:44px;margin:0;box-sizing:border-box}.project-settings .builds-at-once button{min-height:44px;margin:0}` +
|
|
17
17
|
`.project-settings .builds-at-once .meta{margin:0}.project-settings form.remove-project{margin-top:20px;gap:4px}.project-settings form.remove-project button{min-height:44px}.project-settings form.remove-project .meta{margin:0}`;
|
|
18
18
|
/** Builds at once: one number, one Save. The worker's cap is said only when it lowers the number. */
|
|
19
|
-
function buildsHtml(view
|
|
19
|
+
function buildsHtml(view) {
|
|
20
20
|
const b = view.builds;
|
|
21
21
|
if (b === undefined)
|
|
22
22
|
return "";
|
|
23
23
|
const capped = b.capacity !== null && b.capacity < b.setting ? ` Its worker runs ${b.capacity} at once, so ${b.capacity} for now.` : "";
|
|
24
24
|
const now = `${b.building} building now.${capped}`;
|
|
25
25
|
if (!b.canChange)
|
|
26
|
-
return `<section class="builds-at-once"><h2>Builds at once</h2><p>Up to ${b.setting}. ${
|
|
27
|
-
return `<section class="builds-at-once"><h2>Builds at once</h2><
|
|
28
|
-
`<input type="hidden" name="csrf" value="${e(csrf)}"><input type="hidden" name="repo" value="${e(view.repo)}">` +
|
|
29
|
-
`<div class="builds-row"><input type="number" name="concurrency" min="1" max="64" step="1" inputmode="numeric" value="${b.setting}" aria-label="Builds at once" aria-describedby="builds-now"><button type="submit">Save</button></div>` +
|
|
30
|
-
`<p class="meta" id="builds-now">${e(now)}</p></form></section>`;
|
|
26
|
+
return html `<section class="builds-at-once"><h2>Builds at once</h2><p>Up to ${b.setting}. ${now}</p></section>`;
|
|
27
|
+
return html `<section class="builds-at-once"><h2>Builds at once</h2>${postForm("/settings/project/concurrency", html `<div class="builds-row"><input type="number" name="concurrency" min="1" max="64" step="1" inputmode="numeric" value="${b.setting}" aria-label="Builds at once" aria-describedby="builds-now"><button type="submit">Save</button></div><p class="meta" id="builds-now">${now}</p>`, { hidden: { repo: view.repo } })}</section>`;
|
|
31
28
|
}
|
|
32
|
-
const note = (notice) => notice.problem ? `<p class="problem" role="alert">${
|
|
29
|
+
const note = (notice) => notice.problem ? html `<p class="problem" role="alert">${notice.problem}</p>` : notice.said ? html `<p role="status">${notice.said}</p>` : "";
|
|
33
30
|
/** The project's page: its path, what Toolroll holds for it, and (for an instance operator) Delete project. */
|
|
34
|
-
export function projectSettingsHtml(view,
|
|
35
|
-
const held = `<p>Toolroll holds ${
|
|
36
|
-
const head =
|
|
31
|
+
export function projectSettingsHtml(view, notice) {
|
|
32
|
+
const held = html `<p>Toolroll holds ${holdingsWords(view.holdings)} for <strong>${view.name}</strong>.</p>`;
|
|
33
|
+
const head = html `${note(notice)}<p class="path">${view.repo}</p>${held}${buildsHtml(view)}`;
|
|
37
34
|
if (!view.canDelete)
|
|
38
|
-
return
|
|
35
|
+
return html `<section class="project-settings">${head}<p class="meta">An instance operator can delete a project.</p></section>`;
|
|
39
36
|
const body = view.running.length > 0
|
|
40
|
-
? `<p class="problem">Its work is running: ${
|
|
41
|
-
: `<p>Removes everything Toolroll holds for ${
|
|
42
|
-
`<form method="post" action="/settings/project/delete"><input type="hidden" name="csrf" value="${e(csrf)}"><input type="hidden" name="repo" value="${e(view.repo)}">` +
|
|
43
|
-
`<label><span>Type <strong>${e(view.name)}</strong> to continue</span><input name="name" autocomplete="off" autocapitalize="off" spellcheck="false" required></label>` +
|
|
44
|
-
`<button type="submit">Continue</button></form>`;
|
|
37
|
+
? html `<p class="problem">Its work is running: ${view.running.join(", ")}. Stop it before deleting the project.</p>`
|
|
38
|
+
: html `<p>Removes everything Toolroll holds for ${view.name}. The repository and its own branches stay. There's no undo.</p>${postForm("/settings/project/delete", html `<label><span>Type <strong>${view.name}</strong> to continue</span><input name="name" autocomplete="off" autocapitalize="off" spellcheck="false" required></label><button type="submit">Continue</button>`, { hidden: { repo: view.repo } })}`;
|
|
45
39
|
// Removing is the reversible step: off the lists and the builder, everything kept.
|
|
46
|
-
const remove =
|
|
47
|
-
|
|
48
|
-
return `${head}${remove}<details class="danger-zone"${notice.problem ? " open" : ""}><summary>Delete project</summary>${body}</details></section>`;
|
|
40
|
+
const remove = postForm("/projects/remove", html `<button type="submit">Remove from Toolroll</button><p class="meta">Its tasks and results stay saved. Add it again to bring it back.</p>`, { attrs: { class: "remove-project" }, hidden: { repo: view.repo } });
|
|
41
|
+
return html `<section class="project-settings">${head}${remove}<details class="danger-zone"${notice.problem ? html ` open` : ""}><summary>Delete project</summary>${body}</details></section>`;
|
|
49
42
|
}
|
|
50
43
|
/** The second step: exactly what goes, and the password. */
|
|
51
|
-
export function projectDeleteConfirmHtml(view,
|
|
44
|
+
export function projectDeleteConfirmHtml(view, problem) {
|
|
52
45
|
const h = view.holdings;
|
|
53
46
|
const count = (n, one, many = `${one}s`) => `${n} ${n === 1 ? one : many}`;
|
|
54
47
|
const items = [
|
|
@@ -59,10 +52,5 @@ export function projectDeleteConfirmHtml(view, csrf, problem) {
|
|
|
59
52
|
...(h.teammates > 0 ? [count(h.teammates, "teammate")] : []),
|
|
60
53
|
"Its budgets, settings and knowledge",
|
|
61
54
|
];
|
|
62
|
-
return `<section class="project-delete">${problem === null ? "" : `<p class="problem" role="alert">${
|
|
63
|
-
`<p>This removes:</p><ul class="removes">${items.map(one => `<li>${e(one)}</li>`).join("")}</ul>` +
|
|
64
|
-
`<p>The repository at <span class="path">${e(view.repo)}</span> and its own branches stay. The ledger keeps its history. There's no undo.</p>` +
|
|
65
|
-
`<form method="post" action="/settings/project/delete"><input type="hidden" name="csrf" value="${e(csrf)}"><input type="hidden" name="repo" value="${e(view.repo)}"><input type="hidden" name="name" value="${e(view.name)}"><input type="hidden" name="step" value="delete">` +
|
|
66
|
-
`<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password"></label>` +
|
|
67
|
-
`<div class="actions"><button type="submit" class="danger">Delete project</button><a href="/settings/project?repo=${e(encodeURIComponent(view.repo))}">Cancel</a></div></form></section>`;
|
|
55
|
+
return html `<section class="project-delete">${problem === null ? "" : html `<p class="problem" role="alert">${problem}</p>`}<p>This removes:</p><ul class="removes">${items.map(one => html `<li>${one}</li>`)}</ul><p>The repository at <span class="path">${view.repo}</span> and its own branches stay. The ledger keeps its history. There's no undo.</p>${postForm("/settings/project/delete", html `<label>Your Toolroll password<input type="password" name="password" autocomplete="current-password"></label><div class="actions"><button type="submit" class="danger">Delete project</button><a href="/settings/project?repo=${encodeURIComponent(view.repo)}">Cancel</a></div>`, { hidden: { repo: view.repo, name: view.name, step: "delete" } })}</section>`;
|
|
68
56
|
}
|
package/dist/project-delete.js
CHANGED
|
@@ -207,35 +207,30 @@ function deleteRows(store, repo, d, now) {
|
|
|
207
207
|
const notifications = json(ids(db, `SELECT id FROM notification WHERE project = ? OR ${IN("task_ref")} OR ${IN("source_run")}`, repo, T, R));
|
|
208
208
|
const questions = json(ids(db, `SELECT id FROM teammate_question WHERE ${IN("teammate")} OR ${IN("card")}`, M, C));
|
|
209
209
|
const proposals = json(ids(db, `SELECT id FROM mate_proposal WHERE ${IN("thread")}`, H));
|
|
210
|
-
|
|
211
|
-
//
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}
|
|
223
|
-
del("
|
|
224
|
-
del("
|
|
225
|
-
del("
|
|
226
|
-
del("
|
|
227
|
-
del("
|
|
228
|
-
del("telegram_flow_prompt", IN("card"), C);
|
|
229
|
-
del("telegram_flow_choice", IN("card"), C);
|
|
210
|
+
// A chat message about one of the project's tasks (its turn named it), with its reply. Event ids are text ('m123'),
|
|
211
|
+
// and the same id can be another app's: each is kept whole, as provider:id.
|
|
212
|
+
const conversations = json(list(db, `SELECT provider || ':' || id FROM chat_event WHERE kind = 'message' AND json_extract(payload, '$.about.task') IN (SELECT value FROM json_each(?))`, K));
|
|
213
|
+
// Every app's planned parts about the project's work: a result screenshot, or a message naming one of its tasks or runs.
|
|
214
|
+
const parts = `SELECT provider || ':' || id FROM chat_part WHERE provider || ':' || event IN (SELECT value FROM json_each(?))
|
|
215
|
+
OR json_extract(payload, '$.image.taskId') IN (SELECT value FROM json_each(?)) OR json_extract(payload, '$.image.run') IN (SELECT value FROM json_each(?))`;
|
|
216
|
+
const doomedParts = `provider || ':' || part IN (${parts})`;
|
|
217
|
+
// Chats about the project's work, on every app.
|
|
218
|
+
del("chat_progress", IN("run"), R);
|
|
219
|
+
del("chat_action", `${IN("proposal")} OR ${IN("decision")} OR ${doomedParts}`, proposals, decisions, conversations, K, R);
|
|
220
|
+
del("chat_flow_action", `${IN("card")} OR ${doomedParts}`, C, conversations, K, R);
|
|
221
|
+
del("chat_flow_prompt", IN("card"), C);
|
|
222
|
+
del("chat_flow_choice", `${IN("card")} OR ${doomedParts}`, C, conversations, K, R);
|
|
223
|
+
del("chat_flow_note", IN("card"), C);
|
|
224
|
+
del("chat_question_action", `${IN("question")} OR ${doomedParts}`, questions, conversations, K, R);
|
|
225
|
+
del("chat_question_prompt", IN("question"), questions);
|
|
226
|
+
del("chat_ask_action", `${IN("turn")} OR ${doomedParts}`, U, conversations, K, R);
|
|
227
|
+
del("chat_note_draft", IN("decision"), decisions);
|
|
230
228
|
del("flow_send", IN("card"), C);
|
|
231
|
-
del("telegram_question_action", IN("question"), questions);
|
|
232
|
-
del("telegram_question_prompt", IN("question"), questions);
|
|
233
229
|
del("chat_decide_action", IN("task_id"), K);
|
|
234
230
|
del("chat_decide_prompt", IN("task_id"), K);
|
|
235
|
-
del("
|
|
236
|
-
del("
|
|
237
|
-
del("
|
|
238
|
-
del("telegram_outbound_message", `project = ? OR ${IN("notification")} OR ${IN("task_ref")} OR ${IN("source_run")}`, repo, notifications, T, R);
|
|
231
|
+
del("chat_message_ref", `${IN("decision")} OR ${IN("task_id")} OR ${IN("run")} OR project = ? OR ${IN("notification")} OR ${IN("task_ref")}`, decisions, K, R, repo, notifications, T);
|
|
232
|
+
del("chat_part", `provider || ':' || id IN (${parts})`, conversations, K, R);
|
|
233
|
+
del("chat_event", `provider || ':' || id IN (SELECT value FROM json_each(?))`, conversations);
|
|
239
234
|
del("chat_card_task", IN("task_ref"), T);
|
|
240
235
|
del("chat_batch_item", IN("task_ref"), T);
|
|
241
236
|
del("result_shot_sent", IN("run"), R);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** Settings → Projects → Pull requests: one project's pull-request setup, in plain words. Off shows what setup
|
|
2
2
|
* found (or what to fix) and one Turn on; on shows where pull requests go and how they merge. Every change takes
|
|
3
3
|
* the password; turning on restates the exact repository and branch that were checked. */
|
|
4
|
+
import { type Html } from "./html.js";
|
|
4
5
|
import type { Publishing, PublishingPlan } from "./pull-request-flow.js";
|
|
5
6
|
export type PullRequestSettingsView = {
|
|
6
7
|
repo: string;
|
|
@@ -19,5 +20,5 @@ export type PullRequestSettingsView = {
|
|
|
19
20
|
said: string | null;
|
|
20
21
|
problem: string | null;
|
|
21
22
|
};
|
|
22
|
-
export declare function pullRequestSettingsHtml(view: PullRequestSettingsView):
|
|
23
|
+
export declare function pullRequestSettingsHtml(view: PullRequestSettingsView): Html;
|
|
23
24
|
export declare const PULL_REQUEST_SETTINGS_CSS = "\n .pr-settings { display: grid; gap: .6rem; max-width: 40rem; }\n .pr-settings p, .pr-settings h2 { margin: 0; }\n .pr-settings label { display: grid; gap: .25rem; }\n .pr-settings label input[type=password], .pr-settings select { min-height: 44px; box-sizing: border-box; width: 100%; }\n .pr-settings .pr-check { display: flex; align-items: center; gap: .5rem; min-height: 44px; }\n .pr-settings button { min-height: 44px; justify-self: start; }\n .pr-terms { margin: 0; padding-left: 1.1rem; display: grid; gap: .25rem; }\n @media (max-width: 480px) { .pr-settings button { justify-self: stretch; } }\n";
|
package/dist/pull-request-ui.js
CHANGED
|
@@ -1,47 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
/** Settings → Projects → Pull requests: one project's pull-request setup, in plain words. Off shows what setup
|
|
2
|
+
* found (or what to fix) and one Turn on; on shows where pull requests go and how they merge. Every change takes
|
|
3
|
+
* the password; turning on restates the exact repository and branch that were checked. */
|
|
4
|
+
import { html, postForm } from "./html.js";
|
|
2
5
|
const METHOD_WORDS = { squash: "Squash and merge", merge: "Merge commit", rebase: "Rebase and merge" };
|
|
3
6
|
export function pullRequestSettingsHtml(view) {
|
|
4
|
-
const
|
|
5
|
-
const password = `<label>Password<input type="password" name="password" autocomplete="current-password" required></label>`;
|
|
7
|
+
const post = (act, body, hidden = {}) => postForm("/settings/pull-requests", body, { attrs: { class: "card pr-settings" }, hidden: { repo: view.repo, act, ...hidden } });
|
|
8
|
+
const password = html `<label>Password<input type="password" name="password" autocomplete="current-password" required></label>`;
|
|
6
9
|
const notes = [
|
|
7
|
-
view.said === null ? "" : `<p class="notice" role="status">${
|
|
8
|
-
view.problem === null ? "" : `<p class="problem" role="alert">${
|
|
9
|
-
]
|
|
10
|
-
const head = `<p class="meta">${
|
|
10
|
+
view.said === null ? "" : html `<p class="notice" role="status">${view.said}</p>`,
|
|
11
|
+
view.problem === null ? "" : html `<p class="problem" role="alert">${view.problem}</p>`,
|
|
12
|
+
];
|
|
13
|
+
const head = html `<p class="meta">${view.name} · <span class="mono">${view.repo}</span></p>${notes}`;
|
|
11
14
|
const publishing = view.publishing;
|
|
12
15
|
if (publishing.on) {
|
|
13
|
-
const status = `<section class="card pr-settings"><p><strong>On</strong> · <span class="mono">${
|
|
14
|
-
`<p class="meta">${publishing.legacy ? "Every finished build opens a pull request (set up with publish grant)." : "Complete offers “Complete and open a pull request”."} Set up by ${escape(publishing.grantedBy)}.</p></section>`;
|
|
16
|
+
const status = html `<section class="card pr-settings"><p><strong>On</strong> · <span class="mono">${publishing.githubRepo}</span> into <span class="mono">${publishing.base}</span></p><p class="meta">${publishing.legacy ? "Every finished build opens a pull request (set up with publish grant)." : "Complete offers “Complete and open a pull request”."} Set up by ${publishing.grantedBy}.</p></section>`;
|
|
15
17
|
if (!view.canChange)
|
|
16
|
-
return head
|
|
17
|
-
const methods = ["squash", "merge", "rebase"].map(method => `<option value="${method}"${publishing.mergeMethod === method ?
|
|
18
|
-
return head
|
|
19
|
-
`<form method="post" action="/settings/pull-requests" class="card pr-settings">${hidden("settings")}` +
|
|
20
|
-
`<h2>Merging</h2>` +
|
|
21
|
-
`<label>Merge method<select name="method">${methods}</select></label>` +
|
|
22
|
-
`<label class="row pr-check"><input type="checkbox" name="when-green" value="1"${publishing.mergeWhenGreen ? " checked" : ""}> Merge when checks pass</label>` +
|
|
23
|
-
`<p class="meta">Off by default. When on, a pull request merges by itself once its checks pass on the completed commit. Branches are deleted after merging.</p>` +
|
|
24
|
-
`${password}<button type="submit">Save</button></form>` +
|
|
25
|
-
`<details class="settings-more"><summary>Turn off pull requests</summary>` +
|
|
26
|
-
`<form method="post" action="/settings/pull-requests" class="card pr-settings">${hidden("off")}` +
|
|
27
|
-
`<p class="meta">Nothing more is pushed. Open pull requests stay on GitHub.</p>${password}<button type="submit" class="danger">Turn off</button></form></details>`;
|
|
18
|
+
return html `${head}${status}`;
|
|
19
|
+
const methods = ["squash", "merge", "rebase"].map(method => html `<option value="${method}"${publishing.mergeMethod === method ? html ` selected` : ""}>${METHOD_WORDS[method]}${method === "squash" ? " (default)" : ""}</option>`);
|
|
20
|
+
return html `${head}${status}${post("settings", html `<h2>Merging</h2><label>Merge method<select name="method">${methods}</select></label><label class="row pr-check"><input type="checkbox" name="when-green" value="1"${publishing.mergeWhenGreen ? html ` checked` : ""}> Merge when checks pass</label><p class="meta">Off by default. When on, a pull request merges by itself once its checks pass on the completed commit. Branches are deleted after merging.</p>${password}<button type="submit">Save</button>`)}<details class="settings-more"><summary>Turn off pull requests</summary>${post("off", html `<p class="meta">Nothing more is pushed. Open pull requests stay on GitHub.</p>${password}<button type="submit" class="danger">Turn off</button>`)}</details>`;
|
|
28
21
|
}
|
|
29
22
|
const check = view.check;
|
|
30
23
|
if (check === null)
|
|
31
|
-
return head
|
|
24
|
+
return html `${head}<section class="card pr-settings"><p><strong>Off</strong></p><p class="meta">An approver can turn on pull requests.</p></section>`;
|
|
32
25
|
if (!check.ok) {
|
|
33
|
-
return head
|
|
34
|
-
`<p><a class="button-link" href="/settings/pull-requests?repo=${encodeURIComponent(view.repo)}">Check again</a></p></section>`;
|
|
26
|
+
return html `${head}<section class="card pr-settings"><p><strong>Can’t turn on yet</strong></p><p>${check.message}</p><p><a class="button-link" href="/settings/pull-requests?repo=${encodeURIComponent(view.repo)}">Check again</a></p></section>`;
|
|
35
27
|
}
|
|
36
28
|
const plan = check.plan;
|
|
37
|
-
return head
|
|
38
|
-
`<form method="post" action="/settings/pull-requests" class="card pr-settings">${hidden("on")}` +
|
|
39
|
-
`<input type="hidden" name="github" value="${escape(plan.githubRepo)}"><input type="hidden" name="base" value="${escape(plan.base)}">` +
|
|
40
|
-
`<p><strong>Ready to turn on</strong></p>` +
|
|
41
|
-
`<ul class="pr-terms"><li>Pull requests open on <span class="mono">${escape(plan.githubRepo)}</span> into <span class="mono">${escape(plan.base)}</span>${plan.account === null ? "" : `, as ${escape(plan.account)}`}.</li>` +
|
|
42
|
-
`<li>Complete offers “Complete and open a pull request”, from the exact completed commit. A flow's Pull request zone can open one too.</li>` +
|
|
43
|
-
`<li>Merging squashes and deletes the branch. A person merges with their password; a flow merges only when its zone says so, after a person approved the card.</li></ul>` +
|
|
44
|
-
`${password}<button type="submit">Turn on pull requests</button></form>`;
|
|
29
|
+
return html `${head}${post("on", html `<p><strong>Ready to turn on</strong></p><ul class="pr-terms"><li>Pull requests open on <span class="mono">${plan.githubRepo}</span> into <span class="mono">${plan.base}</span>${plan.account === null ? "" : `, as ${plan.account}`}.</li><li>Complete offers “Complete and open a pull request”, from the exact completed commit. A flow's Pull request zone can open one too.</li><li>Merging squashes and deletes the branch. A person merges with their password; a flow merges only when its zone says so, after a person approved the card.</li></ul>${password}<button type="submit">Turn on pull requests</button>`, { github: plan.githubRepo, base: plan.base })}`;
|
|
45
30
|
}
|
|
46
31
|
export const PULL_REQUEST_SETTINGS_CSS = `
|
|
47
32
|
.pr-settings { display: grid; gap: .6rem; max-width: 40rem; }
|