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/project-delete.d.ts
CHANGED
package/dist/project-delete.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Deleting a project: everything Toolroll holds for it goes, and
|
|
3
3
|
* nothing else. Its tasks and their versions, runs and their evidence, the
|
|
4
4
|
* checkouts and branches Toolroll made, its chats and threads, flows
|
|
5
|
-
* and cards,
|
|
5
|
+
* and cards, subagents, budgets and settings. Never while any of its work is
|
|
6
6
|
* running. The project's own repository, its working copy and every branch
|
|
7
7
|
* Toolroll didn't make stay exactly as they are. The action ledger
|
|
8
8
|
* keeps every entry (its hash chain still verifies) and gains one: who
|
|
@@ -36,13 +36,13 @@ function doomed(db, repo) {
|
|
|
36
36
|
const runs = ids(db, "SELECT id FROM run WHERE task_ref IN (SELECT value FROM json_each(?))", json(refs));
|
|
37
37
|
const artifacts = ids(db, "SELECT id FROM artifact WHERE run IN (SELECT value FROM json_each(?))", json(runs));
|
|
38
38
|
// A team conversation's thread is the team's, whatever it talked about.
|
|
39
|
-
const threads = ids(db, `SELECT id FROM
|
|
39
|
+
const threads = ids(db, `SELECT id FROM lead_thread WHERE ((scope_kind = 'project' AND scope_key = ?) OR (scope_kind = 'task' AND scope_key IN (SELECT value FROM json_each(?))))
|
|
40
40
|
AND id NOT IN (SELECT thread FROM team_conversation WHERE thread IS NOT NULL)`, repo, json(tasks));
|
|
41
41
|
const turns = ids(db, "SELECT id FROM mate_turn WHERE thread IN (SELECT value FROM json_each(?))", json(threads));
|
|
42
42
|
const flows = ids(db, "SELECT id FROM flow WHERE repo = ?", repo);
|
|
43
43
|
const cards = ids(db, "SELECT id FROM flow_card WHERE flow IN (SELECT value FROM json_each(?))", json(flows));
|
|
44
|
-
const
|
|
45
|
-
return { refs, tasks, runs, artifacts, threads, turns, flows, cards,
|
|
44
|
+
const subagents = ids(db, "SELECT id FROM subagent WHERE repo = ?", repo);
|
|
45
|
+
return { refs, tasks, runs, artifacts, threads, turns, flows, cards, subagents };
|
|
46
46
|
}
|
|
47
47
|
/** A project's settings: one row per thing someone set for it. */
|
|
48
48
|
const SETTINGS = [
|
|
@@ -61,8 +61,8 @@ export function projectHoldings(store, repo) {
|
|
|
61
61
|
tasks: count("SELECT COUNT(DISTINCT external_id) AS n FROM task_ref WHERE repo = ?", repo) - versions, versions,
|
|
62
62
|
runs: d.runs.length, evidence: d.artifacts.length,
|
|
63
63
|
checkouts: count("SELECT COUNT(*) AS n FROM worktree WHERE repo = ?", repo),
|
|
64
|
-
chats: d.threads.length, flows: d.flows.length, cards: d.cards.length,
|
|
65
|
-
budgets: count(`SELECT COUNT(*) AS n FROM budget WHERE removed_at IS NULL AND ((scope_kind = 'project' AND scope_key = ?) OR (scope_kind = '
|
|
64
|
+
chats: d.threads.length, flows: d.flows.length, cards: d.cards.length, subagents: d.subagents.length,
|
|
65
|
+
budgets: count(`SELECT COUNT(*) AS n FROM budget WHERE removed_at IS NULL AND ((scope_kind = 'project' AND scope_key = ?) OR (scope_kind = 'subagent' AND scope_key IN (SELECT CAST(value AS TEXT) FROM json_each(?))))`, repo, json(d.subagents)),
|
|
66
66
|
settings: SETTINGS.filter(([table]) => tableExists(db, table)).reduce((sum, [table, column]) => sum + count(`SELECT COUNT(*) AS n FROM ${table} WHERE ${column} = ?`, repo), 0),
|
|
67
67
|
};
|
|
68
68
|
}
|
|
@@ -72,7 +72,7 @@ export function holdingsWords(held) {
|
|
|
72
72
|
plural(held.tasks, "task"), ...(held.versions > 0 ? [plural(held.versions, "version")] : []), plural(held.runs, "run"),
|
|
73
73
|
...(held.evidence > 0 ? [plural(held.evidence, "evidence file")] : []), ...(held.checkouts > 0 ? [plural(held.checkouts, "checkout")] : []),
|
|
74
74
|
...(held.branches ? [plural(held.branches, "branch", "branches")] : []), ...(held.chats > 0 ? [plural(held.chats, "chat")] : []),
|
|
75
|
-
...(held.flows > 0 ? [`${plural(held.flows, "flow")} with ${plural(held.cards, "card")}`] : []), ...(held.
|
|
75
|
+
...(held.flows > 0 ? [`${plural(held.flows, "flow")} with ${plural(held.cards, "card")}`] : []), ...(held.subagents > 0 ? [plural(held.subagents, "subagent")] : []),
|
|
76
76
|
...(held.budgets > 0 ? [plural(held.budgets, "budget")] : []), ...(held.settings > 0 ? [plural(held.settings, "setting")] : []),
|
|
77
77
|
];
|
|
78
78
|
return parts.join(", ");
|
|
@@ -87,7 +87,7 @@ export function projectRunning(store, repo, now) {
|
|
|
87
87
|
const slots = count("SELECT COUNT(*) AS n FROM execution_slot WHERE state IN ('reserved','running') AND run IN (SELECT value FROM json_each(?))", json(d.runs));
|
|
88
88
|
const chats = count("SELECT COUNT(*) AS n FROM mate_turn WHERE state IN ('queued','running') AND id IN (SELECT value FROM json_each(?))", json(d.turns));
|
|
89
89
|
const steps = count("SELECT COUNT(*) AS n FROM flow_step_run WHERE state = 'running' AND card IN (SELECT value FROM json_each(?))", json(d.cards));
|
|
90
|
-
const calls = count("SELECT COUNT(*) AS n FROM
|
|
90
|
+
const calls = count("SELECT COUNT(*) AS n FROM subagent_call WHERE state = 'running' AND subagent IN (SELECT value FROM json_each(?))", json(d.subagents));
|
|
91
91
|
const merges = count(`SELECT COUNT(*) AS n FROM merge_intent WHERE state IN ('claimed','firing') AND publication IN (SELECT id FROM publication WHERE task_ref IN (SELECT value FROM json_each(?)))`, json(d.refs));
|
|
92
92
|
const checkouts = count("SELECT COUNT(*) AS n FROM worktree WHERE repo = ? AND runner IS NOT NULL AND released_at IS NULL", repo);
|
|
93
93
|
return [
|
|
@@ -95,7 +95,7 @@ export function projectRunning(store, repo, now) {
|
|
|
95
95
|
...(slots > 0 ? ["an agent is working"] : []),
|
|
96
96
|
...(chats > 0 ? ["a chat is answering"] : []),
|
|
97
97
|
...(steps > 0 ? ["a flow step is running"] : []),
|
|
98
|
-
...(calls > 0 ? ["a
|
|
98
|
+
...(calls > 0 ? ["a subagent is working"] : []),
|
|
99
99
|
...(merges > 0 ? ["a merge is in progress"] : []),
|
|
100
100
|
...(checkouts > 0 && building === 0 ? [`${plural(checkouts, "checkout")} ${checkouts === 1 ? "is" : "are"} in use`] : []),
|
|
101
101
|
];
|
|
@@ -201,12 +201,12 @@ function deleteRows(store, repo, d, now) {
|
|
|
201
201
|
rows += Number(db.prepare(`DELETE FROM ${table} WHERE ${where}`).run(...params).changes);
|
|
202
202
|
};
|
|
203
203
|
const IN = (column) => `${column} IN (SELECT value FROM json_each(?))`;
|
|
204
|
-
const R = json(d.runs), T = json(d.refs), K = json(d.tasks), A = json(d.artifacts), H = json(d.threads), U = json(d.turns), F = json(d.flows), C = json(d.cards), M = json(d.
|
|
204
|
+
const R = json(d.runs), T = json(d.refs), K = json(d.tasks), A = json(d.artifacts), H = json(d.threads), U = json(d.turns), F = json(d.flows), C = json(d.cards), M = json(d.subagents);
|
|
205
205
|
const decisions = json(ids(db, `SELECT id FROM decision WHERE ${IN("run")}`, R));
|
|
206
206
|
const publications = json(ids(db, `SELECT id FROM publication WHERE ${IN("task_ref")} OR ${IN("run")}`, T, R));
|
|
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
|
-
const questions = json(ids(db, `SELECT id FROM
|
|
209
|
-
const proposals = json(ids(db, `SELECT id FROM
|
|
208
|
+
const questions = json(ids(db, `SELECT id FROM subagent_question WHERE ${IN("subagent")} OR ${IN("card")}`, M, C));
|
|
209
|
+
const proposals = json(ids(db, `SELECT id FROM lead_proposal WHERE ${IN("thread")}`, H));
|
|
210
210
|
// A chat message about one of the project's tasks (its turn named it), with its reply. Event ids are text ('m123'),
|
|
211
211
|
// and the same id can be another app's: each is kept whole, as provider:id.
|
|
212
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));
|
|
@@ -241,13 +241,13 @@ function deleteRows(store, repo, d, now) {
|
|
|
241
241
|
del("chat_focus", IN("task"), K);
|
|
242
242
|
del("team_task_owner", IN("task_ref"), T);
|
|
243
243
|
del("team_mate_session", IN("thread"), H);
|
|
244
|
-
del("
|
|
244
|
+
del("lead_turn_evidence", `${IN("turn")} OR ${IN("run")} OR ${IN("task_ref")} OR ${IN("artifact")}`, U, R, T, A);
|
|
245
245
|
del("chat_turn", IN("mate_turn"), U);
|
|
246
|
-
del("
|
|
246
|
+
del("lead_ask", IN("turn"), U);
|
|
247
247
|
del("mate_turn", IN("id"), U);
|
|
248
|
-
del("
|
|
249
|
-
del("
|
|
250
|
-
del("
|
|
248
|
+
del("lead_proposal", IN("id"), proposals);
|
|
249
|
+
del("lead_message", IN("thread"), H);
|
|
250
|
+
del("lead_thread", IN("id"), H);
|
|
251
251
|
// Runs and their evidence.
|
|
252
252
|
del("run_decision", `${IN("run")} OR ${IN("decision")}`, R, decisions);
|
|
253
253
|
del("decision_artifact", `${IN("decision")} OR ${IN("artifact")}`, decisions, A);
|
|
@@ -288,19 +288,19 @@ function deleteRows(store, repo, d, now) {
|
|
|
288
288
|
del("task_scope", IN("task_id"), K);
|
|
289
289
|
del("task_edge", `${IN("blocked")} OR ${IN("blocker")}`, K, K);
|
|
290
290
|
del("task", IN("id"), K);
|
|
291
|
-
// Flows, cards and
|
|
291
|
+
// Flows, cards and subagents.
|
|
292
292
|
del("flow_trigger_event", `${IN("card")} OR trigger IN (SELECT id FROM flow_trigger WHERE ${IN("flow")})`, C, F);
|
|
293
293
|
del("flow_trigger", IN("flow"), F);
|
|
294
294
|
for (const table of ["flow_card_watcher", "flow_comment", "flow_event", "flow_mail", "flow_step_run"])
|
|
295
295
|
del(table, IN("card"), C);
|
|
296
|
-
del("
|
|
297
|
-
del("
|
|
298
|
-
for (const table of ["
|
|
299
|
-
del(table, `${IN("
|
|
300
|
-
for (const table of ["
|
|
301
|
-
del(table, IN("
|
|
302
|
-
del("budget", `(scope_kind = 'project' AND scope_key = ?) OR (scope_kind = '
|
|
303
|
-
del("
|
|
296
|
+
del("subagent_question", IN("id"), questions);
|
|
297
|
+
del("subagent_call", `${IN("subagent")} OR ${IN("card")}`, M, C);
|
|
298
|
+
for (const table of ["subagent_event", "subagent_memory", "subagent_turn"])
|
|
299
|
+
del(table, `${IN("subagent")} OR ${IN("card")}`, M, C);
|
|
300
|
+
for (const table of ["subagent_suggestion", "subagent_tool", "subagent_version"])
|
|
301
|
+
del(table, IN("subagent"), M);
|
|
302
|
+
del("budget", `(scope_kind = 'project' AND scope_key = ?) OR (scope_kind = 'subagent' AND scope_key IN (SELECT CAST(value AS TEXT) FROM json_each(?)))`, repo, M);
|
|
303
|
+
del("subagent", IN("id"), M);
|
|
304
304
|
del("flow_card", IN("id"), C);
|
|
305
305
|
del("flow", IN("id"), F);
|
|
306
306
|
// Settings, knowledge and the project itself.
|
package/dist/project-memory.js
CHANGED
|
@@ -222,7 +222,7 @@ export function reindexProjectMemory(store, repo) {
|
|
|
222
222
|
export function reindexConversations(store) {
|
|
223
223
|
const key = 'memory-search:messages';
|
|
224
224
|
const cursor = store.serviceCursor(key);
|
|
225
|
-
const rows = store.handle.prepare('SELECT m.id,m.thread,m.role,m.text,t.approver FROM
|
|
225
|
+
const rows = store.handle.prepare('SELECT m.id,m.thread,m.role,m.text,t.approver FROM lead_message m JOIN lead_thread t ON t.id=m.thread WHERE m.id>? ORDER BY m.id LIMIT 500').all(cursor);
|
|
226
226
|
if (rows.length === 0)
|
|
227
227
|
return;
|
|
228
228
|
const insert = store.handle.prepare('INSERT INTO memory_search(kind,ref,repo,scope,title,body) VALUES (?,?,?,?,?,?)');
|
package/dist/project-tools.d.ts
CHANGED
|
@@ -91,7 +91,7 @@ export declare function testTool(spec: ToolSpec, values: Record<string, string>,
|
|
|
91
91
|
/**
|
|
92
92
|
* Start the server once and read what each of its tools does (v94): the name,
|
|
93
93
|
* its description, what input it takes, and whether it says it only reads.
|
|
94
|
-
* A
|
|
94
|
+
* A subagent is offered these, each under its own rule.
|
|
95
95
|
*/
|
|
96
96
|
export declare function listProjectToolActions(spec: ToolSpec, values: Record<string, string>, options?: {
|
|
97
97
|
timeoutMs?: number;
|
package/dist/project-tools.js
CHANGED
|
@@ -382,7 +382,7 @@ export async function testTool(spec, values, options = {}) {
|
|
|
382
382
|
/**
|
|
383
383
|
* Start the server once and read what each of its tools does (v94): the name,
|
|
384
384
|
* its description, what input it takes, and whether it says it only reads.
|
|
385
|
-
* A
|
|
385
|
+
* A subagent is offered these, each under its own rule.
|
|
386
386
|
*/
|
|
387
387
|
export async function listProjectToolActions(spec, values, options = {}) {
|
|
388
388
|
const answer = await exchange(spec, values, { method: "tools/list", params: {}, doing: "listing its tools" }, options);
|
|
@@ -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; }
|
package/dist/recipe-ui.d.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { type Recipe, type RecipeDocument, type WorkflowPreview } from "./recipes.js";
|
|
2
|
+
import { type Html } from "./html.js";
|
|
2
3
|
export declare function recipeFromForm(body: URLSearchParams): RecipeDocument;
|
|
3
|
-
export declare function recipeLibraryHtml(starters: Recipe[], saved: Recipe[], project: string | null,
|
|
4
|
+
export declare function recipeLibraryHtml(starters: Recipe[], saved: Recipe[], project: string | null, revision: number, recent: {
|
|
4
5
|
name: string;
|
|
5
6
|
href: string;
|
|
6
7
|
state: string;
|
|
7
|
-
}[]):
|
|
8
|
-
export declare function recipeEditorHtml(recipe: Recipe, repo: string,
|
|
9
|
-
export declare function workflowPreviewHtml(preview: WorkflowPreview,
|
|
8
|
+
}[]): Html;
|
|
9
|
+
export declare function recipeEditorHtml(recipe: Recipe, repo: string, revision: number, problem?: string | null, values?: URLSearchParams, creator?: boolean): Html;
|
|
10
|
+
export declare function workflowPreviewHtml(preview: WorkflowPreview, revision: number, now: Date, readiness: {
|
|
10
11
|
title: string;
|
|
11
12
|
detail: string;
|
|
12
13
|
href?: string;
|
|
13
|
-
}[], auto: boolean):
|
|
14
|
-
export declare function recipeDefinitionPreviewHtml(preview: WorkflowPreview,
|
|
15
|
-
export declare function recipeRunHtml(recipe: Recipe, repo: string,
|
|
14
|
+
}[], auto: boolean): Html;
|
|
15
|
+
export declare function recipeDefinitionPreviewHtml(preview: WorkflowPreview, revision: number): Html;
|
|
16
|
+
export declare function recipeRunHtml(recipe: Recipe, repo: string, revision: number, problem?: string | null, values?: URLSearchParams, saved?: boolean): Html;
|
|
16
17
|
export declare function recipeAnswersFromForm(body: URLSearchParams): Map<string, string>;
|
|
17
18
|
export declare function recipeScript(): string;
|
|
18
19
|
export declare const RECIPE_CSS = "\n.recipe-heading { margin: .5rem 0 1.75rem; max-width: 46rem; }\n.recipe-heading h1 { font-size: clamp(1.65rem,3vw,2.5rem); letter-spacing:-.035em; margin:.35rem 0 .6rem; }\n.recipe-kicker { font-size:.7rem; text-transform:uppercase; letter-spacing:.12em; color:var(--muted-foreground); }\n.recipe-grid { display:grid; grid-template-columns:repeat(3,minmax(0,1fr)); gap:.8rem; margin:1rem 0 2rem; }\n.recipe-card { border:1px solid var(--border); border-radius:var(--radius); padding:1.1rem; background:var(--card); display:flex; flex-direction:column; gap:.5rem; }\n.recipe-card h3 { font-size:1.05rem; margin:0; letter-spacing:-.015em; }\n.recipe-card p { color:var(--muted-foreground); margin:.2rem 0; font-size:.85rem; line-height:1.5; }\n.recipe-card-link { margin-top:auto; padding-top:.75rem; display:flex; justify-content:space-between; }\n.recipe-editor { max-width:52rem; }.recipe-editor h2 { margin-top:0; }\n.recipe-criterion { border:1px solid var(--border); border-radius:calc(var(--radius) - 3px); padding:.8rem; margin:.8rem 0; }\n.recipe-evidence { display:flex; flex-wrap:wrap; gap:.5rem 1rem; }.recipe-evidence label { display:flex; align-items:center; gap:.4rem; font-size:.8rem; }.recipe-evidence input { width:auto; margin:0; }\n.recipe-options { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }\n.recipe-preview-grid { display:grid; grid-template-columns:minmax(0,1.7fr) minmax(16rem,1fr); gap:1rem; }\n.recipe-steps { padding-left:1.4rem; }.recipe-steps li { padding:.4rem 0 .6rem .3rem; }.recipe-steps p { margin:.3rem 0 0; font-size:.85rem; color:var(--muted-foreground); }\n.recipe-launch { display:grid; grid-template-columns:1fr 1fr; gap:1rem; }.recipe-launch form { margin:0 0 .6rem; }\n.recipe-history a { display:flex; justify-content:space-between; gap:1rem; padding:.6rem 0; border-bottom:1px solid var(--border); }.recipe-history span { color:var(--muted-foreground); }\n.recipe-error { border-color:var(--destructive); }\n.recipe-card[hidden] { display:none; }.recipe-search { max-width:28rem; }.recipe-question { border:1px solid var(--border); border-radius:calc(var(--radius) - 3px); margin:1rem 0; padding:.8rem; min-width:0; }.recipe-run { max-width:42rem; }.recipe-run button { margin-top:1rem; }\n@media(max-width:1000px){.recipe-grid { grid-template-columns:repeat(2,minmax(0,1fr)); }}\n@media(max-width:760px){.recipe-grid,.recipe-options,.recipe-preview-grid,.recipe-launch { grid-template-columns:1fr; }.recipe-heading { margin-bottom:1rem; }.recipe-history a { flex-direction:column; gap:.2rem; }}\n";
|
package/dist/recipe-ui.js
CHANGED
|
@@ -2,8 +2,8 @@ import { TASK_TEXT_LIMITS } from "./task-text.js";
|
|
|
2
2
|
import { composerSchedule, scheduleEditorHtml, scheduleEditorScript } from "./task-composer.js";
|
|
3
3
|
import { parseRecipe, workflowSteps, recipeScheduleWords, RecipeError, RECIPE_INPUT_LIMIT } from "./recipes.js";
|
|
4
4
|
import { ACCEPTANCE_LIMITS } from "./scope.js";
|
|
5
|
-
|
|
6
|
-
const hidden = (name, value) => `<input type="hidden" name="${
|
|
5
|
+
import { html, joinHtml, postForm, replaceMarkup } from "./html.js";
|
|
6
|
+
const hidden = (name, value) => html `<input type="hidden" name="${name}" value="${value}">`;
|
|
7
7
|
const evidence = [["check", "Passing tests or commands"], ["changed-path", "Changed files"], ["screenshot", "Screenshot"], ["manual-review", "Reviewer assessment"]];
|
|
8
8
|
export function recipeFromForm(body) {
|
|
9
9
|
for (const name of ["name", "description", "goal", "not", "touches", "planning", "deliverable", "cadence", "repeat", "time", "timezone", "weekday", "interval", "interval-unit", "ceiling", "source", "sourceRevision", "projectRevision", "repo", "preview", "purpose", "sourceFormat"]) {
|
|
@@ -53,63 +53,70 @@ export function recipeFromForm(body) {
|
|
|
53
53
|
return parseRecipe({ format: "standing-orders-recipe", version: inputs.length ? 2 : 1, ...(inputs.length ? { inputs } : {}), name: body.get("name") ?? "", description: body.get("description") ?? "", goal: body.get("goal") ?? "", outOfScope: body.get("not") || null,
|
|
54
54
|
touches: (body.get("touches") ?? "").split(/[\n,]/).map(one => one.trim()).filter(Boolean), acceptance, planning: body.get("planning") ?? "auto", deliverable: body.get("deliverable") ?? "branch", schedule: schedule.schedule, costCeilingUsd: ceiling === "" ? null : Number(ceiling) });
|
|
55
55
|
}
|
|
56
|
-
export function recipeLibraryHtml(starters, saved, project,
|
|
57
|
-
const cards = (recipes, reusable = false) => `<div class="recipe-grid"${reusable
|
|
58
|
-
const savedSection = `<h2>Your project’s recipes</h2>${saved.length >= 100
|
|
59
|
-
return `<div class="recipe-heading"><p class="recipe-kicker">Workflows</p><h1>Good work, on repeat.</h1><p class="hint">Save the work you do often. Answer what changes this time. Kick it off.</p><p><a href="/recipes/new" class="new-task">Create a recipe</a> <a href="/flows">Manage scheduled work →</a></p></div>`
|
|
60
|
-
(project === null ? `<div class="card"><strong>Choose a project to get started.</strong><p>Browse recipes now; <a href="/projects">open a project</a> to customize, save, or run one.</p></div>` : "") +
|
|
61
|
-
(saved.length ? savedSection : "") + `<h2>Start here</h2>${cards(starters)}` + (saved.length ? "" : savedSection) +
|
|
62
|
-
(recent.length ? `<h2>Recent workflows</h2><div class="card recipe-history">${recent.map(one => `<a href="${e(one.href)}"><strong>${e(one.name)}</strong><span>${e(one.state)} →</span></a>`).join("")}</div>` : "") +
|
|
63
|
-
(project === null ? "" : `<details class="card"><summary>Import a shared recipe</summary><p class="hint">Paste exported recipe JSON. You’ll review it in this project before creating any work.</p><form method="post" action="/recipes/import">${hidden("csrf", csrf)}${hidden("repo", project)}${hidden("projectRevision", String(revision))}<label>Recipe JSON<textarea name="document" rows="7" required maxlength="32768" spellcheck="false"></textarea></label><button type="submit">Review imported recipe</button></form></details>`);
|
|
56
|
+
export function recipeLibraryHtml(starters, saved, project, revision, recent) {
|
|
57
|
+
const cards = (recipes, reusable = false) => html `<div class="recipe-grid"${reusable && html ` data-saved-recipes`}>${recipes.map(recipe => html `<article class="recipe-card" data-recipe-card data-recipe-search="${recipe.document.name + " " + recipe.document.description}"><p class="recipe-kicker">${recipe.document.deliverable === "report" ? "Report" : recipe.document.schedule === null ? "One-time work" : "Recurring care"}${recipe.document.version === 2 ? ` · ${recipe.document.inputs.length} question${recipe.document.inputs.length === 1 ? "" : "s"}` : ""}</p><h3>${recipe.document.name}</h3><p>${recipe.document.description}</p><a class="recipe-card-link" href="/recipes/${reusable ? "run" : "start"}?recipe=${recipe.id}&revision=${recipe.revision}">${reusable ? "Use recipe" : "Customize recipe"} <span aria-hidden="true">→</span></a>${reusable && html `<a class="meta" href="/recipes/start?recipe=${recipe.id}&revision=${recipe.revision}">Edit a copy</a>`}</article>`)}</div>`;
|
|
58
|
+
const savedSection = html `<h2>Your project’s recipes</h2>${saved.length >= 100 && html `<p class="meta">Showing 100 recipes, with recently used recipes first.</p>`}${saved.length ? html `<p class="hint">Your usual work, ready to go. Recently used recipes come first.</p><label class="recipe-search">Find a recipe<input type="search" data-recipe-search-input placeholder="Search your recipes…"></label>${cards(saved, true)}<p data-recipe-empty hidden>No recipes match. Try another search.</p>` : html `<p class="hint">Save work you do often. Subagents with project access can use the same recipe.</p>`}`;
|
|
59
|
+
return html `<div class="recipe-heading"><p class="recipe-kicker">Workflows</p><h1>Good work, on repeat.</h1><p class="hint">Save the work you do often. Answer what changes this time. Kick it off.</p><p><a href="/recipes/new" class="new-task">Create a recipe</a> <a href="/flows">Manage scheduled work →</a></p></div>${project === null && html `<div class="card"><strong>Choose a project to get started.</strong><p>Browse recipes now; <a href="/projects">open a project</a> to customize, save, or run one.</p></div>`}${saved.length > 0 && savedSection}<h2>Start here</h2>${cards(starters)}${saved.length === 0 && savedSection}${recent.length > 0 && html `<h2>Recent workflows</h2><div class="card recipe-history">${recent.map(one => html `<a href="${one.href}"><strong>${one.name}</strong><span>${one.state} →</span></a>`)}</div>`}${project !== null && html `<details class="card"><summary>Import a shared recipe</summary><p class="hint">Paste exported recipe JSON. You’ll review it in this project before creating any work.</p>${postForm("/recipes/import", html `<label>Recipe JSON<textarea name="document" rows="7" required maxlength="32768" spellcheck="false"></textarea></label><button type="submit">Review imported recipe</button>`, { projectRevision: String(revision), hidden: { repo: project } })}</details>`}`;
|
|
64
60
|
}
|
|
65
61
|
function criterionHtml(index, criterion, values) {
|
|
66
62
|
const prefix = `criterion-${index}-`;
|
|
67
63
|
const value = (name, fallback) => values?.get(prefix + name) ?? fallback;
|
|
68
64
|
const selected = values ? values.getAll(prefix + "evidence") : criterion?.evidence ?? ["check"];
|
|
69
|
-
return `<fieldset class="recipe-criterion"><legend>${criterion || index === 0 ? `Success check ${index + 1}` : "Additional success check (optional)"}</legend>${hidden(prefix + "id", value("id", criterion?.id ?? `c${index + 1}`))}<label>What should be true?<textarea name="${prefix}statement" rows="2" maxlength="300">${
|
|
65
|
+
return html `<fieldset class="recipe-criterion"><legend>${criterion || index === 0 ? `Success check ${index + 1}` : "Additional success check (optional)"}</legend>${hidden(prefix + "id", value("id", criterion?.id ?? `c${index + 1}`))}<label>What should be true?<textarea name="${prefix}statement" rows="2" maxlength="300">${value("statement", criterion?.statement ?? "")}</textarea></label><div class="recipe-evidence">${evidence.map(([id, label]) => html `<label><input type="checkbox" name="${prefix}evidence" value="${id}"${selected.includes(id) && html ` checked`}>${label}</label>`)}</div><label>How to check it <span class="meta">optional; command or review instructions</span><input type="text" name="${prefix}how" value="${value("how", criterion?.how ?? "")}" maxlength="500"></label></fieldset>`;
|
|
70
66
|
}
|
|
71
|
-
|
|
67
|
+
/** The blank success check the page's script copies, its numbers left for the script to fill in. */
|
|
68
|
+
const criterionTemplate = () => replaceMarkup(criterionHtml(11), /criterion-11-|value="c12"/g, match => match === "criterion-11-" ? html `criterion-INDEX-` : html `value="cNEXT"`);
|
|
69
|
+
export function recipeEditorHtml(recipe, repo, revision, problem = null, values, creator = false) {
|
|
72
70
|
const d = recipe.document;
|
|
73
71
|
const value = (name, fallback) => values?.get(name) ?? fallback;
|
|
74
72
|
const cadence = value("cadence", d.schedule === null ? "once" : "repeat");
|
|
75
73
|
const planning = value("planning", d.planning), deliverable = value("deliverable", d.deliverable);
|
|
76
74
|
const count = values ? Math.min(ACCEPTANCE_LIMITS.criteria, Math.max(d.acceptance.length + 1, ...[...values.keys()].map(key => Number(/^criterion-(\d+)-statement$/.exec(key)?.[1] ?? -1) + 1))) : Math.min(ACCEPTANCE_LIMITS.criteria, d.acceptance.length + 1);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
`<section class="card"><h2>What does success look like?</h2><p class="hint">Give each outcome the evidence you want to see. The workflow checks these before calling the work done.</p><div data-recipe-criteria>${Array.from({ length: count }, (_, i) => criterionHtml(i, d.acceptance[i], values)).join("")}</div><button type="button" data-add-criterion hidden>Add another success check</button><template data-criterion-template>${criterionHtml(11).replaceAll("criterion-11-", "criterion-INDEX-").replaceAll('value="c12"', 'value="cNEXT"')}</template></section>` +
|
|
83
|
-
`<section class="card"><h2>How it should run</h2><div class="recipe-options"><label>Result<select name="deliverable"><option value="branch"${deliverable === "branch" ? " selected" : ""}>Code change with evidence</option><option value="report"${deliverable === "report" ? " selected" : ""}>Report without code changes</option></select></label><label>Planning<select name="planning">${[["auto", "Plan when helpful"], ["required", "Always plan first"], ["skip", "Use this scope directly"]].map(([id, label]) => `<option value="${id}"${planning === id ? " selected" : ""}>${label}</option>`).join("")}</select></label></div><p class="meta">Report work investigates directly. Repeating work files the same scope each time; plan-first workflows run once.</p><label>Frequency<select name="cadence" data-recipe-cadence><option value="once"${cadence === "once" ? " selected" : ""}>Run once</option><option value="repeat"${cadence === "repeat" ? " selected" : ""}>Repeat on a schedule</option></select></label><div data-recipe-recurring>${scheduleEditorHtml(d.schedule, values)}<label>Weekly spending limit <span class="meta">optional, USD</span><input type="number" name="ceiling" min="0.01" step="0.01" value="${e(value("ceiling", d.costCeilingUsd === null ? "" : String(d.costCeilingUsd)))}"></label><p class="meta">One at a time. A spending limit needs an agent that reports dollar costs.</p></div></section>` +
|
|
84
|
-
`<div class="sticky-actions"><button type="submit" name="purpose" value="${creator ? "recipe" : "workflow"}">${creator ? "Preview recipe →" : "Preview workflow →"}</button>${creator ? "" : `<button type="submit" name="purpose" value="recipe" class="quiet">Preview recipe to save</button>`}<p class="meta">${creator ? "Save the instructions once. You can use the recipe whenever this work comes up." : "Previewing runs no agents. You’ll see the approval and scheduling steps next."}</p></div></form>`;
|
|
75
|
+
const outcome = html `<section class="card"><h2>The outcome</h2><label>${creator ? "Recipe name" : "Workflow name"}<input type="text" name="name" value="${value("name", d.name)}" maxlength="200" required></label><label>Short description<input type="text" name="description" value="${value("description", d.description)}" maxlength="400"></label><label>What should the agent do?<textarea name="goal" rows="5" maxlength="${TASK_TEXT_LIMITS.text}" required>${value("goal", d.goal)}</textarea></label><label>What should it leave alone?<textarea name="not" rows="3" maxlength="${TASK_TEXT_LIMITS.text}">${value("not", d.outOfScope ?? "")}</textarea></label><label>Allowed files or folders <span class="meta">optional; one per line</span><textarea name="touches" rows="2" placeholder="src/ tests/">${value("touches", d.touches.join("\n"))}</textarea></label><p class="meta">Blank allows work across this project. Add paths to keep the workflow focused.</p></section>`;
|
|
76
|
+
const success = html `<section class="card"><h2>What does success look like?</h2><p class="hint">Give each outcome the evidence you want to see. The workflow checks these before calling the work done.</p><div data-recipe-criteria>${Array.from({ length: count }, (_, i) => criterionHtml(i, d.acceptance[i], values))}</div><button type="button" data-add-criterion hidden>Add another success check</button><template data-criterion-template>${criterionTemplate()}</template></section>`;
|
|
77
|
+
const running = html `<section class="card"><h2>How it should run</h2><div class="recipe-options"><label>Result<select name="deliverable"><option value="branch"${deliverable === "branch" && html ` selected`}>Code change with evidence</option><option value="report"${deliverable === "report" && html ` selected`}>Report without code changes</option></select></label><label>Planning<select name="planning">${[["auto", "Plan when helpful"], ["required", "Always plan first"], ["skip", "Use this scope directly"]].map(([id, label]) => html `<option value="${id}"${planning === id && html ` selected`}>${label}</option>`)}</select></label></div><p class="meta">Report work investigates directly. Repeating work files the same scope each time; plan-first workflows run once.</p><label>Frequency<select name="cadence" data-recipe-cadence><option value="once"${cadence === "once" && html ` selected`}>Run once</option><option value="repeat"${cadence === "repeat" && html ` selected`}>Repeat on a schedule</option></select></label><div data-recipe-recurring>${scheduleEditorHtml(d.schedule, values)}<label>Weekly spending limit <span class="meta">optional, USD</span><input type="number" name="ceiling" min="0.01" step="0.01" value="${value("ceiling", d.costCeilingUsd === null ? "" : String(d.costCeilingUsd))}"></label><p class="meta">One instance at a time. A spending limit requires an agent that reports dollar costs; approval checks this.</p></div></section>`;
|
|
78
|
+
const actions = html `<div class="sticky-actions"><button type="submit" name="purpose" value="${creator ? "recipe" : "workflow"}">${creator ? "Preview recipe →" : "Preview workflow →"}</button>${!creator && html `<button type="submit" name="purpose" value="recipe" class="quiet">Preview recipe to save</button>`}<p class="meta">${creator ? "Save the instructions once. You can use the recipe whenever this work comes up." : "Previewing runs no agents. You’ll see the approval and scheduling steps next."}</p></div>`;
|
|
79
|
+
return html `<a href="/recipes" class="meta">← All recipes</a><div class="recipe-heading"><p class="recipe-kicker">${creator ? "1 · Describe → 2 · Preview → 3 · Save" : "1 · Choose → 2 · Customize → 3 · Preview"}</p><h1>${creator ? "Create a reusable recipe" : "Make this workflow yours"}</h1><p class="hint">${repo.split(/[\\/]/).pop() || repo} · Choose the outcome, boundaries, and proof you want back.</p></div>${problem !== null && html `<p role="alert" class="card recipe-error">${problem}</p>`}${postForm("/recipes/preview", html `${outcome}${recipeQuestionsHtml(d, values)}${success}${running}${actions}`, { attrs: { class: "recipe-editor" }, projectRevision: String(revision), hidden: { repo, source: recipe.id, sourceRevision: String(recipe.revision), sourceFormat: value("sourceFormat", String(d.version)) } })}`;
|
|
85
80
|
}
|
|
86
|
-
|
|
81
|
+
/** What a success check's evidence is called, for a reader. */
|
|
82
|
+
const evidenceWords = (kinds) => joinHtml(kinds.map(kind => evidence.find(([id]) => id === kind)?.[1] ?? kind), " · ");
|
|
83
|
+
export function workflowPreviewHtml(preview, revision, now, readiness, auto) {
|
|
87
84
|
const d = preview.document;
|
|
88
|
-
const common =
|
|
85
|
+
const common = { projectRevision: String(revision), hidden: { repo: preview.repo, preview: preview.token } };
|
|
89
86
|
const target = preview.taskId === null ? preview.flowId === null ? null : `/flows/${preview.flowId}` : `/t/${encodeURIComponent(preview.taskId)}`;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
const agreed = html `<section class="card"><h2>The agreed work</h2><p class="recap">${d.goal}</p><h3>Leave alone</h3><p class="recap">${d.outOfScope ?? "No additional exclusions."}</p><h3>Allowed paths</h3><p>${d.touches.length ? joinHtml(d.touches, ", ") : "This project"}</p><h3>Success checks</h3><ul>${d.acceptance.map(c => html `<li><strong>${c.statement}</strong><p class="meta">${evidenceWords(c.evidence)}${c.how ? ` · ${c.how}` : ""}</p></li>`)}</ul></section>`;
|
|
88
|
+
const before = html `<aside><section class="card"><h2>Before it starts</h2>${readiness.map(one => html `<p><strong>${one.title}</strong><br><span class="meta">${one.detail}${one.href && html ` <a href="${one.href}">Open settings →</a>`}</span></p>`)}<p class="meta">These checks describe current setup. Agent availability and approval are checked again when work starts.</p></section><section class="card"><h2>Schedule & control</h2><p>${recipeScheduleWords(d, now)}</p><p>${d.costCeilingUsd === null ? "Uses existing project and agent budget controls." : `Up to $${d.costCeilingUsd.toFixed(2)} per rolling seven days.`}</p><p>${d.schedule !== null ? "Approve the routine once on the next screen. Pause it any time from scheduled work." : auto ? "Your signed project policy will be evaluated when you create this workflow." : "Review and approve on the task page. You can stop or resume there."}</p></section></aside>`;
|
|
89
|
+
const launch = html `<div class="card recipe-launch"><div>${target ? html `<a class="new-task" href="${target}">Open created workflow →</a>` : postForm("/recipes/launch", html `<button type="submit">${d.schedule !== null ? "Create scheduled workflow" : auto ? "Create workflow" : "Create task for approval"}</button>`, common)}<p class="meta">${d.schedule !== null ? "Nothing repeats until you turn the schedule on." : "A matching signed policy may start work automatically."} Creating this workflow twice returns the same work.</p></div><div>${preview.savedId ? html `<a href="/recipes/start?recipe=${preview.savedId}&revision=${preview.savedRevision}">Recipe saved · reuse it →</a>` : postForm("/recipes/save", html `<button type="submit" class="quiet">Save as a project recipe</button>`, common)}<a href="/recipes/export?preview=${preview.token}">Export recipe JSON</a><p class="meta">Save or export without running anything. Sharing includes the work definition, never project access or approvals.</p></div></div>`;
|
|
90
|
+
return html `<a href="/recipes/edit?preview=${preview.token}" class="meta">← Edit this workflow</a><div class="recipe-heading"><p class="recipe-kicker">3 · Preview</p><h1>${d.name}</h1><p class="hint">${d.description}</p></div><div class="recipe-preview-grid"><div><section class="card"><h2>What will happen</h2><ol class="recipe-steps">${workflowSteps(d).map(step => html `<li><strong>${step.title}</strong><p>${step.detail}</p></li>`)}</ol></section>${agreed}</div>${before}</div>${launch}`;
|
|
94
91
|
}
|
|
95
92
|
function inputHtml(index, input, values) {
|
|
96
93
|
const value = (field, fallback) => values?.get(`input-${index}-${field}`) ?? fallback;
|
|
97
|
-
return `<fieldset class="recipe-question"><legend>Question ${index + 1}</legend><div class="recipe-options"><label>Short key<input type="text" name="input-${index}-key" value="${
|
|
94
|
+
return html `<fieldset class="recipe-question"><legend>Question ${index + 1}</legend><div class="recipe-options"><label>Short key<input type="text" name="input-${index}-key" value="${value("key", input?.key ?? "")}" maxlength="32" placeholder="module" pattern="[a-z][a-z0-9_]{0,31}"></label><label>What should we ask?<input type="text" name="input-${index}-label" value="${value("label", input?.label ?? "")}" maxlength="80" placeholder="Which module should we work on?"></label></div><label>Default answer <span class="meta">optional</span><input type="text" name="input-${index}-default" value="${value("default", input?.defaultValue ?? "")}" maxlength="500"></label><p class="meta">Use <code data-question-token>{{${value("key", input?.key ?? "module") || "module"}}}</code> in your instructions or a success check. Clear this row and its placeholders to remove the question.</p><button type="button" data-insert-question hidden>Insert into instructions</button></fieldset>`;
|
|
98
95
|
}
|
|
96
|
+
/** The blank question the page's script copies, its numbers left for the script to fill in (the first "Question 8" only, as before). */
|
|
97
|
+
const questionTemplate = () => {
|
|
98
|
+
let numbered = false;
|
|
99
|
+
return replaceMarkup(inputHtml(7), /input-7-|Question 8/g, match => match === "input-7-" ? html `input-INDEX-` : numbered ? null : (numbered = true, html `Question NEXT`));
|
|
100
|
+
};
|
|
99
101
|
function recipeQuestionsHtml(document, values) {
|
|
100
102
|
const inputs = document.version === 2 ? document.inputs : [];
|
|
101
103
|
const submitted = values ? [...values.keys()].filter(key => /^input-[0-7]-/.test(key)).map(key => Number(key.split("-")[1]) + 1) : [];
|
|
102
104
|
const count = Math.min(RECIPE_INPUT_LIMIT, Math.max(1, inputs.length, ...submitted));
|
|
103
|
-
return `<details class="card"${inputs.length || submitted.some(i => values?.get(`input-${i - 1}-key`))
|
|
105
|
+
return html `<details class="card"${(inputs.length > 0 || submitted.some(i => values?.get(`input-${i - 1}-key`))) && html ` open`}><summary>Ask something each time <span class="meta">optional</span></summary><p class="hint">For example: “Test {{module}} and cover {{scenario}}.” Add a question for each placeholder. People answer these when they use the recipe; the saved instructions stay the same.</p><p class="meta">Use answers in the name, instructions, exclusions, or success checks. Keep allowed paths and check commands fixed. Don’t ask for passwords or keys.</p><div data-recipe-questions>${Array.from({ length: count }, (_, i) => inputHtml(i, inputs[i], values))}</div><button type="button" data-add-question hidden>Add a question</button><template data-question-template>${questionTemplate()}</template></details>`;
|
|
104
106
|
}
|
|
105
|
-
export function recipeDefinitionPreviewHtml(preview,
|
|
107
|
+
export function recipeDefinitionPreviewHtml(preview, revision) {
|
|
106
108
|
const d = preview.document;
|
|
107
109
|
const inputs = d.version === 2 ? d.inputs : [];
|
|
108
|
-
|
|
110
|
+
const uses = html `<section class="card"><h2>When someone uses this recipe</h2>${inputs.length ? html `<ol>${inputs.map(input => html `<li><strong>${input.label}</strong><p class="meta">{{${input.key}}}${input.defaultValue ? ` · Default: ${input.defaultValue}` : " · Answer required each time"}</p></li>`)}</ol>` : html `<p>No questions to answer. The saved scope is ready to preview and use.</p>`}<p>Each use previews the exact work before creating it. The project’s approval and recovery settings still apply.</p></section>`;
|
|
111
|
+
const instructions = html `<section class="card"><h2>Saved instructions</h2><p class="recap">${d.goal}</p><h3>Leave alone</h3><p class="recap">${d.outOfScope ?? "No additional exclusions."}</p><h3>Allowed paths</h3><p>${d.touches.length ? joinHtml(d.touches, ", ") : "This project"}</p><h3>Success checks</h3><ul>${d.acceptance.map(one => html `<li>${one.statement}<p class="meta">${evidenceWords(one.evidence)}${one.how ? ` · ${one.how}` : ""}</p></li>`)}</ul><p class="meta">${d.deliverable === "report" ? "Report" : "Code change"} · ${d.schedule === null ? "Run once" : d.schedule} · ${d.planning === "required" ? "Plan first" : d.planning === "auto" ? "Plan when helpful" : "Use the approved scope directly"}${d.costCeilingUsd === null ? "" : ` · $${d.costCeilingUsd} weekly limit`}</p></section>`;
|
|
112
|
+
const save = preview.savedId ? html `<a class="new-task" href="/recipes/run?recipe=${preview.savedId}&revision=${preview.savedRevision}">Use recipe →</a>`
|
|
113
|
+
: postForm("/recipes/save", html `<button type="submit">Save recipe</button>`, { projectRevision: String(revision), hidden: { repo: preview.repo, preview: preview.token, next: "use" } });
|
|
114
|
+
return html `<a class="meta" href="/recipes/edit?preview=${preview.token}&purpose=recipe">← Edit recipe</a><div class="recipe-heading"><p class="recipe-kicker">2 · Preview your recipe</p><h1>${d.name}</h1><p class="hint">${d.description || "A reusable starting point for work you do often."}</p></div>${uses}${instructions}<section class="card recipe-launch"><div>${save}<p class="meta">Saving creates no work. Everyone with project access can reuse this copy.</p></div><a href="/recipes/export?preview=${preview.token}">Export recipe JSON</a></section>`;
|
|
109
115
|
}
|
|
110
|
-
export function recipeRunHtml(recipe, repo,
|
|
116
|
+
export function recipeRunHtml(recipe, repo, revision, problem = null, values, saved = false) {
|
|
111
117
|
const d = recipe.document, inputs = d.version === 2 ? d.inputs : [];
|
|
112
|
-
|
|
118
|
+
const run = postForm("/recipes/prepare", html `<h2>${inputs.length ? "What changes this time?" : "Ready to go"}</h2>${inputs.map(input => html `<label>${input.label}<textarea name="answer-${input.key}" rows="2" maxlength="500" required>${values?.get(`answer-${input.key}`) ?? input.defaultValue ?? ""}</textarea></label>`)}${inputs.length === 0 && html `<p>No setup to repeat. Preview the saved scope and create the work.</p>`}<p class="meta">${d.schedule === null ? "Creates one task each time you use this recipe." : "Creates a new schedule with these answers fixed for every firing. Manage an existing schedule from Routines."}</p><button type="submit">Preview this run →</button><p class="meta">Previewing starts no agents. Existing project approvals apply when you create work.</p>`, { attrs: { class: "recipe-run card" }, projectRevision: String(revision), hidden: { repo, recipe: recipe.id, recipeRevision: String(recipe.revision) } });
|
|
119
|
+
return html `<a class="meta" href="/recipes">← Your recipes</a><div class="recipe-heading"><p class="recipe-kicker">${saved ? "Recipe saved · ready when you are" : "Use a recipe"}</p><h1>${d.name}</h1><p class="hint">${d.description || "Your saved instructions, ready to use again."}</p></div>${problem && html `<p role="alert" class="card recipe-error">${problem}</p>`}${run}<details class="card"><summary>Saved instructions and boundaries</summary><p class="recap">${d.goal}</p><p class="recap">Leave alone: ${d.outOfScope ?? "No additional exclusions."}</p><p>Allowed paths: ${d.touches.length ? joinHtml(d.touches, ", ") : "This project"}</p><ul>${d.acceptance.map(one => html `<li>${one.statement}</li>`)}</ul><a href="/recipes/start?recipe=${recipe.id}&revision=${recipe.revision}">Edit a copy of this recipe →</a></details>`;
|
|
113
120
|
}
|
|
114
121
|
export function recipeAnswersFromForm(body) {
|
|
115
122
|
const answers = new Map();
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The world `toolroll release` acts on (release.ts): the gate checkout through git, GitHub through the signed-in `gh`,
|
|
3
|
+
* the npm registry over HTTPS, the installed Toolroll through its own CLI (`--json`) and launchd definitions, and the
|
|
4
|
+
* Homebrew tap through `gh`. Each call is bounded; none asks for or stores a credential — `gh`, npm and Toolroll use
|
|
5
|
+
* the sign-ins this machine already has.
|
|
6
|
+
*/
|
|
7
|
+
import type { GateState, Installed, ReleaseAdapters } from "./release.js";
|
|
8
|
+
export type Exec = (command: string, args: readonly string[], options?: {
|
|
9
|
+
cwd?: string;
|
|
10
|
+
timeoutMs?: number;
|
|
11
|
+
env?: NodeJS.ProcessEnv;
|
|
12
|
+
}) => Promise<{
|
|
13
|
+
code: number;
|
|
14
|
+
stdout: string;
|
|
15
|
+
stderr: string;
|
|
16
|
+
}>;
|
|
17
|
+
/** A child process, its output kept whole; a missing program or a time limit is exit 127 or 124, never a throw. */
|
|
18
|
+
export declare const exec: Exec;
|
|
19
|
+
/** owner/name from an https or ssh GitHub remote. */
|
|
20
|
+
export declare function githubSlug(url: string): string | null;
|
|
21
|
+
/** The runtime (a `dist` folder, resolved) each launchd service of Toolroll runs: its definition names `<dist>/cli.js`. */
|
|
22
|
+
export declare function serviceRuntimes(agents?: string): string[];
|
|
23
|
+
/** The runtime each CLI name resolves to on PATH: the `dist` folder its `bin.js` sits in, or null when not linked there. */
|
|
24
|
+
export declare function cliRuntimes(path?: string, names?: string[]): Installed["clis"];
|
|
25
|
+
type Json = Record<string, unknown>;
|
|
26
|
+
/** `task show --json`, read as the release needs it. */
|
|
27
|
+
export declare function gateStateOf(answer: Json, accounts?: readonly unknown[]): GateState;
|
|
28
|
+
/** Every process on this machine (`ps`): pid and full command line. */
|
|
29
|
+
export declare function processes(): {
|
|
30
|
+
pid: number;
|
|
31
|
+
command: string;
|
|
32
|
+
}[];
|
|
33
|
+
/** The final line of deploy-browser's output that is a JSON object: its `runtime` (a dist folder), when it has one. */
|
|
34
|
+
export declare function deployedRuntime(stdout: string): string | null;
|
|
35
|
+
/** What a deployment's stage says about the update pause: its id and phase when the journal is before the swap and the
|
|
36
|
+
* live database's pause is that deployment's. `owner` reads the live pause's owner. */
|
|
37
|
+
export declare function stageGate(stage: string, owner: () => string | null): {
|
|
38
|
+
id: string;
|
|
39
|
+
phase: string;
|
|
40
|
+
} | null;
|
|
41
|
+
export type AdapterOptions = {
|
|
42
|
+
checkout: string;
|
|
43
|
+
/** The live database (default: this installation's). */
|
|
44
|
+
database?: string;
|
|
45
|
+
processes?: typeof processes;
|
|
46
|
+
/** owner/name of the Homebrew tap and its formula's path there. */
|
|
47
|
+
tap?: string;
|
|
48
|
+
formula?: string;
|
|
49
|
+
/** How long the deployment itself may run, in minutes. */
|
|
50
|
+
deployMinutes?: number;
|
|
51
|
+
run?: Exec;
|
|
52
|
+
fetch?: typeof fetch;
|
|
53
|
+
say?: (line: string) => void;
|
|
54
|
+
now?: () => Date;
|
|
55
|
+
sleep?: (ms: number) => Promise<void>;
|
|
56
|
+
};
|
|
57
|
+
export declare function releaseAdapters(options: AdapterOptions): ReleaseAdapters;
|
|
58
|
+
/** The formula with its first `url` and `sha256` replaced; anything else is left exactly as it was. */
|
|
59
|
+
export declare function bumpFormula(text: string, url: string, sha256: string): string;
|
|
60
|
+
export {};
|