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/flow-share.js
CHANGED
|
@@ -24,7 +24,7 @@ import { addFlowTriggerTo, describeTrigger, HOOK_PATH, readHooksBase, triggerCon
|
|
|
24
24
|
import { durationMinutes, durationWords, FlowContractError, flowTerms, inStepWords, LANGUAGE_WORDS, validateFlowDefinition } from "./flows.js";
|
|
25
25
|
import { stageReferenceProblems } from "./contracts/stage-output.js";
|
|
26
26
|
import { readVersioned } from "./contracts/contract.js";
|
|
27
|
-
import { FLOW_ALIASES, FLOW_FILE_FORMAT, FLOW_FILE_VERSION, flowFileSchema, PARAMETER_ID } from "./contracts/flow.js";
|
|
27
|
+
import { FLOW_ALIASES, FLOW_FILE_FORMAT, legacyFlowNeed, legacySubagentStep, FLOW_FILE_VERSION, flowFileSchema, PARAMETER_ID } from "./contracts/flow.js";
|
|
28
28
|
import { parseSchedule } from "./flow-schedule.js";
|
|
29
29
|
export { FLOW_FILE_FORMAT, FLOW_FILE_VERSION };
|
|
30
30
|
/** The largest flow file Toolroll reads or fetches. */
|
|
@@ -38,7 +38,7 @@ function stepOf(stage, ask) {
|
|
|
38
38
|
const step = { id: stage.id, title: stage.title, kind: stage.kind };
|
|
39
39
|
const words = (minutes) => durationMinutes(durationWords(minutes)) === minutes ? durationWords(minutes) : minutes;
|
|
40
40
|
// Only what the step's kind says: words a zone kept from a kind it was before stay behind.
|
|
41
|
-
if (stage.instructions !== null && (stage.kind === "task" || stage.kind === "report" || stage.kind === "draft" || stage.kind === "
|
|
41
|
+
if (stage.instructions !== null && (stage.kind === "task" || stage.kind === "report" || stage.kind === "draft" || stage.kind === "subagent"))
|
|
42
42
|
step["instructions"] = stage.instructions;
|
|
43
43
|
if (stage.kind === "task")
|
|
44
44
|
step["planning"] = stage.planning;
|
|
@@ -79,8 +79,8 @@ function stepOf(stage, ask) {
|
|
|
79
79
|
...(stage.wait.for === "hours" ? { from: stage.wait.from, until: stage.wait.to, ...(stage.wait.timeZone === undefined ? {} : { timeZone: stage.wait.timeZone }) } : { wait: words(stage.wait.minutes) }) });
|
|
80
80
|
if (stage.merge !== undefined)
|
|
81
81
|
step["merge"] = stage.merge;
|
|
82
|
-
if (stage.
|
|
83
|
-
step["
|
|
82
|
+
if (stage.subagent !== undefined)
|
|
83
|
+
step["subagent"] = stage.subagent;
|
|
84
84
|
if (stage.reply === true)
|
|
85
85
|
step["reply"] = true;
|
|
86
86
|
// A choice's buttons; "end" ignores the card. A build in another project names a path on this computer, so it stays here.
|
|
@@ -135,8 +135,8 @@ function needsOf(definition, triggers, scripts) {
|
|
|
135
135
|
needs.add("email");
|
|
136
136
|
if (stage.tool !== undefined)
|
|
137
137
|
needs.add(`tool:${stage.tool.server}`);
|
|
138
|
-
if (stage.
|
|
139
|
-
needs.add(`
|
|
138
|
+
if (stage.subagent !== undefined)
|
|
139
|
+
needs.add(`subagent:${stage.subagent}`);
|
|
140
140
|
for (const name of stage.secrets ?? [])
|
|
141
141
|
needs.add(`secret:${name}`);
|
|
142
142
|
for (const value of Object.values(stage.request?.headers ?? {}))
|
|
@@ -296,7 +296,7 @@ function stageInputOf(step, index, find) {
|
|
|
296
296
|
...(kind === "email" ? { email: { to: step["to"], subject: step["subject"], body: step["body"] } } : {}),
|
|
297
297
|
...(kind === "tool" ? { tool: { server: step["server"], name: step["tool"], args: typeof step["args"] === "object" && step["args"] !== null ? JSON.stringify(step["args"]) : step["args"] } } : {}),
|
|
298
298
|
...(kind === "wait" ? { wait: step["waitFor"] === "hours" ? { for: "hours", from: step["from"], to: step["until"], timeZone: step["timeZone"] } : { for: step["waitFor"] ?? "reply", minutes: minutes(step["wait"] ?? "3 days", "wait") } } : {}),
|
|
299
|
-
merge: step["merge"],
|
|
299
|
+
merge: step["merge"], subagent: step["subagent"], reply: step["reply"],
|
|
300
300
|
...(kind === "choose" ? { options: (list("options") ?? []).map((one, n) => ({ label: one["label"], to: one["goesTo"] === "end" ? "end" : find(one["goesTo"], `${at}.options[${n}].goesTo`) })) } : {}),
|
|
301
301
|
...(step["remindAfter"] === undefined ? {} : { limit: { minutes: minutes(step["remindAfter"], "remindAfter"), to: find(step["thenMoveTo"], `${at}.thenMoveTo`) } }),
|
|
302
302
|
next: find(step["next"], `${at}.next`), onFail: find(fail, `${at}.${failKey}`),
|
|
@@ -371,6 +371,11 @@ export function parseFlowFile(text) {
|
|
|
371
371
|
if (kind === "flow")
|
|
372
372
|
refuse(`triggers[${index}].kind: a trigger from another flow can't come from a file; it names a flow on the installation it was made on`);
|
|
373
373
|
});
|
|
374
|
+
// D5: a file written before subagents were named so (a "teammate" zone, a "teammate:" need) reads as one written since.
|
|
375
|
+
if (Array.isArray(input["zones"]))
|
|
376
|
+
input["zones"] = input["zones"].map(legacySubagentStep);
|
|
377
|
+
if (Array.isArray(input["needs"]))
|
|
378
|
+
input["needs"] = input["needs"].map(legacyFlowNeed);
|
|
374
379
|
const read = readVersioned(flowFileSchema, input, {}, FLOW_ALIASES);
|
|
375
380
|
if (!read.ok)
|
|
376
381
|
return refuse(read.issues.map(one => one.line).join("\n"));
|
|
@@ -443,7 +448,7 @@ const NEED_WORDS = {
|
|
|
443
448
|
};
|
|
444
449
|
export function needWords(need) {
|
|
445
450
|
const [kind, name] = need.includes(":") ? [need.slice(0, need.indexOf(":")), need.slice(need.indexOf(":") + 1)] : [need, ""];
|
|
446
|
-
return NEED_WORDS[need] ?? (kind === "secret" ? `A saved secret called ${name}` : kind === "tool" ? `The ${name} tool (Settings → Tools)` : kind === "
|
|
451
|
+
return NEED_WORDS[need] ?? (kind === "secret" ? `A saved secret called ${name}` : kind === "tool" ? `The ${name} tool (Settings → Tools)` : kind === "subagent" ? `A subagent called ${name}` : kind === "script" ? `The ${name} script` : need);
|
|
447
452
|
}
|
|
448
453
|
/** The import in plain words, checked against the project it lands in. Throws FlowFileError. */
|
|
449
454
|
export function planFlowImport(store, repo, file, given, actor) {
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* that's on links to its flow; one that can't run here says why.
|
|
5
5
|
*/
|
|
6
6
|
import type { StarterView } from "./flow-starters.js";
|
|
7
|
+
import { type Html } from "./html.js";
|
|
7
8
|
export declare const STARTERS_CSS: string;
|
|
8
9
|
export declare function startersHtml(input: {
|
|
9
10
|
repo: string;
|
|
@@ -17,4 +18,4 @@ export declare function startersHtml(input: {
|
|
|
17
18
|
suggested: string | null;
|
|
18
19
|
said: string | null;
|
|
19
20
|
problem: string | null;
|
|
20
|
-
}):
|
|
21
|
+
}): Html;
|
package/dist/flow-starters-ui.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
import { html, postForm } from "./html.js";
|
|
2
2
|
export const STARTERS_CSS = `.starters{max-width:760px;min-width:0}.starters h2{margin:24px 0 4px;font-size:.9375rem}` +
|
|
3
3
|
`.starter{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:4px 16px;align-items:start;padding:14px 0;border-top:1px solid var(--so-line);min-width:0}` +
|
|
4
4
|
`.starter:first-of-type{border-top:0}.starter>:not(.starter-action){grid-column:1}.starter h3{margin:0;font-size:.9375rem;display:flex;flex-wrap:wrap;gap:6px 10px;align-items:center}` +
|
|
@@ -13,23 +13,20 @@ export const STARTERS_CSS = `.starters{max-width:760px;min-width:0}.starters h2{
|
|
|
13
13
|
`@media (max-width:560px){.starter{grid-template-columns:minmax(0,1fr)}.starter .starter-action{grid-column:1;grid-row:auto;margin-top:10px}` +
|
|
14
14
|
`.starter .starter-action button,.starter .starter-action .button-link{width:100%;justify-content:center}.starters select{font-size:16px}}`;
|
|
15
15
|
export function startersHtml(input) {
|
|
16
|
-
const note = input.problem ? `<p class="problem" role="alert">${
|
|
16
|
+
const note = input.problem ? html `<p class="problem" role="alert">${input.problem}</p>` : input.said ? html `<p role="status">${input.said}</p>` : "";
|
|
17
17
|
const selector = input.projects.length > 1
|
|
18
|
-
? `<form class="starters-project" method="get" action="/settings/flows"><label>Project<select name="repo">${input.projects.map(one => `<option value="${
|
|
18
|
+
? html `<form class="starters-project" method="get" action="/settings/flows"><label>Project<select name="repo">${input.projects.map(one => html `<option value="${one.path}"${one.path === input.repo && html ` selected`}>${one.name}</option>`)}</select></label><button>Show</button></form>` : "";
|
|
19
19
|
// The one asked about ("Do this every time…") comes first, marked.
|
|
20
20
|
const ordered = [...input.starters].sort((a, b) => Number(b.id === input.suggested) - Number(a.id === input.suggested));
|
|
21
21
|
const rows = ordered.map(one => {
|
|
22
|
-
const action = one.on !== null ? `<p class="starter-action"><a class="button-link starter-open" href="/flows/${one.on.flow}">Open flow</a></p>`
|
|
22
|
+
const action = one.on !== null ? html `<p class="starter-action"><a class="button-link starter-open" href="/flows/${one.on.flow}">Open flow</a></p>`
|
|
23
23
|
: one.blocked !== null || !input.canSwitch ? ""
|
|
24
|
-
:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}).join("");
|
|
33
|
-
const nobody = !input.canSwitch && input.starters.some(one => one.on === null) ? `<p class="meta">An approver switches these on.</p>` : "";
|
|
34
|
-
return `<section class="starters">${note}${selector}<h2>Starter flows</h2>${rows}${nobody}<p class="meta"><a href="/flows">All flows</a></p></section>`;
|
|
24
|
+
: postForm("/settings/flows/on", html `<button type="submit">Switch on</button>`, { attrs: { class: "starter-action" }, hidden: { repo: input.repo, starter: one.id } });
|
|
25
|
+
// What the yes agrees to, in full, before it; nothing to agree to once it's on or can't be.
|
|
26
|
+
const agrees = one.on === null && one.blocked === null && html `<ul>${one.does.map(line => html `<li>${line}</li>`)}</ul><p class="starter-never">${one.never}</p>`;
|
|
27
|
+
const head = html `<h3>${one.name}${one.on === null ? "" : html ` <span class="starter-on"><i aria-hidden="true"></i>On</span>`}</h3>`;
|
|
28
|
+
return html `<div class="starter" id="starter-${one.id}" data-starter="${one.id}" data-suggested="${String(one.id === input.suggested && one.on === null)}">${head}<p>${one.summary}</p>${agrees}${one.on === null && one.blocked !== null && html `<p class="starter-blocked">${one.blocked}</p>`}${action}</div>`;
|
|
29
|
+
});
|
|
30
|
+
const nobody = !input.canSwitch && input.starters.some(one => one.on === null) ? html `<p class="meta">An approver switches these on.</p>` : "";
|
|
31
|
+
return html `<section class="starters">${note}${selector}<h2>Starter flows</h2>${rows}${nobody}<p class="meta"><a href="/flows">All flows</a></p></section>`;
|
|
35
32
|
}
|
package/dist/flow-steps.d.ts
CHANGED
|
@@ -2,8 +2,8 @@ import type { Runner } from "./backend.js";
|
|
|
2
2
|
import { type DraftRunner } from "./flow-draft.js";
|
|
3
3
|
import { type MailSender, type ToolCaller } from "./flow-actions.js";
|
|
4
4
|
import type { Store } from "./store.js";
|
|
5
|
-
import type { TurnRunner } from "./
|
|
6
|
-
import type { ToolLister } from "./
|
|
5
|
+
import type { TurnRunner } from "./subagents.js";
|
|
6
|
+
import type { ToolLister } from "./subagent-tools.js";
|
|
7
7
|
export type StepIo = {
|
|
8
8
|
/** `gh` for GitHub; git and the check's shell, both without a model. */
|
|
9
9
|
gh: Runner;
|
|
@@ -27,9 +27,9 @@ export type StepIo = {
|
|
|
27
27
|
callTool?: ToolCaller;
|
|
28
28
|
/** Where the project tools' secrets live (default: this computer's home). */
|
|
29
29
|
toolHome?: string;
|
|
30
|
-
/** Takes a
|
|
31
|
-
|
|
32
|
-
/** Lists what a project tool offers a
|
|
30
|
+
/** Takes a subagent's turn (default: Claude through this computer's sign-in, answering in its fixed shape). */
|
|
31
|
+
subagent?: TurnRunner;
|
|
32
|
+
/** Lists what a project tool offers a subagent (default: starts its MCP server and asks). */
|
|
33
33
|
listTools?: ToolLister;
|
|
34
34
|
};
|
|
35
35
|
export type StepPass = {
|
package/dist/flow-steps.js
CHANGED
|
@@ -40,14 +40,14 @@ import { agentFence } from "./agent-fence.js";
|
|
|
40
40
|
import { readProviderKey } from "./keys.js";
|
|
41
41
|
import { replyInChannel } from "./chat-inbox.js";
|
|
42
42
|
import { recordSent, threadOf } from "./flow-replies.js";
|
|
43
|
-
import { ASKED,
|
|
43
|
+
import { ASKED, subagentReady, subagentTurn } from "./subagent-work.js";
|
|
44
44
|
import { budgetHoldWords, claudeMachineBilling, monthOf } from "./spend.js";
|
|
45
45
|
import { toolRefusal } from "./policy.js";
|
|
46
46
|
import { pullRequestStep } from "./flow-pull-request.js";
|
|
47
47
|
import { withStageHandoff } from "./contracts/stage-output.js";
|
|
48
48
|
const RETRY_MS = [5 * 60_000, 15 * 60_000];
|
|
49
|
-
/** A
|
|
50
|
-
const
|
|
49
|
+
/** A subagent's turn tries again sooner: a card waiting on a person's colleague shouldn't wait long for a hiccup. */
|
|
50
|
+
const SUBAGENT_RETRY_MS = [60_000, 5 * 60_000];
|
|
51
51
|
const OUTPUT_CHARS = 3000;
|
|
52
52
|
/** How much of a step's output its run keeps as the log. */
|
|
53
53
|
const LOG_CHARS = 64_000;
|
|
@@ -71,9 +71,9 @@ export async function runFlowSteps(store, repo, now, io) {
|
|
|
71
71
|
const stage = known.definition?.stages.find(one => one.id === card.stage);
|
|
72
72
|
if (stage === undefined)
|
|
73
73
|
continue;
|
|
74
|
-
// v92: a
|
|
75
|
-
if (stage.kind === "
|
|
76
|
-
await
|
|
74
|
+
// v92: a subagent's turn — a zone it handles, or a decision it staffs (a person decides when it's paused or gone).
|
|
75
|
+
if (stage.kind === "subagent" || (stage.kind === "approval" && stage.subagent !== undefined)) {
|
|
76
|
+
await subagentStep(store, known.flow, known.definition, stage, card, now, io, pass);
|
|
77
77
|
continue;
|
|
78
78
|
}
|
|
79
79
|
// A pull request: opened for the card's result, then CI decides where it goes (flow-pull-request.ts).
|
|
@@ -108,7 +108,7 @@ export async function runFlowSteps(store, repo, now, io) {
|
|
|
108
108
|
// (or everything) that holds key work holds them.
|
|
109
109
|
const agents = stage.kind === "sort" ? [{ provider: "openrouter", billing: "api-key" }]
|
|
110
110
|
: stage.kind === "draft" ? [{ provider: "claude", billing: claudeMachineBilling(store.handle) }] : null;
|
|
111
|
-
const held = agents === null ? null : budgetGate({ project: repo, person: null,
|
|
111
|
+
const held = agents === null ? null : budgetGate({ project: repo, person: null, subagent: null, agents });
|
|
112
112
|
if (held !== null && held.over !== null) {
|
|
113
113
|
const waiting = `${budgetHoldWords(held, monthOf(now).name)}. Raise it on Spend to go on.`;
|
|
114
114
|
if (card.waiting !== waiting)
|
|
@@ -183,32 +183,32 @@ function claimStep(store, card, step, now) {
|
|
|
183
183
|
return store.claimFlowStep({ card: card.id, entry: card.entry, ...step }, now);
|
|
184
184
|
});
|
|
185
185
|
}
|
|
186
|
-
/** One
|
|
187
|
-
async function
|
|
188
|
-
// v102: on a protected project
|
|
186
|
+
/** One subagent turn, when one is due for this card: claimed like any step, retried on failure, and waiting while its question is open. */
|
|
187
|
+
async function subagentStep(store, flow, definition, stage, card, now, io, pass) {
|
|
188
|
+
// v102: on a protected project a subagent never decides an approval; the engine asks the person.
|
|
189
189
|
if (stage.kind === "approval" && store.approvalRules(flow.repo).protectProject)
|
|
190
190
|
return true;
|
|
191
|
-
const mate = stage.
|
|
192
|
-
const ready =
|
|
191
|
+
const mate = stage.subagent === undefined ? null : store.subagentByHandle(flow.repo, stage.subagent);
|
|
192
|
+
const ready = subagentReady(store, mate, now);
|
|
193
193
|
if (!ready.ok) {
|
|
194
194
|
// A decision goes to its person (the engine asks them). A zone only it handles waits, saying why.
|
|
195
195
|
if (stage.kind === "approval")
|
|
196
196
|
return true;
|
|
197
|
-
const waiting = ready.why === "gone" ? `There's no
|
|
197
|
+
const waiting = ready.why === "gone" ? `There's no subagent called ${stage.subagent ?? "(none)"} in this project.` : `${mate.handle} is ${ready.why === "paused" ? "paused" : `waiting: ${ready.why}`}.`;
|
|
198
198
|
if (card.waiting !== waiting)
|
|
199
199
|
store.updateFlowCard(card.id, { waiting }, now);
|
|
200
200
|
return true;
|
|
201
201
|
}
|
|
202
202
|
// Waiting on a person: its own question, or (v94) a tool call they approve first.
|
|
203
|
-
if (store.
|
|
203
|
+
if (store.openSubagentQuestionOn(card.id, card.entry) !== null)
|
|
204
204
|
return true;
|
|
205
|
-
if (!claimStep(store, card, { stage: stage.id, kind: "
|
|
205
|
+
if (!claimStep(store, card, { stage: stage.id, kind: "subagent", script: null, scriptVersion: null }, now))
|
|
206
206
|
return true;
|
|
207
207
|
pass.ran++;
|
|
208
208
|
const started = Date.now();
|
|
209
209
|
let outcome;
|
|
210
210
|
try {
|
|
211
|
-
outcome = await
|
|
211
|
+
outcome = await subagentTurn(store, flow, definition, stage, card, mate, now, { ...(io.subagent === undefined ? {} : { turn: io.subagent }), ...(io.evidenceRoot === undefined ? {} : { evidenceRoot: io.evidenceRoot }),
|
|
212
212
|
...(io.callTool === undefined ? {} : { callTool: io.callTool }), ...(io.listTools === undefined ? {} : { listTools: io.listTools }), ...(io.toolHome === undefined ? {} : { toolHome: io.toolHome }) });
|
|
213
213
|
}
|
|
214
214
|
catch (error) {
|
|
@@ -218,15 +218,15 @@ async function teammateStep(store, flow, definition, stage, card, now, io, pass)
|
|
|
218
218
|
const kept = { log: outcome.log === undefined ? null : keptLog(outcome.log), durationMs: Date.now() - started, ...(outcome.decisionJson === undefined ? {} : { decisionJson: outcome.decisionJson }) };
|
|
219
219
|
if (outcome.state === "retry") {
|
|
220
220
|
const attempts = run?.attempts ?? 1;
|
|
221
|
-
if (attempts <=
|
|
222
|
-
store.finishFlowStep(card.id, card.entry, { state: "waiting", result: outcome.said, nextAt: new Date(now.getTime() +
|
|
221
|
+
if (attempts <= SUBAGENT_RETRY_MS.length) {
|
|
222
|
+
store.finishFlowStep(card.id, card.entry, { state: "waiting", result: outcome.said, nextAt: new Date(now.getTime() + SUBAGENT_RETRY_MS[attempts - 1]).toISOString(), ...kept }, now);
|
|
223
223
|
if (stage.kind !== "approval")
|
|
224
224
|
store.updateFlowCard(card.id, { waiting: `${outcome.said} Trying again in ${attempts === 1 ? "a minute" : "5 minutes"}.` }, now);
|
|
225
225
|
pass.problems.push(`flow card ${card.id}: ${outcome.said}`);
|
|
226
226
|
return true;
|
|
227
227
|
}
|
|
228
228
|
outcome = { state: "failed", said: `${outcome.said} It didn't work after three tries.` };
|
|
229
|
-
store.
|
|
229
|
+
store.addSubagentEvent({ subagent: mate.id, card: card.id, entry: card.entry, kind: "failed", said: `Couldn't take its turn on “${card.title}”: ${outcome.said}` }, now);
|
|
230
230
|
if (stage.kind !== "approval")
|
|
231
231
|
store.updateFlowCard(card.id, { waiting: outcome.said }, now);
|
|
232
232
|
}
|
package/dist/flow-triggers.d.ts
CHANGED
|
@@ -155,7 +155,7 @@ export declare function planeStatusLink(config: TriggerConfig | null, status: st
|
|
|
155
155
|
label: string;
|
|
156
156
|
href: string;
|
|
157
157
|
} | null;
|
|
158
|
-
/** v96: a schedule (a
|
|
158
|
+
/** v96: a schedule (a subagent's routine) makes its card now, once per press, and keeps its own times. */
|
|
159
159
|
export declare function runScheduleNow(store: Store, trigger: FlowTriggerRow, actor: string, now: Date): {
|
|
160
160
|
ok: true;
|
|
161
161
|
card: number;
|
|
@@ -208,6 +208,7 @@ export declare const FORM_PATH = "/hooks/form/";
|
|
|
208
208
|
/** Share a button as a secret link anyone can fill in, without signing in. The link is shown once; only its hash is kept. */
|
|
209
209
|
export declare function shareFlowButton(store: Store, trigger: FlowTriggerRow, now: Date, dir: string | null): TriggerMade;
|
|
210
210
|
export declare function stopSharingFlowButton(store: Store, trigger: FlowTriggerRow, now: Date): void;
|
|
211
|
+
/** The page's bytes (the public route writes them itself, with its own headers). */
|
|
211
212
|
export type FormAnswer = {
|
|
212
213
|
status: number;
|
|
213
214
|
html: string;
|
package/dist/flow-triggers.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { adapterPolicy } from "./server/route-policy.js";
|
|
2
|
+
import { html, htmlString, postForm, styleElement } from "./html.js";
|
|
2
3
|
/**
|
|
3
4
|
* Flow triggers (v82): what starts cards in a flow without someone adding
|
|
4
5
|
* them by hand.
|
|
@@ -730,7 +731,7 @@ export const NEEDS_YOU_HREF = "/work?view=needs-you";
|
|
|
730
731
|
export function planeStatusLink(config, status) {
|
|
731
732
|
return config?.kind === "plane-review" && status !== null && /waited over \d+ days for you\.$/.test(status) ? { label: "Needs you", href: NEEDS_YOU_HREF } : null;
|
|
732
733
|
}
|
|
733
|
-
/** v96: a schedule (a
|
|
734
|
+
/** v96: a schedule (a subagent's routine) makes its card now, once per press, and keeps its own times. */
|
|
734
735
|
export function runScheduleNow(store, trigger, actor, now) {
|
|
735
736
|
const config = triggerConfigOf(trigger);
|
|
736
737
|
if (config?.kind !== "schedule" || config.script !== undefined)
|
|
@@ -1107,11 +1108,7 @@ export function shareFlowButton(store, trigger, now, dir) {
|
|
|
1107
1108
|
export function stopSharingFlowButton(store, trigger, now) {
|
|
1108
1109
|
store.updateFlowTrigger(trigger.id, { hookHash: null }, now);
|
|
1109
1110
|
}
|
|
1110
|
-
const
|
|
1111
|
-
/** The public page: the button's name and its questions, nothing about the flow, the project or anyone in it. */
|
|
1112
|
-
function formHtml(label, body) {
|
|
1113
|
-
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="robots" content="noindex">
|
|
1114
|
-
<title>${esc(label)}</title><style>
|
|
1111
|
+
const FORM_CSS = `
|
|
1115
1112
|
:root{color-scheme:light dark;--bg:#f7f7f5;--card:#fff;--text:#1c1d1b;--muted:#666a63;--line:#dcded8;--accent:#2d5a45}
|
|
1116
1113
|
@media (prefers-color-scheme:dark){:root{--bg:#141614;--card:#1d201d;--text:#ecefe9;--muted:#9ea39a;--line:#343934;--accent:#8fd0ae}}
|
|
1117
1114
|
*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:16px/1.5 system-ui,-apple-system,"Segoe UI",sans-serif}
|
|
@@ -1119,18 +1116,23 @@ main{max-width:560px;margin:0 auto;padding:40px 16px}h1{font-size:1.5rem;margin:
|
|
|
1119
1116
|
label{display:grid;gap:6px;margin:0 0 16px;font-weight:600}input,textarea{font:inherit;color:inherit;background:transparent;border:1px solid var(--line);border-radius:10px;padding:10px 12px;width:100%}
|
|
1120
1117
|
textarea{min-height:96px;resize:vertical}button{font:inherit;font-weight:600;border:0;border-radius:10px;padding:12px 18px;min-height:44px;background:var(--accent);color:var(--bg);cursor:pointer}
|
|
1121
1118
|
p{color:var(--muted)}.trap{position:absolute;left:-10000px;width:1px;height:1px;overflow:hidden}a{color:var(--accent)}
|
|
1122
|
-
|
|
1119
|
+
`;
|
|
1120
|
+
/** The public page: the button's name and its questions, nothing about the flow, the project or anyone in it. */
|
|
1121
|
+
function formHtml(label, body) {
|
|
1122
|
+
return htmlString(html `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="robots" content="noindex">
|
|
1123
|
+
<title>${label}</title>${styleElement(FORM_CSS)}</head><body><main><h1>${label}</h1><div class="card">${body}</div></main></body></html>`);
|
|
1123
1124
|
}
|
|
1124
1125
|
/** GET a shared form: its questions. */
|
|
1125
1126
|
export function flowFormPage(store, token, now) {
|
|
1126
1127
|
const trigger = /^[A-Za-z0-9_-]{20,64}$/.test(token) ? store.flowTriggerByHook(hookHash(token)) : null;
|
|
1127
1128
|
const config = trigger === null ? null : triggerConfigOf(trigger);
|
|
1128
1129
|
if (trigger === null || config?.kind !== "button")
|
|
1129
|
-
return { status: 404, html: formHtml("Not found",
|
|
1130
|
+
return { status: 404, html: formHtml("Not found", html `<p>This form isn't available. Ask whoever shared it for a new link.</p>`) };
|
|
1130
1131
|
if (!adapterPolicy({ caller: "anonymous", capability: "none" }, undefined, source => source === "flow" && store.getFlow(trigger.flow) !== null).ok)
|
|
1131
1132
|
return { status: 403, html: "" };
|
|
1132
|
-
const fields = config.questions.map((question, index) => `<label>${
|
|
1133
|
-
|
|
1133
|
+
const fields = config.questions.map((question, index) => html `<label>${question}${index === 0 ? html `<input name="a${index}" required maxlength="200" autocomplete="off">` : html `<textarea name="a${index}" maxlength="3000"></textarea>`}</label>`);
|
|
1134
|
+
// Signed out by nature: no session, so no CSRF; the trap, the shown time and the hourly limit guard it. It posts back to its own address.
|
|
1135
|
+
return { status: 200, html: formHtml(config.label, postForm("", html `${fields}<div class="trap" aria-hidden="true"><label>Leave this empty<input name="website" tabindex="-1" autocomplete="off"></label></div><input type="hidden" name="t" value="${now.getTime()}"><button>Send</button>`, { signedOut: true })) };
|
|
1134
1136
|
}
|
|
1135
1137
|
/** POST a shared form: at most one card per submission, keys refused, a limit per hour, and quiet about what a bot did wrong. */
|
|
1136
1138
|
export function receiveFlowForm(store, token, fields, now) {
|
|
@@ -1140,8 +1142,8 @@ export function receiveFlowForm(store, token, fields, now) {
|
|
|
1140
1142
|
return flowFormPage(store, token, now);
|
|
1141
1143
|
if (!adapterPolicy({ caller: "anonymous", capability: "none" }, undefined, source => source === "flow" && store.getFlow(trigger.flow) !== null).ok)
|
|
1142
1144
|
return { status: 403, html: "" };
|
|
1143
|
-
const again = `<p><a href="${
|
|
1144
|
-
const thanks = formHtml(config.label, `<p>Thanks — it's been sent.</p>${again}`);
|
|
1145
|
+
const again = html `<p><a href="${FORM_PATH + token}">Send another</a></p>`;
|
|
1146
|
+
const thanks = formHtml(config.label, html `<p>Thanks — it's been sent.</p>${again}`);
|
|
1145
1147
|
// A filled trap, or a page posted faster than a person could, gets thanks and makes nothing.
|
|
1146
1148
|
const form = readFormSubmission(fields, config.questions.length);
|
|
1147
1149
|
const shown = Number(form.t);
|
|
@@ -1149,14 +1151,14 @@ export function receiveFlowForm(store, token, fields, now) {
|
|
|
1149
1151
|
return { status: 200, html: thanks };
|
|
1150
1152
|
const recent = store.handle.prepare("SELECT COUNT(*) AS n FROM flow_trigger_event WHERE trigger = ? AND key LIKE 'form:%' AND at > ?").get(trigger.id, new Date(now.getTime() - 3_600_000).toISOString());
|
|
1151
1153
|
if (Number(recent?.["n"] ?? 0) >= FORM_CARDS_PER_HOUR)
|
|
1152
|
-
return { status: 429, html: formHtml(config.label, `<p>Too many sent in the last hour. Try again later.</p>`) };
|
|
1154
|
+
return { status: 429, html: formHtml(config.label, html `<p>Too many sent in the last hour. Try again later.</p>`) };
|
|
1153
1155
|
const answers = form.answers.map((answer, index) => (answer ?? "").replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, "").trim().slice(0, index === 0 ? 200 : 3000));
|
|
1154
1156
|
if (answers[0] === "")
|
|
1155
|
-
return { status: 400, html: formHtml(config.label, `<p>Answer “${
|
|
1157
|
+
return { status: 400, html: formHtml(config.label, html `<p>Answer “${config.questions[0]}” first.</p>${again}`) };
|
|
1156
1158
|
const details = config.questions.slice(1).map((question, index) => answers[index + 1] === "" ? null : `${question}\n${answers[index + 1]}`).filter(one => one !== null).join("\n\n");
|
|
1157
1159
|
const made = makeCard(store, trigger, config, { key: `form:${now.toISOString()}:${randomUUID().slice(0, 8)}`, title: answers[0], description: `From the “${config.label}” form:\n\n${details}`.trim(), source: { kind: "form", label: `Form: ${config.label}`, url: null } }, "Form", now);
|
|
1158
1160
|
if (made.made !== "added")
|
|
1159
|
-
return { status: 400, html: formHtml(config.label, `<p>${made.note?.includes("key or password") ? "That looks like it holds a key or password. Take it out and send again." : "That couldn't be sent."}</p>${again}`) };
|
|
1161
|
+
return { status: 400, html: formHtml(config.label, html `<p>${made.note?.includes("key or password") ? "That looks like it holds a key or password. Take it out and send again." : "That couldn't be sent."}</p>${again}`) };
|
|
1160
1162
|
store.updateFlowTrigger(trigger.id, { lastAt: now.toISOString(), lastOutcome: "Someone sent the form." }, now);
|
|
1161
1163
|
return { status: 200, html: thanks };
|
|
1162
1164
|
}
|
package/dist/flows-cli.js
CHANGED
|
@@ -516,7 +516,7 @@ function zoneOf(stage, flow) {
|
|
|
516
516
|
...(stage.kind === 'approval' ? { decider: deciderOf(stage, flow) } : {}),
|
|
517
517
|
...(stage.script === null ? {} : { script: stage.script }),
|
|
518
518
|
...(stage.merge === undefined ? {} : { merge: stage.merge }),
|
|
519
|
-
...(stage.
|
|
519
|
+
...(stage.subagent === undefined ? {} : { subagent: stage.subagent }),
|
|
520
520
|
...(stage.options === undefined ? {} : { options: stage.options.map(one => ({ label: one.label, to: one.to })) }),
|
|
521
521
|
...(stage.repo === undefined ? {} : { repo: stage.repo }),
|
|
522
522
|
};
|
package/dist/flows-ui.d.ts
CHANGED
|
@@ -3,9 +3,10 @@
|
|
|
3
3
|
import type { BrowserFlowView } from "./browser-workspace.js";
|
|
4
4
|
import type { FlowRow, Store } from "./store.js";
|
|
5
5
|
import { type FlowFile, type FlowImportPlan } from "./flow-share.js";
|
|
6
|
+
import { type Html } from "./html.js";
|
|
6
7
|
export declare const FLOWS_CSS = ".flows{max-width:880px;min-width:0}.flows .card{padding:16px 18px;margin:12px 0}.flows .flow-row{display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap}.flows .flow-row h2{font-size:1.05rem;margin:0}.flows .flow-counts{font-size:.85rem;color:var(--so-muted)}.flows form{display:grid;gap:10px;margin:0}.flows label{display:grid;gap:6px}.flows input,.flows select{box-sizing:border-box;width:100%;max-width:100%}.flows button{justify-self:start;min-height:44px}.flows .flow-fallback ol{padding-left:20px}.flows summary{cursor:pointer;min-height:44px;display:flex;align-items:center;font-weight:600}.flows .flow-buttons{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin:10px 0 0}.flows .flow-import{margin-top:18px;padding-top:14px;border-top:1px solid var(--so-line)}.flows .flow-import h3{font-size:.95rem;margin:0}.flows .flow-terms{list-style:none;padding:0;margin:8px 0 16px;display:grid;gap:10px}.flows .flow-terms li{white-space:pre-line;overflow-wrap:anywhere}.flows .flow-terms pre{white-space:pre-wrap;overflow-wrap:anywhere;font-size:.8rem;margin:6px 0 0}.flows .flow-import-preview h2{font-size:1rem;margin:20px 0 4px}.flows .flow-untrusted{font-weight:600}.flows button.primary{background:var(--primary);color:var(--primary-foreground);border-color:var(--primary)}.flows fieldset{border:0;padding:0;margin:0;display:grid;gap:10px;min-width:0}.flows legend{font-weight:600;padding:0;margin-bottom:6px}.flows textarea[hidden]{display:none}.flows button.secondary{background:transparent;color:var(--foreground);border:1px solid var(--border)}@media(max-width:600px){.flows input,.flows select{font-size:16px}}@media(max-width:760px){.flows .card{padding:10px 12px;margin:8px 0;position:relative}.flows .flow-row{row-gap:2px}.flows .flow-row h2 a::after{content:\"\";position:absolute;inset:0;border-radius:inherit}.flows .flow-counts{line-height:1.35}}";
|
|
7
8
|
/** The flows a person can open, with how many cards wait in each, and the new-flow form. */
|
|
8
|
-
export declare function flowsListHtml(store: Store, flows: readonly FlowRow[], projects: readonly string[],
|
|
9
|
+
export declare function flowsListHtml(store: Store, flows: readonly FlowRow[], projects: readonly string[], canCreate: boolean, problem: string | null): Html;
|
|
9
10
|
/** Reads the chosen flow file into the form (the server reads it again, whole, before anything is made). */
|
|
10
11
|
export declare const FLOW_IMPORT_SCRIPT = "(function(){var form=document.querySelector('[data-flow-import]');if(!form)return;var input=form.querySelector('[data-flow-file]'),text=form.querySelector('[data-flow-document]'),note=form.querySelector('[data-flow-file-note]'),address=form.querySelector('[name=url]');function say(words){note.textContent=words;note.hidden=words==='';}var reading=null;input.addEventListener('change',function(){text.value='';say('');var file=input.files&&input.files[0];if(!file){reading=null;return;}if(file.size>262144){say('That file is too big: a flow file is at most 256 KB.');input.value='';reading=null;return;}reading=file.text().then(function(value){text.value=value;});});form.addEventListener('submit',function(event){if(reading!==null&&text.value===''){event.preventDefault();reading.then(function(){form.requestSubmit();});return;}if(text.value===''&&address.value.trim()===''){event.preventDefault();say('Choose a flow file or give its address.');}});})();";
|
|
11
12
|
/** An import's preview: the plan in plain words, what it asks for, and one Import button after the terms. */
|
|
@@ -18,7 +19,7 @@ export declare function flowImportHtml(input: {
|
|
|
18
19
|
values: Record<string, string>;
|
|
19
20
|
previewed: string;
|
|
20
21
|
problem: string | null;
|
|
21
|
-
}):
|
|
22
|
+
}): Html;
|
|
22
23
|
/** One flow's canvas for one person. */
|
|
23
24
|
export declare function flowView(store: Store, flow: FlowRow, viewer: {
|
|
24
25
|
name: string;
|
|
@@ -31,4 +32,4 @@ export declare function flowView(store: Store, flow: FlowRow, viewer: {
|
|
|
31
32
|
toolHome?: string;
|
|
32
33
|
}): BrowserFlowView;
|
|
33
34
|
/** The page the canvas replaces when scripts can't run: each zone and its cards, in order. */
|
|
34
|
-
export declare function flowFallbackHtml(view: BrowserFlowView):
|
|
35
|
+
export declare function flowFallbackHtml(view: BrowserFlowView): Html;
|