toolroll 0.9.54 → 0.9.56
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 +19 -0
- package/README.md +29 -12
- package/dist/action-ledger.d.ts +2 -4
- package/dist/action-ledger.js +2 -10
- package/dist/agent-onboard.js +4 -1
- package/dist/approval-policy.d.ts +5 -5
- package/dist/approval-policy.js +4 -4
- package/dist/approval-rules-ui.d.ts +3 -2
- package/dist/approval-rules-ui.js +17 -22
- 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 +14 -15
- package/dist/browser-workspace.js +0 -3
- package/dist/budget-alerts.js +3 -3
- package/dist/chat-actions.d.ts +23 -18
- package/dist/chat-actions.js +110 -88
- package/dist/chat-ask.d.ts +2 -2
- package/dist/chat-ask.js +5 -5
- package/dist/chat-channel.d.ts +15 -15
- package/dist/chat-channel.js +20 -20
- package/dist/chat-controls.d.ts +0 -4
- package/dist/chat-controls.js +0 -1
- package/dist/chat-core.js +1 -1
- package/dist/chat-delivery-state.js +2 -2
- package/dist/chat-delivery.d.ts +2 -2
- package/dist/chat-delivery.js +27 -34
- package/dist/chat-flow.js +3 -3
- package/dist/chat-messages.d.ts +1 -1
- package/dist/chat-messages.js +4 -4
- package/dist/chat-migration.d.ts +1 -1
- package/dist/chat-migration.js +11 -11
- package/dist/chat-polish.d.ts +5 -4
- package/dist/chat-polish.js +9 -10
- package/dist/chat-rooms.js +3 -3
- package/dist/chat-warmth.d.ts +2 -2
- 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 +15 -10
- 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-actions.d.ts +32 -27
- package/dist/contracts/chat-actions.js +29 -11
- package/dist/contracts/chat-content.d.ts +1 -1
- package/dist/contracts/chat-content.js +1 -1
- package/dist/contracts/chat-tables.js +4 -4
- package/dist/contracts/cli.d.ts +1 -3
- package/dist/contracts/console-api.d.ts +7 -7
- package/dist/contracts/console-api.js +13 -12
- package/dist/contracts/flow-propose.d.ts +3 -3
- package/dist/contracts/flow.d.ts +132 -125
- package/dist/contracts/flow.js +27 -12
- package/dist/contracts/health.d.ts +1 -3
- package/dist/contracts/lead-context.d.ts +1 -1
- package/dist/contracts/lead-context.js +2 -2
- package/dist/contracts/lead-tools.d.ts +20 -18
- package/dist/contracts/lead-tools.js +15 -15
- package/dist/contracts/stage-output.d.ts +1 -1
- package/dist/contracts/stage-output.js +1 -1
- package/dist/contracts/store-json.d.ts +23 -22
- package/dist/contracts/store-json.js +17 -17
- package/dist/contracts/{teammate-turn.d.ts → subagent-turn.d.ts} +5 -5
- package/dist/contracts/{teammate-turn.js → subagent-turn.js} +7 -7
- package/dist/control-ui.d.ts +5 -4
- package/dist/control-ui.js +9 -34
- package/dist/converse.d.ts +23 -23
- package/dist/converse.js +28 -28
- package/dist/coordinator-proposals.d.ts +2 -2
- package/dist/coordinator-proposals.js +1 -1
- package/dist/coordinator.js +1 -1
- 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/deprecations.d.ts +12 -0
- package/dist/deprecations.js +20 -0
- package/dist/desktop-update-gate.js +8 -4
- package/dist/desktop-update.js +6 -1
- package/dist/discord-chat.js +6 -6
- package/dist/discord-settings.d.ts +3 -2
- package/dist/discord-settings.js +17 -42
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +1 -1
- package/dist/envelope.js +10 -1
- 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 +7 -7
- package/dist/flow-engine.d.ts +2 -2
- package/dist/flow-engine.js +21 -21
- package/dist/flow-gallery-ui.d.ts +4 -3
- package/dist/flow-gallery-ui.js +28 -43
- package/dist/flow-insights.d.ts +1 -1
- package/dist/flow-share.js +13 -8
- package/dist/flow-starters-ui.d.ts +2 -1
- package/dist/flow-starters-ui.js +12 -15
- package/dist/flow-steps.d.ts +5 -5
- package/dist/flow-steps.js +19 -19
- package/dist/flow-triggers.d.ts +2 -1
- package/dist/flow-triggers.js +17 -15
- package/dist/flows-cli.js +1 -1
- package/dist/flows-ui.d.ts +4 -3
- package/dist/flows-ui.js +38 -49
- package/dist/flows.js +21 -20
- package/dist/guides.js +112 -6
- 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/invoke.js +1 -1
- package/dist/kits-ui.d.ts +5 -4
- package/dist/kits-ui.js +22 -24
- package/dist/kits.d.ts +12 -12
- package/dist/kits.js +31 -31
- package/dist/knowledge-ui.d.ts +6 -5
- package/dist/knowledge-ui.js +26 -25
- package/dist/{mate-cli.d.ts → lead-cli.d.ts} +11 -11
- package/dist/{mate-cli.js → lead-cli.js} +38 -38
- package/dist/lead-commitments.js +4 -4
- package/dist/lead-context.d.ts +12 -2
- package/dist/lead-context.js +15 -17
- package/dist/{mate-contract.d.ts → lead-contract.d.ts} +3 -3
- package/dist/{mate-contract.js → lead-contract.js} +7 -6
- package/dist/{mate-doors.d.ts → lead-doors.d.ts} +6 -6
- package/dist/{mate-doors.js → lead-doors.js} +24 -24
- package/dist/lead-follow.d.ts +5 -5
- package/dist/lead-follow.js +9 -9
- package/dist/lead-people.d.ts +5 -5
- package/dist/lead-people.js +14 -14
- package/dist/{mate-progress.d.ts → lead-progress.d.ts} +9 -9
- package/dist/{mate-progress.js → lead-progress.js} +3 -3
- package/dist/{mate-tools.d.ts → lead-tools.d.ts} +19 -19
- package/dist/{mate-tools.js → lead-tools.js} +109 -101
- package/dist/{mate.d.ts → lead.d.ts} +27 -27
- package/dist/{mate.js → lead.js} +102 -102
- package/dist/ledger-view.d.ts +2 -1
- package/dist/ledger-view.js +27 -29
- package/dist/limits-ui.d.ts +3 -2
- package/dist/limits-ui.js +8 -8
- package/dist/live-bus.d.ts +50 -8
- package/dist/live-bus.js +133 -50
- package/dist/mcp-connect.d.ts +1 -1
- package/dist/mcp-connect.js +3 -3
- package/dist/mcp-core.js +1 -1
- package/dist/mcp-oauth.js +35 -36
- package/dist/memory-pass.d.ts +2 -2
- package/dist/memory-pass.js +7 -7
- package/dist/models-ui.d.ts +2 -1
- package/dist/models-ui.js +25 -35
- package/dist/monitoring-ui.d.ts +4 -3
- package/dist/monitoring-ui.js +13 -26
- package/dist/notification-rows.d.ts +2 -2
- package/dist/openrouter-models.d.ts +2 -1
- package/dist/openrouter-models.js +5 -9
- package/dist/operate.d.ts +3 -3
- package/dist/operate.js +46 -41
- package/dist/people-audit-ui.d.ts +2 -1
- package/dist/people-audit-ui.js +13 -13
- package/dist/policy-ui.d.ts +3 -2
- package/dist/policy-ui.js +17 -23
- package/dist/policy.d.ts +1 -1
- package/dist/principal.d.ts +1 -1
- package/dist/principal.js +2 -2
- package/dist/project-delete-ui.d.ts +4 -3
- package/dist/project-delete-ui.js +16 -28
- package/dist/project-delete.d.ts +1 -1
- package/dist/project-delete.js +26 -26
- package/dist/project-memory.js +1 -1
- package/dist/project-tools.d.ts +1 -1
- package/dist/project-tools.js +1 -1
- 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/retention-ui.d.ts +7 -1
- package/dist/retention-ui.js +7 -11
- package/dist/retention.js +4 -4
- package/dist/scope.d.ts +2 -2
- package/dist/scout-report.d.ts +1 -1
- package/dist/scout-report.js +1 -1
- package/dist/serve.d.ts +11 -2
- package/dist/serve.js +105 -99
- package/dist/server/chat.d.ts +7 -10
- package/dist/server/chat.js +984 -1021
- 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 +854 -773
- package/dist/server/guards.d.ts +1 -1
- package/dist/server/guards.js +8 -5
- 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 +581 -573
- package/dist/server/remote.d.ts +4 -7
- package/dist/server/remote.js +39 -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 +154 -0
- package/dist/server/render-settings.js +580 -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 +167 -77
- package/dist/server/runtime.d.ts +28 -20
- 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 +2186 -2121
- 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 +1489 -1420
- package/dist/server-telemetry.d.ts +4 -3
- package/dist/server-telemetry.js +4 -8
- package/dist/setup-guide.d.ts +2 -2
- 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-chat.d.ts +2 -2
- package/dist/slack-chat.js +6 -6
- package/dist/slack-settings.d.ts +3 -2
- package/dist/slack-settings.js +17 -42
- package/dist/spend-ui.d.ts +5 -4
- package/dist/spend-ui.js +17 -40
- package/dist/spend.d.ts +14 -14
- package/dist/spend.js +31 -31
- 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-baseline.d.ts +19 -0
- package/dist/store-baseline.js +2873 -0
- package/dist/store.d.ts +197 -249
- package/dist/store.js +747 -3084
- package/dist/subagent-admin.d.ts +53 -0
- package/dist/{teammate-admin.js → subagent-admin.js} +37 -37
- package/dist/{teammate-desk.d.ts → subagent-desk.d.ts} +22 -23
- package/dist/{teammate-desk.js → subagent-desk.js} +26 -35
- package/dist/{teammate-memory.d.ts → subagent-memory.d.ts} +17 -17
- package/dist/{teammate-memory.js → subagent-memory.js} +41 -41
- package/dist/{teammate-question.d.ts → subagent-question.d.ts} +6 -6
- package/dist/{teammate-question.js → subagent-question.js} +17 -17
- package/dist/{teammate-tools.d.ts → subagent-tools.d.ts} +18 -18
- package/dist/{teammate-tools.js → subagent-tools.js} +36 -36
- package/dist/{teammate-week.d.ts → subagent-week.d.ts} +10 -10
- package/dist/{teammate-week.js → subagent-week.js} +23 -23
- package/dist/{teammate-work.d.ts → subagent-work.d.ts} +21 -21
- package/dist/{teammate-work.js → subagent-work.js} +50 -50
- package/dist/subagents-ui.d.ts +22 -0
- package/dist/subagents-ui.js +137 -0
- package/dist/{teammates.d.ts → subagents.d.ts} +11 -11
- package/dist/{teammates.js → subagents.js} +21 -21
- package/dist/subscription-chat.d.ts +13 -13
- package/dist/subscription-chat.js +19 -19
- package/dist/surface.d.ts +19 -2
- package/dist/surface.js +20 -33
- 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-cli.d.ts +1 -0
- package/dist/team-cli.js +14 -2
- package/dist/team-http.d.ts +5 -9
- package/dist/team-http.js +5 -63
- package/dist/team-leads.js +13 -13
- package/dist/team-runtime.d.ts +2 -2
- package/dist/team-runtime.js +8 -8
- package/dist/team-ui.d.ts +2 -1
- package/dist/team-ui.js +2 -5
- package/dist/teams-chat.js +6 -6
- package/dist/teams-settings.d.ts +3 -2
- package/dist/teams-settings.js +20 -31
- package/dist/telegram-flow.d.ts +3 -3
- package/dist/telegram-flow.js +3 -3
- package/dist/{telegram-mate.d.ts → telegram-lead.d.ts} +5 -5
- package/dist/{telegram-mate.js → telegram-lead.js} +27 -27
- package/dist/telegram-settings.d.ts +3 -2
- package/dist/telegram-settings.js +14 -25
- package/dist/telegram-status.js +1 -1
- package/dist/telegram-team.d.ts +1 -1
- package/dist/telegram-team.js +5 -5
- package/dist/telegram.d.ts +1 -1
- package/dist/telegram.js +9 -30
- package/dist/text-limits.d.ts +18 -18
- package/dist/text-limits.js +18 -18
- package/dist/toolroll-update-ui.d.ts +3 -2
- package/dist/toolroll-update-ui.js +35 -39
- package/dist/toolroll-update.d.ts +8 -0
- package/dist/toolroll-update.js +45 -8
- 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-ui.d.ts +4 -2
- package/dist/workspace-ui.js +10 -10
- package/package.json +1 -2
- package/dist/server/shared.d.ts +0 -3151
- package/dist/server/shared.js +0 -12001
- package/dist/teammate-admin.d.ts +0 -53
- package/dist/teammates-ui.d.ts +0 -20
- package/dist/teammates-ui.js +0 -166
package/dist/operate.js
CHANGED
|
@@ -22,7 +22,7 @@ import { checkoutPlan, cleanCheckouts, discardCheckout, finishedCheckouts, slimK
|
|
|
22
22
|
import { closeOutCheckouts, closeOutRuns, stoppedWords } from "./run-closeout.js";
|
|
23
23
|
import { MIN_DAYS, RETENTION_KINDS, countWords, dailyRetention, isRetentionKind, lastSweepAt, parsePeriod, periodLabel, periodWords, retentionPlan, sweepWords } from "./retention.js";
|
|
24
24
|
import { checkPolicy, parseList, policyParts } from "./policy.js";
|
|
25
|
-
import { billingOf, budgetHoldWords, budgetLabel, budgetStates, monthNamed, monthOf, spendItems,
|
|
25
|
+
import { billingOf, budgetHoldWords, budgetLabel, budgetStates, monthNamed, monthOf, spendItems, subagentNames, usd as spendUsd } from "./spend.js";
|
|
26
26
|
import { spendCsv } from "./spend-ui.js";
|
|
27
27
|
import { buildExport, exportSummary, exportZip, writeExportFolder } from "./export.js";
|
|
28
28
|
import { startBudgetAlerts } from "./budget-alerts.js";
|
|
@@ -43,8 +43,8 @@ import { origin, readMonitoring } from "./monitoring-settings.js";
|
|
|
43
43
|
import { logEvent } from "./log.js";
|
|
44
44
|
import { FLOW_EVERY_MS, flowHousekeeping, moveCards, moveCardsAfter } from "./flow-cadence.js";
|
|
45
45
|
import { readHooksBase, scheduleFromWords } from "./flow-triggers.js";
|
|
46
|
-
import {
|
|
47
|
-
import { runRequestedUndos,
|
|
46
|
+
import { sendSubagentSummaries } from "./subagent-admin.js";
|
|
47
|
+
import { runRequestedUndos, sendSubagentWeeklies } from "./subagent-week.js";
|
|
48
48
|
import { refreshConnections } from "./mcp-connect.js";
|
|
49
49
|
import { followDiscord } from "./discord.js";
|
|
50
50
|
import { followTeams } from "./teams.js";
|
|
@@ -156,8 +156,8 @@ import { bridgePass, clearBotToken, createTransport, followBridge, hashPairingCo
|
|
|
156
156
|
import { scanRepo } from "./capscan.js";
|
|
157
157
|
import { computeGaps, describeCapability } from "./gaps.js";
|
|
158
158
|
import { ask as promptAsk, askHidden as promptHidden, confirm as promptConfirm, interactive as promptInteractive, underAgent } from "./prompt.js";
|
|
159
|
-
import {
|
|
160
|
-
import { confirmCoordinatorProposal, dismissCoordinatorProposal } from "./
|
|
159
|
+
import { runLeadCli, answerContextLines } from "./lead-cli.js";
|
|
160
|
+
import { confirmCoordinatorProposal, dismissCoordinatorProposal } from "./lead-doors.js";
|
|
161
161
|
import { verifyApproverByPassword as approverByPassword } from "./principal.js";
|
|
162
162
|
import { activeRemote, REMOTE_MESSAGES, REMOTE_REFUSED_FLAGS, REMOTE_SCOPES, RemoteRefusal, remoteAllows, remoteLensOf, remoteProjectOf, remoteRowOf, remoteSecret, reproveRemote, withRemote } from "./operate-remote.js";
|
|
163
163
|
import { projectAuthority } from "./project-access.js";
|
|
@@ -256,9 +256,9 @@ export const OPERATE_HELP = `toolroll — operating the queue
|
|
|
256
256
|
toolroll integrations [--json] which integrations work: Connected, Not set up or Broken, and what to do (--saved: no new checks)
|
|
257
257
|
toolroll monitoring where the audit stream and traces go, and how each destination is doing
|
|
258
258
|
toolroll check-progress <run> current or final approved check progress
|
|
259
|
-
toolroll spend [--month YYYY-MM] [--csv] what agent work cost, by project, person,
|
|
259
|
+
toolroll spend [--month YYYY-MM] [--csv] what agent work cost, by project, person, subagent and model
|
|
260
260
|
toolroll export --out <path> [--zip] everything Toolroll knows, in a folder or .zip (no secrets)
|
|
261
|
-
toolroll budget list|set|remove monthly budgets (--all | --project <p> | --person <name> | --
|
|
261
|
+
toolroll budget list|set|remove monthly budgets (--all | --project <p> | --person <name> | --subagent <id>) --usd <n> [--alerts-only]
|
|
262
262
|
toolroll notifications [quiet|all] how chats reach you: only when you're needed (the default), or every step
|
|
263
263
|
toolroll notifications digest <HH:MM>|off one evening message: what finished, what waits, what failed
|
|
264
264
|
toolroll notifications mute|unmute --repo <p> no pings for a project; the console and digest keep it
|
|
@@ -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>
|
|
@@ -477,7 +477,7 @@ Agents — which provider and model each phase runs on
|
|
|
477
477
|
toolroll chat --as <you> [--repo <path>…] [--say "…"] [--end]
|
|
478
478
|
[--ceiling-usd <n>] [--json] (password at the prompt;
|
|
479
479
|
--token <t> only for scripts — it lands in shell history)
|
|
480
|
-
|
|
480
|
+
your lead: one conversation across your projects, the same thread the
|
|
481
481
|
console shows; the password mints a spending session once; it reads
|
|
482
482
|
and proposes, you confirm cards (confirm N / dismiss N / open N;
|
|
483
483
|
confirm N yes for an irreversible answer)
|
|
@@ -625,7 +625,7 @@ export const OPERATE_VALUE_FLAGS = new Set([
|
|
|
625
625
|
// v103: the ledger chain and evidence packs.
|
|
626
626
|
"checkpoint", "out", "from", "to",
|
|
627
627
|
// v105: spend and budgets.
|
|
628
|
-
"month", "usd", "person", "teammate",
|
|
628
|
+
"month", "usd", "person", "subagent", "teammate",
|
|
629
629
|
// Sprint 8: the organisation policy.
|
|
630
630
|
"providers", "models", "tools",
|
|
631
631
|
"project-root", "schedule", "ceiling", "require",
|
|
@@ -869,7 +869,7 @@ async function dispatchOn(command, positional, flags, repoList, base, options, e
|
|
|
869
869
|
...(options.flowFetch === undefined ? {} : { flowFetch: options.flowFetch }),
|
|
870
870
|
...(options.integrationIo === undefined ? {} : { integrationIo: options.integrationIo }),
|
|
871
871
|
...(options.shouldStop === undefined ? {} : { shouldStop: options.shouldStop }),
|
|
872
|
-
...(options.
|
|
872
|
+
...(options.leadSeams === undefined ? {} : { leadSeams: options.leadSeams }),
|
|
873
873
|
...(options.waitSleep === undefined ? {} : { waitSleep: options.waitSleep }),
|
|
874
874
|
...(options.releaseIo === undefined ? {} : { releaseIo: options.releaseIo }),
|
|
875
875
|
...(options.installBin === undefined ? {} : { installBin: options.installBin }),
|
|
@@ -1023,7 +1023,7 @@ function telegramCanDeliver(context, token) {
|
|
|
1023
1023
|
function telegramConversation(context, options = {}) {
|
|
1024
1024
|
return {
|
|
1025
1025
|
evidenceRoot: context.evidenceRoot,
|
|
1026
|
-
...(context.
|
|
1026
|
+
...(context.leadSeams?.subscriptionRunner === undefined ? {} : { subscriptionRunner: context.leadSeams.subscriptionRunner }),
|
|
1027
1027
|
// Re-read on every card and every `/task`: the same console-url chat
|
|
1028
1028
|
// links use, held to an https origin, and — inside `up`, where this
|
|
1029
1029
|
// process also serves the console — equal to that console's own
|
|
@@ -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}.`,
|
|
@@ -2269,25 +2273,25 @@ async function tickCommand(flags, context) {
|
|
|
2269
2273
|
const replyPass = housekeeping?.replies ?? { read: 0, taken: 0, problem: null };
|
|
2270
2274
|
const stepPass = housekeeping?.steps ?? { ran: 0, problems: [] };
|
|
2271
2275
|
const flowPass = housekeeping?.flows ?? { moved: 0, filed: [], problems: [] };
|
|
2272
|
-
// Each
|
|
2276
|
+
// Each subagent's daily summary to its manager (v92), once, after 5 pm; its weekly report (v97), Monday mornings.
|
|
2273
2277
|
if (!buildsOnly)
|
|
2274
2278
|
try {
|
|
2275
|
-
|
|
2276
|
-
|
|
2279
|
+
sendSubagentSummaries(store, repo, clock());
|
|
2280
|
+
sendSubagentWeeklies(store, repo, clock());
|
|
2277
2281
|
}
|
|
2278
2282
|
catch (error) {
|
|
2279
|
-
flowPass.problems.push(`
|
|
2283
|
+
flowPass.problems.push(`subagent summaries: ${error instanceof Error ? error.message : "could not send"}`);
|
|
2280
2284
|
}
|
|
2281
|
-
// Undoing a
|
|
2285
|
+
// Undoing a subagent's tool call asked for in chat (v97): made here, as the person who asked.
|
|
2282
2286
|
if (!buildsOnly && context.shouldStop?.() !== true) {
|
|
2283
2287
|
try {
|
|
2284
2288
|
await runRequestedUndos(store, {}, clock());
|
|
2285
2289
|
}
|
|
2286
2290
|
catch (error) {
|
|
2287
|
-
flowPass.problems.push(`
|
|
2291
|
+
flowPass.problems.push(`subagent undo: ${error instanceof Error ? error.message : "could not run"}`);
|
|
2288
2292
|
}
|
|
2289
2293
|
}
|
|
2290
|
-
// Tools connected by signing in: a sign-in that runs out within ten minutes is renewed here, so a build or a
|
|
2294
|
+
// Tools connected by signing in: a sign-in that runs out within ten minutes is renewed here, so a build or a subagent's call starts with a fresh one.
|
|
2291
2295
|
if (!buildsOnly && context.shouldStop?.() !== true) {
|
|
2292
2296
|
try {
|
|
2293
2297
|
flowPass.problems.push(...(await refreshConnections(store, [repo], clock(), context.flowStepIo?.fetch === undefined ? {} : { fetcher: context.flowStepIo.fetch })).problems);
|
|
@@ -6046,19 +6050,19 @@ async function runWatchLoop(args) {
|
|
|
6046
6050
|
: startTelegramFollower({ context, publicUrl: text(flags, "public-url"), signal: followController.signal, progress, label: "watch: bridge", died: { prefix: "watch: ", then: "taps wait for the next watch" } });
|
|
6047
6051
|
const slackFollower = followSlack({ store, dir: dirname(context.databaseFile), signal: followController.signal,
|
|
6048
6052
|
readProjects: telegramReadProjects(context), evidenceRoot: context.evidenceRoot,
|
|
6049
|
-
...(context.
|
|
6053
|
+
...(context.leadSeams?.subscriptionRunner ? { subscriptionRunner: context.leadSeams.subscriptionRunner } : {}),
|
|
6050
6054
|
origin: () => phoneOrigin(process.env, dirname(context.databaseFile), { serverOrigin: text(flags, "public-url") ?? null }),
|
|
6051
6055
|
notifications: () => effectivePrimary(process.env, dirname(context.databaseFile), loadBotToken(process.env, context.telegramTokenFile) !== null).channel === "slack",
|
|
6052
6056
|
}).catch(() => progress("watch: Slack stopped. Check Slack settings before reconnecting."));
|
|
6053
6057
|
const teamsFollower = followTeams({ store, dir: dirname(context.databaseFile), signal: followController.signal,
|
|
6054
6058
|
readProjects: telegramReadProjects(context), evidenceRoot: context.evidenceRoot,
|
|
6055
|
-
...(context.
|
|
6059
|
+
...(context.leadSeams?.subscriptionRunner ? { subscriptionRunner: context.leadSeams.subscriptionRunner } : {}),
|
|
6056
6060
|
origin: () => phoneOrigin(process.env, dirname(context.databaseFile), { serverOrigin: text(flags, "public-url") ?? null }),
|
|
6057
6061
|
notifications: () => effectivePrimary(process.env, dirname(context.databaseFile), loadBotToken(process.env, context.telegramTokenFile) !== null).channel === "teams",
|
|
6058
6062
|
}).catch(() => progress("watch: Teams stopped. Check Teams settings before reconnecting."));
|
|
6059
6063
|
const discordFollower = followDiscord({ store, dir: dirname(context.databaseFile), signal: followController.signal,
|
|
6060
6064
|
readProjects: telegramReadProjects(context), evidenceRoot: context.evidenceRoot,
|
|
6061
|
-
...(context.
|
|
6065
|
+
...(context.leadSeams?.subscriptionRunner ? { subscriptionRunner: context.leadSeams.subscriptionRunner } : {}),
|
|
6062
6066
|
origin: () => phoneOrigin(process.env, dirname(context.databaseFile), { serverOrigin: text(flags, "public-url") ?? null }),
|
|
6063
6067
|
notifications: () => effectivePrimary(process.env, dirname(context.databaseFile), loadBotToken(process.env, context.telegramTokenFile) !== null).channel === "discord",
|
|
6064
6068
|
}).catch(() => progress("watch: Discord stopped. Check Discord settings before reconnecting."));
|
|
@@ -9983,7 +9987,7 @@ function describeApproveFailure(reason, id) {
|
|
|
9983
9987
|
if (reason === "requester")
|
|
9984
9988
|
return `you filed ${id}, and this project needs someone else to approve it`;
|
|
9985
9989
|
if (reason === "person-required")
|
|
9986
|
-
return `${id} is protected work: a person has to approve it, not an operating mode, a schedule or
|
|
9990
|
+
return `${id} is protected work: a person has to approve it, not an operating mode, a schedule or a subagent`;
|
|
9987
9991
|
if (reason === "second-approver")
|
|
9988
9992
|
return `your approval of ${id} is recorded; it is protected work, so a second person needs to approve it (toolroll task approve ${id} as them)`;
|
|
9989
9993
|
if (reason === "no-approvers") {
|
|
@@ -10100,7 +10104,7 @@ async function chatCommand(flags, context) {
|
|
|
10100
10104
|
return fail(write, json, "chat", "usage", "Choose --follow or --no-follow.", EXIT.usage);
|
|
10101
10105
|
const credentials = await askCredentials(flags, context);
|
|
10102
10106
|
if (credentials === null) {
|
|
10103
|
-
return fail(write, json, "chat", "usage", "the
|
|
10107
|
+
return fail(write, json, "chat", "usage", "the lead takes your name and password — `--as <you>` and the hidden prompt; `--token <t>` only where a script must (it lands in shell history)", EXIT.usage);
|
|
10104
10108
|
}
|
|
10105
10109
|
const ceilingGiven = text(flags, "ceiling-usd");
|
|
10106
10110
|
// The ceiling: the `--repo` list, or the ENROLLED registry beside the
|
|
@@ -10116,7 +10120,7 @@ async function chatCommand(flags, context) {
|
|
|
10116
10120
|
return fail(write, json, "chat", "registry", "the enrolled-project registry could not be read — name projects with --repo", EXIT.refused);
|
|
10117
10121
|
repos = loaded.repos;
|
|
10118
10122
|
}
|
|
10119
|
-
const result = await
|
|
10123
|
+
const result = await runLeadCli({
|
|
10120
10124
|
store,
|
|
10121
10125
|
databaseFile: context.databaseFile,
|
|
10122
10126
|
write,
|
|
@@ -10127,7 +10131,7 @@ async function chatCommand(flags, context) {
|
|
|
10127
10131
|
end: flags.has("end"),
|
|
10128
10132
|
...(flags.has("follow") || flags.has("no-follow") ? { follow: flags.has("follow") && !flags.has("no-follow") } : {}),
|
|
10129
10133
|
ceilingUsd: ceilingGiven === undefined ? undefined : Number(ceilingGiven),
|
|
10130
|
-
...(context.
|
|
10134
|
+
...(context.leadSeams === undefined ? {} : { seams: { ...context.leadSeams, clock: context.leadSeams.clock ?? context.clock } }),
|
|
10131
10135
|
...(context.evidenceRoot === undefined ? {} : { evidenceRoot: context.evidenceRoot }),
|
|
10132
10136
|
// The console's own address (and its public one): links there read as "the task", "the result", "Settings → Lead".
|
|
10133
10137
|
appOrigin: [loadConsoleUrl(process.env, dirname(context.databaseFile)), phoneOrigin(process.env, dirname(context.databaseFile))],
|
|
@@ -10694,7 +10698,7 @@ async function projectDeleteCommand(positional, flags, context) {
|
|
|
10694
10698
|
`Deleted ${repo}: ${holdingsWords(done.removed)}.`, ...done.left.map(one => `Git kept ${one}.`), `The ledger records it as entry #${done.ledgerId}.`,
|
|
10695
10699
|
]);
|
|
10696
10700
|
}
|
|
10697
|
-
/** `spend [--month YYYY-MM] [--csv]` (v105): what agent work cost in a month, by project, person,
|
|
10701
|
+
/** `spend [--month YYYY-MM] [--csv]` (v105): what agent work cost in a month, by project, person, subagent and model. */
|
|
10698
10702
|
function spendCommand(flags, context) {
|
|
10699
10703
|
const command = "spend";
|
|
10700
10704
|
for (const name of flags.keys())
|
|
@@ -10704,7 +10708,7 @@ function spendCommand(flags, context) {
|
|
|
10704
10708
|
if (month === null)
|
|
10705
10709
|
return fail(context.write, context.json, command, "usage", "Use spend --month YYYY-MM.", EXIT.usage);
|
|
10706
10710
|
const items = spendItems(context.store.handle, month.from, month.to);
|
|
10707
|
-
const names =
|
|
10711
|
+
const names = subagentNames(context.store.handle);
|
|
10708
10712
|
if (flags.has("csv")) {
|
|
10709
10713
|
context.write(spendCsv(items, names).replace(/^\ufeff/, "").trimEnd());
|
|
10710
10714
|
return EXIT.ok;
|
|
@@ -10718,7 +10722,7 @@ function spendCommand(flags, context) {
|
|
|
10718
10722
|
byProject.set(item.project, (byProject.get(item.project) ?? 0) + (item.microusd ?? 0));
|
|
10719
10723
|
// v105: subscription work is $0; what binds it is its plan's windows, as the provider last said.
|
|
10720
10724
|
const limits = context.store.providerLimits();
|
|
10721
|
-
const windows = limitsView(limits, [], { project: repo => basename(repo),
|
|
10725
|
+
const windows = limitsView(limits, [], { project: repo => basename(repo), subagent: id => String(id) }, context.clock())?.tiles ?? [];
|
|
10722
10726
|
// Sized routing: each tier's tasks, time to a result and plan use, and what the tiers saved.
|
|
10723
10727
|
const tiers = tierReport(context.store.handle, month.from, month.to, items);
|
|
10724
10728
|
return succeed(context.write, context.json, command, { month: month.name, totalMicrousd: total, unpriced, items: items.length, budgets, limits, tiers: { rows: tiers.rows, saved: tiers.saved } }, () => [
|
|
@@ -10821,21 +10825,22 @@ async function budgetCommand(positional, flags, context) {
|
|
|
10821
10825
|
const store = context.store;
|
|
10822
10826
|
if (action === undefined || action === "list") {
|
|
10823
10827
|
const budgets = store.monthSpend(context.clock()).budgets;
|
|
10824
|
-
return succeed(context.write, context.json, command, { budgets }, () => budgets.length === 0 ? ["No budgets. `budget set --all|--project <path>|--person <name>|--
|
|
10828
|
+
return succeed(context.write, context.json, command, { budgets }, () => budgets.length === 0 ? ["No budgets. `budget set --all|--project <path>|--person <name>|--subagent <id> --usd <n>` makes one."]
|
|
10825
10829
|
: budgets.map(one => `#${one.id} ${budgetLabel(one).replace(/'s$/, "")}: ${spendUsd(one.spentMicrousd)} of ${spendUsd(one.limitMicrousd)} this month (${one.percent}%)${one.hardStop ? ", stops API work at 100%" : ", alerts only"}`));
|
|
10826
10830
|
}
|
|
10827
10831
|
if (action !== "set" && action !== "remove")
|
|
10828
10832
|
return fail(context.write, context.json, command, "usage", "Use budget list, budget set or budget remove.", EXIT.usage);
|
|
10829
|
-
|
|
10833
|
+
// --teammate: the flag's name before D5, still read this release.
|
|
10834
|
+
const allowed = new Set(["all", "project", "person", "subagent", "teammate", "usd", "alerts-only", "as", "token", "token-file", "token-env", "db", "json"]);
|
|
10830
10835
|
for (const name of flags.keys())
|
|
10831
10836
|
if (!allowed.has(name))
|
|
10832
10837
|
return fail(context.write, context.json, command, "usage", `--${name} is not a budget option.`, EXIT.usage);
|
|
10833
10838
|
const chosen = [flags.has("all") ? { scope: "installation", key: "*" } : null,
|
|
10834
10839
|
text(flags, "project") === undefined ? null : { scope: "project", key: resolve(text(flags, "project")) },
|
|
10835
10840
|
text(flags, "person") === undefined ? null : { scope: "person", key: text(flags, "person") },
|
|
10836
|
-
text(flags, "teammate") === undefined ? null : { scope: "
|
|
10841
|
+
(text(flags, "subagent") ?? text(flags, "teammate")) === undefined ? null : { scope: "subagent", key: (text(flags, "subagent") ?? text(flags, "teammate")) }].filter(one => one !== null);
|
|
10837
10842
|
if (chosen.length !== 1)
|
|
10838
|
-
return fail(context.write, context.json, command, "usage", "Name one of --all, --project <path>, --person <name> or --
|
|
10843
|
+
return fail(context.write, context.json, command, "usage", "Name one of --all, --project <path>, --person <name> or --subagent <id>.", EXIT.usage);
|
|
10839
10844
|
const target = chosen[0];
|
|
10840
10845
|
const acting = await askCredentials(flags, context);
|
|
10841
10846
|
const verified = acting === null ? null : authenticateApprover(store, acting.name, acting.token);
|
|
@@ -10844,9 +10849,9 @@ async function budgetCommand(positional, flags, context) {
|
|
|
10844
10849
|
}
|
|
10845
10850
|
const known = target.scope === "installation" || (target.scope === "project" ? store.knownRepos().includes(target.key)
|
|
10846
10851
|
: target.scope === "person" ? store.accountFacts().some(one => one.name === target.key && one.revokedAt === null)
|
|
10847
|
-
: store.handle.prepare("SELECT 1 FROM
|
|
10852
|
+
: store.handle.prepare("SELECT 1 FROM subagent WHERE id = ?").get(Number(target.key)) !== undefined);
|
|
10848
10853
|
if (!known)
|
|
10849
|
-
return fail(context.write, context.json, command, "not-found", "That isn't a project, person or
|
|
10854
|
+
return fail(context.write, context.json, command, "not-found", "That isn't a project, person or subagent here.", EXIT.refused);
|
|
10850
10855
|
if (action === "remove") {
|
|
10851
10856
|
const existing = store.budgets().find(one => one.scope === target.scope && one.key === target.key);
|
|
10852
10857
|
if (existing === undefined || !store.removeBudget(existing.id, acting.name, context.clock()))
|
|
@@ -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/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
|
}
|
package/dist/policy.d.ts
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
* (lowered), or, when there is none, doesn't run (refused); the words say which.
|
|
16
16
|
*
|
|
17
17
|
* Every road that admits work asks here: scope approval, the tick before a
|
|
18
|
-
* run starts (and build() as the last look), the lead and project chats,
|
|
18
|
+
* run starts (and build() as the last look), the lead and project chats, subagents and flow steps.
|
|
19
19
|
*/
|
|
20
20
|
import type { Database } from "./store.js";
|
|
21
21
|
import type { ExecutionProfile } from "./scope.js";
|
package/dist/principal.d.ts
CHANGED
|
@@ -9,7 +9,7 @@ export type VerifiedApprover = {
|
|
|
9
9
|
readonly repos: readonly string[];
|
|
10
10
|
readonly ceilingDigest: string;
|
|
11
11
|
};
|
|
12
|
-
/** The
|
|
12
|
+
/** The lead's ceiling digest: the admitted repos IN ORDER, hashed — `rN`
|
|
13
13
|
* is an index, so two surfaces naming the same repos in a different order
|
|
14
14
|
* hold different ceilings (slice-2 review, finding 3). */
|
|
15
15
|
export declare function ceilingDigestOf(repos: readonly string[]): string;
|
package/dist/principal.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The branded approver principal (mate arc, ruling 3).
|
|
3
3
|
*
|
|
4
|
-
* Every act the
|
|
4
|
+
* Every act the lead proposes and every confirmation an operator taps
|
|
5
5
|
* runs under a principal that PROVES it was authenticated — a password
|
|
6
6
|
* ceremony, or a cookie session the edge already proved (csrf, role,
|
|
7
7
|
* generation) and this module re-proves against the approver row. The
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { createHash } from "node:crypto";
|
|
17
17
|
import { authenticateAccount } from "./scope.js";
|
|
18
|
-
/** The
|
|
18
|
+
/** The lead's ceiling digest: the admitted repos IN ORDER, hashed — `rN`
|
|
19
19
|
* is an index, so two surfaces naming the same repos in a different order
|
|
20
20
|
* hold different ceilings (slice-2 review, finding 3). */
|
|
21
21
|
export function ceilingDigestOf(repos) {
|
|
@@ -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 = [
|
|
@@ -56,13 +49,8 @@ export function projectDeleteConfirmHtml(view, csrf, problem) {
|
|
|
56
49
|
"The checkouts and branches Toolroll made",
|
|
57
50
|
...(h.chats > 0 ? [count(h.chats, "chat")] : []),
|
|
58
51
|
...(h.flows > 0 ? [`${count(h.flows, "flow")} and ${count(h.cards, "card")}`] : []),
|
|
59
|
-
...(h.
|
|
52
|
+
...(h.subagents > 0 ? [count(h.subagents, "subagent")] : []),
|
|
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
|
}
|