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/flows-ui.js
CHANGED
|
@@ -12,58 +12,47 @@ import { itemCaption, whyLines } from "./flow-items.js";
|
|
|
12
12
|
import { FLOW_FILE_MAX_BYTES } from "./flow-share.js";
|
|
13
13
|
import { googleConnected } from "./google-mail.js";
|
|
14
14
|
import { mailboxReady } from "./mailbox.js";
|
|
15
|
-
import { labelOf, nameOf } from "./
|
|
16
|
-
import { callWords, receiptWords } from "./
|
|
17
|
-
import { undoFor } from "./
|
|
15
|
+
import { labelOf, nameOf } from "./subagent-admin.js";
|
|
16
|
+
import { callWords, receiptWords } from "./subagent-tools.js";
|
|
17
|
+
import { undoFor } from "./subagent-week.js";
|
|
18
18
|
import { flowCardTaskLine } from "./flow-card-task.js";
|
|
19
19
|
import { readFlowSecrets } from "./flow-secrets.js";
|
|
20
|
-
|
|
20
|
+
import { html, joinHtml, postForm } from "./html.js";
|
|
21
21
|
const projectName = (repo) => repo.split(/[\\/]/).filter(Boolean).pop() ?? repo;
|
|
22
22
|
export 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}}`;
|
|
23
23
|
/** The flows a person can open, with how many cards wait in each, and the new-flow form. */
|
|
24
|
-
export function flowsListHtml(store, flows, projects,
|
|
24
|
+
export function flowsListHtml(store, flows, projects, canCreate, problem) {
|
|
25
25
|
const rows = flows.map(flow => {
|
|
26
26
|
const cards = store.flowCards(flow.id, false);
|
|
27
27
|
const definition = flowDefinitionOf(flow);
|
|
28
28
|
const waiting = cards.filter(card => ["approval", "choose"].includes(definition?.stages.find(one => one.id === card.stage)?.kind ?? "")).length;
|
|
29
29
|
const buttons = store.flowTriggers(flow.id).filter(one => one.state === "active").flatMap(one => { const config = triggerConfigOf(one); return config?.kind === "button" ? [{ id: one.id, label: config.label }] : []; });
|
|
30
30
|
const trouble = troubleWords(flowInsights(store, flow, new Date(), 7));
|
|
31
|
-
return `<article class="card"><div class="flow-row"><h2><a href="/flows/${flow.id}">${
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
const projectField = projects.length === 1 ? `<input type="hidden" name="repo" value="${e(projects[0])}">` : `<label>Project<select name="repo">${projects.map(repo => `<option value="${e(repo)}">${e(projectName(repo))}</option>`).join("")}</select></label>`;
|
|
31
|
+
return html `<article class="card"><div class="flow-row"><h2><a href="/flows/${flow.id}">${flow.name}</a></h2><span class="flow-counts">${projectName(flow.repo)} · ${cards.length} card${cards.length === 1 ? "" : "s"} in progress${waiting > 0 ? ` · ${waiting} waiting for a decision` : ""}${trouble === null ? "" : ` · ${trouble}`}</span></div>${buttons.length > 0 && html `<p class="flow-buttons">${joinHtml(buttons.map(one => html `<a class="button-link" href="/flows/${flow.id}?start=${one.id}">${one.label}</a>`), " ")}</p>`}</article>`;
|
|
32
|
+
});
|
|
33
|
+
const projectField = projects.length === 1 ? html `<input type="hidden" name="repo" value="${projects[0]}">` : html `<label>Project<select name="repo">${projects.map(repo => html `<option value="${repo}">${projectName(repo)}</option>`)}</select></label>`;
|
|
35
34
|
// A flow file (flow-share.ts): chosen here or fetched from a gist, previewed in plain words before anything is made.
|
|
36
|
-
const importForm =
|
|
35
|
+
const importForm = postForm("/flows/import", html `<h3>Or import a flow file</h3>${projectField}<label>File<input type="file" accept=".json,application/json" data-flow-file></label><textarea name="document" hidden data-flow-document></textarea><label>Or its address<input name="url" type="url" inputmode="url" placeholder="https://gist.github.com/…"></label><p class="problem" role="alert" data-flow-file-note hidden></p><button>Preview import</button>`, { attrs: { class: "flow-import", "data-flow-import": true } });
|
|
37
36
|
// A new flow starts from the gallery (Flows → New): templates grouped by what they're for.
|
|
38
|
-
const create = canCreate && projects.length > 0
|
|
37
|
+
const create = canCreate && projects.length > 0 && html `<p class="flow-buttons"><a class="button-link" href="/flows/new">New flow</a></p>${importForm}`;
|
|
39
38
|
// No flows yet: one click makes a working example with a sample question in it.
|
|
40
39
|
const example = flows.length === 0 && canCreate && projects.length > 0
|
|
41
|
-
?
|
|
42
|
-
:
|
|
43
|
-
const intro = `<p class="meta">A flow is your process drawn as zones. Cards move through them: agents do the work, people approve, and the team hears about it.</p>`
|
|
44
|
-
|
|
45
|
-
`<p class="flow-chat">AI teammates can decide and handle cards for you, within rules you write. <a href="/teammates">Teammates</a> · or set one up with its flow in one click: <a href="/kits">Starter kits</a></p>`;
|
|
46
|
-
return `<section class="flows">${problem === null ? "" : `<p class="problem" role="alert">${e(problem)}</p>`}${intro}${create}${rows || example || '<p class="meta">No flows yet.</p>'}</section>`;
|
|
40
|
+
? postForm("/flows/example", html `<h2>See a flow work</h2><p class="meta">Claude drafts a reply to a sample customer question; you approve it here or in your chat app.</p>${projectField}<button>Try an example</button>`, { attrs: { class: "card flow-example" } })
|
|
41
|
+
: null;
|
|
42
|
+
const intro = html `<p class="meta">A flow is your process drawn as zones. Cards move through them: agents do the work, people approve, and the team hears about it.</p>${canCreate && projects.length > 0 && html `<p class="flow-chat">Describe how work should move and your lead drafts the flow for you to confirm, or start from a template. <a href="/chat?draft=${encodeURIComponent("Make a flow for ")}">Describe it in chat</a></p>`}<p class="flow-chat">subagents can decide and handle cards for you, within rules you write. <a href="/settings/lead/subagents">Subagents</a> · or set one up with its flow in one click: <a href="/kits">Starter kits</a></p>`;
|
|
43
|
+
return html `<section class="flows">${problem !== null && html `<p class="problem" role="alert">${problem}</p>`}${intro}${create}${rows.length > 0 ? rows : example ?? html `<p class="meta">No flows yet.</p>`}</section>`;
|
|
47
44
|
}
|
|
48
45
|
/** Reads the chosen flow file into the form (the server reads it again, whole, before anything is made). */
|
|
49
46
|
export 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>${FLOW_FILE_MAX_BYTES}){say('That file is too big: a flow file is at most ${FLOW_FILE_MAX_BYTES / 1024} 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.');}});})();`;
|
|
50
47
|
/** An import's preview: the plan in plain words, what it asks for, and one Import button after the terms. */
|
|
51
48
|
export function flowImportHtml(input) {
|
|
52
49
|
const { plan, file } = input;
|
|
53
|
-
const fields = file.parameters.map(one => `<label><span>${
|
|
54
|
-
const list = (items) => items.length === 0 ? "" : `<ul class="flow-terms">${items.map(term => `<li>${
|
|
50
|
+
const fields = file.parameters.map(one => html `<label><span>${one.about}${one.optional === true && html ` <span class="meta">(optional)</span>`}</span><input name="param.${one.id}" value="${input.values[one.id] ?? plan?.values[one.id] ?? one.default ?? ""}" maxlength="200" autocomplete="off"></label>`);
|
|
51
|
+
const list = (items) => items.length === 0 ? "" : html `<ul class="flow-terms">${items.map(term => html `<li>${term}</li>`)}</ul>`;
|
|
55
52
|
// The steps, what they mean, the triggers and the scripts; each script's code one tap away, as on the Scripts panel.
|
|
56
|
-
const terms = plan === null ? "" :
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
(plan.parts.triggers.length === 0 ? "" : `<h2>Triggers</h2>${list(plan.parts.triggers)}`) +
|
|
60
|
-
(plan.parts.scripts.length + plan.parts.kept.length === 0 ? "" : `<h2>Scripts</h2><ul class="flow-terms">${plan.scripts.add.map((one, index) => `<li>${e(plan.parts.scripts[index])}${one.body === undefined ? "" : `<details><summary>Show the script</summary><pre>${e(one.body)}</pre></details>`}</li>`).join("")}${plan.parts.kept.map(one => `<li>${e(one)}</li>`).join("")}</ul>`) +
|
|
61
|
-
`<h2>Good to know</h2>${list([...plan.parts.notes, ...(plan.parts.needs === null ? [] : [plan.parts.needs])])}`;
|
|
62
|
-
return `<section class="flows flow-import-preview">${input.problem === null ? "" : `<p class="problem" role="alert">${e(input.problem)}</p>`}` +
|
|
63
|
-
`<p class="meta">Into ${e(projectName(input.repo))}. Nothing is made until you import it.</p>${terms}` +
|
|
64
|
-
`<form method="post" action="/flows/import" class="card"><input type="hidden" name="csrf" value="${e(input.csrf)}"><input type="hidden" name="repo" value="${e(input.repo)}"><input type="hidden" name="previewed" value="${e(input.previewed)}"><textarea name="document" hidden>${e(input.document)}</textarea>` +
|
|
65
|
-
(fields === "" ? "" : `<fieldset class="flow-import-fields"><legend>Fill in</legend>${fields}</fieldset>`) +
|
|
66
|
-
`<p class="flow-buttons">${plan === null ? "" : '<button name="confirm" value="yes" class="primary">Import flow</button>'}${fields === "" ? "" : `<button name="confirm" value="" class="${plan === null ? "" : "secondary"}">Update preview</button>`}<a href="/flows">Cancel</a></p></form></section>`;
|
|
53
|
+
const terms = plan === null ? "" : html `${plan.parts.about !== null && html `<p>${plan.parts.about}</p>`}<p class="flow-untrusted">Its instructions come from the file, not from you: read what each step is asked before you import.</p>${list(plan.parts.steps)}${plan.parts.triggers.length > 0 && html `<h2>Triggers</h2>${list(plan.parts.triggers)}`}${plan.parts.scripts.length + plan.parts.kept.length > 0 && html `<h2>Scripts</h2><ul class="flow-terms">${plan.scripts.add.map((one, index) => html `<li>${plan.parts.scripts[index]}${one.body !== undefined && html `<details><summary>Show the script</summary><pre>${one.body}</pre></details>`}</li>`)}${plan.parts.kept.map(one => html `<li>${one}</li>`)}</ul>`}<h2>Good to know</h2>${list([...plan.parts.notes, ...(plan.parts.needs === null ? [] : [plan.parts.needs])])}`;
|
|
54
|
+
const form = postForm("/flows/import", html `<textarea name="document" hidden>${input.document}</textarea>${fields.length > 0 && html `<fieldset class="flow-import-fields"><legend>Fill in</legend>${fields}</fieldset>`}<p class="flow-buttons">${plan !== null && html `<button name="confirm" value="yes" class="primary">Import flow</button>`}${fields.length > 0 && html `<button name="confirm" value="" class="${plan === null ? "" : "secondary"}">Update preview</button>`}<a href="/flows">Cancel</a></p>`, { attrs: { class: "card" }, hidden: { repo: input.repo, previewed: input.previewed } });
|
|
55
|
+
return html `<section class="flows flow-import-preview">${input.problem !== null && html `<p class="problem" role="alert">${input.problem}</p>`}<p class="meta">Into ${projectName(input.repo)}. Nothing is made until you import it.</p>${terms}${form}</section>`;
|
|
67
56
|
}
|
|
68
57
|
const historyText = (event, title, sorts = new Set()) => {
|
|
69
58
|
const who = event.actor === "flow" ? "" : ` by ${event.actor}`;
|
|
@@ -86,7 +75,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
86
75
|
const title = (id) => stages.find(one => one.id === id)?.title ?? id;
|
|
87
76
|
const sortZones = new Set(stages.filter(one => one.kind === "sort").map(one => one.id));
|
|
88
77
|
const decisions = sortZones.size === 0 ? new Map() : store.flowSortDecisions(flow.id);
|
|
89
|
-
const mates = new Map(store.
|
|
78
|
+
const mates = new Map(store.subagents([flow.repo]).map(one => [one.id, one]));
|
|
90
79
|
const taskSecrets = new Map();
|
|
91
80
|
const secretsFor = (repo) => {
|
|
92
81
|
if (!taskSecrets.has(repo))
|
|
@@ -103,9 +92,9 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
103
92
|
const discussion = store.flowComments(card.id);
|
|
104
93
|
const watchers = store.flowCardWatchers(card.id);
|
|
105
94
|
const decider = stage?.kind === "approval" ? deciderOf(stage, flow) : null;
|
|
106
|
-
// While its
|
|
107
|
-
const
|
|
108
|
-
const canDecide = viewer.approver && card.state === "active" && stage?.kind === "approval" && (decider === null || decider === viewer.name) && !
|
|
95
|
+
// While its subagent is deciding (v92), the card isn't anyone else's to decide yet.
|
|
96
|
+
const subagentDeciding = stage?.subagent !== undefined && card.waiting !== null && card.waiting.endsWith(" is deciding");
|
|
97
|
+
const canDecide = viewer.approver && card.state === "active" && stage?.kind === "approval" && (decider === null || decider === viewer.name) && !subagentDeciding;
|
|
109
98
|
// History reads moves and ownership together, newest first.
|
|
110
99
|
const owned = discussion.filter(one => one.kind === "owner").map(one => ({ text: one.body === "" ? `${one.author} left it without an owner` : one.body === one.author ? `${one.author} took it on` : `${one.author} made ${one.body} the owner`, at: one.at }));
|
|
111
100
|
const moves = store.flowEvents(card.id).map(event => ({ text: historyText(event, title, sortZones), at: event.at }));
|
|
@@ -120,15 +109,15 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
120
109
|
label: stage.wait !== undefined && stage.wait.for !== "hours" ? (stage.wait.for === "reply" ? "No reply by" : "Moves on at") : stage.limit.to !== null ? `Moves to ${title(stage.limit.to)} at` : "Reminder at",
|
|
121
110
|
};
|
|
122
111
|
// Its open question: one of its own, or (v94) a tool call waiting for approval.
|
|
123
|
-
const asked = card.state === "active" ? store.
|
|
124
|
-
const pending = asked?.toolCall == null ? null : store.
|
|
125
|
-
// A decision its
|
|
126
|
-
const turn = card.state === "active" && stage?.kind === "approval" && stage.
|
|
112
|
+
const asked = card.state === "active" ? store.openSubagentQuestionOn(card.id, card.entry) : null;
|
|
113
|
+
const pending = asked?.toolCall == null ? null : store.subagentCall(asked.toolCall);
|
|
114
|
+
// A decision its subagent handed over carries what the subagent said (v92).
|
|
115
|
+
const turn = card.state === "active" && stage?.kind === "approval" && stage.subagent !== undefined ? store.flowStepRun(card.id, card.entry) : null;
|
|
127
116
|
let handoff = null;
|
|
128
|
-
if (turn !== null && turn.kind === "
|
|
117
|
+
if (turn !== null && turn.kind === "subagent" && turn.state === "passed") {
|
|
129
118
|
try {
|
|
130
119
|
const said = JSON.parse(turn.decisionJson ?? "{}");
|
|
131
|
-
const mate = store.
|
|
120
|
+
const mate = store.subagentByHandle(flow.repo, stage.subagent);
|
|
132
121
|
if (said.action === "hand_off" && typeof said.note === "string" && said.note !== "" && mate !== null)
|
|
133
122
|
handoff = { from: labelOf(mate), note: said.note };
|
|
134
123
|
}
|
|
@@ -136,7 +125,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
136
125
|
handoff = null;
|
|
137
126
|
}
|
|
138
127
|
}
|
|
139
|
-
const asker = asked === null || asked.state !== "open" ? null : store.
|
|
128
|
+
const asker = asked === null || asked.state !== "open" ? null : store.getSubagent(asked.subagent);
|
|
140
129
|
// "Person chooses" (flow-send.ts): what was sent, and the options, for the person it waits on; the latest send otherwise.
|
|
141
130
|
const choosing = card.state === "active" && stage?.kind === "choose" ? flowChoiceAt(store, card.id, card.entry) : null;
|
|
142
131
|
const latest = store.flowSends(card.id)[0] ?? null;
|
|
@@ -173,11 +162,11 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
173
162
|
question: asked === null || asked.state !== "open" || asker === null ? null
|
|
174
163
|
: { id: asked.id, from: labelOf(asker), question: asked.question, options: asked.options, askedOf: asked.askedOf, mine: asked.askedOf === viewer.name,
|
|
175
164
|
call: pending === null ? null : { why: pending.why, rule: pending.result ?? "" } },
|
|
176
|
-
calls: store.
|
|
177
|
-
const mate = mates.get(call.
|
|
165
|
+
calls: store.subagentCallsOn(card.id).slice(-30).map(call => {
|
|
166
|
+
const mate = mates.get(call.subagent) ?? null;
|
|
178
167
|
// A person's undo (v97) reads as theirs, and its outcome says what it undid.
|
|
179
|
-
return { id: call.id, who: call.undoOf !== null ? call.decidedBy ?? "A person" : mate === null ? "A
|
|
180
|
-
why: call.undoOf !== null ? "" : call.why, result: call.state === "asked" ? null : call.result, at: call.createdAt,
|
|
168
|
+
return { id: call.id, who: call.undoOf !== null ? call.decidedBy ?? "A person" : mate === null ? "A subagent" : nameOf(mate), words: callWords(call.tool, call.action, call.input, 400), state: call.state, outcome: receiptWords(store, call),
|
|
169
|
+
why: call.undoOf !== null ? "" : call.why, result: call.state === "asked" ? null : call.result, at: call.createdAt, subagent: call.subagent, undo: viewer.approver ? undoFor(store, call) : null };
|
|
181
170
|
}),
|
|
182
171
|
};
|
|
183
172
|
});
|
|
@@ -208,7 +197,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
208
197
|
startTrigger: setup.startTrigger ?? null,
|
|
209
198
|
me: viewer.name,
|
|
210
199
|
sortReady: setup.sortReady ?? false,
|
|
211
|
-
|
|
200
|
+
subagents: store.subagents([flow.repo]).map(mate => ({ handle: mate.handle, label: labelOf(mate), name: nameOf(mate), working: mate.state === "active", href: `/settings/lead/subagents/${mate.id}` })),
|
|
212
201
|
emailReady: sendingReady(setup.dir),
|
|
213
202
|
requestSecrets: flowSecretNames(setup.dir, flow.repo),
|
|
214
203
|
tools: projectToolsOf(store, flow.repo).map(tool => ({ name: tool.name, about: tool.spec.about, functions: tool.lastTest?.ok === true ? tool.lastTest.tools : [],
|
|
@@ -230,8 +219,8 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
230
219
|
}
|
|
231
220
|
/** The page the canvas replaces when scripts can't run: each zone and its cards, in order. */
|
|
232
221
|
export function flowFallbackHtml(view) {
|
|
233
|
-
return `<section class="flows flow-fallback"><p class="meta">${
|
|
222
|
+
return html `<section class="flows flow-fallback"><p class="meta">${view.flow.project}</p><ol>${view.stages.map(stage => {
|
|
234
223
|
const cards = view.cards.filter(card => card.stage === stage.id && card.state === "active");
|
|
235
|
-
return `<li><strong>${
|
|
236
|
-
})
|
|
224
|
+
return html `<li><strong>${stage.title}</strong> · ${FLOW_KIND_WORDS[stage.kind].label}${cards.length > 0 && html `<ul>${cards.map(card => html `<li>${card.title}${card.waiting !== null && ` — ${card.waiting}`}</li>`)}</ul>`}</li>`;
|
|
225
|
+
})}</ol></section>`;
|
|
237
226
|
}
|
package/dist/flows.js
CHANGED
|
@@ -49,9 +49,10 @@
|
|
|
49
49
|
*/
|
|
50
50
|
import { TEXT_LIMITS } from "./text-limits.js";
|
|
51
51
|
import { createHash } from "node:crypto";
|
|
52
|
+
import { projectName } from "./project.js";
|
|
52
53
|
import { parseContract, readVersioned } from "./contracts/contract.js";
|
|
53
54
|
import { stageReferenceProblems } from "./contracts/stage-output.js";
|
|
54
|
-
import { CHOICES_MAX, CHOICES_MIN, FLOW_ALIASES, FLOW_COLORS, FLOW_DEFINITION_VERSION, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, flowDefinitionSchema, flowStepsSchema, HEADERS_MAX, LONGEST_WAIT_MINUTES, savedFlowDefinitionSchema, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf, SURE_AT_MAX, SURE_AT_MIN, ZONE_ID, } from "./contracts/flow.js";
|
|
55
|
+
import { CHOICES_MAX, CHOICES_MIN, FLOW_ALIASES, FLOW_COLORS, FLOW_DEFINITION_VERSION, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, flowDefinitionSchema, flowStepsSchema, HEADERS_MAX, legacySubagentStep, LONGEST_WAIT_MINUTES, savedFlowDefinitionSchema, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf, SURE_AT_MAX, SURE_AT_MIN, ZONE_ID, } from "./contracts/flow.js";
|
|
55
56
|
export { CHOICES_MAX, CHOICES_MIN, FLOW_COLORS, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, LONGEST_WAIT_MINUTES, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf };
|
|
56
57
|
/** What each kind is called and does, in the words the canvas uses. */
|
|
57
58
|
export const FLOW_KIND_WORDS = {
|
|
@@ -69,7 +70,7 @@ export const FLOW_KIND_WORDS = {
|
|
|
69
70
|
draft: { label: "Draft", about: "Claude writes a reply, summary or note from the card in seconds. Nothing is sent until a later step sends it." },
|
|
70
71
|
sort: { label: "Sort", about: "Jev reads the card in under a second and sends it where its answer leads. Cards it isn't sure about take the not-sure path." },
|
|
71
72
|
wait: { label: "Wait", about: "Waits for a reply to the card's email, or for a set time. A reply moves the card on; if none comes in time, it takes the no-reply path." },
|
|
72
|
-
|
|
73
|
+
subagent: { label: "Subagent handles it", about: "A subagent reads the card, decides where it goes within its rules, and writes what the next zones send. When its rules say to ask, it asks you first." },
|
|
73
74
|
send: { label: "Send to me", about: "Sends the card's owner what the step before produced: its summary, links and any screenshots, in each chat app they use and on the card. Then moves on." },
|
|
74
75
|
choose: { label: "Person chooses", about: "Sends the card's owner what was done with 2 to 4 buttons you name, in their chat apps and here. Each leads to a zone or ignores the card; a reply instead goes where replies go, as the note." },
|
|
75
76
|
done: { label: "Done", about: "The end of the flow." },
|
|
@@ -169,7 +170,7 @@ function canonicalSort(input) {
|
|
|
169
170
|
sureAt, notes,
|
|
170
171
|
};
|
|
171
172
|
}
|
|
172
|
-
/** A script's or
|
|
173
|
+
/** A script's or subagent's answers as every release has kept them: trimmed, none left out. */
|
|
173
174
|
function canonicalRoutes(input) {
|
|
174
175
|
if (!given(input))
|
|
175
176
|
return undefined;
|
|
@@ -199,7 +200,7 @@ function canonicalWait(input) {
|
|
|
199
200
|
function canonicalStage(input, index) {
|
|
200
201
|
if (!isRecord(input))
|
|
201
202
|
return input;
|
|
202
|
-
const stage = input;
|
|
203
|
+
const stage = legacySubagentStep(input);
|
|
203
204
|
const kind = stage["kind"];
|
|
204
205
|
const zone = isRecord(stage["zone"]) ? stage["zone"] : {};
|
|
205
206
|
const planning = stage["planning"] === "required" || stage["planning"] === "skip" || stage["planning"] === "auto" ? stage["planning"] : null;
|
|
@@ -230,9 +231,9 @@ function canonicalStage(input, index) {
|
|
|
230
231
|
...(kind === "wait" ? { wait: canonicalWait(stage["wait"]) } : {}),
|
|
231
232
|
// true is a squash, the default; false or none is no merge.
|
|
232
233
|
...(kind === "pull-request" && given(stage["merge"]) && stage["merge"] !== false ? { merge: stage["merge"] === true ? "squash" : stage["merge"] } : {}),
|
|
233
|
-
...((kind === "approval" || kind === "
|
|
234
|
-
...(kind === "
|
|
235
|
-
...(kind === "
|
|
234
|
+
...((kind === "approval" || kind === "subagent") && given(stage["subagent"]) && stage["subagent"] !== "" ? { subagent: stage["subagent"] } : {}),
|
|
235
|
+
...(kind === "subagent" && routes !== undefined ? { routes } : {}),
|
|
236
|
+
...(kind === "subagent" && stage["reply"] === true ? { reply: true } : {}),
|
|
236
237
|
...(kind === "choose" ? { options: Array.isArray(stage["options"]) ? stage["options"].map(one => { const row = isRecord(one) ? one : {}; return { label: said(row["label"]), to: said(row["to"]) }; }) : given(stage["options"]) ? stage["options"] : [] } : {}),
|
|
237
238
|
...(kind === "task" && given(stage["repo"]) && stage["repo"] !== "" ? { repo: said(stage["repo"]) } : {}),
|
|
238
239
|
...(limit === undefined ? {} : { limit }),
|
|
@@ -564,7 +565,7 @@ function fillOne(key, stage, card) {
|
|
|
564
565
|
return stage === undefined ? "" : card.outputs[stage] ?? "";
|
|
565
566
|
}
|
|
566
567
|
}
|
|
567
|
-
const KIND_COLORS = { inbox: "slate", task: "blue", report: "violet", approval: "amber", check: "blue", "pull-request": "blue", update: "green", notify: "green", sort: "violet", draft: "violet", request: "blue", email: "green", tool: "blue", wait: "slate",
|
|
568
|
+
const KIND_COLORS = { inbox: "slate", task: "blue", report: "violet", approval: "amber", check: "blue", "pull-request": "blue", update: "green", notify: "green", sort: "violet", draft: "violet", request: "blue", email: "green", tool: "blue", wait: "slate", subagent: "violet", send: "green", choose: "amber", done: "green" };
|
|
568
569
|
/** A step id as the lead may write it (sort_by_hand, Sort-By-Hand) in the one form zones use. */
|
|
569
570
|
const idOf = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
|
|
570
571
|
const slugOf = (title) => title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 28) || "zone";
|
|
@@ -631,7 +632,7 @@ export function withKeptSteps(steps, previous) {
|
|
|
631
632
|
}
|
|
632
633
|
/** A flow's steps, read by their one schema: `steps[0].routes[0]: unknown key 'to' (did you mean goesTo?)`. */
|
|
633
634
|
export function readFlowSteps(input, previous = null) {
|
|
634
|
-
const read = parseContract(flowStepsSchema, { steps: withKeptSteps(input, previous) }, FLOW_ALIASES);
|
|
635
|
+
const read = parseContract(flowStepsSchema, { steps: withKeptSteps(Array.isArray(input) ? input.map(legacySubagentStep) : input, previous) }, FLOW_ALIASES);
|
|
635
636
|
return read.ok ? { ok: true, value: read.value.steps } : read;
|
|
636
637
|
}
|
|
637
638
|
/**
|
|
@@ -706,7 +707,7 @@ export function flowFromSteps(input, previous = null) {
|
|
|
706
707
|
id, title, kind,
|
|
707
708
|
zone: old?.zone ?? { x: 0, y: 0, w: 260, h: kind === "done" || kind === "notify" || kind === "send" ? 220 : 300, color: KIND_COLORS[kind] },
|
|
708
709
|
// A kept step on our default instructions gets the default again, so it reads any research step added before it.
|
|
709
|
-
instructions: kind === "
|
|
710
|
+
instructions: kind === "subagent" ? step.instructions?.trim() || old?.instructions || "Read the card and decide what happens next."
|
|
710
711
|
: kind === "draft" ? step.instructions?.trim() || old?.instructions || DRAFT_DEFAULT
|
|
711
712
|
: kind === "task" || kind === "report"
|
|
712
713
|
? step.instructions?.trim() || (old?.instructions && old.instructions !== defaultInstructions(kind, previous.stages.slice(0, previous.stages.indexOf(old))) ? old.instructions : defaultInstructions(kind, earlier))
|
|
@@ -730,10 +731,10 @@ export function flowFromSteps(input, previous = null) {
|
|
|
730
731
|
...(kind === "tool" ? { tool: { server: step.server ?? old?.tool?.server ?? "", name: step.tool ?? old?.tool?.name ?? "", args: typeof step.args === "string" ? step.args : step.args !== undefined ? JSON.stringify(step.args) : old?.tool?.args ?? "{}" } } : {}),
|
|
731
732
|
...(kind === "wait" ? { wait: waitFromStep(step, old?.wait ?? null, at) } : {}),
|
|
732
733
|
...(kind === "pull-request" && (step.merge === undefined ? old?.merge !== undefined : step.merge !== false) ? { merge: step.merge === undefined || step.merge === true || step.merge === false ? old?.merge ?? "squash" : step.merge } : {}),
|
|
733
|
-
// v92: "nobody" (or "none") takes a
|
|
734
|
-
...((kind === "approval" || kind === "
|
|
735
|
-
...(kind === "
|
|
736
|
-
...(kind === "
|
|
734
|
+
// v92: "nobody" (or "none") takes a subagent off an approval step.
|
|
735
|
+
...((kind === "approval" || kind === "subagent") && (step.subagent === undefined ? old?.subagent !== undefined : !/^(nobody|none|no one)$/i.test(step.subagent.trim())) ? { subagent: idOf(step.subagent ?? old.subagent) } : {}),
|
|
736
|
+
...(kind === "subagent" ? step.routes !== undefined ? routesOf(step.routes) : old?.routes === undefined ? {} : { routes: old.routes } : {}),
|
|
737
|
+
...(kind === "subagent" && (step.reply ?? old?.reply) === true ? { reply: true } : {}),
|
|
737
738
|
...(kind === "choose" ? { options: choicesFromStep(step, old?.options ?? null, at, find) } : {}),
|
|
738
739
|
...(kind === "task" && (step.repo ?? old?.repo) !== undefined && (step.repo ?? old?.repo).trim() !== "" ? { repo: (step.repo ?? old?.repo).trim() } : {}),
|
|
739
740
|
...(() => {
|
|
@@ -869,7 +870,7 @@ export function flowTerms(definition, previous) {
|
|
|
869
870
|
if (stage.kind === "task" && stage.planning !== "auto")
|
|
870
871
|
lines.push(stage.planning === "required" ? "Plans first." : "Builds without a plan.");
|
|
871
872
|
if (stage.kind === "task" && stage.repo !== undefined)
|
|
872
|
-
lines.push(`Builds in
|
|
873
|
+
lines.push(`Builds in project: ${projectName(stage.repo)}`);
|
|
873
874
|
if (stage.kind === "draft")
|
|
874
875
|
lines.push(`Claude writes: ${plain(stage.instructions ?? "")}`, `Then → ${to(stage.next)}`);
|
|
875
876
|
else if (stage.kind === "request" && stage.request !== undefined) {
|
|
@@ -881,9 +882,9 @@ export function flowTerms(definition, previous) {
|
|
|
881
882
|
else if (stage.kind === "tool" && stage.tool !== undefined)
|
|
882
883
|
lines.push(`Uses ${stage.tool.server} → ${stage.tool.name} with ${plain(stage.tool.args).slice(0, 400)}`, `Then → ${to(stage.next)}${stage.onFail === null ? " If it fails → waits there." : ` If it fails → ${to(stage.onFail)}`}`);
|
|
883
884
|
else if (stage.kind === "approval")
|
|
884
|
-
lines.push(`Decides: ${stage.
|
|
885
|
-
else if (stage.kind === "
|
|
886
|
-
lines.push(`The
|
|
885
|
+
lines.push(`Decides: ${stage.subagent !== undefined ? `the subagent ${stage.subagent}, within its rules, handing hard ones to ${stage.toOwner === true ? "the flow's owner" : stage.approver ?? "anyone who approves"}` : stage.toOwner === true ? "the flow's owner, in their chat app" : stage.approver ?? "anyone who approves on this project"}. Approve → ${to(stage.next)} Send back → ${stage.onFail === null ? "not possible." : to(stage.onFail)}`);
|
|
886
|
+
else if (stage.kind === "subagent")
|
|
887
|
+
lines.push(`The subagent ${stage.subagent ?? "(none)"} handles it${stage.instructions === null ? "" : `: ${plain(stage.instructions)}`}`, ...(stage.routes === undefined || stage.routes.length === 0 ? [`Then → ${to(stage.next)}`] : stage.routes.map(one => `${one.answer} → ${to(one.to)}`)), `It asks the flow's owner when its rules say to.${stage.reply === true ? " It sends what it writes back to whoever asked." : ""}${stage.onFail === null ? "" : ` If it can't → ${to(stage.onFail)}`}`);
|
|
887
888
|
else if (stage.kind === "notify")
|
|
888
889
|
lines.push(`Posts: ${plain(stage.message ?? "")}`, `Then → ${to(stage.next)}`);
|
|
889
890
|
else if (stage.kind === "send")
|
|
@@ -944,8 +945,8 @@ export function flowTerms(definition, previous) {
|
|
|
944
945
|
terms.push("Draft steps send each card's text to Claude through the lead chat's sign-in. Nothing a draft writes is sent until a later step sends it.");
|
|
945
946
|
if (definition.stages.some(one => one.kind === "task" && one.repo !== undefined))
|
|
946
947
|
terms.push("A build step in another project files its task there, only when the flow's owner may file work in that project.");
|
|
947
|
-
if (definition.stages.some(one => one.
|
|
948
|
-
terms.push("
|
|
948
|
+
if (definition.stages.some(one => one.subagent !== undefined))
|
|
949
|
+
terms.push("subagents decide and act within their soul files' rules, reading each card through Claude on this computer's sign-in; they never approve code tasks or merges.");
|
|
949
950
|
return terms;
|
|
950
951
|
}
|
|
951
952
|
const zone = (x, y, color, h = 300) => ({ x, y, w: 260, h, color });
|
package/dist/guides.js
CHANGED
|
@@ -65,7 +65,7 @@ ${AUTHORITY_LINE}
|
|
|
65
65
|
terminal, answer from the \`console\` guide (\`skills get console\`):
|
|
66
66
|
exact screens, exact verbs, and which acts are theirs alone.
|
|
67
67
|
- Take work as a registered runner: \`claim <id> --runner <name> --token
|
|
68
|
-
<t>\` → \`heartbeat <lease>\` while working → \`release <lease>\`. A
|
|
68
|
+
<t>\` → \`heartbeat <lease>\` while working → \`worker release <lease>\`. A
|
|
69
69
|
replayed claim (same \`--key\`) answers with \`replayed: true\` — do not
|
|
70
70
|
repeat first-time side effects on it.
|
|
71
71
|
- Respect refusals — each is an ANSWER, never an error to retry blindly:
|
|
@@ -189,7 +189,7 @@ registers it; the token is shown once). Work moves through leases:
|
|
|
189
189
|
not repeat first-time side effects.
|
|
190
190
|
- \`heartbeat <lease>\` while working — it extends the lease. A lease
|
|
191
191
|
that runs out is reaped and the task returns to the queue.
|
|
192
|
-
- \`release <lease>\` when done. If the answer is \`fenced\`, STOP: a newer
|
|
192
|
+
- \`worker release <lease>\` when done. If the answer is \`fenced\`, STOP: a newer
|
|
193
193
|
lease superseded yours and the work is no longer yours — do not write,
|
|
194
194
|
commit, or report anything further for it.
|
|
195
195
|
- \`reap\` releases every expired lease (safe to run any time).
|
|
@@ -318,7 +318,7 @@ ${AUTHORITY_LINE}
|
|
|
318
318
|
transcripts following.
|
|
319
319
|
- **projects** (\`/projects\`): enrol, open, or switch repositories; the
|
|
320
320
|
project name in the header is the one-tap switcher on every screen.
|
|
321
|
-
- **chat** (\`/chat\`): the
|
|
321
|
+
- **chat** (\`/chat\`): the lead — a conversation across every project
|
|
322
322
|
that only PROPOSES (file, reorder, reserve, hold, rewrite a scope,
|
|
323
323
|
cancel, answer a decision); each proposal is a card the person
|
|
324
324
|
confirms. Coordinators over the MCP gateway propose the same way, and
|
|
@@ -340,7 +340,7 @@ ${AUTHORITY_LINE}
|
|
|
340
340
|
- \`toolroll peek\`: one pane per live agent (digits focus, \`q\`
|
|
341
341
|
leaves); \`peek <run>\` follows one; \`peek --tmux\` opens a tmux window
|
|
342
342
|
per run.
|
|
343
|
-
- \`toolroll chat\`: the
|
|
343
|
+
- \`toolroll chat\`: the lead in the terminal (\`--say "…"\` for one
|
|
344
344
|
turn); \`toolroll proposals\` lists and confirms coordinator
|
|
345
345
|
proposals.
|
|
346
346
|
- \`toolroll task add "<title>" --report\`: a SCOUT task — a
|
|
@@ -359,7 +359,8 @@ needs you · queued · waiting · building · done · failed; a *scope* is the
|
|
|
359
359
|
contract a person approves; a *decision* is a typed question an agent
|
|
360
360
|
parked; a *hold* pauses the next attempt; a *stale approval* means the
|
|
361
361
|
approval no longer matches how builds are routed — approving again fixes
|
|
362
|
-
it; a *scout* delivers a report; the *
|
|
362
|
+
it; a *scout* delivers a report; the *lead* proposes and never acts, and
|
|
363
|
+
its *subagents* work within the rules you write for them.
|
|
363
364
|
`,
|
|
364
365
|
};
|
|
365
366
|
const knowledge = {
|
|
@@ -416,7 +417,112 @@ and crew agent reads from it before working:
|
|
|
416
417
|
and its date, and prefer the newest active decision.
|
|
417
418
|
`,
|
|
418
419
|
};
|
|
419
|
-
|
|
420
|
+
const release = {
|
|
421
|
+
name: "release",
|
|
422
|
+
title: "Releasing Toolroll itself",
|
|
423
|
+
oneLiner: "toolroll release <branch>: one command from a gated commit to npm and Homebrew; the owner's approval is the only human step",
|
|
424
|
+
content: `# Releasing Toolroll itself
|
|
425
|
+
|
|
426
|
+
${AUTHORITY_LINE}
|
|
427
|
+
|
|
428
|
+
\`toolroll release <branch> --repo <gate checkout>\` runs the whole release
|
|
429
|
+
of a pushed branch. The owner approving the release scope is the only
|
|
430
|
+
step that needs a person; the rest runs on its own, each waiting step
|
|
431
|
+
with a time limit.
|
|
432
|
+
|
|
433
|
+
## Before you start
|
|
434
|
+
|
|
435
|
+
- The branch is pushed, with an open pull request into main.
|
|
436
|
+
- package.json has the new version, and the lead wrote its CHANGELOG.md
|
|
437
|
+
entry (\`## <version>\`). No tag or npm package has that version yet.
|
|
438
|
+
- \`gh\` is signed in, the console, worker and both CLI names
|
|
439
|
+
(\`toolroll\`, \`standing-orders\`) run one build, and the gate checkout
|
|
440
|
+
is a clone whose origin is the GitHub repository. Credentials are never
|
|
441
|
+
asked for in chat or written to the release journal.
|
|
442
|
+
|
|
443
|
+
## What it does, in order
|
|
444
|
+
|
|
445
|
+
1. **gate**: files task \`release-<version>-<sha7>\` pinned to the
|
|
446
|
+
branch's exact commit with \`--checks full\`, regardless of the project's default.
|
|
447
|
+
2. **approval**: waits for the owner to approve that exact scope
|
|
448
|
+
(\`toolroll task approve <id> --digest <d>\`, or the console). A
|
|
449
|
+
mode, an AI teammate or the lead approving it stops the release: the
|
|
450
|
+
yes must come from an active account with the approver role. Its standing
|
|
451
|
+
is checked again before completion and deployment.
|
|
452
|
+
3. **check**: requires a sealed, passing **Full** check on that commit.
|
|
453
|
+
Quick passes and Off checks are refused. Failures stay visible; nothing reruns them.
|
|
454
|
+
4. **complete**: marks that exact result Complete.
|
|
455
|
+
5. **quiet**: waits until nothing is building.
|
|
456
|
+
6. **deploy**: the candidate's own deployment
|
|
457
|
+
(\`scripts/deploy-browser.mjs\` from the builder's checkout): drain,
|
|
458
|
+
backup, rehearsal, swap, health. The staging path is saved before it
|
|
459
|
+
starts, and the runtime its final JSON names is kept. It goes on only
|
|
460
|
+
when the console and worker run that runtime and it records the gated
|
|
461
|
+
commit; matching versions alone do not count.
|
|
462
|
+
7. **link**: points \`toolroll\` and \`standing-orders\` at
|
|
463
|
+
\`<runtime>/bin.js\`, each link replaced whole. Where they pointed is
|
|
464
|
+
saved first; if either name doesn't answer as the new build, both go
|
|
465
|
+
back. It goes on only when the console, worker and both names run the
|
|
466
|
+
gated commit. A rerun relinks without deploying again.
|
|
467
|
+
8. **ci**: waits for every pull-request check on the gated commit:
|
|
468
|
+
Ubuntu on Node 22 and 24, Linux and Windows containment. Pending,
|
|
469
|
+
missing, skipped, cancelled or failed never merges.
|
|
470
|
+
9. **merge**: squash-merges with the head pinned to the gated commit,
|
|
471
|
+
once main has nothing the branch lacks, then deletes the remote branch
|
|
472
|
+
only if its head still matches. A resumed merge also finishes this cleanup.
|
|
473
|
+
10. **tree**: main's merge commit must have exactly the gated tree, or
|
|
474
|
+
nothing is tagged.
|
|
475
|
+
11. **tag**: pushes \`v<version>\` on that merge commit. In publish.yml,
|
|
476
|
+
macOS Node 22 and 24 must both pass before npm and GitHub publication.
|
|
477
|
+
macos.yml keeps the nightly matrix.
|
|
478
|
+
12. **publish**: waits for npm, the GitHub release and the tag's macOS
|
|
479
|
+
and publish checks.
|
|
480
|
+
13. **homebrew**: updates the tap's formula, waits for checks to register
|
|
481
|
+
and every registered check to pass, then merges. No checks means waiting,
|
|
482
|
+
within the Homebrew time limit; a failed or skipped check stops the release.
|
|
483
|
+
|
|
484
|
+
## When it stops
|
|
485
|
+
|
|
486
|
+
A time limit, a failure or Ctrl-C ends with the step and the reason,
|
|
487
|
+
and the command to continue: the same \`toolroll release <branch> --repo
|
|
488
|
+
<checkout>\`. Its journal (\`releases/<branch>.json\` in the state folder,
|
|
489
|
+
private) records what each step was about to do before it does it, so a
|
|
490
|
+
rerun finds the task, completion, merge, tag and tap change already made
|
|
491
|
+
instead of making them twice.
|
|
492
|
+
|
|
493
|
+
- \`drift\`: the branch or pull request moved after gating. Release the
|
|
494
|
+
new commit with \`--new\` (refused once merging began).
|
|
495
|
+
- \`check-level\`: the result has no Full check. File the gate with Full
|
|
496
|
+
checks and obtain a passing result; Quick and Off cannot authorize a release.
|
|
497
|
+
- \`check-failed\`: fix the branch and release the new commit with \`--new\`.
|
|
498
|
+
- \`ci-failed\`: fix the failed GitHub check, then rerun. Nothing was merged.
|
|
499
|
+
- \`publish-failed\` / \`homebrew-failed\`: fix the failed workflow before
|
|
500
|
+
resuming. A failed tag check cannot finish publication or update Homebrew.
|
|
501
|
+
- \`base-moved\`: main gained a commit the branch doesn't have, so the
|
|
502
|
+
merge wouldn't be the checked tree. Nothing was merged: update the
|
|
503
|
+
branch and release it with \`--new\`.
|
|
504
|
+
- \`tree-mismatch\`: main changed what was checked. Nothing was tagged;
|
|
505
|
+
the deployed build stays.
|
|
506
|
+
- \`timeout\`: rerun to keep waiting; \`--limit <step>=<minutes>\`
|
|
507
|
+
changes one step's limit.
|
|
508
|
+
- An interrupted deployment resumes with \`deploy-browser.mjs --stage <saved
|
|
509
|
+
path> --phase recover\` before any new attempt, including \`--new\`.
|
|
510
|
+
Failed recovery stops there; the original deployment journal remains available.
|
|
511
|
+
- \`link\`: a CLI name didn't answer as the new build, so both were put
|
|
512
|
+
back. The service already runs the new build; fix the name and rerun.
|
|
513
|
+
- \`gate-held\`: the deployment stopped before its swap but left new work
|
|
514
|
+
paused, and its journal can't say so by itself (typically killed while
|
|
515
|
+
preparing). \`toolroll release --release-gate <id>\` shows the proof that
|
|
516
|
+
the swap never began: the pause is that deployment's, its journal is
|
|
517
|
+
before the swap, the database schema, service definition and running
|
|
518
|
+
service are still the previous build's, and no deployment is running.
|
|
519
|
+
Add \`--yes\` to lift that one pause, then rerun the release. Missing or
|
|
520
|
+
contradicting evidence refuses and leaves the pause in place.
|
|
521
|
+
|
|
522
|
+
A runner's lease is returned with \`toolroll worker release <lease>\`.
|
|
523
|
+
`,
|
|
524
|
+
};
|
|
525
|
+
export const GUIDES = [operating, runner, steering, externalWork, console_, knowledge, release];
|
|
420
526
|
export function guideNamed(name) {
|
|
421
527
|
return GUIDES.find(one => one.name === name) ?? null;
|
|
422
528
|
}
|
package/dist/html.d.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server HTML, escaped by construction.
|
|
3
|
+
*
|
|
4
|
+
* - `Html` is markup this module made. Nothing else can make one: the class
|
|
5
|
+
* is private and branded, so a string never passes for markup.
|
|
6
|
+
* - `html` is the only way to write markup. Its literal text is the
|
|
7
|
+
* author's; every interpolated value is text and is escaped, unless it is
|
|
8
|
+
* already `Html`. Arrays render in order; null, undefined and booleans
|
|
9
|
+
* render nothing, so `${cond && html`…`}` reads naturally.
|
|
10
|
+
* - `postForm` is the only way to write a POST form. It adds the request's
|
|
11
|
+
* CSRF field itself, so a form cannot forget it.
|
|
12
|
+
* - Page sinks (the shell, `respond` for text/html, the workspace's page
|
|
13
|
+
* snapshot) take `Html` and turn it into bytes with `htmlString`.
|
|
14
|
+
*
|
|
15
|
+
* The script, style and JSON constructors exist for the page shell: their
|
|
16
|
+
* sources are code, and each refuses content that could close its element.
|
|
17
|
+
*
|
|
18
|
+
* This module has no server dependencies (the browser bundle shares time
|
|
19
|
+
* helpers that use it); the server's request context supplies the CSRF token.
|
|
20
|
+
*/
|
|
21
|
+
declare const MADE_HERE: unique symbol;
|
|
22
|
+
/** Markup this module made. Only this module holds the token its constructor asks for. */
|
|
23
|
+
export declare class Html {
|
|
24
|
+
#private;
|
|
25
|
+
constructor(token: typeof MADE_HERE, markup: string);
|
|
26
|
+
/** The bytes, for a sink. */
|
|
27
|
+
static markupOf(fragment: Html): string;
|
|
28
|
+
toString(): string;
|
|
29
|
+
toJSON(): string;
|
|
30
|
+
}
|
|
31
|
+
export type HtmlValue = Html | string | number | bigint | boolean | null | undefined | readonly HtmlValue[];
|
|
32
|
+
export declare const isHtml: (value: unknown) => value is Html;
|
|
33
|
+
/** The markup of an `Html` value, for a sink that writes bytes. */
|
|
34
|
+
export declare function htmlString(fragment: Html): string;
|
|
35
|
+
/** Markup with every interpolation escaped as text, except values that are already `Html`. */
|
|
36
|
+
export declare function html(strings: TemplateStringsArray, ...values: readonly HtmlValue[]): Html;
|
|
37
|
+
/** Parts in order, with an optional separator between them. */
|
|
38
|
+
export declare function joinHtml(parts: Iterable<HtmlValue>, separator?: HtmlValue): Html;
|
|
39
|
+
/** Plain text, escaped (for a sink that takes Html when the content is only words). */
|
|
40
|
+
export declare const textHtml: (text: string) => Html;
|
|
41
|
+
export type AttributeValue = string | number | boolean | null | undefined;
|
|
42
|
+
/** Attributes as `name="value"` pairs: true is a bare attribute; false, null and undefined are left out. */
|
|
43
|
+
export declare function attributes(values: Readonly<Record<string, AttributeValue>>): Html;
|
|
44
|
+
export type PostFormOptions = {
|
|
45
|
+
/** Attributes on the form element (class, id, aria-label, data-…). */
|
|
46
|
+
attrs?: Readonly<Record<string, AttributeValue>>;
|
|
47
|
+
/** Hidden fields, after the CSRF field (null and undefined are left out). */
|
|
48
|
+
hidden?: Readonly<Record<string, string | number | null | undefined>>;
|
|
49
|
+
/** The open project's revision this page was rendered under: a stale tab's post is refused. */
|
|
50
|
+
projectRevision?: number | string | null;
|
|
51
|
+
/** Where the action returns afterwards (a same-site path). */
|
|
52
|
+
returnTo?: string | null;
|
|
53
|
+
/** Forms before a session exists (sign-in, sign-up, a join link) have no CSRF field. */
|
|
54
|
+
signedOut?: true;
|
|
55
|
+
};
|
|
56
|
+
/** The server's request context supplies the request's CSRF token (server/request-context.ts). */
|
|
57
|
+
export declare function provideFormToken(source: () => string | undefined): void;
|
|
58
|
+
/**
|
|
59
|
+
* A POST form: the action, the request's one CSRF field, the optional
|
|
60
|
+
* project revision and return fields, other hidden fields, then the body.
|
|
61
|
+
* Outside a console request there is no CSRF to give, so it refuses.
|
|
62
|
+
*/
|
|
63
|
+
export declare function postForm(action: string, body: HtmlValue, options?: PostFormOptions): Html;
|
|
64
|
+
/**
|
|
65
|
+
* Markup edited after rendering: each match of `pattern` in the page is replaced by markup the replacer builds
|
|
66
|
+
* (with html`…`), or kept as it was (null), so nothing unescaped can enter. For whole-page passes (a sign-in that
|
|
67
|
+
* stands in for passwords, the browser shell's insertions).
|
|
68
|
+
*/
|
|
69
|
+
export declare function replaceMarkup(page: Html, pattern: RegExp, replace: (match: string, ...groups: string[]) => Html | null): Html;
|
|
70
|
+
/** An inline script whose source is code. Its text may not close the element or open a comment. */
|
|
71
|
+
export declare function scriptElement(source: string, attrs?: Readonly<Record<string, AttributeValue>>): Html;
|
|
72
|
+
/** A style element whose rules are code. */
|
|
73
|
+
export declare function styleElement(css: string, attrs?: Readonly<Record<string, AttributeValue>>): Html;
|
|
74
|
+
/** Inert JSON for a page script to read: no character of it can end the element. */
|
|
75
|
+
export declare function jsonScript(value: unknown, attrs?: Readonly<Record<string, AttributeValue>>): Html;
|
|
76
|
+
export {};
|