refactor(core): rename RoleSignal → RoleStep, StartSignal → StartStep
- RoleStep now includes content and timestamp fields (aligned with StartStep) - ModeratorContext.signal → ModeratorContext.step - workflow-utils: start-signal.ts → start-step.ts, isDryRun updated Fixes #109
This commit is contained in:
@@ -8,7 +8,7 @@ Shared types and configuration parser for the [nerve](../../README.md) observati
|
||||
- **Config parser** — `parseNerveConfig(yaml)` validates and parses `nerve.yaml` into `NerveConfig` (rejects reflex entries that declare a `workflow` key; reflexes only schedule senses)
|
||||
- **Sense → workflow routing** — `parseSenseWorkflowDirective`, `routeSenseComputeOutput`, and types `ParsedSenseWorkflowDirective`, `SenseComputeRoute`
|
||||
- **Daemon IPC protocol** — request/response types (`DaemonIpcRequest`, `DaemonIpcResponse`, …) and `parseDaemonIpcRequest` for newline-delimited JSON on the CLI ↔ daemon socket
|
||||
- **Workflow automaton types** — `START` / `END` sentinel constants, `WorkflowMessage`, `StartSignal`, `RoleSignal`, `Moderator`, `WorkflowDefinition`, `Role`, `SenseResult`, plus `DEFAULT_ENGINE_MAX_ROUNDS`
|
||||
- **Workflow automaton types** — `START` / `END` sentinel constants, `WorkflowMessage`, `StartStep`, `RoleStep`, `Moderator`, `WorkflowDefinition`, `Role`, `SenseResult`, plus `DEFAULT_ENGINE_MAX_ROUNDS`
|
||||
- **Result type** — `Result<T>` with `ok()` / `err()` helpers for explicit error handling (no thrown exceptions for parse paths)
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -12,8 +12,8 @@ export type {
|
||||
RoleResult,
|
||||
Role,
|
||||
RoleMeta,
|
||||
StartSignal,
|
||||
RoleSignal,
|
||||
StartStep,
|
||||
RoleStep,
|
||||
ModeratorContext,
|
||||
Moderator,
|
||||
WorkflowDefinition,
|
||||
|
||||
@@ -82,7 +82,7 @@ export type RoleResult<Meta> = { content: string; meta: Meta };
|
||||
* script, HTTP request, etc.
|
||||
*/
|
||||
export type Role<Meta> = (
|
||||
start: StartSignal,
|
||||
start: StartStep,
|
||||
messages: WorkflowMessage[],
|
||||
) => Promise<RoleResult<Meta>>;
|
||||
|
||||
@@ -90,25 +90,25 @@ export type Role<Meta> = (
|
||||
export type RoleMeta = Record<string, Record<string, unknown>>;
|
||||
|
||||
/** Engine start frame: prompt, max rounds cap, dry-run flag, and timestamps for the thread. */
|
||||
export type StartSignal = {
|
||||
export type StartStep = {
|
||||
role: START;
|
||||
content: string;
|
||||
meta: { maxRounds: number; dryRun: boolean };
|
||||
timestamp: number;
|
||||
};
|
||||
|
||||
/** A discriminated union of signals from each role, derived from the meta map. */
|
||||
export type RoleSignal<M extends RoleMeta> = {
|
||||
[K in keyof M & string]: { role: K; meta: M[K] };
|
||||
/** A discriminated union of role steps after each execution, aligned with `StartStep` shape. */
|
||||
export type RoleStep<M extends RoleMeta> = {
|
||||
[K in keyof M & string]: { role: K; meta: M[K]; content: string; timestamp: number };
|
||||
}[keyof M & string];
|
||||
|
||||
/**
|
||||
* Moderator input: either the initial start frame or a role signal after a step.
|
||||
* Moderator input: either the initial start frame or a role step after a step.
|
||||
* Lets implementations branch on `context.kind` with full typing for each arm.
|
||||
*/
|
||||
export type ModeratorContext<M extends RoleMeta> =
|
||||
| { kind: "start"; start: StartSignal }
|
||||
| { kind: "step"; signal: RoleSignal<M> };
|
||||
| { kind: "start"; start: StartStep }
|
||||
| { kind: "step"; step: RoleStep<M> };
|
||||
|
||||
/**
|
||||
* The moderator — a pure routing function. Receives start vs step context,
|
||||
|
||||
@@ -15,7 +15,7 @@ import { join, resolve } from "node:path";
|
||||
import type {
|
||||
ModeratorContext,
|
||||
RoleMeta,
|
||||
StartSignal,
|
||||
StartStep,
|
||||
WorkflowDefinition,
|
||||
WorkflowMessage,
|
||||
} from "@uncaged/nerve-core";
|
||||
@@ -85,13 +85,13 @@ function validateRoleResult(
|
||||
return true;
|
||||
}
|
||||
|
||||
function isStartMeta(meta: unknown): meta is StartSignal["meta"] {
|
||||
function isStartMeta(meta: unknown): meta is StartStep["meta"] {
|
||||
return (
|
||||
isPlainRecord(meta) && typeof meta.maxRounds === "number" && typeof meta.dryRun === "boolean"
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeStartMeta(meta: unknown, maxRoundsFallback: number): StartSignal["meta"] {
|
||||
function normalizeStartMeta(meta: unknown, maxRoundsFallback: number): StartStep["meta"] {
|
||||
if (!isPlainRecord(meta)) {
|
||||
return { maxRounds: maxRoundsFallback, dryRun: false };
|
||||
}
|
||||
@@ -100,10 +100,7 @@ function normalizeStartMeta(meta: unknown, maxRoundsFallback: number): StartSign
|
||||
return { maxRounds, dryRun };
|
||||
}
|
||||
|
||||
function startSignalFromWorkflowMessage(
|
||||
msg: WorkflowMessage,
|
||||
maxRoundsFallback: number,
|
||||
): StartSignal {
|
||||
function startStepFromWorkflowMessage(msg: WorkflowMessage, maxRoundsFallback: number): StartStep {
|
||||
if (msg.role !== START) {
|
||||
return {
|
||||
role: START,
|
||||
@@ -122,7 +119,7 @@ function startSignalFromWorkflowMessage(
|
||||
}
|
||||
|
||||
type ThreadMessagesState = {
|
||||
start: StartSignal;
|
||||
start: StartStep;
|
||||
/** Role outputs only; never includes the `__start__` frame. */
|
||||
messages: WorkflowMessage[];
|
||||
};
|
||||
@@ -138,7 +135,7 @@ function initThreadMessages(
|
||||
const [first, ...rest] = resumeMessages;
|
||||
if (first.role === START) {
|
||||
return {
|
||||
start: startSignalFromWorkflowMessage(first, maxRounds),
|
||||
start: startStepFromWorkflowMessage(first, maxRounds),
|
||||
messages: [...rest],
|
||||
};
|
||||
}
|
||||
@@ -154,7 +151,7 @@ function initThreadMessages(
|
||||
};
|
||||
}
|
||||
const prompt = freshPrompt ?? "";
|
||||
const start: StartSignal = {
|
||||
const start: StartStep = {
|
||||
role: START,
|
||||
content: prompt,
|
||||
meta: { maxRounds, dryRun },
|
||||
@@ -172,7 +169,7 @@ function initThreadMessages(
|
||||
async function executeRole(
|
||||
def: WorkflowDefinition<RoleMeta>,
|
||||
nextRole: string,
|
||||
start: StartSignal,
|
||||
start: StartStep,
|
||||
messages: WorkflowMessage[],
|
||||
runId: string,
|
||||
): Promise<{ content: string; meta: Record<string, unknown> } | null> {
|
||||
@@ -236,7 +233,12 @@ async function runThread(
|
||||
|
||||
const stepContext: ModeratorContext<RoleMeta> = {
|
||||
kind: "step",
|
||||
signal: { role: nextRole, meta: result.meta },
|
||||
step: {
|
||||
role: nextRole,
|
||||
meta: result.meta,
|
||||
content: result.content,
|
||||
timestamp: message.timestamp,
|
||||
},
|
||||
};
|
||||
nextRole = def.moderator(stepContext, roleRound, maxRounds);
|
||||
|
||||
|
||||
@@ -19,4 +19,4 @@ export {
|
||||
type SpawnResult,
|
||||
type SpawnSafeOptions,
|
||||
} from "./spawn-safe.js";
|
||||
export { isDryRun } from "./start-signal.js";
|
||||
export { isDryRun } from "./start-step.js";
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import type { StartSignal } from "@uncaged/nerve-core";
|
||||
|
||||
/** Returns the thread-level dry-run flag from the workflow start frame. */
|
||||
export function isDryRun(start: StartSignal): boolean {
|
||||
return start.meta.dryRun;
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import type { StartStep } from "@uncaged/nerve-core";
|
||||
|
||||
/** Returns the thread-level dry-run flag from the workflow start frame. */
|
||||
export function isDryRun(start: StartStep): boolean {
|
||||
return start.meta.dryRun;
|
||||
}
|
||||
Reference in New Issue
Block a user