chore(workflow): post-extraction cleanup #326

Merged
xiaomo merged 1 commits from chore/325-workflow-cleanup into main 2026-05-05 13:54:30 +00:00
8 changed files with 29 additions and 39 deletions
+2 -1
View File
@@ -7,7 +7,8 @@
"pnpm": {
"overrides": {
"@uncaged/nerve-core": "workspace:*",
"@uncaged/nerve-store": "workspace:*"
"@uncaged/nerve-store": "workspace:*",
"@uncaged/workflow": "workspace:*"
}
},
"scripts": {
+1
View File
@@ -20,6 +20,7 @@
"test": "vitest run"
},
"dependencies": {
"@uncaged/workflow": "workspace:*",
"yaml": "^2.8.3"
},
"devDependencies": {
+2 -16
View File
@@ -1,23 +1,9 @@
import { parse } from "yaml";
import type { DropOverflowConfig, QueueOverflowConfig, WorkflowConfig } from "@uncaged/workflow";
import { type Result, err, isPlainRecord, ok, parseDurationStringToMs } from "./util.js";
/**
* Workflow queue/runtime limits parsed from nerve.yaml.
* Shapes match the standalone workflow package — core must not depend on it (#320).
*/
export type DropOverflowConfig = {
concurrency: number;
overflow: "drop";
};
export type QueueOverflowConfig = {
concurrency: number;
overflow: "queue";
maxQueue: number;
};
export type WorkflowConfig = DropOverflowConfig | QueueOverflowConfig;
export type { DropOverflowConfig, QueueOverflowConfig, WorkflowConfig };
/** Engine-wide fallback when nerve.yaml omits max_rounds (keep in sync with workflow package default). */
export const DEFAULT_ENGINE_MAX_ROUNDS = 100;
-1
View File
@@ -32,7 +32,6 @@ export type {
WorkflowWorkerToParentMessage,
WorkflowWorkerReadyMessage,
WorkflowChildToParentMessage,
WorkflowWorkerOutboundMessage,
} from "./ipc.js";
export {
parseWorkflowParentMessage,
+11 -16
View File
@@ -100,11 +100,6 @@ export type WorkflowChildToParentMessage =
| WorkflowWorkerReadyMessage
| WorkflowWorkerToParentMessage;
/** Messages a workflow worker process may send upstream (ready + persisted workflow IPC). */
export type WorkflowWorkerOutboundMessage =
| WorkflowWorkerReadyMessage
| WorkflowWorkerToParentMessage;
const WORKFLOW_PARENT_MSG_TYPES = new Set([
"start-thread",
"resume-thread",
@@ -135,12 +130,12 @@ function parseParentStartThread(obj: Record<string, unknown>): Result<StartThrea
if (errMsg !== null) return err(new Error(errMsg));
return ok({
type: "start-thread",
runId: obj.runId,
workflow: obj.workflow,
prompt: obj.prompt,
maxRounds: obj.maxRounds,
dryRun: obj.dryRun,
} as StartThreadMessage);
runId: obj.runId as string,
workflow: obj.workflow as string,
prompt: obj.prompt as string,
maxRounds: obj.maxRounds as number,
dryRun: obj.dryRun as boolean,
});
}
function parseParentResumeThread(obj: Record<string, unknown>): Result<ResumeThreadMessage> {
@@ -148,18 +143,18 @@ function parseParentResumeThread(obj: Record<string, unknown>): Result<ResumeThr
if (errMsg !== null) return err(new Error(errMsg));
return ok({
type: "resume-thread",
runId: obj.runId,
runId: obj.runId as string,
messages: obj.messages as ResumeThreadMessage["messages"],
maxRounds: obj.maxRounds,
dryRun: obj.dryRun,
} as ResumeThreadMessage);
maxRounds: obj.maxRounds as number,
dryRun: obj.dryRun as boolean,
});
}
function parseParentKillThread(obj: Record<string, unknown>): Result<KillThreadMessage> {
if (typeof obj.runId !== "string") {
return err(new Error("'kill-thread' message missing string 'runId'"));
}
return ok({ type: "kill-thread", runId: obj.runId } as KillThreadMessage);
return ok({ type: "kill-thread", runId: obj.runId as string });
}
/** Validate and parse an unknown IPC message for a workflow worker process. */
+7 -3
View File
@@ -58,8 +58,9 @@ export type WorkflowManager = {
* Drain active threads for a workflow, then respawn its worker process.
* Used for hot reload when bundled workflow output under dist/workflows/<name>/ changes.
* Waits up to `drainTimeoutMs` for threads to complete before force-killing.
* Pass `null` to use the manager default timeout.
*/
drainAndRespawn: (workflowName: string, drainTimeoutMs?: number) => Promise<void>;
drainAndRespawn: (workflowName: string, drainTimeoutMs?: number | null) => Promise<void>;
/**
* Schedule a drain+respawn that waits for in-flight runs to finish first.
* If no runs are active, drains immediately. Otherwise marks a pending reload
@@ -450,13 +451,16 @@ export function createWorkflowManager(
async function drainAndRespawn(
workflowName: string,
drainTimeoutMs: number = DEFAULT_DRAIN_TIMEOUT_MS,
drainTimeoutMs: number | null = null,
): Promise<void> {
if (!trackedWorkflows.has(workflowName)) {
return;
}
const shutdownMs = Math.max(drainTimeoutMs, WORKER_SHUTDOWN_TIMEOUT_MS);
const shutdownMs = Math.max(
drainTimeoutMs ?? DEFAULT_DRAIN_TIMEOUT_MS,
WORKER_SHUTDOWN_TIMEOUT_MS,
);
hotReloadEvicting.add(workflowName);
try {
await runtime.evict(workflowName, { shutdownTimeoutMs: shutdownMs });
+2 -2
View File
@@ -19,7 +19,7 @@ import { isPlainRecord } from "@uncaged/nerve-core";
import type {
ThreadEventType,
ThreadWorkflowMessage,
WorkflowWorkerOutboundMessage,
WorkflowChildToParentMessage,
} from "./ipc.js";
import { parseWorkflowParentMessage } from "./ipc.js";
import type {
@@ -37,7 +37,7 @@ import { ignoreSessionBroadcastSignals } from "./worker-signals.js";
// IPC helpers
// ---------------------------------------------------------------------------
function send(msg: WorkflowWorkerOutboundMessage): void {
function send(msg: WorkflowChildToParentMessage): void {
if (process.send) {
process.send(msg);
}
+4
View File
@@ -7,6 +7,7 @@ settings:
overrides:
'@uncaged/nerve-core': workspace:*
'@uncaged/nerve-store': workspace:*
'@uncaged/workflow': workspace:*
importers:
@@ -99,6 +100,9 @@ importers:
packages/core:
dependencies:
'@uncaged/workflow':
specifier: workspace:*
version: link:../workflow
yaml:
specifier: ^2.8.3
version: 2.8.3