@evandu/super-pi 0.1.141 → 0.1.143
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/agents/super-pi-task/core.ts +55 -27
- package/agents/super-pi-task/index.ts +76 -4
- package/dist/.super-pi-check-inputs.json +2 -2
- package/dist/client/.super-pi-client-sources.json +7 -8
- package/dist/client/assets/{CodeMirrorEditor-Dm5IDKy9.js → CodeMirrorEditor-CXDQ0ikN.js} +2 -2
- package/dist/client/assets/{dist-BGaKp7BG.js → dist-BK_KWsQr.js} +1 -1
- package/dist/client/assets/{dist-CN1j5LO2.js → dist-BKsr7wc2.js} +1 -1
- package/dist/client/assets/{dist-C7UQGfmz.js → dist-BXQWaRci.js} +1 -1
- package/dist/client/assets/{dist-BvCNiV_z.js → dist-B_uWAP2-.js} +1 -1
- package/dist/client/assets/{dist-CNgQgdyS.js → dist-BwwacXyS.js} +1 -1
- package/dist/client/assets/{dist-GAPsnCM8.js → dist-CWd7iD1S.js} +1 -1
- package/dist/client/assets/{dist-DWj0wg8M.js → dist-CbjfwVs-.js} +1 -1
- package/dist/client/assets/{dist-PQZBepmy.js → dist-CoEavaUe.js} +1 -1
- package/dist/client/assets/{dist-BqXU6S8h.js → dist-D60BDUMe.js} +1 -1
- package/dist/client/assets/{dist-LCDxbTTI.js → dist-DBbQHcwG.js} +1 -1
- package/dist/client/assets/{dist-DtO-7WEZ.js → dist-DVEBhaUf.js} +1 -1
- package/dist/client/assets/{dist-D0eIQjoI.js → dist-Db6KH9Qv.js} +1 -1
- package/dist/client/assets/{dist-BMbwbGH9.js → dist-Dsj0Q8gO.js} +1 -1
- package/dist/client/assets/{dist-4baaMdqn.js → dist-H0dUhAgy.js} +1 -1
- package/dist/client/assets/{dist-D0-9TI_k.js → dist-MZSBP_1m.js} +1 -1
- package/dist/client/assets/{dist-B-edBMDa.js → dist-PNt22XLU.js} +1 -1
- package/dist/client/assets/{dist-M3BFsjal.js → dist-S5UwGLvb.js} +1 -1
- package/dist/client/assets/{dist-F7KOq8Xm.js → dist-_jioFcsD.js} +1 -1
- package/dist/client/assets/{dist-CSDLiQKt.js → dist-hmP8KP8-.js} +1 -1
- package/dist/client/assets/{dist-D7YZUqfA.js → dist-jV9tqAoC.js} +1 -1
- package/dist/client/assets/{dist-CMDW84Eh.js → dist-oqETQw0G.js} +1 -1
- package/dist/client/assets/{dist-DQWPdnen.js → dist-oz2WOWPd.js} +1 -1
- package/dist/client/assets/{index-BJIfLtDs.css → index-6h_u-X47.css} +1 -1
- package/dist/client/assets/{index-DHsjaI7m.js → index-iSnWwgJ-.js} +26 -26
- package/dist/client/index.html +2 -2
- package/dist/server/application/conversation-service.d.ts +2 -1
- package/dist/server/application/conversation-service.js +25 -7
- package/dist/server/application/run-service.d.ts +15 -0
- package/dist/server/application/run-service.js +59 -1
- package/dist/server/application/session-orchestrator-service.d.ts +9 -1
- package/dist/server/application/session-orchestrator-service.js +79 -10
- package/dist/server/http-server.js +18 -11
- package/dist/server/runtime.js +7 -6
- package/dist/server/store.d.ts +12 -1
- package/dist/server/store.js +77 -4
- package/dist/server/task-tool.d.ts +4 -2
- package/dist/server/task-tool.js +6 -3
- package/dist/server/time-zone.d.ts +20 -0
- package/dist/server/time-zone.js +32 -0
- package/dist/server/types.d.ts +12 -0
- package/dist/server/workflow-engine.d.ts +11 -95
- package/dist/server/workflow-engine.js +122 -134
- package/dist/server/workflow.d.ts +73 -31
- package/dist/server/workflow.js +112 -30
- package/package.json +17 -2
|
@@ -71,6 +71,8 @@ export declare const TASK_EXEC_ENDPOINT_ENV = "SUPER_PI_EXEC_ENDPOINT";
|
|
|
71
71
|
export declare const TASK_ROSTER_ENV = "SUPER_PI_TASK_ROSTER";
|
|
72
72
|
/** The conversation the dispatch belongs to. The server resolves the workspace from this itself. */
|
|
73
73
|
export declare const TASK_CONVERSATION_ENV = "SUPER_PI_TASK_CONVERSATION";
|
|
74
|
+
/** The conversation that spawned this one. Empty means a top-level conversation. */
|
|
75
|
+
export declare const PARENT_CONVERSATION_ENV = "SUPER_PI_PARENT_CONVERSATION";
|
|
74
76
|
/** How many task hops deep this process already is. A primary is `0`. See `DEFAULT_SUBAGENT_DEPTH`. */
|
|
75
77
|
export declare const TASK_DEPTH_ENV = "SUPER_PI_TASK_DEPTH";
|
|
76
78
|
/**
|
|
@@ -255,9 +257,9 @@ export declare const SESSION_STATUS_TOOL_NAME = "session_status";
|
|
|
255
257
|
export declare const SESSION_DISPATCH_TOOL_NAME = "session_dispatch";
|
|
256
258
|
export declare const SESSION_STOP_TOOL_NAME = "session_stop";
|
|
257
259
|
/** Model-facing description for session_spawn. */
|
|
258
|
-
export declare const SESSION_SPAWN_DESCRIPTION = "Create a new sibling conversation in the current workspace and immediately dispatch its initial task.\n\nDIFFERENCE FROM THE 'task' TOOL:\n- The 'task' tool delegates to a transient, one-shot subagent process (scout, critic, patcher) that runs once, returns its final text, and terminates. It has no conversation history and does not appear in the workspace sidebar.\n- 'session_spawn' creates a real, persistent sibling CONVERSATION in Super Pi. It appears in the sidebar, retains multi-turn message history, can receive follow-up messages via session_dispatch, can be inspected via session_status, and can be stopped via session_stop. The user can also switch to it in the Web UI.\n\nWHEN TO USE THIS:\n- Long-running, multi-phase tracks of work that require ongoing context, multiple turns of dialogue, or independent user visibility.\n- Parallel work streams (e.g., Track A: backend refactor, Track B: frontend component update) where both need full conversational capabilities.\n- When the user asks you to act as an orchestrator/manager who delegates to multiple sessions.\n\nWHEN NOT TO USE THIS:\n- For one-off, focused delegation with a single answer: use 'task' (with scout/critic/patcher). It is faster, cheaper, and returns directly to your transcript.\n- For running shell commands or build watchers: use 'run_async'.\n- For a linear multi-step subagent pipeline: use 'workflow'.\n\nUsage notes:\n1. Spawning is ASYNCHRONOUS by default: it creates the conversation, enqueues the initial task, and returns the new conversationId immediately. The sibling session runs in the background.\n2. The initial task should provide clear context and goals since the new session starts with a fresh conversation history.\n3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.";
|
|
260
|
+
export declare const SESSION_SPAWN_DESCRIPTION = "Create a new sibling conversation in the current workspace and immediately dispatch its initial task.\n\nDIFFERENCE FROM THE 'task' TOOL:\n- The 'task' tool delegates to a transient, one-shot subagent process (scout, critic, patcher) that runs once, returns its final text, and terminates. It has no conversation history and does not appear in the workspace sidebar.\n- 'session_spawn' creates a real, persistent sibling CONVERSATION in Super Pi. It appears in the sidebar, retains multi-turn message history, can receive follow-up messages via session_dispatch, can be inspected via session_status, and can be stopped via session_stop. The user can also switch to it in the Web UI.\n\nWHEN TO USE THIS:\n- Long-running, multi-phase tracks of work that require ongoing context, multiple turns of dialogue, or independent user visibility.\n- Parallel work streams (e.g., Track A: backend refactor, Track B: frontend component update) where both need full conversational capabilities.\n- When the user asks you to act as an orchestrator/manager who delegates to multiple sessions.\n\nWHEN NOT TO USE THIS:\n- For one-off, focused delegation with a single answer: use 'task' (with scout/critic/patcher). It is faster, cheaper, and returns directly to your transcript.\n- For running shell commands or build watchers: use 'run_async'.\n- For a linear multi-step subagent pipeline: use 'workflow'.\n\nUsage notes:\n1. Spawning is ASYNCHRONOUS by default: it creates the conversation, enqueues the initial task, and returns the new conversationId immediately. The sibling session runs in the background.\n2. The initial task should provide clear context and goals since the new session starts with a fresh conversation history.\n3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.\n4. The new session reports completion to its DIRECT parent automatically. 'parentConversationId' optionally selects that parent; by default it is the calling session.";
|
|
259
261
|
/** Model-facing description for session_status. */
|
|
260
|
-
export declare const SESSION_STATUS_DESCRIPTION = "Inspect the progress, run state, and latest activity of sibling conversations created by the orchestrator.\n\nWHEN TO USE THIS:\n- To check whether a spawned sibling conversation is still running, has finished (idle), is stopped, or encountered an error.\n- To read the latest assistant progress snippet without bloating your context with the session's entire raw message transcript.\n- To list all sibling conversations under your coordination when conversationId is omitted.\n\nUsage notes:\n1. When 'conversationId' is provided: returns a compact summary for that specific session, including its run status ('idle', 'running', 'waiting', 'stopped', 'failed'), messageCount, turnCount, lastAssistantSnippet, lastUserSnippet, activeTool, and error (if any).\n2. When 'conversationId' is omitted: returns a summary list of
|
|
262
|
+
export declare const SESSION_STATUS_DESCRIPTION = "Inspect the progress, run state, and latest activity of sibling conversations created by the orchestrator.\n\nWHEN TO USE THIS:\n- To check whether a spawned sibling conversation is still running, has finished (idle), is stopped, or encountered an error.\n- To read the latest assistant progress snippet without bloating your context with the session's entire raw message transcript.\n- To list all sibling conversations under your coordination when conversationId is omitted.\n\nUsage notes:\n1. When 'conversationId' is provided: returns a compact summary for that specific session, including its run status ('idle', 'running', 'waiting', 'stopped', 'failed'), messageCount, turnCount, lastAssistantSnippet, lastUserSnippet, activeTool, and error (if any).\n2. When 'conversationId' is omitted: returns a summary list of sibling conversations whose direct parent is 'parentConversationId' (the calling session by default).\n3. By default it never returns full transcripts; concise summaries keep your context window lean. Set 'includeOutput' with a conversationId to read the final output from its result file, capped to the last 64Ki characters with an explicit truncation notice.";
|
|
261
263
|
/** Model-facing description for session_dispatch. */
|
|
262
264
|
export declare const SESSION_DISPATCH_DESCRIPTION = "Send a message, follow-up instructions, or steering guidance to an existing sibling conversation.\n\nWHEN TO USE THIS:\n- To follow up with a sibling session after reading its progress with session_status.\n- To give feedback, provide additional requirements, or answer a question from a sibling session.\n- To kick off a new turn on an idle or stopped sibling session.\n\nUsage notes:\n1. 'conversationId' is required and must identify an existing sibling conversation.\n2. 'content' is the text instruction sent to that session, just as if typed by a user in that session's chat.\n3. Dispatch is ASYNCHRONOUS by default: it enqueues the message into the session's queue and returns immediately.\n4. If the sibling session is currently busy running a turn, the dispatched message is placed in its waiting queue and processed when the active turn completes.";
|
|
263
265
|
/** Model-facing description for session_stop. */
|
package/dist/server/task-tool.js
CHANGED
|
@@ -70,6 +70,8 @@ export const TASK_EXEC_ENDPOINT_ENV = "SUPER_PI_EXEC_ENDPOINT";
|
|
|
70
70
|
export const TASK_ROSTER_ENV = "SUPER_PI_TASK_ROSTER";
|
|
71
71
|
/** The conversation the dispatch belongs to. The server resolves the workspace from this itself. */
|
|
72
72
|
export const TASK_CONVERSATION_ENV = "SUPER_PI_TASK_CONVERSATION";
|
|
73
|
+
/** The conversation that spawned this one. Empty means a top-level conversation. */
|
|
74
|
+
export const PARENT_CONVERSATION_ENV = "SUPER_PI_PARENT_CONVERSATION";
|
|
73
75
|
/** How many task hops deep this process already is. A primary is `0`. See `DEFAULT_SUBAGENT_DEPTH`. */
|
|
74
76
|
export const TASK_DEPTH_ENV = "SUPER_PI_TASK_DEPTH";
|
|
75
77
|
/**
|
|
@@ -379,7 +381,8 @@ WHEN NOT TO USE THIS:
|
|
|
379
381
|
Usage notes:
|
|
380
382
|
1. Spawning is ASYNCHRONOUS by default: it creates the conversation, enqueues the initial task, and returns the new conversationId immediately. The sibling session runs in the background.
|
|
381
383
|
2. The initial task should provide clear context and goals since the new session starts with a fresh conversation history.
|
|
382
|
-
3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages
|
|
384
|
+
3. Use session_status with the returned conversationId to check progress, or session_dispatch to send further messages.
|
|
385
|
+
4. The new session reports completion to its DIRECT parent automatically. 'parentConversationId' optionally selects that parent; by default it is the calling session.`;
|
|
383
386
|
/** Model-facing description for session_status. */
|
|
384
387
|
export const SESSION_STATUS_DESCRIPTION = `Inspect the progress, run state, and latest activity of sibling conversations created by the orchestrator.
|
|
385
388
|
|
|
@@ -390,8 +393,8 @@ WHEN TO USE THIS:
|
|
|
390
393
|
|
|
391
394
|
Usage notes:
|
|
392
395
|
1. When 'conversationId' is provided: returns a compact summary for that specific session, including its run status ('idle', 'running', 'waiting', 'stopped', 'failed'), messageCount, turnCount, lastAssistantSnippet, lastUserSnippet, activeTool, and error (if any).
|
|
393
|
-
2. When 'conversationId' is omitted: returns a summary list of
|
|
394
|
-
3.
|
|
396
|
+
2. When 'conversationId' is omitted: returns a summary list of sibling conversations whose direct parent is 'parentConversationId' (the calling session by default).
|
|
397
|
+
3. By default it never returns full transcripts; concise summaries keep your context window lean. Set 'includeOutput' with a conversationId to read the final output from its result file, capped to the last 64Ki characters with an explicit truncation notice.`;
|
|
395
398
|
/** Model-facing description for session_dispatch. */
|
|
396
399
|
export const SESSION_DISPATCH_DESCRIPTION = `Send a message, follow-up instructions, or steering guidance to an existing sibling conversation.
|
|
397
400
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE CLOCK THIS PROCESS SCHEDULES BY.
|
|
3
|
+
*
|
|
4
|
+
* A scheduled task's 「每天 09:00」 is a WALL CLOCK, and the wall clock it means is the one this
|
|
5
|
+
* process runs on - the machine holding the server. The browser that reads the result may be on a
|
|
6
|
+
* different clock entirely, so a moment computed here and rendered there says one thing in one place
|
|
7
|
+
* and another thing in the other.
|
|
8
|
+
*
|
|
9
|
+
* The name is the IANA zone rather than an offset, because an offset is ambiguous twice a year: it
|
|
10
|
+
* does not say whether 「09:00」 is the summer or the winter offset, and a task that drifts by an
|
|
11
|
+
* hour twice a year is the kind of bug nobody reports.
|
|
12
|
+
*/
|
|
13
|
+
export declare function serverTimeZone(): string;
|
|
14
|
+
/**
|
|
15
|
+
* The zone's short name for the moment, the way a person would say it: 「GMT+8」, 「UTC」.
|
|
16
|
+
*
|
|
17
|
+
* Used in the panel next to a time, so the reader can tell WHICH clock they are looking at without
|
|
18
|
+
* having to know what 「Asia/Shanghai」 means.
|
|
19
|
+
*/
|
|
20
|
+
export declare function timeZoneLabel(at?: number): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE CLOCK THIS PROCESS SCHEDULES BY.
|
|
3
|
+
*
|
|
4
|
+
* A scheduled task's 「每天 09:00」 is a WALL CLOCK, and the wall clock it means is the one this
|
|
5
|
+
* process runs on - the machine holding the server. The browser that reads the result may be on a
|
|
6
|
+
* different clock entirely, so a moment computed here and rendered there says one thing in one place
|
|
7
|
+
* and another thing in the other.
|
|
8
|
+
*
|
|
9
|
+
* The name is the IANA zone rather than an offset, because an offset is ambiguous twice a year: it
|
|
10
|
+
* does not say whether 「09:00」 is the summer or the winter offset, and a task that drifts by an
|
|
11
|
+
* hour twice a year is the kind of bug nobody reports.
|
|
12
|
+
*/
|
|
13
|
+
export function serverTimeZone() {
|
|
14
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The zone's short name for the moment, the way a person would say it: 「GMT+8」, 「UTC」.
|
|
18
|
+
*
|
|
19
|
+
* Used in the panel next to a time, so the reader can tell WHICH clock they are looking at without
|
|
20
|
+
* having to know what 「Asia/Shanghai」 means.
|
|
21
|
+
*/
|
|
22
|
+
export function timeZoneLabel(at = Date.now()) {
|
|
23
|
+
const zone = serverTimeZone();
|
|
24
|
+
try {
|
|
25
|
+
const parts = new Intl.DateTimeFormat("en-US", { timeZone: zone, timeZoneName: "shortOffset" }).formatToParts(at);
|
|
26
|
+
const name = parts.find((part) => part.type === "timeZoneName")?.value;
|
|
27
|
+
return name ?? zone;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return zone;
|
|
31
|
+
}
|
|
32
|
+
}
|
package/dist/server/types.d.ts
CHANGED
|
@@ -503,6 +503,9 @@ export type Conversation = {
|
|
|
503
503
|
* not what is true right now.
|
|
504
504
|
*/
|
|
505
505
|
modelNotice?: string;
|
|
506
|
+
/** Why this conversation started on Pi's default agent instead of the workspace's deleted default.
|
|
507
|
+
* Written once at birth, like modelNotice, and rendered verbatim. */
|
|
508
|
+
agentNotice?: string;
|
|
506
509
|
/** Absent means "Pi's own default", which is why a conversation created before the picker existed still works. */
|
|
507
510
|
model?: ModelSelection;
|
|
508
511
|
thinkingLevel?: string;
|
|
@@ -885,6 +888,12 @@ export type AppState = {
|
|
|
885
888
|
* "nothing collapsed", which is the correct reading of a preference that did not exist yet.
|
|
886
889
|
*/
|
|
887
890
|
collapsedConversationIds?: string[];
|
|
891
|
+
/**
|
|
892
|
+
* THE CLOCK THIS SERVER SCHEDULES BY. A scheduled task's 「每天 09:00」 is the SERVER's wall clock,
|
|
893
|
+
* and the panel has to say so, because the browser rendering 「下次」 may be on a different clock
|
|
894
|
+
* entirely. Without this the two lines in one row quietly mean two different things.
|
|
895
|
+
*/
|
|
896
|
+
serverTimeZone?: string;
|
|
888
897
|
};
|
|
889
898
|
export type TodoItem = {
|
|
890
899
|
id?: string;
|
|
@@ -914,6 +923,9 @@ export type OrchestratorSessionSummary = {
|
|
|
914
923
|
updatedAt: string;
|
|
915
924
|
createdAt?: number;
|
|
916
925
|
lastProgress?: string;
|
|
926
|
+
output?: string;
|
|
927
|
+
outputPath?: string;
|
|
928
|
+
outputTruncated?: boolean;
|
|
917
929
|
};
|
|
918
930
|
/**
|
|
919
931
|
* What Pi reports about ITS OWN queue, forwarded to the browser as it arrives.
|
|
@@ -3,129 +3,45 @@ import type { Store } from "./store.js";
|
|
|
3
3
|
import type { ModelSelection, ServerEvent } from "./types.js";
|
|
4
4
|
import { type WorkflowPlan, type WorkflowRun } from "./workflow.js";
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* publishes. The engine's whole job is to call `dispatch` in the right ORDER and decide what to do when
|
|
11
|
-
* a step does not succeed, and `SubagentRunRecorder.dispatch` is the seam that makes that possible: it
|
|
12
|
-
* ALWAYS RESOLVES with a settled record, including for a dispatch that never started (`subagent-runs.ts:159`).
|
|
13
|
-
* So an engine step is `await` one call, read `status` off the record, and move on. There is no error
|
|
14
|
-
* parsing here, no exception handling around a spawn, and no way for a child to be left running because
|
|
15
|
-
* the thing above it threw - which is the entire reason this feature is small.
|
|
16
|
-
*
|
|
17
|
-
* WHAT THE ENGINE IS NOT ALLOWED TO DO, stated as the two rules it is built around:
|
|
18
|
-
*
|
|
19
|
-
* 1. IT DOES NOT DECIDE WHAT A STEP MEANS. `role` is written through to the run record and read by
|
|
20
|
-
* nobody in this process. A step that says "verify" and a step that says "look at it" execute the same
|
|
21
|
-
* way. The rules about who does what live in `agents/*.md` and in the model that wrote the plan, not
|
|
22
|
-
* in a dispatcher holding a table of role names.
|
|
23
|
-
* 2. IT DOES NOT JUDGE RESULTS. A `verifier` step that completes with 结论:失败 has SUCCEEDED, as far as
|
|
24
|
-
* this loop is concerned, and the plan advances. The verdict lives in the text, and the component that
|
|
25
|
-
* can read the text - the primary agent - is what decides whether to run another round. A server that
|
|
26
|
-
* parsed 通过/失败 would be guessing at a convention `verifier.md` states in prose and `scout.md` and
|
|
27
|
-
* `critic.md` do not have at all, and would have no correct answer when the text said neither.
|
|
6
|
+
* Recursive descent over the primary's plan. This engine owns execution order and the mechanical `until`
|
|
7
|
+
* comparison; it does not infer tasks, discover foreach items, or ask a child whether another round should
|
|
8
|
+
* run. Spawning, streaming and teardown remain behind `SubagentRunRecorder.dispatch`, which always resolves
|
|
9
|
+
* with a settled record.
|
|
28
10
|
*/
|
|
29
|
-
/** What the engine needs from the server around it. Injected, so this file holds no globals. */
|
|
30
11
|
export type WorkflowEngineHooks = {
|
|
31
12
|
store: Store;
|
|
32
|
-
/** Publishes one event to whoever is listening. A plain function, so a test can pass an array. */
|
|
33
13
|
broadcast: (event: ServerEvent) => void;
|
|
34
14
|
};
|
|
35
|
-
/** Everything one plan needs to run. The plan itself arrives already validated and normalized. */
|
|
36
15
|
export type WorkflowRunInput = {
|
|
37
16
|
conversationId: string;
|
|
38
|
-
/** The dispatching conversation's workspace, resolved by the caller. Never defaulted to the server's own directory. */
|
|
39
17
|
cwd: string;
|
|
40
18
|
plan: WorkflowPlan;
|
|
41
19
|
model?: ModelSelection;
|
|
42
|
-
/** Tools the primary agent denied, inherited by every step, exactly as a single dispatch inherits them. */
|
|
43
20
|
denyTools?: readonly string[];
|
|
44
|
-
/** Cancels the whole plan. Each step is dispatched under it, so one signal reaches the loop and the child. */
|
|
45
21
|
signal?: AbortSignal;
|
|
46
22
|
};
|
|
47
|
-
/** The outcome of a plan, as the endpoint reports it. Always produced, never thrown. */
|
|
48
23
|
export type WorkflowOutcome = {
|
|
49
24
|
workflow: WorkflowRun;
|
|
50
25
|
};
|
|
51
26
|
export declare class WorkflowEngine {
|
|
52
27
|
private readonly recorder;
|
|
53
28
|
private readonly hooks;
|
|
54
|
-
/**
|
|
55
|
-
* One controller per in-flight plan.
|
|
56
|
-
*
|
|
57
|
-
* NEEDED SEPARATELY FROM THE RECORDER'S, and the difference is what stops a plan between steps. The
|
|
58
|
-
* recorder's map only holds runs that are executing right now, so a plan halted by a signal while no
|
|
59
|
-
* child is out would have nothing to abort and would happily start the next one. This map holds the
|
|
60
|
-
* PLAN, which exists for its whole length, so `stop` reaches it in every phase.
|
|
61
|
-
*/
|
|
62
29
|
private readonly controllers;
|
|
30
|
+
private readonly owners;
|
|
63
31
|
constructor(recorder: SubagentRunRecorder, hooks: WorkflowEngineHooks);
|
|
64
|
-
/**
|
|
65
|
-
* Stops ONE plan, and says whether there was anything to stop.
|
|
66
|
-
*
|
|
67
|
-
* `false` for an unknown or already-finished id is deliberate and matches `SubagentRunRecorder.stop`:
|
|
68
|
-
* the click that produces it raced the plan's own terminal event, and an error reply would put a
|
|
69
|
-
* failure banner on a timeline that says it finished normally.
|
|
70
|
-
*
|
|
71
|
-
* The abort reaches the child too, because every step is dispatched under this controller's signal and
|
|
72
|
-
* `dispatch` turns it into the same abort-then-killTree the recorder uses. Stopping a plan really does
|
|
73
|
-
* stop the process it is on.
|
|
74
|
-
*/
|
|
75
32
|
stop(workflowId: string): boolean;
|
|
76
|
-
/** Stops every plan in flight for one conversation, and returns how many were signalled. */
|
|
77
33
|
stopConversation(conversationId: string): number;
|
|
78
|
-
/** Aborts every plan in flight. Called on shutdown, so no child outlives the server that owns it. */
|
|
79
34
|
abortAll(): void;
|
|
80
|
-
/** Waits until aborted plans have committed their terminal state. */
|
|
81
35
|
waitForIdle(): Promise<void>;
|
|
82
|
-
/** The conversation a plan belongs to, which only this map knows while the plan is running. */
|
|
83
36
|
private conversationOf;
|
|
84
|
-
/**
|
|
85
|
-
* Which conversation each in-flight plan belongs to.
|
|
86
|
-
*
|
|
87
|
-
* Held beside the controllers rather than read back from the store, because the store is the right
|
|
88
|
-
* place for what a person may read later and the wrong place for a synchronous lookup in a stop
|
|
89
|
-
* handler - it clones the entire state, and this runs on a click.
|
|
90
|
-
*/
|
|
91
|
-
private readonly owners;
|
|
92
|
-
/**
|
|
93
|
-
* Executes one plan to a terminal state, and returns the record as it ended.
|
|
94
|
-
*
|
|
95
|
-
* ALWAYS RESOLVES, like `dispatch` does and for the same reason: by the time the first await runs, the
|
|
96
|
-
* whole plan is already on disk, so the honest account of a plan that cannot proceed is a record that
|
|
97
|
-
* says why, rather than a rejection that leaves it claiming to be running until the next restart.
|
|
98
|
-
*/
|
|
99
37
|
run(input: WorkflowRunInput): Promise<WorkflowOutcome>;
|
|
100
|
-
/**
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
* to have absolutely.
|
|
106
|
-
*/
|
|
38
|
+
/** Allocates execution rows once per statically known occurrence; loop occurrences are allocated per round. */
|
|
39
|
+
private materializeNodes;
|
|
40
|
+
private runNodes;
|
|
41
|
+
private runLoop;
|
|
42
|
+
private runForeach;
|
|
107
43
|
private runStep;
|
|
108
|
-
/**
|
|
109
|
-
* Marks every step from `from` onward as stopped, because none of them will run.
|
|
110
|
-
*
|
|
111
|
-
* LEAVING THEM `pending` WOULD BE A LIE IN THE SPECIFIC DIRECTION THAT MATTERS: a browser renders
|
|
112
|
-
* "未开始" for a pending step, so a plan that died after step one of three would show three steps with
|
|
113
|
-
* two still waiting, and a person would reasonably expect the rest to happen. They will not.
|
|
114
|
-
*/
|
|
115
44
|
private haltRemaining;
|
|
116
|
-
/**
|
|
117
|
-
* Persists a plan, THEN announces it.
|
|
118
|
-
*
|
|
119
|
-
* PERSIST FIRST, ANNOUNCE SECOND, and the order is load-bearing here for exactly the reason it is in
|
|
120
|
-
* `SubagentRunRecorder.settle`: a browser that is told a step finished while `state.json` still says it
|
|
121
|
-
* is running will show a timeline a reload immediately contradicts, and the startup reconciliation that
|
|
122
|
-
* would fix it runs on a SERVER restart, not a page refresh.
|
|
123
|
-
*
|
|
124
|
-
* Unlike the recorder, this AWAITS both halves rather than writing fire-and-forget, and that is not an
|
|
125
|
-
* inconsistency. The recorder cannot await: it publishes on every text delta, and a drain loop that
|
|
126
|
-
* flushed to disk per token would throttle the child it is streaming. This publishes twice per step -
|
|
127
|
-
* at most sixteen times for a whole plan - so awaiting costs nothing measurable and buys the strong
|
|
128
|
-
* version of the guarantee.
|
|
129
|
-
*/
|
|
45
|
+
/** Persist first, announce second, so a reload never contradicts an event already shown. */
|
|
130
46
|
private commit;
|
|
131
47
|
}
|