toolroll 0.9.55 → 0.9.57
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 +18 -0
- package/README.md +24 -8
- package/dist/action-ledger.d.ts +2 -4
- package/dist/action-ledger.js +2 -10
- package/dist/approval-policy.d.ts +5 -5
- package/dist/approval-policy.js +4 -4
- package/dist/approval-rules-ui.js +2 -2
- package/dist/assignment-adapters.d.ts +5 -0
- package/dist/assignment.d.ts +24 -3
- package/dist/assignment.js +36 -10
- package/dist/browser/workspace.js +5 -5
- package/dist/browser-workspace.d.ts +12 -12
- package/dist/browser-workspace.js +1 -1
- 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 +30 -37
- 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-rooms.js +3 -3
- package/dist/chat-voice.js +1 -1
- package/dist/chat-warmth.d.ts +2 -2
- package/dist/check-levels.d.ts +3 -0
- package/dist/check-levels.js +5 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +6 -7
- 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/console-api.d.ts +6 -6
- package/dist/contracts/console-api.js +7 -7
- 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/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/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/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/dispatch.d.ts +1 -1
- package/dist/dispatch.js +1 -1
- package/dist/dispose.js +0 -5
- package/dist/envelope.js +0 -1
- package/dist/export-ui.js +1 -1
- package/dist/export.js +7 -7
- package/dist/flow-engine.d.ts +2 -2
- package/dist/flow-engine.js +21 -21
- package/dist/flow-insights.d.ts +1 -1
- package/dist/flow-share.js +13 -8
- package/dist/flow-steps.d.ts +5 -5
- package/dist/flow-steps.js +19 -19
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +1 -1
- package/dist/flows-cli.js +1 -1
- package/dist/flows-ui.js +20 -20
- package/dist/flows.js +19 -19
- package/dist/guides.js +4 -3
- package/dist/invoke.js +1 -1
- package/dist/kits-ui.js +5 -5
- package/dist/kits.d.ts +12 -12
- package/dist/kits.js +31 -31
- 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 +2 -2
- package/dist/lead-context.js +9 -9
- 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/lead-status.d.ts +1 -1
- package/dist/lead-status.js +2 -2
- 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/limits-ui.d.ts +1 -1
- package/dist/limits-ui.js +1 -1
- package/dist/mcp-connect.d.ts +1 -1
- package/dist/mcp-connect.js +3 -3
- package/dist/mcp-core.js +1 -1
- package/dist/memory-pass.d.ts +2 -2
- package/dist/memory-pass.js +7 -7
- package/dist/notification-rows.d.ts +2 -2
- package/dist/operate.d.ts +3 -3
- package/dist/operate.js +52 -42
- 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.js +1 -1
- 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/proof.d.ts +9 -0
- package/dist/proof.js +12 -0
- package/dist/recipe-ui.js +1 -1
- package/dist/release-coverage.d.ts +16 -0
- package/dist/release-coverage.js +92 -0
- 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.js +40 -45
- package/dist/server/chat.d.ts +4 -4
- package/dist/server/chat.js +105 -103
- package/dist/server/chrome.js +3 -3
- package/dist/server/flows.js +69 -62
- package/dist/server/guards.js +3 -3
- package/dist/server/http.d.ts +3 -3
- package/dist/server/http.js +1 -1
- package/dist/server/live.js +6 -6
- package/dist/server/people-tokens.js +1 -1
- package/dist/server/remote.js +12 -10
- package/dist/server/render-chat.d.ts +30 -30
- package/dist/server/render-chat.js +33 -33
- package/dist/server/render-pages.js +2 -2
- package/dist/server/render-results.d.ts +4 -1
- package/dist/server/render-results.js +23 -9
- package/dist/server/render-settings.d.ts +7 -0
- package/dist/server/render-settings.js +3 -0
- package/dist/server/route-table.js +9 -7
- package/dist/server/runtime.d.ts +13 -13
- package/dist/server/settings.js +14 -13
- package/dist/server/tasks.js +34 -30
- package/dist/setup-guide.d.ts +2 -2
- package/dist/slack-chat.d.ts +2 -2
- package/dist/slack-chat.js +6 -6
- package/dist/spend-ui.d.ts +3 -3
- package/dist/spend-ui.js +9 -9
- package/dist/spend.d.ts +14 -14
- package/dist/spend.js +31 -31
- package/dist/store-baseline.d.ts +19 -0
- package/dist/store-baseline.js +2873 -0
- package/dist/store.d.ts +200 -254
- package/dist/store.js +830 -3152
- 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/{teammates-ui.js → subagents-ui.js} +45 -45
- 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 +7 -31
- package/dist/task-outcome-cli.js +3 -1
- package/dist/task-status.d.ts +59 -7
- package/dist/task-status.js +132 -16
- package/dist/team-cli.d.ts +1 -0
- package/dist/team-cli.js +14 -2
- package/dist/team-leads.js +13 -13
- package/dist/team-runtime.d.ts +2 -2
- package/dist/team-runtime.js +9 -9
- package/dist/teams-chat.js +6 -6
- 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-status.d.ts +11 -4
- package/dist/telegram-status.js +18 -11
- 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 +13 -34
- package/dist/text-limits.d.ts +18 -18
- package/dist/text-limits.js +18 -18
- package/dist/toolroll-update.d.ts +8 -0
- package/dist/toolroll-update.js +45 -8
- package/dist/work-index.d.ts +6 -1
- package/dist/work-index.js +50 -8
- package/dist/workspace-ui.js +2 -1
- package/package.json +1 -2
- package/dist/teammate-admin.d.ts +0 -53
- package/dist/teammates-ui.d.ts +0 -21
package/dist/flow-engine.d.ts
CHANGED
|
@@ -72,8 +72,8 @@ export declare function decideFlowCard(store: Store, input: {
|
|
|
72
72
|
draft?: string | null;
|
|
73
73
|
/** The visit the person saw (a chat button's): a card that moved on since is refused. */
|
|
74
74
|
entry?: number;
|
|
75
|
-
/** v92: the
|
|
76
|
-
|
|
75
|
+
/** v92: the subagent (by handle) deciding a zone it staffs; `actor` is then how it reads in history. */
|
|
76
|
+
subagent?: string;
|
|
77
77
|
/** Where the decision was made, for its ledger line: "the console", "the command line", "Telegram". */
|
|
78
78
|
where?: string;
|
|
79
79
|
}, now: Date): FlowDecision;
|
package/dist/flow-engine.js
CHANGED
|
@@ -18,7 +18,7 @@ import { readVerifiedReport, scanForSecrets } from "./evidence.js";
|
|
|
18
18
|
import { cardFollowers, notifyPeople } from "./flow-people.js";
|
|
19
19
|
import { DRAFT_CHARS, keptDraft } from "./flow-draft.js";
|
|
20
20
|
import { chooseStep, flowPersonOf, sendStep } from "./flow-send.js";
|
|
21
|
-
import { parseSoul,
|
|
21
|
+
import { parseSoul, subagentLabel } from "./subagents.js";
|
|
22
22
|
import { withStageHandoff } from "./contracts/stage-output.js";
|
|
23
23
|
/** The flow's drawing, or null when it can't be read (the card then says so and waits). */
|
|
24
24
|
export function flowDefinitionOf(flow) {
|
|
@@ -42,15 +42,15 @@ function fill(template, card) {
|
|
|
42
42
|
}
|
|
43
43
|
const titleIn = (definition, id) => definition.stages.find(one => one.id === id)?.title ?? id;
|
|
44
44
|
const flowDefinitionStage = (definition, id) => definition.stages.find(one => one.id === id);
|
|
45
|
-
/** A
|
|
46
|
-
function
|
|
45
|
+
/** A subagent's name as its soul file gives it (its handle when the file can't be read). */
|
|
46
|
+
function subagentName(mate) {
|
|
47
47
|
const read = parseSoul(mate.soul);
|
|
48
48
|
return read.ok ? read.soul.name : mate.handle;
|
|
49
49
|
}
|
|
50
|
-
/** What a
|
|
50
|
+
/** What a subagent said when it handed a card to its person, or why it couldn't decide. */
|
|
51
51
|
function handoffWords(mate, turn) {
|
|
52
52
|
const read = parseSoul(mate.soul);
|
|
53
|
-
const label = read.ok ?
|
|
53
|
+
const label = read.ok ? subagentLabel(read.soul) : mate.handle;
|
|
54
54
|
let note = "";
|
|
55
55
|
try {
|
|
56
56
|
const decided = JSON.parse(turn.decisionJson ?? "{}");
|
|
@@ -187,20 +187,20 @@ function advanceCard(store, flow, definition, card, now, options, outcome) {
|
|
|
187
187
|
case "approval": {
|
|
188
188
|
const decider = deciderOf(stage, flow);
|
|
189
189
|
const who = decider ?? "an approver";
|
|
190
|
-
// v92: a zone an active
|
|
191
|
-
// about the card only when the
|
|
192
|
-
// v102: on a protected project
|
|
193
|
-
const mate = stage.
|
|
190
|
+
// v92: a zone an active subagent staffs is its to decide first (the step pass runs it). A person hears
|
|
191
|
+
// about the card only when the subagent hands it over, can't decide, or is paused or gone.
|
|
192
|
+
// v102: on a protected project a subagent never decides; the person does.
|
|
193
|
+
const mate = stage.subagent === undefined || store.approvalRules(flow.repo).protectProject ? null : store.subagentByHandle(flow.repo, stage.subagent);
|
|
194
194
|
const turn = mate === null ? null : store.flowStepRun(card.id, card.entry);
|
|
195
|
-
const handed = turn !== null && turn.kind === "
|
|
195
|
+
const handed = turn !== null && turn.kind === "subagent" && (turn.state === "failed" || turn.state === "passed");
|
|
196
196
|
if (mate !== null && mate.state === "active" && !handed) {
|
|
197
|
-
const name =
|
|
197
|
+
const name = subagentName(mate);
|
|
198
198
|
if (card.waiting !== `${name} is deciding`)
|
|
199
199
|
store.updateFlowCard(card.id, { waiting: `${name} is deciding` }, now);
|
|
200
200
|
return;
|
|
201
201
|
}
|
|
202
202
|
const handoff = handed && mate !== null ? handoffWords(mate, turn) : null;
|
|
203
|
-
if (card.waiting === null || (mate !== null && card.waiting === `${
|
|
203
|
+
if (card.waiting === null || (mate !== null && card.waiting === `${subagentName(mate)} is deciding`)) {
|
|
204
204
|
// A named decider hears it alone; "anyone who approves" pages everyone who can.
|
|
205
205
|
// With a draft in front of it, the decision carries the draft itself: it can be read (and answered) where it arrives.
|
|
206
206
|
const draft = draftFor(definition, stage);
|
|
@@ -447,8 +447,8 @@ export function cancelFlowCard(store, card, actor, now) {
|
|
|
447
447
|
/** The draft a decision is about: the draft zone that sends its cards to this one. */
|
|
448
448
|
export function draftFor(definition, stage) {
|
|
449
449
|
return definition.stages.find(one => one.kind === "draft" && one.next === stage.id)
|
|
450
|
-
// A
|
|
451
|
-
?? definition.stages.find(one => one.kind === "
|
|
450
|
+
// A subagent's zone that leads here (by its next, or one of its answers) wrote the draft this decision checks (starter kits).
|
|
451
|
+
?? definition.stages.find(one => one.kind === "subagent" && (one.next === stage.id || (one.routes ?? []).some(route => route.to === stage.id))) ?? null;
|
|
452
452
|
}
|
|
453
453
|
/** Back to a build zone with a finished result: a revision of that same work, carrying the note; null when it isn't one. */
|
|
454
454
|
export function revisionWithNote(store, card, target, note, actor, repos, evidenceRoot, now) {
|
|
@@ -478,21 +478,21 @@ export function decideFlowCard(store, input, now) {
|
|
|
478
478
|
const stage = definition.stages.find(one => one.id === card.stage);
|
|
479
479
|
if (stage === undefined || stage.kind !== "approval" || (input.entry !== undefined && input.entry !== card.entry))
|
|
480
480
|
return { ok: false, message: "That card has moved on since; nothing was changed." };
|
|
481
|
-
// A
|
|
481
|
+
// A subagent decides only a zone it staffs; its person (and only them) can always decide instead.
|
|
482
482
|
const decider = deciderOf(stage, flow);
|
|
483
|
-
if (input.
|
|
483
|
+
if (input.subagent !== undefined ? stage.subagent !== input.subagent : decider !== null && decider !== input.actor)
|
|
484
484
|
return { ok: false, message: `Only ${decider ?? "an approver"} decides here.` };
|
|
485
|
-
// v102: a protected project's decisions are a person's, never
|
|
486
|
-
if (input.
|
|
485
|
+
// v102: a protected project's decisions are a person's, never a subagent's.
|
|
486
|
+
if (input.subagent !== undefined && store.approvalRules(flow.repo).protectProject)
|
|
487
487
|
return { ok: false, message: "This project is protected: a person decides here." };
|
|
488
488
|
// Every decision is one ledger line, the same wherever it was made; only `via` says where.
|
|
489
489
|
const ledger = (outcome) => store.recordAction({ at: now.toISOString(), actor: input.actor, repo: flow.repo, taskId: card.primaryTask, runId: null,
|
|
490
|
-
action: "flow decision", outcome, source: "request", detail: `${flow.name} · card ${card.id} · ${stage.title}${input.
|
|
490
|
+
action: "flow decision", outcome, source: "request", detail: `${flow.name} · card ${card.id} · ${stage.title}${input.subagent === undefined ? "" : ` · subagent ${input.subagent}`} · via ${input.where ?? "Toolroll"}` });
|
|
491
491
|
if (input.decision === "approve") {
|
|
492
492
|
// An edited draft replaces the one Claude wrote, so the steps after this send what the person approved.
|
|
493
493
|
const draft = draftFor(definition, stage);
|
|
494
|
-
// A person's edit over the draft limit is refused with it, never cut. (A
|
|
495
|
-
if (draft !== null && input.
|
|
494
|
+
// A person's edit over the draft limit is refused with it, never cut. (A subagent's was asked once to shorten and is kept whole.)
|
|
495
|
+
if (draft !== null && input.subagent === undefined && typeof input.draft === "string" && input.draft.trim().length > DRAFT_CHARS) {
|
|
496
496
|
return { ok: false, message: `Keep the draft to ${DRAFT_CHARS.toLocaleString("en-US")} characters; this is ${input.draft.trim().length.toLocaleString("en-US")}.` };
|
|
497
497
|
}
|
|
498
498
|
if (draft !== null && typeof input.draft === "string" && input.draft.trim() !== "" && input.draft.trim() !== card.outputs[draft.id]?.trim()) {
|
package/dist/flow-insights.d.ts
CHANGED
|
@@ -58,7 +58,7 @@ export type StepRunInsight = {
|
|
|
58
58
|
entry: number;
|
|
59
59
|
zone: string;
|
|
60
60
|
zoneTitle: string;
|
|
61
|
-
kind: "check" | "update" | "sort" | "draft" | "request" | "email" | "tool" | "
|
|
61
|
+
kind: "check" | "update" | "sort" | "draft" | "request" | "email" | "tool" | "subagent";
|
|
62
62
|
script: string | null;
|
|
63
63
|
version: number | null;
|
|
64
64
|
state: FlowStepRunRow["state"];
|
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) {
|
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;
|
package/dist/flow-triggers.js
CHANGED
|
@@ -731,7 +731,7 @@ export const NEEDS_YOU_HREF = "/work?view=needs-you";
|
|
|
731
731
|
export function planeStatusLink(config, status) {
|
|
732
732
|
return config?.kind === "plane-review" && status !== null && /waited over \d+ days for you\.$/.test(status) ? { label: "Needs you", href: NEEDS_YOU_HREF } : null;
|
|
733
733
|
}
|
|
734
|
-
/** v96: a schedule (a
|
|
734
|
+
/** v96: a schedule (a subagent's routine) makes its card now, once per press, and keeps its own times. */
|
|
735
735
|
export function runScheduleNow(store, trigger, actor, now) {
|
|
736
736
|
const config = triggerConfigOf(trigger);
|
|
737
737
|
if (config?.kind !== "schedule" || config.script !== undefined)
|
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.js
CHANGED
|
@@ -12,9 +12,9 @@ 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";
|
|
@@ -39,7 +39,7 @@ export function flowsListHtml(store, flows, projects, canCreate, problem) {
|
|
|
39
39
|
const example = flows.length === 0 && canCreate && projects.length > 0
|
|
40
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
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">
|
|
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
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>`;
|
|
44
44
|
}
|
|
45
45
|
/** Reads the chosen flow file into the form (the server reads it again, whole, before anything is made). */
|
|
@@ -75,7 +75,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
75
75
|
const title = (id) => stages.find(one => one.id === id)?.title ?? id;
|
|
76
76
|
const sortZones = new Set(stages.filter(one => one.kind === "sort").map(one => one.id));
|
|
77
77
|
const decisions = sortZones.size === 0 ? new Map() : store.flowSortDecisions(flow.id);
|
|
78
|
-
const mates = new Map(store.
|
|
78
|
+
const mates = new Map(store.subagents([flow.repo]).map(one => [one.id, one]));
|
|
79
79
|
const taskSecrets = new Map();
|
|
80
80
|
const secretsFor = (repo) => {
|
|
81
81
|
if (!taskSecrets.has(repo))
|
|
@@ -92,9 +92,9 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
92
92
|
const discussion = store.flowComments(card.id);
|
|
93
93
|
const watchers = store.flowCardWatchers(card.id);
|
|
94
94
|
const decider = stage?.kind === "approval" ? deciderOf(stage, flow) : null;
|
|
95
|
-
// While its
|
|
96
|
-
const
|
|
97
|
-
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;
|
|
98
98
|
// History reads moves and ownership together, newest first.
|
|
99
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 }));
|
|
100
100
|
const moves = store.flowEvents(card.id).map(event => ({ text: historyText(event, title, sortZones), at: event.at }));
|
|
@@ -109,15 +109,15 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
109
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",
|
|
110
110
|
};
|
|
111
111
|
// Its open question: one of its own, or (v94) a tool call waiting for approval.
|
|
112
|
-
const asked = card.state === "active" ? store.
|
|
113
|
-
const pending = asked?.toolCall == null ? null : store.
|
|
114
|
-
// A decision its
|
|
115
|
-
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;
|
|
116
116
|
let handoff = null;
|
|
117
|
-
if (turn !== null && turn.kind === "
|
|
117
|
+
if (turn !== null && turn.kind === "subagent" && turn.state === "passed") {
|
|
118
118
|
try {
|
|
119
119
|
const said = JSON.parse(turn.decisionJson ?? "{}");
|
|
120
|
-
const mate = store.
|
|
120
|
+
const mate = store.subagentByHandle(flow.repo, stage.subagent);
|
|
121
121
|
if (said.action === "hand_off" && typeof said.note === "string" && said.note !== "" && mate !== null)
|
|
122
122
|
handoff = { from: labelOf(mate), note: said.note };
|
|
123
123
|
}
|
|
@@ -125,7 +125,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
125
125
|
handoff = null;
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
const asker = asked === null || asked.state !== "open" ? null : store.
|
|
128
|
+
const asker = asked === null || asked.state !== "open" ? null : store.getSubagent(asked.subagent);
|
|
129
129
|
// "Person chooses" (flow-send.ts): what was sent, and the options, for the person it waits on; the latest send otherwise.
|
|
130
130
|
const choosing = card.state === "active" && stage?.kind === "choose" ? flowChoiceAt(store, card.id, card.entry) : null;
|
|
131
131
|
const latest = store.flowSends(card.id)[0] ?? null;
|
|
@@ -162,11 +162,11 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
162
162
|
question: asked === null || asked.state !== "open" || asker === null ? null
|
|
163
163
|
: { id: asked.id, from: labelOf(asker), question: asked.question, options: asked.options, askedOf: asked.askedOf, mine: asked.askedOf === viewer.name,
|
|
164
164
|
call: pending === null ? null : { why: pending.why, rule: pending.result ?? "" } },
|
|
165
|
-
calls: store.
|
|
166
|
-
const mate = mates.get(call.
|
|
165
|
+
calls: store.subagentCallsOn(card.id).slice(-30).map(call => {
|
|
166
|
+
const mate = mates.get(call.subagent) ?? null;
|
|
167
167
|
// A person's undo (v97) reads as theirs, and its outcome says what it undid.
|
|
168
|
-
return { id: call.id, who: call.undoOf !== null ? call.decidedBy ?? "A person" : mate === null ? "A
|
|
169
|
-
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 };
|
|
170
170
|
}),
|
|
171
171
|
};
|
|
172
172
|
});
|
|
@@ -197,7 +197,7 @@ export function flowView(store, flow, viewer, selectedCard, setup = { dir: null,
|
|
|
197
197
|
startTrigger: setup.startTrigger ?? null,
|
|
198
198
|
me: viewer.name,
|
|
199
199
|
sortReady: setup.sortReady ?? false,
|
|
200
|
-
|
|
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}` })),
|
|
201
201
|
emailReady: sendingReady(setup.dir),
|
|
202
202
|
requestSecrets: flowSecretNames(setup.dir, flow.repo),
|
|
203
203
|
tools: projectToolsOf(store, flow.repo).map(tool => ({ name: tool.name, about: tool.spec.about, functions: tool.lastTest?.ok === true ? tool.lastTest.tools : [],
|
package/dist/flows.js
CHANGED
|
@@ -52,7 +52,7 @@ import { createHash } from "node:crypto";
|
|
|
52
52
|
import { projectName } from "./project.js";
|
|
53
53
|
import { parseContract, readVersioned } from "./contracts/contract.js";
|
|
54
54
|
import { stageReferenceProblems } from "./contracts/stage-output.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, 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";
|
|
56
56
|
export { CHOICES_MAX, CHOICES_MIN, FLOW_COLORS, FLOW_END, FLOW_MERGE_METHODS, FLOW_STAGE_KINDS, LONGEST_WAIT_MINUTES, SCRIPT_LANGUAGES, SCRIPT_NAME, sortKeyOf };
|
|
57
57
|
/** What each kind is called and does, in the words the canvas uses. */
|
|
58
58
|
export const FLOW_KIND_WORDS = {
|
|
@@ -70,7 +70,7 @@ export const FLOW_KIND_WORDS = {
|
|
|
70
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." },
|
|
71
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." },
|
|
72
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." },
|
|
73
|
-
|
|
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." },
|
|
74
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." },
|
|
75
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." },
|
|
76
76
|
done: { label: "Done", about: "The end of the flow." },
|
|
@@ -170,7 +170,7 @@ function canonicalSort(input) {
|
|
|
170
170
|
sureAt, notes,
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
|
-
/** A script's or
|
|
173
|
+
/** A script's or subagent's answers as every release has kept them: trimmed, none left out. */
|
|
174
174
|
function canonicalRoutes(input) {
|
|
175
175
|
if (!given(input))
|
|
176
176
|
return undefined;
|
|
@@ -200,7 +200,7 @@ function canonicalWait(input) {
|
|
|
200
200
|
function canonicalStage(input, index) {
|
|
201
201
|
if (!isRecord(input))
|
|
202
202
|
return input;
|
|
203
|
-
const stage = input;
|
|
203
|
+
const stage = legacySubagentStep(input);
|
|
204
204
|
const kind = stage["kind"];
|
|
205
205
|
const zone = isRecord(stage["zone"]) ? stage["zone"] : {};
|
|
206
206
|
const planning = stage["planning"] === "required" || stage["planning"] === "skip" || stage["planning"] === "auto" ? stage["planning"] : null;
|
|
@@ -231,9 +231,9 @@ function canonicalStage(input, index) {
|
|
|
231
231
|
...(kind === "wait" ? { wait: canonicalWait(stage["wait"]) } : {}),
|
|
232
232
|
// true is a squash, the default; false or none is no merge.
|
|
233
233
|
...(kind === "pull-request" && given(stage["merge"]) && stage["merge"] !== false ? { merge: stage["merge"] === true ? "squash" : stage["merge"] } : {}),
|
|
234
|
-
...((kind === "approval" || kind === "
|
|
235
|
-
...(kind === "
|
|
236
|
-
...(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 } : {}),
|
|
237
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"] : [] } : {}),
|
|
238
238
|
...(kind === "task" && given(stage["repo"]) && stage["repo"] !== "" ? { repo: said(stage["repo"]) } : {}),
|
|
239
239
|
...(limit === undefined ? {} : { limit }),
|
|
@@ -565,7 +565,7 @@ function fillOne(key, stage, card) {
|
|
|
565
565
|
return stage === undefined ? "" : card.outputs[stage] ?? "";
|
|
566
566
|
}
|
|
567
567
|
}
|
|
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",
|
|
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" };
|
|
569
569
|
/** A step id as the lead may write it (sort_by_hand, Sort-By-Hand) in the one form zones use. */
|
|
570
570
|
const idOf = (value) => value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
|
|
571
571
|
const slugOf = (title) => title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 28) || "zone";
|
|
@@ -632,7 +632,7 @@ export function withKeptSteps(steps, previous) {
|
|
|
632
632
|
}
|
|
633
633
|
/** A flow's steps, read by their one schema: `steps[0].routes[0]: unknown key 'to' (did you mean goesTo?)`. */
|
|
634
634
|
export function readFlowSteps(input, previous = null) {
|
|
635
|
-
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);
|
|
636
636
|
return read.ok ? { ok: true, value: read.value.steps } : read;
|
|
637
637
|
}
|
|
638
638
|
/**
|
|
@@ -707,7 +707,7 @@ export function flowFromSteps(input, previous = null) {
|
|
|
707
707
|
id, title, kind,
|
|
708
708
|
zone: old?.zone ?? { x: 0, y: 0, w: 260, h: kind === "done" || kind === "notify" || kind === "send" ? 220 : 300, color: KIND_COLORS[kind] },
|
|
709
709
|
// A kept step on our default instructions gets the default again, so it reads any research step added before it.
|
|
710
|
-
instructions: kind === "
|
|
710
|
+
instructions: kind === "subagent" ? step.instructions?.trim() || old?.instructions || "Read the card and decide what happens next."
|
|
711
711
|
: kind === "draft" ? step.instructions?.trim() || old?.instructions || DRAFT_DEFAULT
|
|
712
712
|
: kind === "task" || kind === "report"
|
|
713
713
|
? step.instructions?.trim() || (old?.instructions && old.instructions !== defaultInstructions(kind, previous.stages.slice(0, previous.stages.indexOf(old))) ? old.instructions : defaultInstructions(kind, earlier))
|
|
@@ -731,10 +731,10 @@ export function flowFromSteps(input, previous = null) {
|
|
|
731
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 ?? "{}" } } : {}),
|
|
732
732
|
...(kind === "wait" ? { wait: waitFromStep(step, old?.wait ?? null, at) } : {}),
|
|
733
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 } : {}),
|
|
734
|
-
// v92: "nobody" (or "none") takes a
|
|
735
|
-
...((kind === "approval" || kind === "
|
|
736
|
-
...(kind === "
|
|
737
|
-
...(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 } : {}),
|
|
738
738
|
...(kind === "choose" ? { options: choicesFromStep(step, old?.options ?? null, at, find) } : {}),
|
|
739
739
|
...(kind === "task" && (step.repo ?? old?.repo) !== undefined && (step.repo ?? old?.repo).trim() !== "" ? { repo: (step.repo ?? old?.repo).trim() } : {}),
|
|
740
740
|
...(() => {
|
|
@@ -882,9 +882,9 @@ export function flowTerms(definition, previous) {
|
|
|
882
882
|
else if (stage.kind === "tool" && stage.tool !== undefined)
|
|
883
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)}`}`);
|
|
884
884
|
else if (stage.kind === "approval")
|
|
885
|
-
lines.push(`Decides: ${stage.
|
|
886
|
-
else if (stage.kind === "
|
|
887
|
-
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)}`}`);
|
|
888
888
|
else if (stage.kind === "notify")
|
|
889
889
|
lines.push(`Posts: ${plain(stage.message ?? "")}`, `Then → ${to(stage.next)}`);
|
|
890
890
|
else if (stage.kind === "send")
|
|
@@ -945,8 +945,8 @@ export function flowTerms(definition, previous) {
|
|
|
945
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.");
|
|
946
946
|
if (definition.stages.some(one => one.kind === "task" && one.repo !== undefined))
|
|
947
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.");
|
|
948
|
-
if (definition.stages.some(one => one.
|
|
949
|
-
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.");
|
|
950
950
|
return terms;
|
|
951
951
|
}
|
|
952
952
|
const zone = (x, y, color, h = 300) => ({ x, y, w: 260, h, color });
|