From 7d0200fa154c4dd4c911ae54d9215da0e5dc6bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Tue, 5 May 2026 10:16:33 +0000 Subject: [PATCH 1/4] docs: add implementation plan for @uncaged/workflow extraction Refs #320 --- .../2026-05-05-extract-workflow-package.md | 496 ++++++++++++++++++ 1 file changed, 496 insertions(+) create mode 100644 docs/plans/2026-05-05-extract-workflow-package.md diff --git a/docs/plans/2026-05-05-extract-workflow-package.md b/docs/plans/2026-05-05-extract-workflow-package.md new file mode 100644 index 0000000..c7727e2 --- /dev/null +++ b/docs/plans/2026-05-05-extract-workflow-package.md @@ -0,0 +1,496 @@ +# Extract Workflow Engine into `@uncaged/workflow` — Implementation Plan + +> **For Hermes:** Use subagent-driven-development skill to implement this plan task-by-task. + +**Goal:** Extract the workflow engine (types, runtime, IPC, manager) from nerve-core and nerve-daemon into a standalone `@uncaged/workflow` package. + +**Architecture:** Create `packages/workflow/` as a new pnpm workspace package. Move workflow types from core and workflow runtime from daemon into it. The daemon becomes a consumer of `@uncaged/workflow`. No backward-compat re-exports — breaking change, update all consumers in one shot. + +**Tech Stack:** TypeScript, pnpm workspace, rslib (bundler, same as other packages), Biome + +**Ref:** Fixes #320 + +--- + +## Phase 1: Scaffold `packages/workflow/` + +### Task 1: Create package skeleton + +**Objective:** Create the `@uncaged/workflow` package with package.json, tsconfig, rslib config. + +**Files:** +- Create: `packages/workflow/package.json` +- Create: `packages/workflow/tsconfig.json` +- Create: `packages/workflow/rslib.config.ts` +- Create: `packages/workflow/src/index.ts` (empty barrel) + +**Steps:** + +1. Copy `packages/workflow-utils/package.json` as template, change name to `@uncaged/workflow`, remove all dependencies except dev deps (typescript, rslib, etc.). No runtime deps initially. + +2. Copy `packages/workflow-utils/tsconfig.json` and `rslib.config.ts` as-is (same monorepo conventions). + +3. Create empty `packages/workflow/src/index.ts`: +```typescript +// @uncaged/workflow — standalone workflow orchestration engine +``` + +4. Verify: +```bash +cd packages/workflow && pnpm install && pnpm run build +``` + +5. Commit: +```bash +git add packages/workflow/ +git commit -m "chore(workflow): scaffold @uncaged/workflow package + +Refs #320" +``` + +--- + +## Phase 2: Move Types from Core + +### Task 2: Move `workflow.ts` types to `@uncaged/workflow` + +**Objective:** Move all workflow types and constants from `packages/core/src/workflow.ts` → `packages/workflow/src/types.ts`. + +**Files:** +- Create: `packages/workflow/src/types.ts` +- Modify: `packages/workflow/src/index.ts` + +**Steps:** + +1. Copy `packages/core/src/workflow.ts` → `packages/workflow/src/types.ts` verbatim (all 83 lines — `START`, `END`, `DEFAULT_ENGINE_MAX_ROUNDS`, all types). + +2. Export everything from `packages/workflow/src/index.ts`: +```typescript +export { + START, + END, + DEFAULT_ENGINE_MAX_ROUNDS, +} from "./types.js"; +export type { + WorkflowMessage, + RoleResult, + Role, + RoleMeta, + StartStep, + ThreadContext, + WorkflowContext, + AgentFn, + RoleStep, + ModeratorContext, + Moderator, + WorkflowDefinition, +} from "./types.js"; +``` + +3. Build to verify types compile: +```bash +cd packages/workflow && pnpm run build +``` + +4. Commit: +```bash +git commit -am "refactor(workflow): move workflow types from core to @uncaged/workflow + +Refs #320" +``` + +### Task 3: Move `WorkflowConfig` to `@uncaged/workflow` + +**Objective:** Move the `WorkflowConfig` type (and its constituent types `DropOverflowConfig`, `QueueOverflowConfig`) from core/config.ts to the workflow package. + +**Files:** +- Create: `packages/workflow/src/config.ts` +- Modify: `packages/workflow/src/index.ts` +- Modify: `packages/core/src/config.ts` — remove `WorkflowConfig`, `DropOverflowConfig`, `QueueOverflowConfig`; import from `@uncaged/workflow` instead + +**Steps:** + +1. Create `packages/workflow/src/config.ts` with the three types: +```typescript +export type DropOverflowConfig = { + concurrency: number; + overflow: "drop"; +}; + +export type QueueOverflowConfig = { + concurrency: number; + overflow: "queue"; + maxQueue: number; +}; + +export type WorkflowConfig = DropOverflowConfig | QueueOverflowConfig; +``` + +2. Re-export from `packages/workflow/src/index.ts`. + +3. In `packages/core/package.json`, add dependency: `"@uncaged/workflow": "workspace:*"`. + +4. In `packages/core/src/config.ts`: + - Remove the three type definitions + - Add `import type { WorkflowConfig, DropOverflowConfig, QueueOverflowConfig } from "@uncaged/workflow";` + - Keep the re-export from `packages/core/src/index.ts` pointing to config.ts (which now re-exports from workflow) + +5. Build entire workspace: +```bash +pnpm run build +``` + +6. Commit: +```bash +git commit -am "refactor(workflow): move WorkflowConfig types to @uncaged/workflow + +Refs #320" +``` + +### Task 4: Remove workflow types from core, update core exports + +**Objective:** Delete `packages/core/src/workflow.ts` entirely. Core re-exports workflow types from `@uncaged/workflow`. + +**Files:** +- Delete: `packages/core/src/workflow.ts` +- Modify: `packages/core/src/index.ts` — change workflow exports to re-export from `@uncaged/workflow` +- Modify: `packages/core/src/config.ts` — remove import of `DEFAULT_ENGINE_MAX_ROUNDS` from deleted file + +**Steps:** + +1. In `packages/core/src/config.ts`, replace: + ```typescript + import { DEFAULT_ENGINE_MAX_ROUNDS } from "./workflow.js"; + ``` + with: + ```typescript + import { DEFAULT_ENGINE_MAX_ROUNDS } from "@uncaged/workflow"; + ``` + +2. In `packages/core/src/index.ts`, replace all workflow.js imports with re-exports from `@uncaged/workflow`: + ```typescript + // Workflow types — re-exported from @uncaged/workflow + export { + START, END, DEFAULT_ENGINE_MAX_ROUNDS, + } from "@uncaged/workflow"; + export type { + WorkflowMessage, RoleResult, Role, RoleMeta, StartStep, + ThreadContext, WorkflowContext, AgentFn, RoleStep, + ModeratorContext, Moderator, WorkflowDefinition, + } from "@uncaged/workflow"; + ``` + +3. Delete `packages/core/src/workflow.ts`. + +4. Full build + test: +```bash +pnpm run build && pnpm test +``` + +5. Commit: +```bash +git commit -am "refactor(core): remove workflow.ts, re-export from @uncaged/workflow + +Refs #320" +``` + +--- + +## Phase 3: Move Workflow IPC Messages + +### Task 5: Extract workflow IPC types to `@uncaged/workflow` + +**Objective:** Move workflow-related IPC message types from `packages/daemon/src/ipc.ts` to `packages/workflow/src/ipc.ts`. Sense IPC stays in daemon. + +**Files:** +- Create: `packages/workflow/src/ipc.ts` +- Modify: `packages/workflow/src/index.ts` +- Modify: `packages/daemon/src/ipc.ts` — remove workflow IPC types, import from `@uncaged/workflow` + +**Steps:** + +1. Extract to `packages/workflow/src/ipc.ts`: + - `StartThreadMessage`, `ResumeThreadMessage`, `KillThreadMessage` + - `ThreadLifecycleEvent`, `ThreadEventMessage`, `WorkflowErrorMessage`, `ThreadWorkflowMessage` + - Workflow-related validation logic from `parseParentToWorkerMessage` + - Union type for workflow parent→worker messages + +2. Keep in daemon `ipc.ts`: + - `ComputeMessage`, `ShutdownMessage`, `HealthRequestMessage` + - Sense-related worker→parent messages + - The combined `ParentToWorkerMessage` union (imports workflow types from `@uncaged/workflow`) + +3. Add `@uncaged/workflow` as dependency to `packages/daemon/package.json`. + +4. Build + test: +```bash +pnpm run build && pnpm test +``` + +5. Commit: +```bash +git commit -am "refactor(workflow): move workflow IPC types to @uncaged/workflow + +Refs #320" +``` + +--- + +## Phase 4: Move Workflow Runtime + +### Task 6: Move `workflow-worker.ts` to `@uncaged/workflow` + +**Objective:** Move workflow execution runtime (the worker that runs inside a child process) from daemon to the workflow package. + +**Files:** +- Move: `packages/daemon/src/workflow-worker.ts` → `packages/workflow/src/worker.ts` +- Modify: `packages/workflow/src/index.ts` +- Modify: `packages/daemon/` — update worker spawn path + +**Steps:** + +1. Copy `workflow-worker.ts` to `packages/workflow/src/worker.ts`. + +2. Update imports: replace `@uncaged/nerve-core` with local imports from `./types.js`, `./ipc.js`. + +3. Export the worker entry point or the worker file path from `@uncaged/workflow` so daemon can spawn it. + +4. In daemon, update worker spawn to reference `@uncaged/workflow`'s worker. + +5. Delete `packages/daemon/src/workflow-worker.ts`. + +6. Build + test: +```bash +pnpm run build && pnpm test +``` + +7. Commit: +```bash +git commit -am "refactor(workflow): move workflow-worker runtime to @uncaged/workflow + +Refs #320" +``` + +### Task 7: Move `workflow-manager.ts` and `workflow-manager-support.ts` to `@uncaged/workflow` + +**Objective:** Move workflow process management from daemon to workflow package. + +**Files:** +- Move: `packages/daemon/src/workflow-manager.ts` → `packages/workflow/src/manager.ts` +- Move: `packages/daemon/src/workflow-manager-support.ts` → `packages/workflow/src/manager-support.ts` +- Modify: `packages/daemon/src/kernel.ts` — import from `@uncaged/workflow` + +**Steps:** + +1. Copy both files to `packages/workflow/src/`. + +2. Update imports to use local paths within the workflow package. + +3. Export manager creation function from `packages/workflow/src/index.ts`. + +4. Update `packages/daemon/src/kernel.ts` to import workflow manager from `@uncaged/workflow`. + +5. Delete the original files from daemon. + +6. Build + test: +```bash +pnpm run build && pnpm test +``` + +7. Commit: +```bash +git commit -am "refactor(workflow): move workflow-manager to @uncaged/workflow + +Refs #320" +``` + +--- + +## Phase 5: Update All Consumers + +### Task 8: Update `workflow-utils` to depend on `@uncaged/workflow` + +**Objective:** Change `@uncaged/nerve-workflow-utils` to import workflow types from `@uncaged/workflow` instead of `@uncaged/nerve-core`. + +**Files:** +- Modify: `packages/workflow-utils/package.json` — replace `@uncaged/nerve-core` dep with `@uncaged/workflow` +- Modify: all `packages/workflow-utils/src/*.ts` — update import paths + +**Steps:** + +1. In `package.json`, replace `"@uncaged/nerve-core": "workspace:*"` with `"@uncaged/workflow": "workspace:*"`. + +2. Find-and-replace all `from "@uncaged/nerve-core"` → `from "@uncaged/workflow"` in `packages/workflow-utils/src/`. + +3. Build + test: +```bash +pnpm run build && pnpm test +``` + +4. Commit: +```bash +git commit -am "refactor(workflow-utils): import from @uncaged/workflow + +Refs #320" +``` + +### Task 9: Update `workflow-meta` to depend on `@uncaged/workflow` + +**Objective:** Same as Task 8 but for `packages/workflow-meta/`. + +**Files:** +- Modify: `packages/workflow-meta/package.json` +- Modify: all `packages/workflow-meta/src/**/*.ts` + +**Steps:** Same pattern as Task 8. + +### Task 10: Update adapter packages + +**Objective:** Update `adapter-cursor` and `adapter-hermes` to import workflow types from `@uncaged/workflow`. + +**Files:** +- Modify: `packages/adapter-cursor/src/index.ts` +- Modify: `packages/adapter-cursor/package.json` +- Modify: `packages/adapter-hermes/src/index.ts` +- Modify: `packages/adapter-hermes/package.json` + +**Steps:** Same pattern — add `@uncaged/workflow` dep, update imports. + +### Task 11: Update CLI package + +**Objective:** Update CLI to import workflow types from `@uncaged/workflow`. + +**Files:** +- Modify: `packages/cli/package.json` +- Modify: `packages/cli/src/commands/workflow.ts` +- Modify: `packages/cli/src/commands/thread.ts` +- Modify: `packages/cli/src/commands/create.ts` +- Modify: `packages/cli/src/workflow-agent-validation.ts` +- Modify: other files that import workflow types from nerve-core + +**Steps:** + +1. Add `"@uncaged/workflow": "workspace:*"` to `packages/cli/package.json`. + +2. In each file, change workflow-related imports from `@uncaged/nerve-core` to `@uncaged/workflow`. + +3. Build + test: +```bash +pnpm run build && pnpm test +``` + +4. Commit: +```bash +git commit -am "refactor(cli): import workflow types from @uncaged/workflow + +Refs #320" +``` + +--- + +## Phase 6: Clean Up Core Re-exports + +### Task 12: Remove workflow re-exports from `@uncaged/nerve-core` + +**Objective:** Core no longer re-exports any workflow types. All consumers import directly from `@uncaged/workflow`. + +**Files:** +- Modify: `packages/core/src/index.ts` — remove all workflow re-exports +- Modify: `packages/core/package.json` — remove `@uncaged/workflow` dependency (core no longer needs it) + +**Steps:** + +1. Remove all workflow-related `export` lines from `packages/core/src/index.ts`. + +2. Remove `@uncaged/workflow` from core's dependencies. + +3. Full build + full test: +```bash +pnpm run build && pnpm test +``` + +4. Run biome check: +```bash +pnpm run check +``` + +5. Commit: +```bash +git commit -am "refactor(core): remove workflow re-exports, clean break + +Fixes #320" +``` + +--- + +## Phase 7: Verify & PR + +### Task 13: Final verification + +**Objective:** Full workspace build, all tests pass, biome clean. + +**Steps:** + +1. Clean build: +```bash +pnpm run build +``` + +2. Full tests: +```bash +pnpm test +``` + +3. Biome: +```bash +pnpm run check +``` + +4. Verify monorepo structure matches issue spec: +``` +packages/ + core/ # @uncaged/nerve-core — sense types, config (no workflow) + workflow/ # @uncaged/workflow — standalone orchestration engine + workflow-utils/ # helper roles, extract layer + workflow-meta/ # meta-workflows + daemon/ # @uncaged/nerve-daemon — sense engine + workflow integration + cli/ # @uncaged/nerve-cli +``` + +5. Create PR: +```bash +tea pr create --title "refactor: extract workflow engine into @uncaged/workflow" \ + --description "## What +Extract workflow engine into standalone @uncaged/workflow package. + +## Why +Workflow engine is now independent of sense observation — can be used standalone. + +## Changes +- packages/workflow/ — new package with types, IPC, worker, manager +- packages/core/ — removed workflow types, no longer re-exports them +- packages/daemon/ — imports workflow runtime from @uncaged/workflow +- packages/cli/ — imports workflow types from @uncaged/workflow +- packages/workflow-utils/ — depends on @uncaged/workflow +- packages/workflow-meta/ — depends on @uncaged/workflow +- packages/adapter-*/ — depends on @uncaged/workflow + +## Ref +Fixes #320" +``` + +--- + +## Pitfalls & Notes + +1. **Worker spawn path**: `workflow-worker.ts` is spawned as a child process via `node`. After moving to `@uncaged/workflow`, the daemon needs to resolve the worker entry point from the package (e.g. `require.resolve("@uncaged/workflow/worker")`). This likely requires a separate export in `package.json` exports map. + +2. **Dynamic import exception**: `workflow-worker.ts` uses dynamic `import()` for user workflow modules — this exception carries over to the new package. Add comment per CLAUDE.md conventions. + +3. **IPC split**: The `ipc.ts` in daemon has both sense and workflow messages in one file with shared validation. The split needs careful handling of the `ParentToWorkerMessage` union type and `parseParentToWorkerMessage` function. + +4. **No backward compat**: Per user preference, no deprecated re-exports — straight breaking change. Phase 6 removes re-exports from core entirely. + +5. **`workflow-utils` may still need `nerve-core`**: If workflow-utils imports non-workflow types (like `Schema`, `ExtractFn`, `ExtractError`) from core, it will need both deps. Check carefully. + +6. **Test files**: Many test files in daemon import workflow types. They need updating in Phase 5 alongside the source files. -- 2.43.0 From 591be21bb0151d2d36be648b49cf858537023f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Tue, 5 May 2026 10:27:08 +0000 Subject: [PATCH 2/4] refactor(workflow): scaffold @uncaged/workflow, move types from core - Create packages/workflow/ with types.ts (from core/workflow.ts) and config.ts - Core re-exports workflow types from @uncaged/workflow - Delete packages/core/src/workflow.ts Phase 1+2 of #320, Testing: #321 --- packages/core/package.json | 1 + packages/core/src/config.ts | 22 ++++++---------- packages/core/src/index.ts | 4 +-- packages/workflow/package.json | 25 +++++++++++++++++++ packages/workflow/rslib.config.ts | 19 ++++++++++++++ packages/workflow/src/config.ts | 12 +++++++++ packages/workflow/src/index.ts | 19 ++++++++++++++ .../src/workflow.ts => workflow/src/types.ts} | 0 packages/workflow/tsconfig.json | 9 +++++++ pnpm-lock.yaml | 12 +++++++++ 10 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 packages/workflow/package.json create mode 100644 packages/workflow/rslib.config.ts create mode 100644 packages/workflow/src/config.ts create mode 100644 packages/workflow/src/index.ts rename packages/{core/src/workflow.ts => workflow/src/types.ts} (100%) create mode 100644 packages/workflow/tsconfig.json diff --git a/packages/core/package.json b/packages/core/package.json index e784889..4727056 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -20,6 +20,7 @@ "test": "vitest run" }, "dependencies": { + "@uncaged/workflow": "workspace:*", "yaml": "^2.8.3" }, "devDependencies": { diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index fb835d7..045d8b6 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -1,7 +1,14 @@ import { parse } from "yaml"; +import { + DEFAULT_ENGINE_MAX_ROUNDS, + type DropOverflowConfig, + type QueueOverflowConfig, + type WorkflowConfig, +} from "@uncaged/workflow"; import { type Result, err, isPlainRecord, ok, parseDurationStringToMs } from "./util.js"; -import { DEFAULT_ENGINE_MAX_ROUNDS } from "./workflow.js"; + +export type { DropOverflowConfig, QueueOverflowConfig, WorkflowConfig }; export type SenseConfig = { group: string; @@ -14,19 +21,6 @@ export type SenseConfig = { on: string[]; }; -export type DropOverflowConfig = { - concurrency: number; - overflow: "drop"; -}; - -export type QueueOverflowConfig = { - concurrency: number; - overflow: "queue"; - maxQueue: number; -}; - -export type WorkflowConfig = DropOverflowConfig | QueueOverflowConfig; - /** Optional HTTP control plane. When `port` is null, the HTTP server is not started. */ export type NerveApiConfig = { port: number | null; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 18f7135..e57283c 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -25,8 +25,8 @@ export type { ModeratorContext, Moderator, WorkflowDefinition, -} from "./workflow.js"; -export { START, END, DEFAULT_ENGINE_MAX_ROUNDS } from "./workflow.js"; +} from "@uncaged/workflow"; +export { START, END, DEFAULT_ENGINE_MAX_ROUNDS } from "@uncaged/workflow"; export type { Schema, ExtractFn } from "./agent.js"; export { ExtractError } from "./agent.js"; export type { Result } from "./util.js"; diff --git a/packages/workflow/package.json b/packages/workflow/package.json new file mode 100644 index 0000000..c56a3e2 --- /dev/null +++ b/packages/workflow/package.json @@ -0,0 +1,25 @@ +{ + "name": "@uncaged/workflow", + "version": "0.5.0", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "files": ["dist"], + "publishConfig": { + "access": "public" + }, + "scripts": { + "prepublishOnly": "bash ../../scripts/prepublish-check.sh", + "build": "rslib build" + }, + "devDependencies": { + "@rslib/core": "^0.21.3", + "@types/node": "^22.0.0" + } +} diff --git a/packages/workflow/rslib.config.ts b/packages/workflow/rslib.config.ts new file mode 100644 index 0000000..87bb8a3 --- /dev/null +++ b/packages/workflow/rslib.config.ts @@ -0,0 +1,19 @@ +import { defineConfig } from "@rslib/core"; + +export default defineConfig({ + lib: [ + { + format: "esm", + dts: true, + }, + ], + source: { + entry: { + index: "src/index.ts", + }, + }, + output: { + target: "node", + cleanDistPath: true, + }, +}); diff --git a/packages/workflow/src/config.ts b/packages/workflow/src/config.ts new file mode 100644 index 0000000..5fa1200 --- /dev/null +++ b/packages/workflow/src/config.ts @@ -0,0 +1,12 @@ +export type DropOverflowConfig = { + concurrency: number; + overflow: "drop"; +}; + +export type QueueOverflowConfig = { + concurrency: number; + overflow: "queue"; + maxQueue: number; +}; + +export type WorkflowConfig = DropOverflowConfig | QueueOverflowConfig; diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts new file mode 100644 index 0000000..a2d0388 --- /dev/null +++ b/packages/workflow/src/index.ts @@ -0,0 +1,19 @@ +// @uncaged/workflow — standalone workflow orchestration engine + +export { START, END, DEFAULT_ENGINE_MAX_ROUNDS } from "./types.js"; +export type { + WorkflowMessage, + RoleResult, + Role, + RoleMeta, + StartStep, + ThreadContext, + WorkflowContext, + AgentFn, + RoleStep, + ModeratorContext, + Moderator, + WorkflowDefinition, +} from "./types.js"; + +export type { DropOverflowConfig, QueueOverflowConfig, WorkflowConfig } from "./config.js"; diff --git a/packages/core/src/workflow.ts b/packages/workflow/src/types.ts similarity index 100% rename from packages/core/src/workflow.ts rename to packages/workflow/src/types.ts diff --git a/packages/workflow/tsconfig.json b/packages/workflow/tsconfig.json new file mode 100644 index 0000000..9036088 --- /dev/null +++ b/packages/workflow/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "composite": false + }, + "include": ["src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7f48b7d..5e29a24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,6 +86,9 @@ importers: packages/core: dependencies: + '@uncaged/workflow': + specifier: workspace:* + version: link:../workflow yaml: specifier: ^2.8.3 version: 2.8.3 @@ -215,6 +218,15 @@ importers: specifier: ^4.1.5 version: 4.1.5(@types/node@22.19.17)(vite@8.0.9(@types/node@22.19.17)(esbuild@0.27.7)(yaml@2.8.3)) + packages/workflow: + devDependencies: + '@rslib/core': + specifier: ^0.21.3 + version: 0.21.3(typescript@5.9.3) + '@types/node': + specifier: ^22.0.0 + version: 22.19.17 + packages/workflow-meta: dependencies: '@uncaged/nerve-core': -- 2.43.0 From cee65bbd8765296cbd306cbfb2c5e1a392e2ba0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Tue, 5 May 2026 10:41:59 +0000 Subject: [PATCH 3/4] refactor(workflow): move IPC, worker, manager from daemon to @uncaged/workflow - Move workflow IPC types (StartThread, ResumeThread, etc.) to workflow/ipc.ts - Move workflow-worker.ts, workflow-manager.ts, workflow-manager-support.ts - Move worker-runtime.ts and worker-signals.ts (shared infrastructure) - Daemon now imports workflow runtime from @uncaged/workflow - Export WORKFLOW_WORKER_PATH for daemon to spawn workers Phase 3+4 of #320, Testing: #322 --- .cursor/rules/no-dynamic-import.mdc | 2 +- package.json | 2 +- packages/cli/package.json | 3 +- packages/cli/src/__tests__/e2e-harness.ts | 5 +- packages/core/src/agent.d.ts | 17 + packages/core/src/agent.d.ts.map | 1 + packages/core/src/config.d.ts | 63 ++++ packages/core/src/config.d.ts.map | 1 + packages/core/src/config.ts | 2 +- packages/core/src/daemon.d.ts | 120 +++++++ packages/core/src/daemon.d.ts.map | 1 + packages/core/src/index.d.ts | 21 ++ packages/core/src/index.d.ts.map | 1 + packages/core/src/index.ts | 4 +- packages/core/src/sense.d.ts | 39 +++ packages/core/src/sense.d.ts.map | 1 + packages/core/src/util.d.ts | 66 ++++ packages/core/src/util.d.ts.map | 1 + packages/daemon/package.json | 3 +- packages/daemon/rslib.config.ts | 1 - .../src/__tests__/crash-recovery.test.ts | 2 +- .../daemon/src/__tests__/hot-reload.test.ts | 2 +- .../src/__tests__/worker-runtime.test.ts | 2 +- .../src/__tests__/workflow-manager.test.ts | 2 +- packages/daemon/src/daemon-handlers.ts | 2 +- packages/daemon/src/index.ts | 6 +- packages/daemon/src/ipc.ts | 258 +++----------- packages/daemon/src/kernel-file-watch.ts | 2 +- packages/daemon/src/kernel.ts | 4 +- packages/daemon/src/worker-pool.ts | 2 +- packages/store/src/blob-store.d.ts | 17 + packages/store/src/blob-store.d.ts.map | 1 + packages/store/src/index.d.ts | 8 + packages/store/src/index.d.ts.map | 1 + packages/store/src/log-archive.d.ts | 32 ++ packages/store/src/log-archive.d.ts.map | 1 + packages/store/src/log-store.d.ts | 134 ++++++++ packages/store/src/log-store.d.ts.map | 1 + packages/workflow/package.json | 14 +- packages/workflow/rslib.config.ts | 1 + .../src/experimental-warning-suppression.ts | 23 ++ packages/workflow/src/index.ts | 34 ++ packages/workflow/src/ipc.ts | 314 ++++++++++++++++++ .../src/manager-support.ts} | 29 +- .../src/manager.ts} | 10 +- packages/workflow/src/paths.ts | 5 + packages/workflow/src/public-types.ts | 22 ++ .../src/worker-runtime.ts | 0 packages/workflow/src/worker-signals.ts | 17 + .../src/worker.ts} | 25 +- packages/workflow/tsconfig.public-types.json | 12 + pnpm-lock.yaml | 13 + 52 files changed, 1072 insertions(+), 278 deletions(-) create mode 100644 packages/core/src/agent.d.ts create mode 100644 packages/core/src/agent.d.ts.map create mode 100644 packages/core/src/config.d.ts create mode 100644 packages/core/src/config.d.ts.map create mode 100644 packages/core/src/daemon.d.ts create mode 100644 packages/core/src/daemon.d.ts.map create mode 100644 packages/core/src/index.d.ts create mode 100644 packages/core/src/index.d.ts.map create mode 100644 packages/core/src/sense.d.ts create mode 100644 packages/core/src/sense.d.ts.map create mode 100644 packages/core/src/util.d.ts create mode 100644 packages/core/src/util.d.ts.map create mode 100644 packages/store/src/blob-store.d.ts create mode 100644 packages/store/src/blob-store.d.ts.map create mode 100644 packages/store/src/index.d.ts create mode 100644 packages/store/src/index.d.ts.map create mode 100644 packages/store/src/log-archive.d.ts create mode 100644 packages/store/src/log-archive.d.ts.map create mode 100644 packages/store/src/log-store.d.ts create mode 100644 packages/store/src/log-store.d.ts.map create mode 100644 packages/workflow/src/experimental-warning-suppression.ts create mode 100644 packages/workflow/src/ipc.ts rename packages/{daemon/src/workflow-manager-support.ts => workflow/src/manager-support.ts} (90%) rename packages/{daemon/src/workflow-manager.ts => workflow/src/manager.ts} (98%) create mode 100644 packages/workflow/src/paths.ts create mode 100644 packages/workflow/src/public-types.ts rename packages/{daemon => workflow}/src/worker-runtime.ts (100%) create mode 100644 packages/workflow/src/worker-signals.ts rename packages/{daemon/src/workflow-worker.ts => workflow/src/worker.ts} (97%) create mode 100644 packages/workflow/tsconfig.public-types.json diff --git a/.cursor/rules/no-dynamic-import.mdc b/.cursor/rules/no-dynamic-import.mdc index 5378994..1cf0b50 100644 --- a/.cursor/rules/no-dynamic-import.mdc +++ b/.cursor/rules/no-dynamic-import.mdc @@ -20,7 +20,7 @@ Always use static top-level `import` statements. ## Exceptions (must include a comment explaining why) 1. **`sense-runtime.ts`** — loads user-authored sense modules whose paths are only known at runtime -2. **`workflow-worker.ts`** — loads user-authored workflow modules whose paths are only known at runtime +2. **`packages/workflow/src/worker.ts`** — loads user-authored workflow modules whose paths are only known at runtime When suppressing, add a comment directly above: diff --git a/package.json b/package.json index 064f41b..3917ea6 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ }, "scripts": { "prepare": "husky", - "build": "pnpm -r run build", + "build": "pnpm --filter @uncaged/workflow run build:public-types && pnpm --filter @uncaged/nerve-core run build && pnpm --filter @uncaged/nerve-store run build && pnpm --filter @uncaged/workflow run build && pnpm -r --filter '!@uncaged/nerve-core' --filter '!@uncaged/nerve-store' --filter '!@uncaged/workflow' run build", "test": "pnpm -r test", "check": "biome check .", "format": "biome format --write .", diff --git a/packages/cli/package.json b/packages/cli/package.json index 60edfe2..5399da5 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -17,7 +17,7 @@ "scripts": { "prepublishOnly": "bash ../../scripts/prepublish-check.sh", "build": "rslib build", - "pretest": "pnpm --filter @uncaged/nerve-core run build && pnpm --filter @uncaged/nerve-daemon run build", + "pretest": "pnpm --filter @uncaged/workflow run build:public-types && pnpm --filter @uncaged/nerve-core run build && pnpm --filter @uncaged/nerve-store run build && pnpm --filter @uncaged/workflow run build && pnpm --filter @uncaged/nerve-daemon run build", "test": "vitest run" }, "dependencies": { @@ -31,6 +31,7 @@ "@rslib/core": "^0.21.3", "@types/node": "^22.0.0", "@uncaged/nerve-daemon": "workspace:*", + "@uncaged/workflow": "workspace:*", "vitest": "^4.1.5" } } diff --git a/packages/cli/src/__tests__/e2e-harness.ts b/packages/cli/src/__tests__/e2e-harness.ts index 67355b7..1f39c0a 100644 --- a/packages/cli/src/__tests__/e2e-harness.ts +++ b/packages/cli/src/__tests__/e2e-harness.ts @@ -51,8 +51,9 @@ import { workflowCommand } from "../commands/workflow.js"; const require = createRequire(import.meta.url); const nerveDaemonRoot = dirname(require.resolve("@uncaged/nerve-daemon/package.json")); +const nerveWorkflowRoot = dirname(require.resolve("@uncaged/workflow/package.json")); const senseWorkerScript = join(nerveDaemonRoot, "dist", "sense-worker.js"); -const workflowWorkerScript = join(nerveDaemonRoot, "dist", "workflow-worker.js"); +const workflowWorkerScript = join(nerveWorkflowRoot, "dist", "worker.js"); const nerveYamlTemplate = `senses: counter: @@ -274,7 +275,7 @@ export async function startTestDaemon( } if (!existsSync(workflowWorkerScript)) { throw new Error( - `Missing "${workflowWorkerScript}". Run \`pnpm --filter @uncaged/nerve-daemon build\`.`, + `Missing "${workflowWorkerScript}". Run \`pnpm --filter @uncaged/workflow build\`.`, ); } diff --git a/packages/core/src/agent.d.ts b/packages/core/src/agent.d.ts new file mode 100644 index 0000000..c94c2a5 --- /dev/null +++ b/packages/core/src/agent.d.ts @@ -0,0 +1,17 @@ +/** + * Extract layer types — parses agent raw string output into typed meta (RFC-003). + */ +/** Structured meta validation descriptor for `ExtractFn`; concrete validators are provider-defined. */ +export type Schema = { + readonly witness: T | null; +}; +export type ExtractFn = (raw: string, schema: Schema) => Promise; +export declare class ExtractError extends Error { + readonly raw: string; + readonly causeError: Error | null; + constructor(message: string, detail: { + raw: string; + causeError: Error | null; + }); +} +//# sourceMappingURL=agent.d.ts.map \ No newline at end of file diff --git a/packages/core/src/agent.d.ts.map b/packages/core/src/agent.d.ts.map new file mode 100644 index 0000000..da8f9d5 --- /dev/null +++ b/packages/core/src/agent.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["agent.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,uGAAuG;AACvG,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAE1E,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC;gBAEtB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,KAAK,GAAG,IAAI,CAAA;KAAE;CAO/E"} \ No newline at end of file diff --git a/packages/core/src/config.d.ts b/packages/core/src/config.d.ts new file mode 100644 index 0000000..55533a8 --- /dev/null +++ b/packages/core/src/config.d.ts @@ -0,0 +1,63 @@ +import { type DropOverflowConfig, type QueueOverflowConfig, type WorkflowConfig } from "@uncaged/workflow"; +import { type Result } from "./util.js"; +export type { DropOverflowConfig, QueueOverflowConfig, WorkflowConfig }; +export type SenseConfig = { + group: string; + throttle: number | null; + timeout: number | null; + gracePeriod: number | null; + /** Polling interval (ms). When set, the sense is triggered periodically. */ + interval: number | null; + /** Other sense names whose successful computes schedule this sense (kernel reverse-index). */ + on: string[]; +}; +/** Optional HTTP control plane. When `port` is null, the HTTP server is not started. */ +export type NerveApiConfig = { + port: number | null; + /** When set, HTTP API requires `Authorization: Bearer `. */ + token: string | null; + /** Bind address (e.g. `127.0.0.1`, `0.0.0.0`). Meaningful when `port` is set. */ + host: string; +}; +/** Adapter factory input (model, timeout); used by adapter packages (RFC-003). */ +export type AgentConfig = { + /** Adapter id (e.g. `cursor`, `hermes`, `echo`) — informational for factories that branch on type. */ + type: string; + /** Model id or `"auto"` for adapter defaults. */ + model: string; + /** Wall-clock cap in milliseconds, or `null` for adapter-specific default. */ + timeout: number | null; +}; +/** Global extract provider for typed meta from agent raw output (RFC-003). */ +export type ExtractConfig = { + provider: string; + model: string; +}; +/** + * Optional shell side effect after a successful sense `compute()`. + * Executed in the sense worker (`spawn` with `shell: true`, cwd = nerve root). + * Workflows are started only via CLI / daemon IPC, not from sense compute results. + */ +export type SenseTrigger = { + command: string; +}; +export type NerveConfig = { + /** Engine-wide default max moderator rounds (e.g. CLI workflow trigger when omitted). */ + maxRounds: number; + senses: Record; + workflows: Record; + api: NerveApiConfig; + /** Global extract defaults; `null` when the section is omitted. */ + extract: ExtractConfig | null; +}; +export type KnowledgeConfig = { + include: ReadonlyArray; + exclude: ReadonlyArray; +}; +export declare function parseNerveConfig(raw: string): Result; +/** + * Parse `knowledge.yaml` at the repo root (RFC-003 Knowledge Layer). + * `include` / `exclude` entries are glob patterns resolved against the repo root. + */ +export declare function parseKnowledgeYaml(raw: string): Result; +//# sourceMappingURL=config.d.ts.map \ No newline at end of file diff --git a/packages/core/src/config.d.ts.map b/packages/core/src/config.d.ts.map new file mode 100644 index 0000000..e59e34c --- /dev/null +++ b/packages/core/src/config.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,MAAM,EAAmD,MAAM,WAAW,CAAC;AAEzF,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,cAAc,EAAE,CAAC;AAExE,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,8FAA8F;IAC9F,EAAE,EAAE,MAAM,EAAE,CAAC;CACd,CAAC;AAEF,wFAAwF;AACxF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,mEAAmE;IACnE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,kFAAkF;AAClF,MAAM,MAAM,WAAW,GAAG;IACxB,sGAAsG;IACtG,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1C,GAAG,EAAE,cAAc,CAAC;IACpB,mEAAmE;IACnE,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChC,CAAC;AAuQF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,CAwDjE;AAoBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC,CA8BvE"} \ No newline at end of file diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 045d8b6..292ea6f 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -5,7 +5,7 @@ import { type DropOverflowConfig, type QueueOverflowConfig, type WorkflowConfig, -} from "@uncaged/workflow"; +} from "@uncaged/workflow/public-types"; import { type Result, err, isPlainRecord, ok, parseDurationStringToMs } from "./util.js"; export type { DropOverflowConfig, QueueOverflowConfig, WorkflowConfig }; diff --git a/packages/core/src/daemon.d.ts b/packages/core/src/daemon.d.ts new file mode 100644 index 0000000..0746d31 --- /dev/null +++ b/packages/core/src/daemon.d.ts @@ -0,0 +1,120 @@ +/** + * Daemon Unix-socket IPC protocol (CLI → daemon). + * Newline-delimited JSON: one request object per line from the client, + * one response object per line from the daemon. + */ +import type { SenseInfo } from "./sense.js"; +/** Runtime status of a registered workflow (for listing / observability). */ +export type WorkflowStatus = { + name: string; + activeThreads: number; + /** Run IDs currently executing (same identifiers accepted by kill-workflow). */ + activeRunIds: string[]; + queuedThreads: number; + config: { + concurrency: number; + overflow: string; + }; +}; +/** Public health payload for HTTP / IPC. */ +export type HealthInfo = { + ok: boolean; + version: string; + uptime: number; + startedAt: string; + hostname: string; +}; +/** Client → daemon: start a workflow run. */ +export type DaemonIpcTriggerWorkflowRequest = { + type: "trigger-workflow"; + workflow: string; + prompt: string; + maxRounds: number; + dryRun: boolean; +}; +/** Client → daemon: run a sense compute on demand. */ +export type DaemonIpcTriggerSenseRequest = { + type: "trigger-sense"; + sense: string; +}; +/** Client → daemon: list registered senses. */ +export type DaemonIpcListSensesRequest = { + type: "list-senses"; +}; +/** Client → daemon: kill a running or queued workflow thread by runId. */ +export type DaemonIpcKillWorkflowRequest = { + type: "kill-workflow"; + runId: string; +}; +/** Client → daemon: list registered workflows and queue/active counts. */ +export type DaemonIpcListWorkflowsRequest = { + type: "list-workflows"; +}; +/** Client → daemon: public health snapshot. */ +export type DaemonIpcHealthRequest = { + type: "health"; +}; +/** Union of all JSON requests the daemon IPC server accepts. */ +export type DaemonIpcRequest = DaemonIpcTriggerWorkflowRequest | DaemonIpcTriggerSenseRequest | DaemonIpcListSensesRequest | DaemonIpcKillWorkflowRequest | DaemonIpcListWorkflowsRequest | DaemonIpcHealthRequest; +/** Successful trigger / trigger-sense reply (no body). */ +export type DaemonIpcTriggerOkResponse = { + ok: true; +}; +export type DaemonIpcErrorResponse = { + ok: false; + error: string; +}; +/** Replies for trigger-workflow and trigger-sense. */ +export type DaemonIpcTriggerResponse = DaemonIpcTriggerOkResponse | DaemonIpcErrorResponse; +/** Reply for list-senses. */ +export type DaemonIpcListSensesResponse = { + ok: true; + senses: SenseInfo[]; +} | DaemonIpcErrorResponse; +/** Reply for list-workflows. */ +export type DaemonIpcListWorkflowsResponse = { + ok: true; + workflows: WorkflowStatus[]; +} | DaemonIpcErrorResponse; +/** Reply for health. */ +export type DaemonIpcHealthResponse = { + ok: true; + health: HealthInfo; +} | DaemonIpcErrorResponse; +/** Any JSON response the daemon may write on the IPC socket. */ +export type DaemonIpcResponse = DaemonIpcTriggerOkResponse | DaemonIpcErrorResponse | DaemonIpcListSensesResponse | DaemonIpcListWorkflowsResponse | DaemonIpcHealthResponse; +export type DaemonTransportTriggerResult = { + ok: true; +} | { + ok: false; + error: string; +}; +export type DaemonTransportWorkflowLaunch = { + prompt: string; + maxRounds: number; + dryRun: boolean; +}; +/** + * Abstraction over daemon control plane (Unix socket IPC today, HTTP in Phase 2). + * Implementations live in CLI / tools; the daemon kernel uses shared handler logic. + */ +export type DaemonTransport = { + health(): Promise; + listSenses(): Promise; + listWorkflows(): Promise; + triggerSense(name: string): Promise; + /** When `launch` is null, implementations use engine defaults (empty prompt, default max rounds, dryRun false). */ + triggerWorkflow(name: string, launch: DaemonTransportWorkflowLaunch | null): Promise; + /** Kill a running or queued workflow thread by `runId` (same field as IPC `kill-workflow`). */ + killWorkflow(runId: string): Promise; +}; +/** + * Parse a single line of JSON into a {@link DaemonIpcRequest}, or null if invalid. + * Kept in core with the request types so CLI and daemon stay aligned at compile time. + */ +export declare function parseDaemonIpcRequest(line: string): DaemonIpcRequest | null; +/** Type guard for JSON {@link SenseInfo} payloads from daemon HTTP/IPC. */ +export declare function isSenseInfo(value: unknown): value is SenseInfo; +/** Type guard for JSON {@link WorkflowStatus} payloads from daemon HTTP/IPC. */ +export declare function isWorkflowStatus(value: unknown): value is WorkflowStatus; +//# sourceMappingURL=daemon.d.ts.map \ No newline at end of file diff --git a/packages/core/src/daemon.d.ts.map b/packages/core/src/daemon.d.ts.map new file mode 100644 index 0000000..3ce4770 --- /dev/null +++ b/packages/core/src/daemon.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["daemon.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/core/src/index.d.ts b/packages/core/src/index.d.ts new file mode 100644 index 0000000..a73caa9 --- /dev/null +++ b/packages/core/src/index.d.ts @@ -0,0 +1,21 @@ +export type { SenseConfig, DropOverflowConfig, QueueOverflowConfig, WorkflowConfig, NerveApiConfig, AgentConfig, ExtractConfig, NerveConfig, SenseTrigger, } from "./config.js"; +export type { SenseInfo } from "./sense.js"; +export type { SenseComputeFn, SenseModule } from "./sense.js"; +export { senseTriggerLabels } from "./sense.js"; +export type { WorkflowMessage, RoleResult, Role, RoleMeta, StartStep, ThreadContext, WorkflowContext, AgentFn, RoleStep, ModeratorContext, Moderator, WorkflowDefinition, } from "@uncaged/workflow"; +export { START, END, DEFAULT_ENGINE_MAX_ROUNDS } from "@uncaged/workflow"; +export type { Schema, ExtractFn } from "./agent.js"; +export { ExtractError } from "./agent.js"; +export type { Result } from "./util.js"; +export { ok, err } from "./util.js"; +export { nerveCommandEnv, spawnSafe, type SpawnEnv, type SpawnError, type SpawnResult, type SpawnSafeOptions, } from "./util.js"; +export { parseNerveConfig } from "./config.js"; +export type { KnowledgeConfig } from "./config.js"; +export { parseKnowledgeYaml } from "./config.js"; +export { isPlainRecord } from "./util.js"; +export { parseSenseTrigger } from "./sense.js"; +export { isSenseInfo, isWorkflowStatus } from "./daemon.js"; +export type { WorkflowStatus, HealthInfo, DaemonIpcTriggerWorkflowRequest, DaemonIpcTriggerSenseRequest, DaemonIpcListSensesRequest, DaemonIpcKillWorkflowRequest, DaemonIpcListWorkflowsRequest, DaemonIpcHealthRequest, DaemonIpcRequest, DaemonIpcTriggerOkResponse, DaemonIpcErrorResponse, DaemonIpcTriggerResponse, DaemonIpcListSensesResponse, DaemonIpcListWorkflowsResponse, DaemonIpcHealthResponse, DaemonIpcResponse, } from "./daemon.js"; +export { parseDaemonIpcRequest } from "./daemon.js"; +export type { DaemonTransport, DaemonTransportTriggerResult, DaemonTransportWorkflowLaunch, } from "./daemon.js"; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/core/src/index.d.ts.map b/packages/core/src/index.d.ts.map new file mode 100644 index 0000000..3af0e82 --- /dev/null +++ b/packages/core/src/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,YAAY,EACV,eAAe,EACf,UAAU,EACV,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,aAAa,EACb,eAAe,EACf,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC1E,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,eAAe,EACf,SAAS,EACT,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,gBAAgB,GACtB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC5D,YAAY,EACV,cAAc,EACd,UAAU,EACV,+BAA+B,EAC/B,4BAA4B,EAC5B,0BAA0B,EAC1B,4BAA4B,EAC5B,6BAA6B,EAC7B,sBAAsB,EACtB,gBAAgB,EAChB,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EACxB,2BAA2B,EAC3B,8BAA8B,EAC9B,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,YAAY,EACV,eAAe,EACf,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,aAAa,CAAC"} \ No newline at end of file diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e57283c..b0b8f3e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -25,8 +25,8 @@ export type { ModeratorContext, Moderator, WorkflowDefinition, -} from "@uncaged/workflow"; -export { START, END, DEFAULT_ENGINE_MAX_ROUNDS } from "@uncaged/workflow"; +} from "@uncaged/workflow/public-types"; +export { START, END, DEFAULT_ENGINE_MAX_ROUNDS } from "@uncaged/workflow/public-types"; export type { Schema, ExtractFn } from "./agent.js"; export { ExtractError } from "./agent.js"; export type { Result } from "./util.js"; diff --git a/packages/core/src/sense.d.ts b/packages/core/src/sense.d.ts new file mode 100644 index 0000000..5562d57 --- /dev/null +++ b/packages/core/src/sense.d.ts @@ -0,0 +1,39 @@ +import type { SenseConfig, SenseTrigger } from "./config.js"; +import { type Result } from "./util.js"; +/** Runtime metadata for a sense (e.g. daemon list-senses IPC). */ +export type SenseInfo = { + name: string; + group: string; + throttle: number | null; + timeout: number | null; + /** Declarative schedule (`interval` / `on`) for this sense (derived from nerve.yaml). */ + triggers: ReadonlyArray; +}; +/** + * The function signature every sense `src/index.ts` must export as a named + * `compute` export. + * + * Pure: no DB, no peers. + * Returns the next sense state and an optional trigger (`trigger: null` means no side effect). + */ +export type SenseComputeFn = (state: S) => Promise<{ + state: S; + trigger: SenseTrigger | null; +}>; +/** + * The full shape a sense module (`src/index.ts`) must export. + */ +export type SenseModule = { + compute: SenseComputeFn; + initialState: S; +}; +/** Human-readable label for a sense schedule (`interval` and/or `on`). */ +export declare function labelSenseTrigger(slice: Pick): string; +/** + * Human-readable trigger labels for a sense from its `SenseConfig.interval` / `.on`. + * Returns an empty array when the sense is missing or has no schedule. + */ +export declare function senseTriggerLabels(senseName: string, senses: Record): string[]; +/** Validates `{ command: string }` from Sense compute or IPC (`trigger` field). */ +export declare function parseSenseTrigger(value: unknown): Result; +//# sourceMappingURL=sense.d.ts.map \ No newline at end of file diff --git a/packages/core/src/sense.d.ts.map b/packages/core/src/sense.d.ts.map new file mode 100644 index 0000000..4419f25 --- /dev/null +++ b/packages/core/src/sense.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"sense.d.ts","sourceRoot":"","sources":["sense.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,KAAK,MAAM,EAA0B,MAAM,WAAW,CAAC;AAEhE,kEAAkE;AAClE,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,yFAAyF;IACzF,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI,CACxC,KAAK,EAAE,CAAC,KACL,OAAO,CAAC;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;CAAE,CAAC,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,OAAO,IAAI;IACrC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAC3B,YAAY,EAAE,CAAC,CAAC;CACjB,CAAC;AAYF,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,IAAI,CAAC,GAAG,MAAM,CAYrF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAClC,MAAM,EAAE,CAKV;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,CActE"} \ No newline at end of file diff --git a/packages/core/src/util.d.ts b/packages/core/src/util.d.ts new file mode 100644 index 0000000..560d9a3 --- /dev/null +++ b/packages/core/src/util.d.ts @@ -0,0 +1,66 @@ +export type Result = { + ok: true; + value: T; +} | { + ok: false; + error: E; +}; +/** Compatible with `process.env` for `child_process.spawn`. */ +export type SpawnEnv = Record; +export type SpawnResult = { + stdout: string; + stderr: string; + exitCode: number; + /** OS signal name (e.g. `"SIGTERM"`) when terminated by signal; otherwise `null`. */ + signal: string | null; +}; +export type SpawnError = { + kind: "non_zero_exit"; + stdout: string; + stderr: string; + exitCode: number; + signal: string | null; +} | { + kind: "timeout"; + stdout: string; + stderr: string; +} | { + kind: "spawn_failed"; + message: string; +} | { + kind: "aborted"; +}; +export type SpawnSafeOptions = { + cwd: string | null; + /** When null, merges {@link nerveCommandEnv} over `process.env`. When set, merges over that default. */ + env: SpawnEnv | null; + timeoutMs: number | null; + dryRun: boolean; + /** When non-null, child is terminated on abort; if `timeoutMs` is also null, no internal wall-clock timer is used. */ + abortSignal: AbortSignal | null; +}; +type SpawnSafeOptionsInput = SpawnSafeOptions | Omit; +export declare function ok(value: T): Result; +export declare function err(error: E): Result; +/** + * Narrows `unknown` to a plain JSON-style object (not null, not array). + * Use after `JSON.parse` / YAML / IPC when validating structure field-by-field. + */ +export declare function isPlainRecord(value: unknown): value is Record; +/** + * Parse a duration string such as `5s`, `10m`, `1h` to milliseconds. + * Used by `parseNerveConfig` sense/workflow duration fields. + */ +export declare function parseDurationStringToMs(value: string): Result; +/** + * PATH and PNPM_HOME for running `pnpm` and `nerve` from workflow roles. + * Uses the pnpm store home only (no npm user bin); binaries must resolve via PATH. + */ +export declare function nerveCommandEnv(): SpawnEnv; +/** + * Spawn a process with `shell: false` (argv only), default {@link nerveCommandEnv}, and optional timeout. + * Returns `ok` only when the process exits with code 0. + */ +export declare function spawnSafe(command: string, args: ReadonlyArray, options: SpawnSafeOptionsInput): Promise>; +export {}; +//# sourceMappingURL=util.d.ts.map \ No newline at end of file diff --git a/packages/core/src/util.d.ts.map b/packages/core/src/util.d.ts.map new file mode 100644 index 0000000..8f637fb --- /dev/null +++ b/packages/core/src/util.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["util.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAEpF,+DAA+D;AAC/D,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAE1D,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAClB;IACE,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,GACD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAExB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,wGAAwG;IACxG,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,sHAAsH;IACtH,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;CACjC,CAAC;AAEF,KAAK,qBAAqB,GAAG,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;AAIjF,wBAAgB,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAEhD;AAED,wBAAgB,GAAG,CAAC,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAEzD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE9E;AAUD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAMrE;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,QAAQ,CAQ1C;AAgCD;;;GAGG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAoG1C"} \ No newline at end of file diff --git a/packages/daemon/package.json b/packages/daemon/package.json index e205d1d..73dcc97 100644 --- a/packages/daemon/package.json +++ b/packages/daemon/package.json @@ -22,10 +22,11 @@ "scripts": { "prepublishOnly": "bash ../../scripts/prepublish-check.sh", "build": "rslib build", - "pretest": "pnpm --filter @uncaged/nerve-core run build", + "pretest": "pnpm --filter @uncaged/workflow run build:public-types && pnpm --filter @uncaged/nerve-core run build && pnpm --filter @uncaged/nerve-store run build && pnpm --filter @uncaged/workflow run build", "test": "vitest run" }, "dependencies": { + "@uncaged/workflow": "workspace:*", "@uncaged/nerve-core": "workspace:*", "@uncaged/nerve-store": "workspace:*", "yaml": "^2.8.3" diff --git a/packages/daemon/rslib.config.ts b/packages/daemon/rslib.config.ts index ee4c746..ef3ab7c 100644 --- a/packages/daemon/rslib.config.ts +++ b/packages/daemon/rslib.config.ts @@ -11,7 +11,6 @@ export default defineConfig({ entry: { index: "src/index.ts", "sense-worker": "src/sense-worker.ts", - "workflow-worker": "src/workflow-worker.ts", "experimental-warning-suppression": "src/experimental-warning-suppression.ts", }, }, diff --git a/packages/daemon/src/__tests__/crash-recovery.test.ts b/packages/daemon/src/__tests__/crash-recovery.test.ts index 4891997..fee159c 100644 --- a/packages/daemon/src/__tests__/crash-recovery.test.ts +++ b/packages/daemon/src/__tests__/crash-recovery.test.ts @@ -62,7 +62,7 @@ vi.mock("node:child_process", () => ({ }), })); -const { createWorkflowManager } = await import("../workflow-manager.js"); +const { createWorkflowManager } = await import("@uncaged/workflow"); function makeConfig(workflows: Record = {}): NerveConfig { return { diff --git a/packages/daemon/src/__tests__/hot-reload.test.ts b/packages/daemon/src/__tests__/hot-reload.test.ts index 678affe..7225d8f 100644 --- a/packages/daemon/src/__tests__/hot-reload.test.ts +++ b/packages/daemon/src/__tests__/hot-reload.test.ts @@ -67,7 +67,7 @@ vi.mock("node:child_process", () => ({ }), })); -const { createWorkflowManager } = await import("../workflow-manager.js"); +const { createWorkflowManager } = await import("@uncaged/workflow"); const { createKernel } = await import("../kernel.js"); function makeWfConfig(workflows: Record = {}): NerveConfig { diff --git a/packages/daemon/src/__tests__/worker-runtime.test.ts b/packages/daemon/src/__tests__/worker-runtime.test.ts index 642a33e..e500b98 100644 --- a/packages/daemon/src/__tests__/worker-runtime.test.ts +++ b/packages/daemon/src/__tests__/worker-runtime.test.ts @@ -1,7 +1,7 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { afterEach, describe, expect, it, vi } from "vitest"; -import { createWorkerRuntime } from "../worker-runtime.js"; +import { createWorkerRuntime } from "@uncaged/workflow"; const fixturesDir = join(dirname(fileURLToPath(import.meta.url)), "fixtures"); const echoWorkerPath = join(fixturesDir, "echo-worker.js"); diff --git a/packages/daemon/src/__tests__/workflow-manager.test.ts b/packages/daemon/src/__tests__/workflow-manager.test.ts index 280ad41..ed8b0ee 100644 --- a/packages/daemon/src/__tests__/workflow-manager.test.ts +++ b/packages/daemon/src/__tests__/workflow-manager.test.ts @@ -61,7 +61,7 @@ vi.mock("node:child_process", () => ({ })); // Import after mock is set up -const { createWorkflowManager } = await import("../workflow-manager.js"); +const { createWorkflowManager } = await import("@uncaged/workflow"); // --------------------------------------------------------------------------- // Helpers diff --git a/packages/daemon/src/daemon-handlers.ts b/packages/daemon/src/daemon-handlers.ts index bd97d5e..6f34644 100644 --- a/packages/daemon/src/daemon-handlers.ts +++ b/packages/daemon/src/daemon-handlers.ts @@ -1,6 +1,6 @@ import type { HealthInfo, SenseInfo, WorkflowStatus } from "@uncaged/nerve-core"; -import type { WorkflowManager } from "./workflow-manager.js"; +import type { WorkflowManager } from "@uncaged/workflow"; export type DaemonHandlerBundle = { health: () => HealthInfo; diff --git a/packages/daemon/src/index.ts b/packages/daemon/src/index.ts index 0d88a47..64f844b 100644 --- a/packages/daemon/src/index.ts +++ b/packages/daemon/src/index.ts @@ -12,7 +12,7 @@ export type { ResumeThreadMessage, ThreadEventMessage, WorkflowErrorMessage, - ThreadWorkflowMessageMessage, + ThreadWorkflowMessage, } from "./ipc.js"; export { loadSenseModule, executeCompute, readState, writeState } from "./sense-runtime.js"; @@ -45,5 +45,5 @@ export type { WorkflowRunStatus, } from "@uncaged/nerve-store"; -export { createWorkflowManager } from "./workflow-manager.js"; -export type { WorkflowManager } from "./workflow-manager.js"; +export { createWorkflowManager } from "@uncaged/workflow"; +export type { WorkflowManager } from "@uncaged/workflow"; diff --git a/packages/daemon/src/ipc.ts b/packages/daemon/src/ipc.ts index e23f692..d38a217 100644 --- a/packages/daemon/src/ipc.ts +++ b/packages/daemon/src/ipc.ts @@ -1,10 +1,30 @@ /** * IPC message types for parent (kernel) ↔ sense worker communication. * Protocol per RFC §5.2: hub-and-spoke, all messages through engine. + * + * Workflow worker IPC types and parsers live in `@uncaged/workflow`. */ import type { Result, SenseTrigger } from "@uncaged/nerve-core"; import { err, isPlainRecord, ok, parseSenseTrigger } from "@uncaged/nerve-core"; +import type { + KillThreadMessage, + ResumeThreadMessage, + StartThreadMessage, + ThreadEventMessage, + ThreadWorkflowMessage, + WorkflowErrorMessage, +} from "@uncaged/workflow"; +import { parseWorkflowParentMessage, parseWorkflowWorkerToParentMessage } from "@uncaged/workflow"; + +export type { + KillThreadMessage, + ResumeThreadMessage, + StartThreadMessage, + ThreadEventMessage, + ThreadWorkflowMessage, + WorkflowErrorMessage, +} from "@uncaged/workflow"; /** Parent → Worker: trigger one compute cycle for a sense */ export type ComputeMessage = { @@ -22,40 +42,6 @@ export type HealthRequestMessage = { type: "health-request"; }; -// --------------------------------------------------------------------------- -// Workflow IPC messages (RFC-002 §5.2) -// --------------------------------------------------------------------------- - -/** Parent → Workflow Worker: start a new thread */ -export type StartThreadMessage = { - type: "start-thread"; - runId: string; - workflow: string; - prompt: string; - /** Safety-valve: max moderator rounds for this thread (engine launch parameter). */ - maxRounds: number; - /** When true, roles may skip side effects (thread-level hint on the start frame). */ - dryRun: boolean; -}; - -/** Parent → Workflow Worker: resume an existing thread after crash recovery */ -export type ResumeThreadMessage = { - type: "resume-thread"; - runId: string; - /** Serialised WorkflowMessage history to rebuild chain (must begin with `__start__`). */ - messages: Array<{ role: string; content: string; meta: unknown; timestamp: number }>; - /** Safety-valve: max moderator rounds for this thread. */ - maxRounds: number; - /** Thread-level dry-run hint (aligns with persisted `__start__` meta when replaying). */ - dryRun: boolean; -}; - -/** Parent → Workflow Worker: kill a specific running thread */ -export type KillThreadMessage = { - type: "kill-thread"; - runId: string; -}; - /** Union of all messages the parent sends to a worker */ export type ParentToWorkerMessage = | ComputeMessage @@ -92,46 +78,6 @@ export type HealthResponseMessage = { inFlightCount: number; }; -// --------------------------------------------------------------------------- -// Workflow Worker → Parent messages (RFC-002 §5.2) -// --------------------------------------------------------------------------- - -/** Valid lifecycle event types for a workflow thread. */ -export type ThreadEventType = - | "queued" - | "started" - | "step_complete" - | "completed" - | "failed" - | "killed"; - -/** - * Workflow Worker → Parent: a thread lifecycle event. - */ -export type ThreadEventMessage = { - type: "thread-event"; - runId: string; - eventType: ThreadEventType; - payload: unknown; -}; - -/** Workflow Worker → Parent: a thread encountered an unrecoverable error. */ -export type WorkflowErrorMessage = { - type: "workflow-error"; - runId: string; - error: string; - /** Exit code conveying the failure reason (1=role error, 2=maxRounds exhausted). */ - exitCode: number; -}; - -/** Workflow Worker → Parent: a WorkflowMessage produced by a role (for crash recovery). */ -export type ThreadWorkflowMessageMessage = { - type: "thread-workflow-message"; - runId: string; - /** The WorkflowMessage produced by the role — persisted for crash recovery. */ - message: { role: string; content: string; meta: unknown; timestamp: number }; -}; - /** Union of all messages a worker sends to the parent */ export type WorkerToParentMessage = | ComputeResultMessage @@ -140,7 +86,7 @@ export type WorkerToParentMessage = | HealthResponseMessage | ThreadEventMessage | WorkflowErrorMessage - | ThreadWorkflowMessageMessage; + | ThreadWorkflowMessage; const PARENT_MSG_TYPES = new Set([ "compute", @@ -151,24 +97,6 @@ const PARENT_MSG_TYPES = new Set([ "kill-thread", ]); -function validateStartThreadMsg(obj: Record): string | null { - if (typeof obj.runId !== "string") return "'start-thread' message missing string 'runId'"; - if (typeof obj.workflow !== "string") return "'start-thread' message missing string 'workflow'"; - if (typeof obj.prompt !== "string") return "'start-thread' message missing string 'prompt'"; - if (typeof obj.maxRounds !== "number") return "'start-thread' message missing number 'maxRounds'"; - if (typeof obj.dryRun !== "boolean") return "'start-thread' message missing boolean 'dryRun'"; - return null; -} - -function validateResumeThreadMsg(obj: Record): string | null { - if (typeof obj.runId !== "string") return "'resume-thread' message missing string 'runId'"; - if (!Array.isArray(obj.messages)) return "'resume-thread' message missing 'messages' array"; - if (typeof obj.maxRounds !== "number") - return "'resume-thread' message missing number 'maxRounds'"; - if (typeof obj.dryRun !== "boolean") return "'resume-thread' message missing boolean 'dryRun'"; - return null; -} - function parseParentCompute(obj: Record): Result { if (typeof obj.sense !== "string") { return err(new Error("IPC 'compute' message missing string 'sense' field")); @@ -176,40 +104,6 @@ function parseParentCompute(obj: Record): Result): Result { - const errMsg = validateStartThreadMsg(obj); - if (errMsg !== null) return err(new Error(errMsg)); - // Field types are validated above; `Record` values stay `unknown` to TypeScript. - return ok({ - type: "start-thread", - runId: obj.runId, - workflow: obj.workflow, - prompt: obj.prompt, - maxRounds: obj.maxRounds, - dryRun: obj.dryRun, - } as StartThreadMessage); -} - -function parseParentResumeThread(obj: Record): Result { - const errMsg = validateResumeThreadMsg(obj); - if (errMsg !== null) return err(new Error(errMsg)); - // Elements are validated as plain objects by the kernel; trust the wire shape here. - return ok({ - type: "resume-thread", - runId: obj.runId, - messages: obj.messages as ResumeThreadMessage["messages"], - maxRounds: obj.maxRounds, - dryRun: obj.dryRun, - } as ResumeThreadMessage); -} - -function parseParentKillThread(obj: Record): Result { - 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); -} - /** Validate and parse an unknown IPC message received from the parent process. */ export function parseParentMessage(raw: unknown): Result { if (!isPlainRecord(raw)) { @@ -230,11 +124,14 @@ export function parseParentMessage(raw: unknown): Result case "health-request": return ok({ type: "health-request" }); case "start-thread": - return parseParentStartThread(obj); case "resume-thread": - return parseParentResumeThread(obj); - case "kill-thread": - return parseParentKillThread(obj); + case "kill-thread": { + const wf = parseWorkflowParentMessage(raw); + if (!wf.ok) { + return wf; + } + return ok(wf.value as ParentToWorkerMessage); + } default: return err(new Error(`Unhandled IPC message type: "${obj.type}"`)); } @@ -299,55 +196,11 @@ function parseHealthResponseMsg(obj: Record): Result): Result { - if (typeof obj.runId !== "string") { - return err(new Error("Worker 'thread-event' message missing string 'runId' field")); - } - if (typeof obj.eventType !== "string" || !isThreadEventType(obj.eventType)) { - return err( - new Error(`Worker 'thread-event' message has invalid 'eventType': "${obj.eventType}"`), - ); - } - if (!("payload" in obj)) { - return err(new Error("Worker 'thread-event' message missing 'payload' field")); - } - return ok({ - type: "thread-event", - runId: obj.runId, - eventType: obj.eventType, - payload: obj.payload, - }); -} - -function parseWorkflowErrorMsg(obj: Record): Result { - if (typeof obj.runId !== "string") { - return err(new Error("Worker 'workflow-error' message missing string 'runId' field")); - } - if (typeof obj.error !== "string") { - return err(new Error("Worker 'workflow-error' message missing string 'error' field")); - } - const exitCode = typeof obj.exitCode === "number" ? obj.exitCode : 1; - return ok({ - type: "workflow-error", - runId: obj.runId, - error: obj.error, - exitCode, - }); -} +const WORKFLOW_WORKER_MSG_TYPES = new Set([ + "thread-event", + "workflow-error", + "thread-workflow-message", +]); const WORKER_MSG_TYPES = new Set([ "compute-result", @@ -359,41 +212,6 @@ const WORKER_MSG_TYPES = new Set([ "thread-workflow-message", ]); -function parseThreadWorkflowMessageMsg( - obj: Record, -): Result { - if (typeof obj.runId !== "string") { - return err(new Error("Worker 'thread-workflow-message' missing string 'runId' field")); - } - if (!isPlainRecord(obj.message)) { - return err(new Error("Worker 'thread-workflow-message' missing object 'message' field")); - } - const msg = obj.message; - if (typeof msg.role !== "string") { - return err(new Error("Worker 'thread-workflow-message' message missing string 'role' field")); - } - if (typeof msg.content !== "string") { - return err( - new Error("Worker 'thread-workflow-message' message missing string 'content' field"), - ); - } - if (typeof msg.timestamp !== "number") { - return err( - new Error("Worker 'thread-workflow-message' message missing number 'timestamp' field"), - ); - } - return ok({ - type: "thread-workflow-message", - runId: obj.runId, - message: { - role: msg.role, - content: msg.content, - meta: "meta" in msg ? msg.meta : undefined, - timestamp: msg.timestamp, - }, - }); -} - /** Validate and parse an unknown IPC message received from a worker process. */ export function parseWorkerMessage(raw: unknown): Result { if (!isPlainRecord(raw)) { @@ -406,11 +224,15 @@ export function parseWorkerMessage(raw: unknown): Result if (!WORKER_MSG_TYPES.has(obj.type)) { return err(new Error(`Unknown worker IPC message type: "${obj.type}"`)); } + if (WORKFLOW_WORKER_MSG_TYPES.has(obj.type)) { + const wf = parseWorkflowWorkerToParentMessage(raw); + if (!wf.ok) { + return wf; + } + return ok(wf.value as WorkerToParentMessage); + } if (obj.type === "compute-result") return parseComputeResultMsg(obj); if (obj.type === "error") return parseErrorMsg(obj); if (obj.type === "health-response") return parseHealthResponseMsg(obj); - if (obj.type === "thread-event") return parseThreadEventMsg(obj); - if (obj.type === "workflow-error") return parseWorkflowErrorMsg(obj); - if (obj.type === "thread-workflow-message") return parseThreadWorkflowMessageMsg(obj); return ok({ type: "ready" }); } diff --git a/packages/daemon/src/kernel-file-watch.ts b/packages/daemon/src/kernel-file-watch.ts index f0b353e..aec5c2e 100644 --- a/packages/daemon/src/kernel-file-watch.ts +++ b/packages/daemon/src/kernel-file-watch.ts @@ -9,7 +9,7 @@ import type { NerveConfig } from "@uncaged/nerve-core"; import { parseNerveConfig } from "@uncaged/nerve-core"; import type { LogStore } from "@uncaged/nerve-store"; -import type { WorkflowManager } from "./workflow-manager.js"; +import type { WorkflowManager } from "@uncaged/workflow"; export type KernelFileWatchDeps = { nerveRoot: string; diff --git a/packages/daemon/src/kernel.ts b/packages/daemon/src/kernel.ts index d411a84..558529e 100644 --- a/packages/daemon/src/kernel.ts +++ b/packages/daemon/src/kernel.ts @@ -36,8 +36,8 @@ import { import { createSenseScheduler } from "./sense-scheduler.js"; import type { SenseScheduler } from "./sense-scheduler.js"; import { createSenseWorkerPool, resolveWorkerScript } from "./worker-pool.js"; -import { createWorkflowManager } from "./workflow-manager.js"; -import type { WorkflowManager } from "./workflow-manager.js"; +import { createWorkflowManager } from "@uncaged/workflow"; +import type { WorkflowManager } from "@uncaged/workflow"; export type KernelHealth = { uptime: number; diff --git a/packages/daemon/src/worker-pool.ts b/packages/daemon/src/worker-pool.ts index d93560f..03d8dab 100644 --- a/packages/daemon/src/worker-pool.ts +++ b/packages/daemon/src/worker-pool.ts @@ -10,7 +10,7 @@ import { createWorkerRuntime, formatCapturedStderrTail, formatChildExitSummary, -} from "./worker-runtime.js"; +} from "@uncaged/workflow"; export function resolveWorkerScript(): string { const __filename = fileURLToPath(import.meta.url); diff --git a/packages/store/src/blob-store.d.ts b/packages/store/src/blob-store.d.ts new file mode 100644 index 0000000..605c004 --- /dev/null +++ b/packages/store/src/blob-store.d.ts @@ -0,0 +1,17 @@ +/** + * CAS blob store — sha256 content-addressable files under `data/blobs/`. + * + * Layout: `/<2-hex-shard>/<62-hex-rest>` (RFC-001 §8). + */ +export type BlobStore = { + /** Persist UTF-8 or raw bytes; returns lowercase hex sha256. Idempotent for identical content. */ + write: (content: string | Uint8Array | Buffer) => string; + /** Returns bytes or null if the hash is invalid or no blob exists. Verifies digest matches path. */ + read: (hash: string) => Buffer | null; + /** True when hash is well-formed and the blob file is present. */ + exists: (hash: string) => boolean; +}; +/** @returns normalized lowercase hex or null if not a valid sha256 hex string */ +export declare function normalizeBlobHash(hash: string): string | null; +export declare function createBlobStore(blobsRoot: string): BlobStore; +//# sourceMappingURL=blob-store.d.ts.map \ No newline at end of file diff --git a/packages/store/src/blob-store.d.ts.map b/packages/store/src/blob-store.d.ts.map new file mode 100644 index 0000000..d2169b0 --- /dev/null +++ b/packages/store/src/blob-store.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"blob-store.d.ts","sourceRoot":"","sources":["blob-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgBH,MAAM,MAAM,SAAS,GAAG;IACtB,kGAAkG;IAClG,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,KAAK,MAAM,CAAC;IACzD,oGAAoG;IACpG,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACtC,kEAAkE;IAClE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC,CAAC;AAYF,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK7D;AAiBD,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CA2C5D"} \ No newline at end of file diff --git a/packages/store/src/index.d.ts b/packages/store/src/index.d.ts new file mode 100644 index 0000000..2834dbd --- /dev/null +++ b/packages/store/src/index.d.ts @@ -0,0 +1,8 @@ +/** + * @uncaged/nerve-store — append-only log storage, cold-archive helpers, CAS blob store. + */ +export * from "./blob-store.js"; +export * from "./log-archive.js"; +export { createLogStore } from "./log-store.js"; +export type { GetThreadRoundsParams, LogEntry, LogQuery, LogStore, ThreadRoundRow, WorkflowRun, WorkflowRunStatus, } from "./log-store.js"; +//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/store/src/index.d.ts.map b/packages/store/src/index.d.ts.map new file mode 100644 index 0000000..5946df7 --- /dev/null +++ b/packages/store/src/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EACV,qBAAqB,EACrB,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,WAAW,EACX,iBAAiB,GAClB,MAAM,gBAAgB,CAAC"} \ No newline at end of file diff --git a/packages/store/src/log-archive.d.ts b/packages/store/src/log-archive.d.ts new file mode 100644 index 0000000..b7a6ee1 --- /dev/null +++ b/packages/store/src/log-archive.d.ts @@ -0,0 +1,32 @@ +/** Log cold-archive helpers (RFC-001 §5.4) — UTC calendar days, JSONL export. */ +export declare const LOG_ARCHIVE_META_KEY = "archived_up_to"; +export declare const DEFAULT_LOG_RETENTION_MS: number; +export type ArchiveLogsOptions = { + /** Wall clock for retention boundary (default: `Date.now()`). */ + now?: number; + /** Run `VACUUM` after archiving (outside the per-day transaction). */ + vacuum?: boolean; + /** Max UTC days to process in one call (default: unlimited). */ + maxDays?: number; + /** Override default 30-day retention (tests). */ + retentionMs?: number; +}; +export type ArchiveLogsDayResult = { + day: string; + rowCount: number; + filePath: string; +}; +export type ArchiveLogsResult = { + days: ArchiveLogsDayResult[]; + vacuumed: boolean; +}; +export declare function utcDateStringFromMs(ms: number): string; +export declare function assertValidUtcDay(day: string): void; +export declare function utcDayStartMs(day: string): number; +export declare function utcDayEndExclusiveMs(day: string): number; +export declare function prevUtcDay(day: string): string; +export declare function nextUtcDay(day: string): string; +/** Last UTC calendar day D such that the exclusive end of D is ≤ boundaryMs. */ +export declare function lastArchivableUtcDay(boundaryMs: number): string; +export declare function compareIsoDays(a: string, b: string): number; +//# sourceMappingURL=log-archive.d.ts.map \ No newline at end of file diff --git a/packages/store/src/log-archive.d.ts.map b/packages/store/src/log-archive.d.ts.map new file mode 100644 index 0000000..f4d0fc1 --- /dev/null +++ b/packages/store/src/log-archive.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"log-archive.d.ts","sourceRoot":"","sources":["log-archive.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAEjF,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AAErD,eAAO,MAAM,wBAAwB,QAAkB,CAAC;AAExD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,oBAAoB,EAAE,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEtD;AAiBD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAI3D"} \ No newline at end of file diff --git a/packages/store/src/log-store.d.ts b/packages/store/src/log-store.d.ts new file mode 100644 index 0000000..861bbd9 --- /dev/null +++ b/packages/store/src/log-store.d.ts @@ -0,0 +1,134 @@ +/** + * Log Store — append-only structured log storage backed by SQLite. + * + * Stores system, sense-scheduler (`sense_scheduler` source), sense, and workflow log entries in a single table. + * Logs are data assets for audit/analysis — they MUST NOT feed back into scheduling or workflows as triggers. + * + * Also provides a `meta` key-value table for bookkeeping (e.g. archive watermarks). + */ +import type { ArchiveLogsOptions, ArchiveLogsResult } from "./log-archive.js"; +export { LOG_ARCHIVE_META_KEY } from "./log-archive.js"; +export type { ArchiveLogsDayResult, ArchiveLogsOptions, ArchiveLogsResult } from "./log-archive.js"; +export type LogEntry = { + id?: number; + source: string; + type: string; + refId: string | null; + payload: string | null; + timestamp: number; +}; +export type LogQuery = { + source?: string; + type?: string; + refId?: string; + since?: number; + until?: number; + limit?: number; +}; +export type WorkflowRunStatus = "queued" | "started" | "completed" | "failed" | "crashed" | "dropped" | "interrupted" | "killed"; +/** One row in the workflow_runs materialized table. */ +export type WorkflowRun = { + runId: string; + workflow: string; + status: WorkflowRunStatus; + timestamp: number; + exitCode: number | null; +}; +/** One role-produced workflow-message row with 1-based round index (ROW_NUMBER over role messages only). */ +export type ThreadRoundRow = { + round: number; + logId: number; + timestamp: number; + message: { + role: string; + content: string; + meta: unknown; + timestamp: number; + }; +}; +/** Parameters for {@link LogStore.getThreadRounds}. */ +export type GetThreadRoundsParams = { + /** + * Exclusive upper bound on round index (1-based among role events). + * Use `0` to include all rounds (subject to `limit`). + */ + before: number; + /** Maximum rows returned from the DB (DESC by round). */ + limit: number; +}; +export type LogStore = { + append: (entry: Omit) => LogEntry; + query: (filter?: LogQuery) => LogEntry[]; + getMeta: (key: string) => string | null; + setMeta: (key: string, value: string) => void; + /** + * Append a workflow log event and atomically upsert the workflow_runs + * materialized table — both in a single SQLite transaction (RFC-002 §6.2). + */ + upsertWorkflowRun: (entry: Omit, run: WorkflowRun) => LogEntry; + /** + * Alias for upsertWorkflowRun — append a log entry and update workflow_runs + * in one atomic transaction. + */ + appendWithWorkflowUpdate: (entry: Omit, run: WorkflowRun) => LogEntry; + /** Get the current materialized state of a specific workflow run. */ + getWorkflowRun: (runId: string) => WorkflowRun | null; + /** + * Get all workflow runs with status 'queued' or 'started'. + * Optionally filter by workflow name. + */ + getActiveWorkflowRuns: (workflowName?: string) => WorkflowRun[]; + /** + * Get all workflow runs regardless of status, sorted by timestamp descending. + * Optionally filter by workflow name. + */ + getAllWorkflowRuns: (workflowName: string | null) => WorkflowRun[]; + /** + * Get the trigger payload for a workflow run (stored in the 'started' log entry). + * Returns null if not found. + */ + getTriggerPayload: (runId: string) => unknown; + /** + * Get all workflow CommandEvents for a specific run, ordered by id ASC. + * @deprecated Use getThreadMessages for the new WorkflowMessage format. + */ + getThreadEvents: (runId: string) => Array<{ + type: string; + [key: string]: unknown; + }>; + /** + * Get all WorkflowMessages for a specific run, ordered by id ASC. + * Used for crash recovery to rebuild the message chain. + */ + getThreadMessages: (runId: string) => Array<{ + role: string; + content: string; + meta: unknown; + timestamp: number; + }>; + /** + * Count role command events for a run (excludes `thread_start`/`__start__` messages and invalid payloads). + * Round indices for {@link getThreadRounds} are 1..count in chronological order. + */ + getThreadRoundCount: (runId: string) => number; + /** + * Role rounds for agent-oriented retrieval: each row is one `thread_command_event` or + * `thread_workflow_message` whose JSON `type` is not `thread_start` and `role` is not `__start__`, + * with `round` from ROW_NUMBER() OVER (ORDER BY id ASC). No schema migration — numbering is computed in SQL. + */ + getThreadRounds: (runId: string, params: GetThreadRoundsParams) => ThreadRoundRow[]; + /** + * The workflow `__start__` message for a run (if persisted), as a {@link ThreadRoundRow} + * with `round` 0 — not part of {@link getThreadRoundCount} / {@link getThreadRounds} numbering. + */ + getThreadStartMessage: (runId: string) => ThreadRoundRow | null; + /** + * Export logs older than the retention window to `data/archive/logs/YYYY-MM-DD.jsonl`, + * then delete those rows and advance `meta.archived_up_to` in one transaction per day + * (RFC-001 §5.4). + */ + archiveLogs: (options?: ArchiveLogsOptions) => ArchiveLogsResult; + close: () => void; +}; +export declare function createLogStore(dbPath: string): LogStore; +//# sourceMappingURL=log-store.d.ts.map \ No newline at end of file diff --git a/packages/store/src/log-store.d.ts.map b/packages/store/src/log-store.d.ts.map new file mode 100644 index 0000000..61de455 --- /dev/null +++ b/packages/store/src/log-store.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"log-store.d.ts","sourceRoot":"","sources":["log-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAmBH,OAAO,KAAK,EAAwB,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEpG,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AACxD,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAEpG,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAMF,MAAM,MAAM,iBAAiB,GACzB,QAAQ,GACR,SAAS,GACT,WAAW,GACX,QAAQ,GACR,SAAS,GACT,SAAS,GACT,aAAa,GACb,QAAQ,CAAC;AAwBb,uDAAuD;AACvD,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB,CAAC;AAEF,4GAA4G;AAC5G,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CAC9E,CAAC;AAEF,uDAAuD;AACvD,MAAM,MAAM,qBAAqB,GAAG;IAClC;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,yDAAyD;IACzD,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,QAAQ,CAAC;IAClD,KAAK,EAAE,CAAC,MAAM,CAAC,EAAE,QAAQ,KAAK,QAAQ,EAAE,CAAC;IACzC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACxC,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C;;;OAGG;IACH,iBAAiB,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,WAAW,KAAK,QAAQ,CAAC;IAC/E;;;OAGG;IACH,wBAAwB,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,GAAG,EAAE,WAAW,KAAK,QAAQ,CAAC;IACtF,qEAAqE;IACrE,cAAc,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,WAAW,GAAG,IAAI,CAAC;IACtD;;;OAGG;IACH,qBAAqB,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,KAAK,WAAW,EAAE,CAAC;IAChE;;;OAGG;IACH,kBAAkB,EAAE,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,KAAK,WAAW,EAAE,CAAC;IACnE;;;OAGG;IACH,iBAAiB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9C;;;OAGG;IACH,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,CAAC,CAAC;IACpF;;;OAGG;IACH,iBAAiB,EAAE,CACjB,KAAK,EAAE,MAAM,KACV,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChF;;;OAGG;IACH,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC/C;;;;OAIG;IACH,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,KAAK,cAAc,EAAE,CAAC;IACpF;;;OAGG;IACH,qBAAqB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,cAAc,GAAG,IAAI,CAAC;IAChE;;;;OAIG;IACH,WAAW,EAAE,CAAC,OAAO,CAAC,EAAE,kBAAkB,KAAK,iBAAiB,CAAC;IACjE,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AA4JF,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,QAAQ,CAqbvD"} \ No newline at end of file diff --git a/packages/workflow/package.json b/packages/workflow/package.json index c56a3e2..84fc580 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -8,7 +8,12 @@ ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" - } + }, + "./public-types": { + "types": "./dist/public-types.d.ts", + "default": "./dist/public-types.js" + }, + "./package.json": "./package.json" }, "files": ["dist"], "publishConfig": { @@ -16,7 +21,12 @@ }, "scripts": { "prepublishOnly": "bash ../../scripts/prepublish-check.sh", - "build": "rslib build" + "build": "rslib build && pnpm run build:public-types", + "build:public-types": "tsc -p tsconfig.public-types.json" + }, + "dependencies": { + "@uncaged/nerve-core": "workspace:*", + "@uncaged/nerve-store": "workspace:*" }, "devDependencies": { "@rslib/core": "^0.21.3", diff --git a/packages/workflow/rslib.config.ts b/packages/workflow/rslib.config.ts index 87bb8a3..6894607 100644 --- a/packages/workflow/rslib.config.ts +++ b/packages/workflow/rslib.config.ts @@ -10,6 +10,7 @@ export default defineConfig({ source: { entry: { index: "src/index.ts", + worker: "src/worker.ts", }, }, output: { diff --git a/packages/workflow/src/experimental-warning-suppression.ts b/packages/workflow/src/experimental-warning-suppression.ts new file mode 100644 index 0000000..f33086d --- /dev/null +++ b/packages/workflow/src/experimental-warning-suppression.ts @@ -0,0 +1,23 @@ +/** + * Patches `process.emit` so `ExperimentalWarning` (e.g. from `node:sqlite`) is not + * forwarded to the default handler. Other warning types are unchanged. + * + * Import this module before any code that loads `node:sqlite`. + */ + +const WARNING_EVENT = "warning"; +const EXPERIMENTAL_WARNING_NAME = "ExperimentalWarning"; + +type EmitFn = typeof process.emit; + +const originalEmit = process.emit.bind(process) as EmitFn; + +process.emit = ((event: string | symbol, ...args: unknown[]): boolean => { + if (event === WARNING_EVENT) { + const w = args[0]; + if (w instanceof Error && w.name === EXPERIMENTAL_WARNING_NAME) { + return false; + } + } + return Reflect.apply(originalEmit, process, [event, ...args]) as boolean; +}) as EmitFn; diff --git a/packages/workflow/src/index.ts b/packages/workflow/src/index.ts index a2d0388..e2193fe 100644 --- a/packages/workflow/src/index.ts +++ b/packages/workflow/src/index.ts @@ -17,3 +17,37 @@ export type { } from "./types.js"; export type { DropOverflowConfig, QueueOverflowConfig, WorkflowConfig } from "./config.js"; + +export type { + KillThreadMessage, + ResumeThreadMessage, + StartThreadMessage, + WorkflowParentToWorkerMessage, + WorkflowWorkerShutdownMessage, + ThreadEventType, + ThreadLifecycleEvent, + ThreadEventMessage, + WorkflowErrorMessage, + ThreadWorkflowMessage, + WorkflowWorkerToParentMessage, + WorkflowWorkerReadyMessage, + WorkflowChildToParentMessage, + WorkflowWorkerOutboundMessage, +} from "./ipc.js"; +export { + parseWorkflowParentMessage, + parseWorkflowWorkerToParentMessage, + parseWorkflowChildMessage, +} from "./ipc.js"; + +export { WORKFLOW_WORKER_PATH } from "./paths.js"; + +export { + createWorkerRuntime, + formatCapturedStderrTail, + formatChildExitSummary, +} from "./worker-runtime.js"; +export type { WorkerDrainOpts, WorkerRuntime, WorkerRuntimeConfig } from "./worker-runtime.js"; + +export { createWorkflowManager } from "./manager.js"; +export type { WorkflowLaunchParams, WorkflowManager } from "./manager.js"; diff --git a/packages/workflow/src/ipc.ts b/packages/workflow/src/ipc.ts new file mode 100644 index 0000000..94da576 --- /dev/null +++ b/packages/workflow/src/ipc.ts @@ -0,0 +1,314 @@ +/** + * IPC message types for parent (kernel) ↔ workflow worker communication. + * Protocol per RFC-002 §5.2. + */ + +import type { Result } from "@uncaged/nerve-core"; +import { err, isPlainRecord, ok } from "@uncaged/nerve-core"; + +/** Parent → Workflow Worker: start a new thread */ +export type StartThreadMessage = { + type: "start-thread"; + runId: string; + workflow: string; + prompt: string; + /** Safety-valve: max moderator rounds for this thread (engine launch parameter). */ + maxRounds: number; + /** When true, roles may skip side effects (thread-level hint on the start frame). */ + dryRun: boolean; +}; + +/** Parent → Workflow Worker: resume an existing thread after crash recovery */ +export type ResumeThreadMessage = { + type: "resume-thread"; + runId: string; + /** Serialised WorkflowMessage history to rebuild chain (must begin with `__start__`). */ + messages: Array<{ role: string; content: string; meta: unknown; timestamp: number }>; + /** Safety-valve: max moderator rounds for this thread. */ + maxRounds: number; + /** Thread-level dry-run hint (aligns with persisted `__start__` meta when replaying). */ + dryRun: boolean; +}; + +/** Parent → Workflow Worker: kill a specific running thread */ +export type KillThreadMessage = { + type: "kill-thread"; + runId: string; +}; + +/** Parent → Workflow Worker: graceful shutdown (same wire shape as sense worker). */ +export type WorkflowWorkerShutdownMessage = { + type: "shutdown"; +}; + +/** Messages the parent sends to a workflow worker process */ +export type WorkflowParentToWorkerMessage = + | StartThreadMessage + | ResumeThreadMessage + | KillThreadMessage + | WorkflowWorkerShutdownMessage; + +/** Valid lifecycle event types for a workflow thread. */ +export type ThreadEventType = + | "queued" + | "started" + | "step_complete" + | "completed" + | "failed" + | "killed"; + +/** Alias — lifecycle channel uses `ThreadEventType` values. */ +export type ThreadLifecycleEvent = ThreadEventType; + +/** + * Workflow Worker → Parent: a thread lifecycle event. + */ +export type ThreadEventMessage = { + type: "thread-event"; + runId: string; + eventType: ThreadEventType; + payload: unknown; +}; + +/** Workflow Worker → Parent: a thread encountered an unrecoverable error. */ +export type WorkflowErrorMessage = { + type: "workflow-error"; + runId: string; + error: string; + /** Exit code conveying the failure reason (1=role error, 2=maxRounds exhausted). */ + exitCode: number; +}; + +/** Workflow Worker → Parent: a WorkflowMessage produced by a role (for crash recovery). */ +export type ThreadWorkflowMessage = { + type: "thread-workflow-message"; + runId: string; + /** The WorkflowMessage produced by the role — persisted for crash recovery. */ + message: { role: string; content: string; meta: unknown; timestamp: number }; +}; + +/** Messages a workflow worker sends to the parent (subset of full worker IPC). */ +export type WorkflowWorkerToParentMessage = + | ThreadEventMessage + | WorkflowErrorMessage + | ThreadWorkflowMessage; + +export type WorkflowWorkerReadyMessage = { type: "ready" }; + +/** Messages a workflow child may emit on IPC (including bootstrap ready). */ +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", + "kill-thread", + "shutdown", +]); + +function validateStartThreadMsg(obj: Record): string | null { + if (typeof obj.runId !== "string") return "'start-thread' message missing string 'runId'"; + if (typeof obj.workflow !== "string") return "'start-thread' message missing string 'workflow'"; + if (typeof obj.prompt !== "string") return "'start-thread' message missing string 'prompt'"; + if (typeof obj.maxRounds !== "number") return "'start-thread' message missing number 'maxRounds'"; + if (typeof obj.dryRun !== "boolean") return "'start-thread' message missing boolean 'dryRun'"; + return null; +} + +function validateResumeThreadMsg(obj: Record): string | null { + if (typeof obj.runId !== "string") return "'resume-thread' message missing string 'runId'"; + if (!Array.isArray(obj.messages)) return "'resume-thread' message missing 'messages' array"; + if (typeof obj.maxRounds !== "number") + return "'resume-thread' message missing number 'maxRounds'"; + if (typeof obj.dryRun !== "boolean") return "'resume-thread' message missing boolean 'dryRun'"; + return null; +} + +function parseParentStartThread(obj: Record): Result { + const errMsg = validateStartThreadMsg(obj); + 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); +} + +function parseParentResumeThread(obj: Record): Result { + const errMsg = validateResumeThreadMsg(obj); + if (errMsg !== null) return err(new Error(errMsg)); + return ok({ + type: "resume-thread", + runId: obj.runId, + messages: obj.messages as ResumeThreadMessage["messages"], + maxRounds: obj.maxRounds, + dryRun: obj.dryRun, + } as ResumeThreadMessage); +} + +function parseParentKillThread(obj: Record): Result { + 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); +} + +/** Validate and parse an unknown IPC message for a workflow worker process. */ +export function parseWorkflowParentMessage(raw: unknown): Result { + if (!isPlainRecord(raw)) { + return err(new Error("IPC message is not an object")); + } + const obj = raw; + if (typeof obj.type !== "string") { + return err(new Error("IPC message missing string 'type' field")); + } + if (!WORKFLOW_PARENT_MSG_TYPES.has(obj.type)) { + return err(new Error(`Unknown workflow IPC message type: "${obj.type}"`)); + } + switch (obj.type) { + case "shutdown": + return ok({ type: "shutdown" }); + case "start-thread": + return parseParentStartThread(obj); + case "resume-thread": + return parseParentResumeThread(obj); + case "kill-thread": + return parseParentKillThread(obj); + default: + return err(new Error(`Unhandled workflow IPC message type: "${obj.type}"`)); + } +} + +function isThreadEventType(value: string): value is ThreadEventType { + switch (value) { + case "queued": + case "started": + case "step_complete": + case "completed": + case "failed": + case "killed": + return true; + default: + return false; + } +} + +function parseThreadEventMsg(obj: Record): Result { + if (typeof obj.runId !== "string") { + return err(new Error("Worker 'thread-event' message missing string 'runId' field")); + } + if (typeof obj.eventType !== "string" || !isThreadEventType(obj.eventType)) { + return err( + new Error(`Worker 'thread-event' message has invalid 'eventType': "${obj.eventType}"`), + ); + } + if (!("payload" in obj)) { + return err(new Error("Worker 'thread-event' message missing 'payload' field")); + } + return ok({ + type: "thread-event", + runId: obj.runId, + eventType: obj.eventType, + payload: obj.payload, + }); +} + +function parseWorkflowErrorMsg(obj: Record): Result { + if (typeof obj.runId !== "string") { + return err(new Error("Worker 'workflow-error' message missing string 'runId' field")); + } + if (typeof obj.error !== "string") { + return err(new Error("Worker 'workflow-error' message missing string 'error' field")); + } + const exitCode = typeof obj.exitCode === "number" ? obj.exitCode : 1; + return ok({ + type: "workflow-error", + runId: obj.runId, + error: obj.error, + exitCode, + }); +} + +function parseThreadWorkflowMessageMsg(obj: Record): Result { + if (typeof obj.runId !== "string") { + return err(new Error("Worker 'thread-workflow-message' missing string 'runId' field")); + } + if (!isPlainRecord(obj.message)) { + return err(new Error("Worker 'thread-workflow-message' missing object 'message' field")); + } + const msg = obj.message; + if (typeof msg.role !== "string") { + return err(new Error("Worker 'thread-workflow-message' message missing string 'role' field")); + } + if (typeof msg.content !== "string") { + return err( + new Error("Worker 'thread-workflow-message' message missing string 'content' field"), + ); + } + if (typeof msg.timestamp !== "number") { + return err( + new Error("Worker 'thread-workflow-message' message missing number 'timestamp' field"), + ); + } + return ok({ + type: "thread-workflow-message", + runId: obj.runId, + message: { + role: msg.role, + content: msg.content, + meta: "meta" in msg ? msg.meta : null, + timestamp: msg.timestamp, + }, + }); +} + +const WORKFLOW_WORKER_MSG_TYPES = new Set([ + "thread-event", + "workflow-error", + "thread-workflow-message", +]); + +/** Validate and parse workflow worker → parent IPC messages. */ +export function parseWorkflowWorkerToParentMessage( + raw: unknown, +): Result { + if (!isPlainRecord(raw)) { + return err(new Error("Worker IPC message is not an object")); + } + const obj = raw; + if (typeof obj.type !== "string") { + return err(new Error("Worker IPC message missing string 'type' field")); + } + if (!WORKFLOW_WORKER_MSG_TYPES.has(obj.type)) { + return err(new Error(`Unknown workflow worker IPC message type: "${obj.type}"`)); + } + if (obj.type === "thread-event") return parseThreadEventMsg(obj); + if (obj.type === "workflow-error") return parseWorkflowErrorMsg(obj); + return parseThreadWorkflowMessageMsg(obj); +} + +/** Parse messages from a workflow worker child (thread IPC + optional `ready`). */ +export function parseWorkflowChildMessage( + raw: unknown, +): Result { + if (!isPlainRecord(raw)) { + return err(new Error("Worker IPC message is not an object")); + } + const obj = raw; + if (typeof obj.type !== "string") { + return err(new Error("Worker IPC message missing string 'type' field")); + } + if (obj.type === "ready") { + return ok({ type: "ready" }); + } + return parseWorkflowWorkerToParentMessage(raw); +} diff --git a/packages/daemon/src/workflow-manager-support.ts b/packages/workflow/src/manager-support.ts similarity index 90% rename from packages/daemon/src/workflow-manager-support.ts rename to packages/workflow/src/manager-support.ts index 800030c..bde8ef7 100644 --- a/packages/daemon/src/workflow-manager-support.ts +++ b/packages/workflow/src/manager-support.ts @@ -2,15 +2,13 @@ * Pure helpers and IPC branching for workflow-manager (keeps workflow-manager.ts lean). */ -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; - -import type { WorkflowMessage } from "@uncaged/nerve-core"; -import { START, isPlainRecord } from "@uncaged/nerve-core"; +import { isPlainRecord } from "@uncaged/nerve-core"; import type { LogStore, WorkflowRunStatus } from "@uncaged/nerve-store"; import type { ResumeThreadMessage, ThreadEventMessage } from "./ipc.js"; -import type { WorkerToParentMessage } from "./ipc.js"; +import type { WorkflowChildToParentMessage } from "./ipc.js"; +import type { WorkflowMessage } from "./types.js"; +import { START } from "./types.js"; export type PendingThread = { runId: string; @@ -33,7 +31,7 @@ export const WORKFLOW_WORKER_RESPAWN = { } as const; /** - * Worker shutdown timeout — must stay in sync with SHUTDOWN_TIMEOUT_MS in workflow-worker.ts. + * Worker shutdown timeout — must stay in sync with shutdown handling in `worker.ts`. * The drain timeout passed to drainAndRespawn must be >= this value so the worker has * enough time to finish in-flight threads before the parent force-kills it. */ @@ -79,12 +77,6 @@ export function ensureThreadMessagesWithStart( return [start, ...mapped]; } -export function resolveWorkflowWorkerScript(): string { - const __filename = fileURLToPath(import.meta.url); - const __dir = dirname(__filename); - return join(__dir, "workflow-worker.js"); -} - export function mapWorkflowRunStatus(eventType: string): WorkflowRunStatus | null { const map: Record = { started: "started", @@ -223,9 +215,13 @@ export type WorkflowManagerMessageDeps = { export function dispatchWorkflowWorkerMessage( workflowName: string, - msg: WorkerToParentMessage, + msg: WorkflowChildToParentMessage, deps: WorkflowManagerMessageDeps, ): void { + if (msg.type === "ready") { + return; + } + if (msg.type === "thread-event") { deps.handleThreadEvent(workflowName, msg); return; @@ -247,10 +243,5 @@ export function dispatchWorkflowWorkerMessage( `[workflow-manager] workflow-error for runId "${msg.runId}" in "${workflowName}": ${msg.error}\n`, ); deps.onWorkflowRoleError(workflowName, msg.runId, msg.error, msg.exitCode); - return; - } - - if (msg.type === "error") { - process.stderr.write(`[workflow-manager] error from "${workflowName}" worker: ${msg.error}\n`); } } diff --git a/packages/daemon/src/workflow-manager.ts b/packages/workflow/src/manager.ts similarity index 98% rename from packages/daemon/src/workflow-manager.ts rename to packages/workflow/src/manager.ts index 9048097..1e55270 100644 --- a/packages/daemon/src/workflow-manager.ts +++ b/packages/workflow/src/manager.ts @@ -10,12 +10,13 @@ import type { NerveConfig, WorkflowConfig, WorkflowStatus } from "@uncaged/nerve import type { LogStore } from "@uncaged/nerve-store"; import type { KillThreadMessage, StartThreadMessage, ThreadEventMessage } from "./ipc.js"; -import { parseWorkerMessage } from "./ipc.js"; +import { parseWorkflowChildMessage } from "./ipc.js"; import { createWorkerRuntime, formatCapturedStderrTail, formatChildExitSummary, } from "./worker-runtime.js"; +import { WORKFLOW_WORKER_PATH } from "./paths.js"; import { DEFAULT_MAX_QUEUE, WORKER_SHUTDOWN_TIMEOUT_MS, @@ -25,8 +26,7 @@ import { dispatchWorkflowWorkerMessage, extractExitCodeFromPayload, recoverThreadsFromStore, - resolveWorkflowWorkerScript, -} from "./workflow-manager-support.js"; +} from "./manager-support.js"; export type WorkflowLaunchParams = { prompt: string; @@ -73,7 +73,7 @@ export function createWorkflowManager( initialConfig: NerveConfig, logStore: LogStore, ): WorkflowManager { - const workerScript = resolveWorkflowWorkerScript(); + const workerScript = WORKFLOW_WORKER_PATH; /** * Default drain timeout must be at least WORKER_SHUTDOWN_TIMEOUT_MS so the worker @@ -309,7 +309,7 @@ export function createWorkflowManager( } function handleWorkerMessage(workflowName: string, raw: unknown): void { - const result = parseWorkerMessage(raw); + const result = parseWorkflowChildMessage(raw); if (!result.ok) { process.stderr.write( `[workflow-manager] invalid message from "${workflowName}" worker: ${result.error.message}\n`, diff --git a/packages/workflow/src/paths.ts b/packages/workflow/src/paths.ts new file mode 100644 index 0000000..4bc2a95 --- /dev/null +++ b/packages/workflow/src/paths.ts @@ -0,0 +1,5 @@ +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +/** Resolved path to the workflow child-process entry (`worker.js` adjacent to this module in dist). */ +export const WORKFLOW_WORKER_PATH = join(dirname(fileURLToPath(import.meta.url)), "worker.js"); diff --git a/packages/workflow/src/public-types.ts b/packages/workflow/src/public-types.ts new file mode 100644 index 0000000..0e39bff --- /dev/null +++ b/packages/workflow/src/public-types.ts @@ -0,0 +1,22 @@ +/** + * Narrow surface for `@uncaged/nerve-core` — workflow automaton types + config only. + * Keeps core's declaration graph free of IPC / manager / store. + */ + +export { START, END, DEFAULT_ENGINE_MAX_ROUNDS } from "./types.js"; +export type { + WorkflowMessage, + RoleResult, + Role, + RoleMeta, + StartStep, + ThreadContext, + WorkflowContext, + AgentFn, + RoleStep, + ModeratorContext, + Moderator, + WorkflowDefinition, +} from "./types.js"; + +export type { DropOverflowConfig, QueueOverflowConfig, WorkflowConfig } from "./config.js"; diff --git a/packages/daemon/src/worker-runtime.ts b/packages/workflow/src/worker-runtime.ts similarity index 100% rename from packages/daemon/src/worker-runtime.ts rename to packages/workflow/src/worker-runtime.ts diff --git a/packages/workflow/src/worker-signals.ts b/packages/workflow/src/worker-signals.ts new file mode 100644 index 0000000..b9b20ef --- /dev/null +++ b/packages/workflow/src/worker-signals.ts @@ -0,0 +1,17 @@ +/** + * Worker-process signal handling (fork IPC children only). + * Worker entrypoints import this module — not worker-runtime.ts (parent/kernel code). + */ + +/** + * Forked workers inherit the parent's process group. In foreground `nerve dev`, + * terminal-driven SIGINT/SIGTERM is delivered to the whole group, so workers can exit + * on the default handler before the kernel sends `{ type: "shutdown" }` over IPC. + * Swallow these in worker processes so the parent coordinates shutdown (issue #55). + * Only call when `process.send` is defined (fork IPC); standalone `node …-worker.js` keeps default Ctrl+C behaviour. + */ +export function ignoreSessionBroadcastSignals(): void { + const swallow = (): void => {}; + process.on("SIGINT", swallow); + process.on("SIGTERM", swallow); +} diff --git a/packages/daemon/src/workflow-worker.ts b/packages/workflow/src/worker.ts similarity index 97% rename from packages/daemon/src/workflow-worker.ts rename to packages/workflow/src/worker.ts index 29c36e5..c2ab882 100644 --- a/packages/daemon/src/workflow-worker.ts +++ b/packages/workflow/src/worker.ts @@ -14,6 +14,14 @@ import "./experimental-warning-suppression.js"; import { existsSync } from "node:fs"; import { join, resolve } from "node:path"; +import { isPlainRecord } from "@uncaged/nerve-core"; + +import type { + ThreadEventType, + ThreadWorkflowMessage, + WorkflowWorkerOutboundMessage, +} from "./ipc.js"; +import { parseWorkflowParentMessage } from "./ipc.js"; import type { RoleMeta, RoleStep, @@ -21,22 +29,15 @@ import type { ThreadContext, WorkflowDefinition, WorkflowMessage, -} from "@uncaged/nerve-core"; -import { END, START, isPlainRecord } from "@uncaged/nerve-core"; - -import type { - ThreadEventType, - ThreadWorkflowMessageMessage, - WorkerToParentMessage, -} from "./ipc.js"; -import { parseParentMessage } from "./ipc.js"; +} from "./types.js"; +import { END, START } from "./types.js"; import { ignoreSessionBroadcastSignals } from "./worker-signals.js"; // --------------------------------------------------------------------------- // IPC helpers // --------------------------------------------------------------------------- -function send(msg: WorkerToParentMessage): void { +function send(msg: WorkflowWorkerOutboundMessage): void { if (process.send) { process.send(msg); } @@ -55,7 +56,7 @@ function sendWorkflowError(runId: string, error: string, exitCode = 1): void { } function sendWorkflowMessage(runId: string, message: WorkflowMessage): void { - const msg: ThreadWorkflowMessageMessage = { + const msg: ThreadWorkflowMessage = { type: "thread-workflow-message", runId, message: { @@ -334,7 +335,7 @@ function handleMessage( killFlags: Map, shuttingDown: { value: boolean }, ): void { - const parseResult = parseParentMessage(raw); + const parseResult = parseWorkflowParentMessage(raw); if (!parseResult.ok) { process.stderr.write(`[workflow-worker] Invalid IPC message: ${parseResult.error.message}\n`); return; diff --git a/packages/workflow/tsconfig.public-types.json b/packages/workflow/tsconfig.public-types.json new file mode 100644 index 0000000..6972ed5 --- /dev/null +++ b/packages/workflow/tsconfig.public-types.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src", + "declaration": true, + "declarationMap": true, + "composite": false, + "noEmit": false + }, + "include": ["src/types.ts", "src/config.ts", "src/public-types.ts"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5e29a24..1032bbf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -80,6 +80,9 @@ importers: '@uncaged/nerve-daemon': specifier: workspace:* version: link:../daemon + '@uncaged/workflow': + specifier: workspace:* + version: link:../workflow vitest: specifier: ^4.1.5 version: 4.1.5(@types/node@22.19.17)(vite@8.0.9(@types/node@22.19.17)(esbuild@0.27.7)(yaml@2.8.3)) @@ -111,6 +114,9 @@ importers: '@uncaged/nerve-store': specifier: workspace:* version: link:../store + '@uncaged/workflow': + specifier: workspace:* + version: link:../workflow yaml: specifier: ^2.8.3 version: 2.8.3 @@ -219,6 +225,13 @@ importers: version: 4.1.5(@types/node@22.19.17)(vite@8.0.9(@types/node@22.19.17)(esbuild@0.27.7)(yaml@2.8.3)) packages/workflow: + dependencies: + '@uncaged/nerve-core': + specifier: workspace:* + version: link:../core + '@uncaged/nerve-store': + specifier: workspace:* + version: link:../store devDependencies: '@rslib/core': specifier: ^0.21.3 -- 2.43.0 From 4a43a7f3dd319cd851e9948fb6932f3e3037d44f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E6=A9=98?= Date: Tue, 5 May 2026 10:56:12 +0000 Subject: [PATCH 4/4] refactor: update all consumers to import from @uncaged/workflow - workflow-utils, workflow-meta: import workflow types from @uncaged/workflow - adapter-cursor, adapter-hermes: same - cli: same - core: remove workflow re-exports, no longer depends on @uncaged/workflow Phase 5+6 of #320, Testing: #323 --- package.json | 6 + packages/adapter-cursor/package.json | 3 +- packages/adapter-cursor/src/index.ts | 3 +- packages/adapter-hermes/package.json | 3 +- packages/adapter-hermes/src/index.ts | 3 +- packages/cli/package.json | 2 +- .../cli/src/__tests__/create-workflow.test.ts | 4 +- packages/cli/src/__tests__/e2e-create.test.ts | 28 +++- packages/cli/src/commands/create.ts | 6 +- packages/cli/src/commands/init.ts | 1 + packages/cli/src/commands/workflow.ts | 3 +- packages/cli/src/daemon-client.ts | 8 +- packages/cli/src/http-transport.ts | 8 +- packages/core/package.json | 1 - packages/core/src/agent.d.ts | 17 --- packages/core/src/agent.d.ts.map | 1 - packages/core/src/config.d.ts | 63 -------- packages/core/src/config.d.ts.map | 1 - packages/core/src/config.ts | 26 +++- packages/core/src/daemon.d.ts | 120 ---------------- packages/core/src/daemon.d.ts.map | 1 - packages/core/src/index.d.ts | 21 --- packages/core/src/index.d.ts.map | 1 - packages/core/src/index.ts | 18 --- packages/core/src/sense.d.ts | 39 ----- packages/core/src/sense.d.ts.map | 1 - packages/core/src/util.d.ts | 66 --------- packages/core/src/util.d.ts.map | 1 - .../src/__tests__/crash-recovery.test.ts | 3 +- .../daemon/src/__tests__/hot-reload.test.ts | 3 +- .../src/__tests__/worker-runtime.test.ts | 2 +- packages/daemon/src/kernel.ts | 4 +- packages/daemon/src/worker-pool.ts | 2 +- packages/role-committer/package.json | 1 + packages/role-committer/src/index.ts | 2 +- packages/role-reviewer/package.json | 1 + packages/role-reviewer/src/reviewer.ts | 2 +- packages/store/src/blob-store.d.ts | 17 --- packages/store/src/blob-store.d.ts.map | 1 - packages/store/src/index.d.ts | 8 -- packages/store/src/index.d.ts.map | 1 - packages/store/src/log-archive.d.ts | 32 ----- packages/store/src/log-archive.d.ts.map | 1 - packages/store/src/log-store.d.ts | 134 ------------------ packages/store/src/log-store.d.ts.map | 1 - packages/workflow-meta/package.json | 1 + .../workflow-meta/src/develop-sense/build.ts | 2 +- .../src/develop-sense/moderator.ts | 4 +- .../src/develop-sense/roles/coder.ts | 2 +- .../src/develop-sense/roles/planner.ts | 2 +- .../src/develop-sense/roles/tester.ts | 2 +- .../src/develop-workflow/build.ts | 2 +- .../src/develop-workflow/moderator.ts | 4 +- .../src/develop-workflow/roles/coder.ts | 2 +- .../src/develop-workflow/roles/planner.ts | 2 +- .../src/develop-workflow/roles/tester.ts | 2 +- packages/workflow-utils/package.json | 1 + .../src/__tests__/create-llm-adapter.test.ts | 2 +- .../src/__tests__/create-role.test.ts | 4 +- .../src/__tests__/role-decorators.test.ts | 4 +- .../src/__tests__/role-factories.test.ts | 2 +- .../workflow-utils/src/create-llm-adapter.ts | 2 +- packages/workflow-utils/src/create-role.ts | 2 +- packages/workflow-utils/src/role-cursor.ts | 3 +- .../workflow-utils/src/role-decorators.ts | 2 +- packages/workflow-utils/src/role-hermes.ts | 2 +- packages/workflow-utils/src/role-llm.ts | 2 +- packages/workflow-utils/src/role-react.ts | 2 +- packages/workflow-utils/src/role-types.ts | 3 +- packages/workflow/package.json | 4 +- packages/workflow/src/ipc.ts | 8 +- packages/workflow/src/manager.ts | 16 ++- pnpm-lock.yaml | 31 +++- 73 files changed, 153 insertions(+), 632 deletions(-) delete mode 100644 packages/core/src/agent.d.ts delete mode 100644 packages/core/src/agent.d.ts.map delete mode 100644 packages/core/src/config.d.ts delete mode 100644 packages/core/src/config.d.ts.map delete mode 100644 packages/core/src/daemon.d.ts delete mode 100644 packages/core/src/daemon.d.ts.map delete mode 100644 packages/core/src/index.d.ts delete mode 100644 packages/core/src/index.d.ts.map delete mode 100644 packages/core/src/sense.d.ts delete mode 100644 packages/core/src/sense.d.ts.map delete mode 100644 packages/core/src/util.d.ts delete mode 100644 packages/core/src/util.d.ts.map delete mode 100644 packages/store/src/blob-store.d.ts delete mode 100644 packages/store/src/blob-store.d.ts.map delete mode 100644 packages/store/src/index.d.ts delete mode 100644 packages/store/src/index.d.ts.map delete mode 100644 packages/store/src/log-archive.d.ts delete mode 100644 packages/store/src/log-archive.d.ts.map delete mode 100644 packages/store/src/log-store.d.ts delete mode 100644 packages/store/src/log-store.d.ts.map diff --git a/package.json b/package.json index 3917ea6..15c974a 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,12 @@ "engines": { "node": ">=22.5.0" }, + "pnpm": { + "overrides": { + "@uncaged/nerve-core": "workspace:*", + "@uncaged/nerve-store": "workspace:*" + } + }, "scripts": { "prepare": "husky", "build": "pnpm --filter @uncaged/workflow run build:public-types && pnpm --filter @uncaged/nerve-core run build && pnpm --filter @uncaged/nerve-store run build && pnpm --filter @uncaged/workflow run build && pnpm -r --filter '!@uncaged/nerve-core' --filter '!@uncaged/nerve-store' --filter '!@uncaged/workflow' run build", diff --git a/packages/adapter-cursor/package.json b/packages/adapter-cursor/package.json index b42e498..dd77846 100644 --- a/packages/adapter-cursor/package.json +++ b/packages/adapter-cursor/package.json @@ -14,7 +14,8 @@ "test": "vitest run --passWithNoTests" }, "dependencies": { - "@uncaged/nerve-core": "workspace:*" + "@uncaged/nerve-core": "workspace:*", + "@uncaged/workflow": "workspace:*" }, "devDependencies": { "@rslib/core": "^0.21.3", diff --git a/packages/adapter-cursor/src/index.ts b/packages/adapter-cursor/src/index.ts index a9bf382..778f154 100644 --- a/packages/adapter-cursor/src/index.ts +++ b/packages/adapter-cursor/src/index.ts @@ -1,5 +1,6 @@ -import type { AgentConfig, AgentFn, ThreadContext } from "@uncaged/nerve-core"; +import type { AgentConfig } from "@uncaged/nerve-core"; import { type Result, type SpawnEnv, type SpawnError, ok, spawnSafe } from "@uncaged/nerve-core"; +import type { AgentFn, ThreadContext } from "@uncaged/workflow"; export type CursorAgentMode = "plan" | "ask" | "default"; diff --git a/packages/adapter-hermes/package.json b/packages/adapter-hermes/package.json index f7cda3e..f48867a 100644 --- a/packages/adapter-hermes/package.json +++ b/packages/adapter-hermes/package.json @@ -14,7 +14,8 @@ "test": "vitest run --passWithNoTests" }, "dependencies": { - "@uncaged/nerve-core": "workspace:*" + "@uncaged/nerve-core": "workspace:*", + "@uncaged/workflow": "workspace:*" }, "devDependencies": { "@rslib/core": "^0.21.3", diff --git a/packages/adapter-hermes/src/index.ts b/packages/adapter-hermes/src/index.ts index 085c85a..2ca9e1d 100644 --- a/packages/adapter-hermes/src/index.ts +++ b/packages/adapter-hermes/src/index.ts @@ -1,5 +1,6 @@ -import type { AgentConfig, AgentFn, ThreadContext } from "@uncaged/nerve-core"; +import type { AgentConfig } from "@uncaged/nerve-core"; import { type Result, type SpawnEnv, type SpawnError, ok, spawnSafe } from "@uncaged/nerve-core"; +import type { AgentFn, ThreadContext } from "@uncaged/workflow"; /** * Spawns a non-interactive `hermes chat` invocation with YOLO enabled, argv-only diff --git a/packages/cli/package.json b/packages/cli/package.json index 5399da5..4585f1e 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -23,6 +23,7 @@ "dependencies": { "@uncaged/nerve-core": "workspace:*", "@uncaged/nerve-store": "workspace:*", + "@uncaged/workflow": "workspace:*", "citty": "^0.1.6", "picomatch": "^4.0.2", "yaml": "^2.8.3" @@ -31,7 +32,6 @@ "@rslib/core": "^0.21.3", "@types/node": "^22.0.0", "@uncaged/nerve-daemon": "workspace:*", - "@uncaged/workflow": "workspace:*", "vitest": "^4.1.5" } } diff --git a/packages/cli/src/__tests__/create-workflow.test.ts b/packages/cli/src/__tests__/create-workflow.test.ts index e64fc07..7d2721a 100644 --- a/packages/cli/src/__tests__/create-workflow.test.ts +++ b/packages/cli/src/__tests__/create-workflow.test.ts @@ -27,10 +27,10 @@ describe("buildWorkflowScaffold", () => { expect(roleMainIndexTs).toContain("my-workflow started"); }); - it("root index contains WorkflowDefinition import from nerve-core", () => { + it("root index contains WorkflowDefinition import from @uncaged/workflow", () => { const { indexTs } = buildWorkflowScaffold("test"); expect(indexTs).toContain("WorkflowDefinition"); - expect(indexTs).toContain("@uncaged/nerve-core"); + expect(indexTs).toContain("@uncaged/workflow"); }); it("root index wires moderator with ThreadContext and END", () => { diff --git a/packages/cli/src/__tests__/e2e-create.test.ts b/packages/cli/src/__tests__/e2e-create.test.ts index e82de11..705fc66 100644 --- a/packages/cli/src/__tests__/e2e-create.test.ts +++ b/packages/cli/src/__tests__/e2e-create.test.ts @@ -2,9 +2,12 @@ * E2E-style tests for `nerve create workflow` and `nerve create sense`. */ +import { execFile } from "node:child_process"; import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { createRequire } from "node:module"; import { tmpdir } from "node:os"; -import { join } from "node:path"; +import { dirname, join } from "node:path"; +import { promisify } from "node:util"; import { defineCommand, runCommand } from "citty"; import { afterEach, describe, expect, it } from "vitest"; @@ -12,6 +15,23 @@ import { afterEach, describe, expect, it } from "vitest"; import { createCommand } from "../commands/create.js"; import { initCommand } from "../commands/init.js"; +const execFileAsync = promisify(execFile); +const requireFromHere = createRequire(import.meta.url); + +/** + * Default init pins `@uncaged/workflow` to npm `latest`, but that package is not published yet. + * Install from the monorepo workspace so `pnpm run build` can resolve workflow types. + */ +async function installWorkspaceWithLocalWorkflow(nerveRoot: string): Promise { + const pkgPath = join(nerveRoot, "package.json"); + const pkg = JSON.parse(readFileSync(pkgPath, "utf8")) as { dependencies: Record }; + const wfRoot = dirname(requireFromHere.resolve("@uncaged/workflow/package.json")); + pkg.dependencies["@uncaged/workflow"] = `file:${wfRoot}`; + writeFileSync(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`, "utf8"); + await execFileAsync("pnpm", ["install", "--no-frozen-lockfile"], { cwd: nerveRoot }); + await execFileAsync("pnpm", ["run", "build"], { cwd: nerveRoot }); +} + const testRootCommand = defineCommand({ meta: { name: "nerve", description: "e2e-create" }, subCommands: { @@ -128,7 +148,8 @@ describe("e2e create", () => { fakeHome = mkdtempSync(join(tmpdir(), "nerve-create-e2e-")); const nerveRoot = join(fakeHome, ".uncaged-nerve"); - await runTestCli(fakeHome, ["init", "--force"]); + await runTestCli(fakeHome, ["init", "--force", "--skip-install"]); + await installWorkspaceWithLocalWorkflow(nerveRoot); const wf = await runTestCli(fakeHome, ["create", "workflow", "e2e-flow"]); expect(wf.exitCode).toBe(0); @@ -153,7 +174,8 @@ describe("e2e create", () => { fakeHome = mkdtempSync(join(tmpdir(), "nerve-create-e2e-")); const nerveRoot = join(fakeHome, ".uncaged-nerve"); - await runTestCli(fakeHome, ["init", "--force"]); + await runTestCli(fakeHome, ["init", "--force", "--skip-install"]); + await installWorkspaceWithLocalWorkflow(nerveRoot); const sense = await runTestCli(fakeHome, ["create", "sense", "e2e-sense"]); expect(sense.exitCode).toBe(0); diff --git a/packages/cli/src/commands/create.ts b/packages/cli/src/commands/create.ts index dde6d82..fb2b46a 100644 --- a/packages/cli/src/commands/create.ts +++ b/packages/cli/src/commands/create.ts @@ -31,8 +31,8 @@ export function buildWorkflowScaffold(name: string): WorkflowScaffoldFiles { } function buildWorkflowIndexTs(name: string): string { - return `import type { ThreadContext, WorkflowDefinition } from "@uncaged/nerve-core"; -import { END } from "@uncaged/nerve-core"; + return `import type { ThreadContext, WorkflowDefinition } from "@uncaged/workflow"; +import { END } from "@uncaged/workflow"; import { mainRole } from "./roles/main/index.js"; @@ -56,7 +56,7 @@ export default workflow; } function buildWorkflowMainRoleIndexTs(name: string): string { - return `import type { RoleResult, ThreadContext } from "@uncaged/nerve-core"; + return `import type { RoleResult, ThreadContext } from "@uncaged/workflow"; /** * Main role — implement LLM calls, scripts, HTTP, etc. diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index 48c530d..dd703f7 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -54,6 +54,7 @@ const PACKAGE_JSON = `${JSON.stringify( dependencies: { "@uncaged/nerve-core": "latest", "@uncaged/nerve-daemon": "latest", + "@uncaged/workflow": "latest", zod: "^4.3.6", }, devDependencies: { diff --git a/packages/cli/src/commands/workflow.ts b/packages/cli/src/commands/workflow.ts index 4c7cc31..f218709 100644 --- a/packages/cli/src/commands/workflow.ts +++ b/packages/cli/src/commands/workflow.ts @@ -1,7 +1,8 @@ import { existsSync, readFileSync } from "node:fs"; import { join } from "node:path"; -import { DEFAULT_ENGINE_MAX_ROUNDS, isPlainRecord, parseNerveConfig } from "@uncaged/nerve-core"; +import { isPlainRecord, parseNerveConfig } from "@uncaged/nerve-core"; +import { DEFAULT_ENGINE_MAX_ROUNDS } from "@uncaged/workflow"; import { defineCommand } from "citty"; import { stringify } from "yaml"; diff --git a/packages/cli/src/daemon-client.ts b/packages/cli/src/daemon-client.ts index 052dcfa..a81f936 100644 --- a/packages/cli/src/daemon-client.ts +++ b/packages/cli/src/daemon-client.ts @@ -20,12 +20,8 @@ import type { SenseInfo, WorkflowStatus, } from "@uncaged/nerve-core"; -import { - DEFAULT_ENGINE_MAX_ROUNDS, - isPlainRecord, - isSenseInfo, - isWorkflowStatus, -} from "@uncaged/nerve-core"; +import { isPlainRecord, isSenseInfo, isWorkflowStatus } from "@uncaged/nerve-core"; +import { DEFAULT_ENGINE_MAX_ROUNDS } from "@uncaged/workflow"; import { getCliDaemonApiToken, getCliDaemonHost } from "./cli-global.js"; import { HttpTransport } from "./http-transport.js"; diff --git a/packages/cli/src/http-transport.ts b/packages/cli/src/http-transport.ts index fa05511..5acdb52 100644 --- a/packages/cli/src/http-transport.ts +++ b/packages/cli/src/http-transport.ts @@ -6,12 +6,8 @@ import type { SenseInfo, WorkflowStatus, } from "@uncaged/nerve-core"; -import { - DEFAULT_ENGINE_MAX_ROUNDS, - isPlainRecord, - isSenseInfo, - isWorkflowStatus, -} from "@uncaged/nerve-core"; +import { isPlainRecord, isSenseInfo, isWorkflowStatus } from "@uncaged/nerve-core"; +import { DEFAULT_ENGINE_MAX_ROUNDS } from "@uncaged/workflow"; function normalizeBaseUrl(host: string): string { const t = host.trim(); diff --git a/packages/core/package.json b/packages/core/package.json index 4727056..e784889 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -20,7 +20,6 @@ "test": "vitest run" }, "dependencies": { - "@uncaged/workflow": "workspace:*", "yaml": "^2.8.3" }, "devDependencies": { diff --git a/packages/core/src/agent.d.ts b/packages/core/src/agent.d.ts deleted file mode 100644 index c94c2a5..0000000 --- a/packages/core/src/agent.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * Extract layer types — parses agent raw string output into typed meta (RFC-003). - */ -/** Structured meta validation descriptor for `ExtractFn`; concrete validators are provider-defined. */ -export type Schema = { - readonly witness: T | null; -}; -export type ExtractFn = (raw: string, schema: Schema) => Promise; -export declare class ExtractError extends Error { - readonly raw: string; - readonly causeError: Error | null; - constructor(message: string, detail: { - raw: string; - causeError: Error | null; - }); -} -//# sourceMappingURL=agent.d.ts.map \ No newline at end of file diff --git a/packages/core/src/agent.d.ts.map b/packages/core/src/agent.d.ts.map deleted file mode 100644 index da8f9d5..0000000 --- a/packages/core/src/agent.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["agent.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,uGAAuG;AACvG,MAAM,MAAM,MAAM,CAAC,CAAC,IAAI;IACtB,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC;CAC5B,CAAC;AAEF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC;AAE1E,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,EAAE,KAAK,GAAG,IAAI,CAAC;gBAEtB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,KAAK,GAAG,IAAI,CAAA;KAAE;CAO/E"} \ No newline at end of file diff --git a/packages/core/src/config.d.ts b/packages/core/src/config.d.ts deleted file mode 100644 index 55533a8..0000000 --- a/packages/core/src/config.d.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { type DropOverflowConfig, type QueueOverflowConfig, type WorkflowConfig } from "@uncaged/workflow"; -import { type Result } from "./util.js"; -export type { DropOverflowConfig, QueueOverflowConfig, WorkflowConfig }; -export type SenseConfig = { - group: string; - throttle: number | null; - timeout: number | null; - gracePeriod: number | null; - /** Polling interval (ms). When set, the sense is triggered periodically. */ - interval: number | null; - /** Other sense names whose successful computes schedule this sense (kernel reverse-index). */ - on: string[]; -}; -/** Optional HTTP control plane. When `port` is null, the HTTP server is not started. */ -export type NerveApiConfig = { - port: number | null; - /** When set, HTTP API requires `Authorization: Bearer `. */ - token: string | null; - /** Bind address (e.g. `127.0.0.1`, `0.0.0.0`). Meaningful when `port` is set. */ - host: string; -}; -/** Adapter factory input (model, timeout); used by adapter packages (RFC-003). */ -export type AgentConfig = { - /** Adapter id (e.g. `cursor`, `hermes`, `echo`) — informational for factories that branch on type. */ - type: string; - /** Model id or `"auto"` for adapter defaults. */ - model: string; - /** Wall-clock cap in milliseconds, or `null` for adapter-specific default. */ - timeout: number | null; -}; -/** Global extract provider for typed meta from agent raw output (RFC-003). */ -export type ExtractConfig = { - provider: string; - model: string; -}; -/** - * Optional shell side effect after a successful sense `compute()`. - * Executed in the sense worker (`spawn` with `shell: true`, cwd = nerve root). - * Workflows are started only via CLI / daemon IPC, not from sense compute results. - */ -export type SenseTrigger = { - command: string; -}; -export type NerveConfig = { - /** Engine-wide default max moderator rounds (e.g. CLI workflow trigger when omitted). */ - maxRounds: number; - senses: Record; - workflows: Record; - api: NerveApiConfig; - /** Global extract defaults; `null` when the section is omitted. */ - extract: ExtractConfig | null; -}; -export type KnowledgeConfig = { - include: ReadonlyArray; - exclude: ReadonlyArray; -}; -export declare function parseNerveConfig(raw: string): Result; -/** - * Parse `knowledge.yaml` at the repo root (RFC-003 Knowledge Layer). - * `include` / `exclude` entries are glob patterns resolved against the repo root. - */ -export declare function parseKnowledgeYaml(raw: string): Result; -//# sourceMappingURL=config.d.ts.map \ No newline at end of file diff --git a/packages/core/src/config.d.ts.map b/packages/core/src/config.d.ts.map deleted file mode 100644 index e59e34c..0000000 --- a/packages/core/src/config.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["config.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACpB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,MAAM,EAAmD,MAAM,WAAW,CAAC;AAEzF,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,cAAc,EAAE,CAAC;AAExE,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,8FAA8F;IAC9F,EAAE,EAAE,MAAM,EAAE,CAAC;CACd,CAAC;AAEF,wFAAwF;AACxF,MAAM,MAAM,cAAc,GAAG;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,mEAAmE;IACnE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,iFAAiF;IACjF,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,kFAAkF;AAClF,MAAM,MAAM,WAAW,GAAG;IACxB,sGAAsG;IACtG,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB,CAAC;AAEF,8EAA8E;AAC9E,MAAM,MAAM,aAAa,GAAG;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,yFAAyF;IACzF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC1C,GAAG,EAAE,cAAc,CAAC;IACpB,mEAAmE;IACnE,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC;CAC/B,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAChC,CAAC;AAuQF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,WAAW,CAAC,CAwDjE;AAoBD;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,eAAe,CAAC,CA8BvE"} \ No newline at end of file diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 292ea6f..b33b7f6 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -1,14 +1,26 @@ import { parse } from "yaml"; -import { - DEFAULT_ENGINE_MAX_ROUNDS, - type DropOverflowConfig, - type QueueOverflowConfig, - type WorkflowConfig, -} from "@uncaged/workflow/public-types"; import { type Result, err, isPlainRecord, ok, parseDurationStringToMs } from "./util.js"; -export type { DropOverflowConfig, QueueOverflowConfig, WorkflowConfig }; +/** + * 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; + +/** Engine-wide fallback when nerve.yaml omits max_rounds (keep in sync with workflow package default). */ +export const DEFAULT_ENGINE_MAX_ROUNDS = 100; export type SenseConfig = { group: string; diff --git a/packages/core/src/daemon.d.ts b/packages/core/src/daemon.d.ts deleted file mode 100644 index 0746d31..0000000 --- a/packages/core/src/daemon.d.ts +++ /dev/null @@ -1,120 +0,0 @@ -/** - * Daemon Unix-socket IPC protocol (CLI → daemon). - * Newline-delimited JSON: one request object per line from the client, - * one response object per line from the daemon. - */ -import type { SenseInfo } from "./sense.js"; -/** Runtime status of a registered workflow (for listing / observability). */ -export type WorkflowStatus = { - name: string; - activeThreads: number; - /** Run IDs currently executing (same identifiers accepted by kill-workflow). */ - activeRunIds: string[]; - queuedThreads: number; - config: { - concurrency: number; - overflow: string; - }; -}; -/** Public health payload for HTTP / IPC. */ -export type HealthInfo = { - ok: boolean; - version: string; - uptime: number; - startedAt: string; - hostname: string; -}; -/** Client → daemon: start a workflow run. */ -export type DaemonIpcTriggerWorkflowRequest = { - type: "trigger-workflow"; - workflow: string; - prompt: string; - maxRounds: number; - dryRun: boolean; -}; -/** Client → daemon: run a sense compute on demand. */ -export type DaemonIpcTriggerSenseRequest = { - type: "trigger-sense"; - sense: string; -}; -/** Client → daemon: list registered senses. */ -export type DaemonIpcListSensesRequest = { - type: "list-senses"; -}; -/** Client → daemon: kill a running or queued workflow thread by runId. */ -export type DaemonIpcKillWorkflowRequest = { - type: "kill-workflow"; - runId: string; -}; -/** Client → daemon: list registered workflows and queue/active counts. */ -export type DaemonIpcListWorkflowsRequest = { - type: "list-workflows"; -}; -/** Client → daemon: public health snapshot. */ -export type DaemonIpcHealthRequest = { - type: "health"; -}; -/** Union of all JSON requests the daemon IPC server accepts. */ -export type DaemonIpcRequest = DaemonIpcTriggerWorkflowRequest | DaemonIpcTriggerSenseRequest | DaemonIpcListSensesRequest | DaemonIpcKillWorkflowRequest | DaemonIpcListWorkflowsRequest | DaemonIpcHealthRequest; -/** Successful trigger / trigger-sense reply (no body). */ -export type DaemonIpcTriggerOkResponse = { - ok: true; -}; -export type DaemonIpcErrorResponse = { - ok: false; - error: string; -}; -/** Replies for trigger-workflow and trigger-sense. */ -export type DaemonIpcTriggerResponse = DaemonIpcTriggerOkResponse | DaemonIpcErrorResponse; -/** Reply for list-senses. */ -export type DaemonIpcListSensesResponse = { - ok: true; - senses: SenseInfo[]; -} | DaemonIpcErrorResponse; -/** Reply for list-workflows. */ -export type DaemonIpcListWorkflowsResponse = { - ok: true; - workflows: WorkflowStatus[]; -} | DaemonIpcErrorResponse; -/** Reply for health. */ -export type DaemonIpcHealthResponse = { - ok: true; - health: HealthInfo; -} | DaemonIpcErrorResponse; -/** Any JSON response the daemon may write on the IPC socket. */ -export type DaemonIpcResponse = DaemonIpcTriggerOkResponse | DaemonIpcErrorResponse | DaemonIpcListSensesResponse | DaemonIpcListWorkflowsResponse | DaemonIpcHealthResponse; -export type DaemonTransportTriggerResult = { - ok: true; -} | { - ok: false; - error: string; -}; -export type DaemonTransportWorkflowLaunch = { - prompt: string; - maxRounds: number; - dryRun: boolean; -}; -/** - * Abstraction over daemon control plane (Unix socket IPC today, HTTP in Phase 2). - * Implementations live in CLI / tools; the daemon kernel uses shared handler logic. - */ -export type DaemonTransport = { - health(): Promise; - listSenses(): Promise; - listWorkflows(): Promise; - triggerSense(name: string): Promise; - /** When `launch` is null, implementations use engine defaults (empty prompt, default max rounds, dryRun false). */ - triggerWorkflow(name: string, launch: DaemonTransportWorkflowLaunch | null): Promise; - /** Kill a running or queued workflow thread by `runId` (same field as IPC `kill-workflow`). */ - killWorkflow(runId: string): Promise; -}; -/** - * Parse a single line of JSON into a {@link DaemonIpcRequest}, or null if invalid. - * Kept in core with the request types so CLI and daemon stay aligned at compile time. - */ -export declare function parseDaemonIpcRequest(line: string): DaemonIpcRequest | null; -/** Type guard for JSON {@link SenseInfo} payloads from daemon HTTP/IPC. */ -export declare function isSenseInfo(value: unknown): value is SenseInfo; -/** Type guard for JSON {@link WorkflowStatus} payloads from daemon HTTP/IPC. */ -export declare function isWorkflowStatus(value: unknown): value is WorkflowStatus; -//# sourceMappingURL=daemon.d.ts.map \ No newline at end of file diff --git a/packages/core/src/daemon.d.ts.map b/packages/core/src/daemon.d.ts.map deleted file mode 100644 index 3ce4770..0000000 --- a/packages/core/src/daemon.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"daemon.d.ts","sourceRoot":"","sources":["daemon.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/core/src/index.d.ts b/packages/core/src/index.d.ts deleted file mode 100644 index a73caa9..0000000 --- a/packages/core/src/index.d.ts +++ /dev/null @@ -1,21 +0,0 @@ -export type { SenseConfig, DropOverflowConfig, QueueOverflowConfig, WorkflowConfig, NerveApiConfig, AgentConfig, ExtractConfig, NerveConfig, SenseTrigger, } from "./config.js"; -export type { SenseInfo } from "./sense.js"; -export type { SenseComputeFn, SenseModule } from "./sense.js"; -export { senseTriggerLabels } from "./sense.js"; -export type { WorkflowMessage, RoleResult, Role, RoleMeta, StartStep, ThreadContext, WorkflowContext, AgentFn, RoleStep, ModeratorContext, Moderator, WorkflowDefinition, } from "@uncaged/workflow"; -export { START, END, DEFAULT_ENGINE_MAX_ROUNDS } from "@uncaged/workflow"; -export type { Schema, ExtractFn } from "./agent.js"; -export { ExtractError } from "./agent.js"; -export type { Result } from "./util.js"; -export { ok, err } from "./util.js"; -export { nerveCommandEnv, spawnSafe, type SpawnEnv, type SpawnError, type SpawnResult, type SpawnSafeOptions, } from "./util.js"; -export { parseNerveConfig } from "./config.js"; -export type { KnowledgeConfig } from "./config.js"; -export { parseKnowledgeYaml } from "./config.js"; -export { isPlainRecord } from "./util.js"; -export { parseSenseTrigger } from "./sense.js"; -export { isSenseInfo, isWorkflowStatus } from "./daemon.js"; -export type { WorkflowStatus, HealthInfo, DaemonIpcTriggerWorkflowRequest, DaemonIpcTriggerSenseRequest, DaemonIpcListSensesRequest, DaemonIpcKillWorkflowRequest, DaemonIpcListWorkflowsRequest, DaemonIpcHealthRequest, DaemonIpcRequest, DaemonIpcTriggerOkResponse, DaemonIpcErrorResponse, DaemonIpcTriggerResponse, DaemonIpcListSensesResponse, DaemonIpcListWorkflowsResponse, DaemonIpcHealthResponse, DaemonIpcResponse, } from "./daemon.js"; -export { parseDaemonIpcRequest } from "./daemon.js"; -export type { DaemonTransport, DaemonTransportTriggerResult, DaemonTransportWorkflowLaunch, } from "./daemon.js"; -//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/core/src/index.d.ts.map b/packages/core/src/index.d.ts.map deleted file mode 100644 index 3af0e82..0000000 --- a/packages/core/src/index.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,WAAW,EACX,aAAa,EACb,WAAW,EACX,YAAY,GACb,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAChD,YAAY,EACV,eAAe,EACf,UAAU,EACV,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,aAAa,EACb,eAAe,EACf,OAAO,EACP,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,kBAAkB,GACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC1E,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,YAAY,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AACxC,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,eAAe,EACf,SAAS,EACT,KAAK,QAAQ,EACb,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,gBAAgB,GACtB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,YAAY,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC5D,YAAY,EACV,cAAc,EACd,UAAU,EACV,+BAA+B,EAC/B,4BAA4B,EAC5B,0BAA0B,EAC1B,4BAA4B,EAC5B,6BAA6B,EAC7B,sBAAsB,EACtB,gBAAgB,EAChB,0BAA0B,EAC1B,sBAAsB,EACtB,wBAAwB,EACxB,2BAA2B,EAC3B,8BAA8B,EAC9B,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AACpD,YAAY,EACV,eAAe,EACf,4BAA4B,EAC5B,6BAA6B,GAC9B,MAAM,aAAa,CAAC"} \ No newline at end of file diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b0b8f3e..90da4bf 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,8 +1,5 @@ export type { SenseConfig, - DropOverflowConfig, - QueueOverflowConfig, - WorkflowConfig, NerveApiConfig, AgentConfig, ExtractConfig, @@ -12,21 +9,6 @@ export type { export type { SenseInfo } from "./sense.js"; export type { SenseComputeFn, SenseModule } from "./sense.js"; export { senseTriggerLabels } from "./sense.js"; -export type { - WorkflowMessage, - RoleResult, - Role, - RoleMeta, - StartStep, - ThreadContext, - WorkflowContext, - AgentFn, - RoleStep, - ModeratorContext, - Moderator, - WorkflowDefinition, -} from "@uncaged/workflow/public-types"; -export { START, END, DEFAULT_ENGINE_MAX_ROUNDS } from "@uncaged/workflow/public-types"; export type { Schema, ExtractFn } from "./agent.js"; export { ExtractError } from "./agent.js"; export type { Result } from "./util.js"; diff --git a/packages/core/src/sense.d.ts b/packages/core/src/sense.d.ts deleted file mode 100644 index 5562d57..0000000 --- a/packages/core/src/sense.d.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { SenseConfig, SenseTrigger } from "./config.js"; -import { type Result } from "./util.js"; -/** Runtime metadata for a sense (e.g. daemon list-senses IPC). */ -export type SenseInfo = { - name: string; - group: string; - throttle: number | null; - timeout: number | null; - /** Declarative schedule (`interval` / `on`) for this sense (derived from nerve.yaml). */ - triggers: ReadonlyArray; -}; -/** - * The function signature every sense `src/index.ts` must export as a named - * `compute` export. - * - * Pure: no DB, no peers. - * Returns the next sense state and an optional trigger (`trigger: null` means no side effect). - */ -export type SenseComputeFn = (state: S) => Promise<{ - state: S; - trigger: SenseTrigger | null; -}>; -/** - * The full shape a sense module (`src/index.ts`) must export. - */ -export type SenseModule = { - compute: SenseComputeFn; - initialState: S; -}; -/** Human-readable label for a sense schedule (`interval` and/or `on`). */ -export declare function labelSenseTrigger(slice: Pick): string; -/** - * Human-readable trigger labels for a sense from its `SenseConfig.interval` / `.on`. - * Returns an empty array when the sense is missing or has no schedule. - */ -export declare function senseTriggerLabels(senseName: string, senses: Record): string[]; -/** Validates `{ command: string }` from Sense compute or IPC (`trigger` field). */ -export declare function parseSenseTrigger(value: unknown): Result; -//# sourceMappingURL=sense.d.ts.map \ No newline at end of file diff --git a/packages/core/src/sense.d.ts.map b/packages/core/src/sense.d.ts.map deleted file mode 100644 index 4419f25..0000000 --- a/packages/core/src/sense.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"sense.d.ts","sourceRoot":"","sources":["sense.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,KAAK,MAAM,EAA0B,MAAM,WAAW,CAAC;AAEhE,kEAAkE;AAClE,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,yFAAyF;IACzF,QAAQ,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACjC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,GAAG,OAAO,IAAI,CACxC,KAAK,EAAE,CAAC,KACL,OAAO,CAAC;IAAE,KAAK,EAAE,CAAC,CAAC;IAAC,OAAO,EAAE,YAAY,GAAG,IAAI,CAAA;CAAE,CAAC,CAAC;AAEzD;;GAEG;AACH,MAAM,MAAM,WAAW,CAAC,CAAC,GAAG,OAAO,IAAI;IACrC,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IAC3B,YAAY,EAAE,CAAC,CAAC;CACjB,CAAC;AAYF,0EAA0E;AAC1E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,IAAI,CAAC,WAAW,EAAE,UAAU,GAAG,IAAI,CAAC,GAAG,MAAM,CAYrF;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAClC,MAAM,EAAE,CAKV;AAED,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAAC,YAAY,CAAC,CActE"} \ No newline at end of file diff --git a/packages/core/src/util.d.ts b/packages/core/src/util.d.ts deleted file mode 100644 index 560d9a3..0000000 --- a/packages/core/src/util.d.ts +++ /dev/null @@ -1,66 +0,0 @@ -export type Result = { - ok: true; - value: T; -} | { - ok: false; - error: E; -}; -/** Compatible with `process.env` for `child_process.spawn`. */ -export type SpawnEnv = Record; -export type SpawnResult = { - stdout: string; - stderr: string; - exitCode: number; - /** OS signal name (e.g. `"SIGTERM"`) when terminated by signal; otherwise `null`. */ - signal: string | null; -}; -export type SpawnError = { - kind: "non_zero_exit"; - stdout: string; - stderr: string; - exitCode: number; - signal: string | null; -} | { - kind: "timeout"; - stdout: string; - stderr: string; -} | { - kind: "spawn_failed"; - message: string; -} | { - kind: "aborted"; -}; -export type SpawnSafeOptions = { - cwd: string | null; - /** When null, merges {@link nerveCommandEnv} over `process.env`. When set, merges over that default. */ - env: SpawnEnv | null; - timeoutMs: number | null; - dryRun: boolean; - /** When non-null, child is terminated on abort; if `timeoutMs` is also null, no internal wall-clock timer is used. */ - abortSignal: AbortSignal | null; -}; -type SpawnSafeOptionsInput = SpawnSafeOptions | Omit; -export declare function ok(value: T): Result; -export declare function err(error: E): Result; -/** - * Narrows `unknown` to a plain JSON-style object (not null, not array). - * Use after `JSON.parse` / YAML / IPC when validating structure field-by-field. - */ -export declare function isPlainRecord(value: unknown): value is Record; -/** - * Parse a duration string such as `5s`, `10m`, `1h` to milliseconds. - * Used by `parseNerveConfig` sense/workflow duration fields. - */ -export declare function parseDurationStringToMs(value: string): Result; -/** - * PATH and PNPM_HOME for running `pnpm` and `nerve` from workflow roles. - * Uses the pnpm store home only (no npm user bin); binaries must resolve via PATH. - */ -export declare function nerveCommandEnv(): SpawnEnv; -/** - * Spawn a process with `shell: false` (argv only), default {@link nerveCommandEnv}, and optional timeout. - * Returns `ok` only when the process exits with code 0. - */ -export declare function spawnSafe(command: string, args: ReadonlyArray, options: SpawnSafeOptionsInput): Promise>; -export {}; -//# sourceMappingURL=util.d.ts.map \ No newline at end of file diff --git a/packages/core/src/util.d.ts.map b/packages/core/src/util.d.ts.map deleted file mode 100644 index 8f637fb..0000000 --- a/packages/core/src/util.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["util.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,MAAM,CAAC,CAAC,EAAE,CAAC,GAAG,KAAK,IAAI;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,CAAC,CAAA;CAAE,CAAC;AAEpF,+DAA+D;AAC/D,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;AAE1D,MAAM,MAAM,WAAW,GAAG;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,UAAU,GAClB;IACE,IAAI,EAAE,eAAe,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,GACD;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACnD;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GACzC;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,CAAC;AAExB,MAAM,MAAM,gBAAgB,GAAG;IAC7B,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,wGAAwG;IACxG,GAAG,EAAE,QAAQ,GAAG,IAAI,CAAC;IACrB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,MAAM,EAAE,OAAO,CAAC;IAChB,sHAAsH;IACtH,WAAW,EAAE,WAAW,GAAG,IAAI,CAAC;CACjC,CAAC;AAEF,KAAK,qBAAqB,GAAG,gBAAgB,GAAG,IAAI,CAAC,gBAAgB,EAAE,QAAQ,CAAC,CAAC;AAIjF,wBAAgB,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,KAAK,CAAC,CAEhD;AAED,wBAAgB,GAAG,CAAC,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAEzD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAE9E;AAUD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAMrE;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,QAAQ,CAQ1C;AAgCD;;;GAGG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,EAC3B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC,CAoG1C"} \ No newline at end of file diff --git a/packages/daemon/src/__tests__/crash-recovery.test.ts b/packages/daemon/src/__tests__/crash-recovery.test.ts index fee159c..252aba5 100644 --- a/packages/daemon/src/__tests__/crash-recovery.test.ts +++ b/packages/daemon/src/__tests__/crash-recovery.test.ts @@ -10,7 +10,8 @@ import { EventEmitter } from "node:events"; -import type { NerveConfig, WorkflowConfig } from "@uncaged/nerve-core"; +import type { NerveConfig } from "@uncaged/nerve-core"; +import type { WorkflowConfig } from "@uncaged/workflow"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; type MockChild = EventEmitter & { diff --git a/packages/daemon/src/__tests__/hot-reload.test.ts b/packages/daemon/src/__tests__/hot-reload.test.ts index 7225d8f..13eddde 100644 --- a/packages/daemon/src/__tests__/hot-reload.test.ts +++ b/packages/daemon/src/__tests__/hot-reload.test.ts @@ -15,7 +15,8 @@ import { mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { NerveConfig, WorkflowConfig } from "@uncaged/nerve-core"; +import type { NerveConfig } from "@uncaged/nerve-core"; +import type { WorkflowConfig } from "@uncaged/workflow"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; type MockChild = EventEmitter & { diff --git a/packages/daemon/src/__tests__/worker-runtime.test.ts b/packages/daemon/src/__tests__/worker-runtime.test.ts index e500b98..ed83756 100644 --- a/packages/daemon/src/__tests__/worker-runtime.test.ts +++ b/packages/daemon/src/__tests__/worker-runtime.test.ts @@ -1,7 +1,7 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import { afterEach, describe, expect, it, vi } from "vitest"; import { createWorkerRuntime } from "@uncaged/workflow"; +import { afterEach, describe, expect, it, vi } from "vitest"; const fixturesDir = join(dirname(fileURLToPath(import.meta.url)), "fixtures"); const echoWorkerPath = join(fixturesDir, "echo-worker.js"); diff --git a/packages/daemon/src/kernel.ts b/packages/daemon/src/kernel.ts index 558529e..68d4b52 100644 --- a/packages/daemon/src/kernel.ts +++ b/packages/daemon/src/kernel.ts @@ -18,6 +18,8 @@ import { import { createLogStore } from "@uncaged/nerve-store"; import type { LogStore } from "@uncaged/nerve-store"; +import { createWorkflowManager } from "@uncaged/workflow"; +import type { WorkflowManager } from "@uncaged/workflow"; import { createDaemonHandlers } from "./daemon-handlers.js"; import { createDaemonIpcServer } from "./daemon-ipc.js"; import type { DaemonIpcServer } from "./daemon-ipc.js"; @@ -36,8 +38,6 @@ import { import { createSenseScheduler } from "./sense-scheduler.js"; import type { SenseScheduler } from "./sense-scheduler.js"; import { createSenseWorkerPool, resolveWorkerScript } from "./worker-pool.js"; -import { createWorkflowManager } from "@uncaged/workflow"; -import type { WorkflowManager } from "@uncaged/workflow"; export type KernelHealth = { uptime: number; diff --git a/packages/daemon/src/worker-pool.ts b/packages/daemon/src/worker-pool.ts index 03d8dab..165e8ff 100644 --- a/packages/daemon/src/worker-pool.ts +++ b/packages/daemon/src/worker-pool.ts @@ -5,12 +5,12 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; -import type { ComputeMessage } from "./ipc.js"; import { createWorkerRuntime, formatCapturedStderrTail, formatChildExitSummary, } from "@uncaged/workflow"; +import type { ComputeMessage } from "./ipc.js"; export function resolveWorkerScript(): string { const __filename = fileURLToPath(import.meta.url); diff --git a/packages/role-committer/package.json b/packages/role-committer/package.json index a7221d4..3869790 100644 --- a/packages/role-committer/package.json +++ b/packages/role-committer/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@uncaged/nerve-core": "workspace:*", + "@uncaged/workflow": "workspace:*", "@uncaged/nerve-workflow-utils": "workspace:*", "zod": "^4.3.6" }, diff --git a/packages/role-committer/src/index.ts b/packages/role-committer/src/index.ts index 6c39e84..6c16824 100644 --- a/packages/role-committer/src/index.ts +++ b/packages/role-committer/src/index.ts @@ -1,6 +1,6 @@ -import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole, decorateRole, onFail, withDryRun } from "@uncaged/nerve-workflow-utils"; +import type { AgentFn, Role, ThreadContext } from "@uncaged/workflow"; import { z } from "zod"; export const committerMetaSchema = z.object({ diff --git a/packages/role-reviewer/package.json b/packages/role-reviewer/package.json index 211f413..a5d123c 100644 --- a/packages/role-reviewer/package.json +++ b/packages/role-reviewer/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@uncaged/nerve-core": "workspace:*", + "@uncaged/workflow": "workspace:*", "@uncaged/nerve-workflow-utils": "workspace:*", "zod": "^4.3.6" }, diff --git a/packages/role-reviewer/src/reviewer.ts b/packages/role-reviewer/src/reviewer.ts index 449c6a4..4ee3cb1 100644 --- a/packages/role-reviewer/src/reviewer.ts +++ b/packages/role-reviewer/src/reviewer.ts @@ -1,6 +1,6 @@ -import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole } from "@uncaged/nerve-workflow-utils"; +import type { AgentFn, Role, ThreadContext } from "@uncaged/workflow"; import { z } from "zod"; export const reviewerMetaSchema = z.object({ diff --git a/packages/store/src/blob-store.d.ts b/packages/store/src/blob-store.d.ts deleted file mode 100644 index 605c004..0000000 --- a/packages/store/src/blob-store.d.ts +++ /dev/null @@ -1,17 +0,0 @@ -/** - * CAS blob store — sha256 content-addressable files under `data/blobs/`. - * - * Layout: `/<2-hex-shard>/<62-hex-rest>` (RFC-001 §8). - */ -export type BlobStore = { - /** Persist UTF-8 or raw bytes; returns lowercase hex sha256. Idempotent for identical content. */ - write: (content: string | Uint8Array | Buffer) => string; - /** Returns bytes or null if the hash is invalid or no blob exists. Verifies digest matches path. */ - read: (hash: string) => Buffer | null; - /** True when hash is well-formed and the blob file is present. */ - exists: (hash: string) => boolean; -}; -/** @returns normalized lowercase hex or null if not a valid sha256 hex string */ -export declare function normalizeBlobHash(hash: string): string | null; -export declare function createBlobStore(blobsRoot: string): BlobStore; -//# sourceMappingURL=blob-store.d.ts.map \ No newline at end of file diff --git a/packages/store/src/blob-store.d.ts.map b/packages/store/src/blob-store.d.ts.map deleted file mode 100644 index d2169b0..0000000 --- a/packages/store/src/blob-store.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"blob-store.d.ts","sourceRoot":"","sources":["blob-store.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAgBH,MAAM,MAAM,SAAS,GAAG;IACtB,kGAAkG;IAClG,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,KAAK,MAAM,CAAC;IACzD,oGAAoG;IACpG,IAAI,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,GAAG,IAAI,CAAC;IACtC,kEAAkE;IAClE,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC,CAAC;AAYF,iFAAiF;AACjF,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAK7D;AAiBD,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,CA2C5D"} \ No newline at end of file diff --git a/packages/store/src/index.d.ts b/packages/store/src/index.d.ts deleted file mode 100644 index 2834dbd..0000000 --- a/packages/store/src/index.d.ts +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @uncaged/nerve-store — append-only log storage, cold-archive helpers, CAS blob store. - */ -export * from "./blob-store.js"; -export * from "./log-archive.js"; -export { createLogStore } from "./log-store.js"; -export type { GetThreadRoundsParams, LogEntry, LogQuery, LogStore, ThreadRoundRow, WorkflowRun, WorkflowRunStatus, } from "./log-store.js"; -//# sourceMappingURL=index.d.ts.map \ No newline at end of file diff --git a/packages/store/src/index.d.ts.map b/packages/store/src/index.d.ts.map deleted file mode 100644 index 5946df7..0000000 --- a/packages/store/src/index.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,YAAY,EACV,qBAAqB,EACrB,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,cAAc,EACd,WAAW,EACX,iBAAiB,GAClB,MAAM,gBAAgB,CAAC"} \ No newline at end of file diff --git a/packages/store/src/log-archive.d.ts b/packages/store/src/log-archive.d.ts deleted file mode 100644 index b7a6ee1..0000000 --- a/packages/store/src/log-archive.d.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** Log cold-archive helpers (RFC-001 §5.4) — UTC calendar days, JSONL export. */ -export declare const LOG_ARCHIVE_META_KEY = "archived_up_to"; -export declare const DEFAULT_LOG_RETENTION_MS: number; -export type ArchiveLogsOptions = { - /** Wall clock for retention boundary (default: `Date.now()`). */ - now?: number; - /** Run `VACUUM` after archiving (outside the per-day transaction). */ - vacuum?: boolean; - /** Max UTC days to process in one call (default: unlimited). */ - maxDays?: number; - /** Override default 30-day retention (tests). */ - retentionMs?: number; -}; -export type ArchiveLogsDayResult = { - day: string; - rowCount: number; - filePath: string; -}; -export type ArchiveLogsResult = { - days: ArchiveLogsDayResult[]; - vacuumed: boolean; -}; -export declare function utcDateStringFromMs(ms: number): string; -export declare function assertValidUtcDay(day: string): void; -export declare function utcDayStartMs(day: string): number; -export declare function utcDayEndExclusiveMs(day: string): number; -export declare function prevUtcDay(day: string): string; -export declare function nextUtcDay(day: string): string; -/** Last UTC calendar day D such that the exclusive end of D is ≤ boundaryMs. */ -export declare function lastArchivableUtcDay(boundaryMs: number): string; -export declare function compareIsoDays(a: string, b: string): number; -//# sourceMappingURL=log-archive.d.ts.map \ No newline at end of file diff --git a/packages/store/src/log-archive.d.ts.map b/packages/store/src/log-archive.d.ts.map deleted file mode 100644 index f4d0fc1..0000000 --- a/packages/store/src/log-archive.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"log-archive.d.ts","sourceRoot":"","sources":["log-archive.ts"],"names":[],"mappings":"AAAA,iFAAiF;AAEjF,eAAO,MAAM,oBAAoB,mBAAmB,CAAC;AAErD,eAAO,MAAM,wBAAwB,QAAkB,CAAC;AAExD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,gEAAgE;IAChE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,EAAE,oBAAoB,EAAE,CAAC;IAC7B,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAEtD;AAiBD,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAGjD;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAExD;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAI3D"} \ No newline at end of file diff --git a/packages/store/src/log-store.d.ts b/packages/store/src/log-store.d.ts deleted file mode 100644 index 861bbd9..0000000 --- a/packages/store/src/log-store.d.ts +++ /dev/null @@ -1,134 +0,0 @@ -/** - * Log Store — append-only structured log storage backed by SQLite. - * - * Stores system, sense-scheduler (`sense_scheduler` source), sense, and workflow log entries in a single table. - * Logs are data assets for audit/analysis — they MUST NOT feed back into scheduling or workflows as triggers. - * - * Also provides a `meta` key-value table for bookkeeping (e.g. archive watermarks). - */ -import type { ArchiveLogsOptions, ArchiveLogsResult } from "./log-archive.js"; -export { LOG_ARCHIVE_META_KEY } from "./log-archive.js"; -export type { ArchiveLogsDayResult, ArchiveLogsOptions, ArchiveLogsResult } from "./log-archive.js"; -export type LogEntry = { - id?: number; - source: string; - type: string; - refId: string | null; - payload: string | null; - timestamp: number; -}; -export type LogQuery = { - source?: string; - type?: string; - refId?: string; - since?: number; - until?: number; - limit?: number; -}; -export type WorkflowRunStatus = "queued" | "started" | "completed" | "failed" | "crashed" | "dropped" | "interrupted" | "killed"; -/** One row in the workflow_runs materialized table. */ -export type WorkflowRun = { - runId: string; - workflow: string; - status: WorkflowRunStatus; - timestamp: number; - exitCode: number | null; -}; -/** One role-produced workflow-message row with 1-based round index (ROW_NUMBER over role messages only). */ -export type ThreadRoundRow = { - round: number; - logId: number; - timestamp: number; - message: { - role: string; - content: string; - meta: unknown; - timestamp: number; - }; -}; -/** Parameters for {@link LogStore.getThreadRounds}. */ -export type GetThreadRoundsParams = { - /** - * Exclusive upper bound on round index (1-based among role events). - * Use `0` to include all rounds (subject to `limit`). - */ - before: number; - /** Maximum rows returned from the DB (DESC by round). */ - limit: number; -}; -export type LogStore = { - append: (entry: Omit) => LogEntry; - query: (filter?: LogQuery) => LogEntry[]; - getMeta: (key: string) => string | null; - setMeta: (key: string, value: string) => void; - /** - * Append a workflow log event and atomically upsert the workflow_runs - * materialized table — both in a single SQLite transaction (RFC-002 §6.2). - */ - upsertWorkflowRun: (entry: Omit, run: WorkflowRun) => LogEntry; - /** - * Alias for upsertWorkflowRun — append a log entry and update workflow_runs - * in one atomic transaction. - */ - appendWithWorkflowUpdate: (entry: Omit, run: WorkflowRun) => LogEntry; - /** Get the current materialized state of a specific workflow run. */ - getWorkflowRun: (runId: string) => WorkflowRun | null; - /** - * Get all workflow runs with status 'queued' or 'started'. - * Optionally filter by workflow name. - */ - getActiveWorkflowRuns: (workflowName?: string) => WorkflowRun[]; - /** - * Get all workflow runs regardless of status, sorted by timestamp descending. - * Optionally filter by workflow name. - */ - getAllWorkflowRuns: (workflowName: string | null) => WorkflowRun[]; - /** - * Get the trigger payload for a workflow run (stored in the 'started' log entry). - * Returns null if not found. - */ - getTriggerPayload: (runId: string) => unknown; - /** - * Get all workflow CommandEvents for a specific run, ordered by id ASC. - * @deprecated Use getThreadMessages for the new WorkflowMessage format. - */ - getThreadEvents: (runId: string) => Array<{ - type: string; - [key: string]: unknown; - }>; - /** - * Get all WorkflowMessages for a specific run, ordered by id ASC. - * Used for crash recovery to rebuild the message chain. - */ - getThreadMessages: (runId: string) => Array<{ - role: string; - content: string; - meta: unknown; - timestamp: number; - }>; - /** - * Count role command events for a run (excludes `thread_start`/`__start__` messages and invalid payloads). - * Round indices for {@link getThreadRounds} are 1..count in chronological order. - */ - getThreadRoundCount: (runId: string) => number; - /** - * Role rounds for agent-oriented retrieval: each row is one `thread_command_event` or - * `thread_workflow_message` whose JSON `type` is not `thread_start` and `role` is not `__start__`, - * with `round` from ROW_NUMBER() OVER (ORDER BY id ASC). No schema migration — numbering is computed in SQL. - */ - getThreadRounds: (runId: string, params: GetThreadRoundsParams) => ThreadRoundRow[]; - /** - * The workflow `__start__` message for a run (if persisted), as a {@link ThreadRoundRow} - * with `round` 0 — not part of {@link getThreadRoundCount} / {@link getThreadRounds} numbering. - */ - getThreadStartMessage: (runId: string) => ThreadRoundRow | null; - /** - * Export logs older than the retention window to `data/archive/logs/YYYY-MM-DD.jsonl`, - * then delete those rows and advance `meta.archived_up_to` in one transaction per day - * (RFC-001 §5.4). - */ - archiveLogs: (options?: ArchiveLogsOptions) => ArchiveLogsResult; - close: () => void; -}; -export declare function createLogStore(dbPath: string): LogStore; -//# sourceMappingURL=log-store.d.ts.map \ No newline at end of file diff --git a/packages/store/src/log-store.d.ts.map b/packages/store/src/log-store.d.ts.map deleted file mode 100644 index 61de455..0000000 --- a/packages/store/src/log-store.d.ts.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"log-store.d.ts","sourceRoot":"","sources":["log-store.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/packages/workflow-meta/package.json b/packages/workflow-meta/package.json index 987c79a..adbb459 100644 --- a/packages/workflow-meta/package.json +++ b/packages/workflow-meta/package.json @@ -15,6 +15,7 @@ }, "dependencies": { "@uncaged/nerve-core": "workspace:*", + "@uncaged/workflow": "workspace:*", "@uncaged/nerve-role-committer": "workspace:*", "@uncaged/nerve-role-reviewer": "workspace:*", "@uncaged/nerve-workflow-utils": "workspace:*", diff --git a/packages/workflow-meta/src/develop-sense/build.ts b/packages/workflow-meta/src/develop-sense/build.ts index 526c6a7..30d6166 100644 --- a/packages/workflow-meta/src/develop-sense/build.ts +++ b/packages/workflow-meta/src/develop-sense/build.ts @@ -1,7 +1,7 @@ -import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core"; import { createCommitterRole } from "@uncaged/nerve-role-committer"; import { createReviewerRole } from "@uncaged/nerve-role-reviewer"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; +import type { AgentFn, WorkflowDefinition } from "@uncaged/workflow"; import { moderator } from "./moderator.js"; import type { SenseMeta } from "./moderator.js"; diff --git a/packages/workflow-meta/src/develop-sense/moderator.ts b/packages/workflow-meta/src/develop-sense/moderator.ts index f24d46e..bde617a 100644 --- a/packages/workflow-meta/src/develop-sense/moderator.ts +++ b/packages/workflow-meta/src/develop-sense/moderator.ts @@ -1,7 +1,7 @@ -import { END } from "@uncaged/nerve-core"; -import type { Moderator } from "@uncaged/nerve-core"; import type { CommitterMeta } from "@uncaged/nerve-role-committer"; import type { ReviewerMeta } from "@uncaged/nerve-role-reviewer"; +import { END } from "@uncaged/workflow"; +import type { Moderator } from "@uncaged/workflow"; import type { CoderMeta } from "./roles/coder.js"; import type { PlannerMeta } from "./roles/planner.js"; import type { TesterMeta } from "./roles/tester.js"; diff --git a/packages/workflow-meta/src/develop-sense/roles/coder.ts b/packages/workflow-meta/src/develop-sense/roles/coder.ts index 3254414..ef39cb3 100644 --- a/packages/workflow-meta/src/develop-sense/roles/coder.ts +++ b/packages/workflow-meta/src/develop-sense/roles/coder.ts @@ -1,6 +1,6 @@ -import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole } from "@uncaged/nerve-workflow-utils"; +import type { AgentFn, Role, ThreadContext } from "@uncaged/workflow"; import { z } from "zod"; export const coderMetaSchema = z.object({ diff --git a/packages/workflow-meta/src/develop-sense/roles/planner.ts b/packages/workflow-meta/src/develop-sense/roles/planner.ts index bf75bac..1fdba57 100644 --- a/packages/workflow-meta/src/develop-sense/roles/planner.ts +++ b/packages/workflow-meta/src/develop-sense/roles/planner.ts @@ -1,6 +1,6 @@ -import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole } from "@uncaged/nerve-workflow-utils"; +import type { AgentFn, Role, ThreadContext } from "@uncaged/workflow"; import { z } from "zod"; export const plannerMetaSchema = z.object({ diff --git a/packages/workflow-meta/src/develop-sense/roles/tester.ts b/packages/workflow-meta/src/develop-sense/roles/tester.ts index c96ab0e..2fe10bc 100644 --- a/packages/workflow-meta/src/develop-sense/roles/tester.ts +++ b/packages/workflow-meta/src/develop-sense/roles/tester.ts @@ -1,6 +1,6 @@ -import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole } from "@uncaged/nerve-workflow-utils"; +import type { AgentFn, Role, ThreadContext } from "@uncaged/workflow"; import { z } from "zod"; export const testerMetaSchema = z.object({ diff --git a/packages/workflow-meta/src/develop-workflow/build.ts b/packages/workflow-meta/src/develop-workflow/build.ts index 3659368..332180e 100644 --- a/packages/workflow-meta/src/develop-workflow/build.ts +++ b/packages/workflow-meta/src/develop-workflow/build.ts @@ -1,7 +1,7 @@ -import type { AgentFn, WorkflowDefinition } from "@uncaged/nerve-core"; import { createCommitterRole } from "@uncaged/nerve-role-committer"; import { createReviewerRole } from "@uncaged/nerve-role-reviewer"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; +import type { AgentFn, WorkflowDefinition } from "@uncaged/workflow"; import { moderator } from "./moderator.js"; import type { WorkflowMeta } from "./moderator.js"; diff --git a/packages/workflow-meta/src/develop-workflow/moderator.ts b/packages/workflow-meta/src/develop-workflow/moderator.ts index a55fa15..f28a2b7 100644 --- a/packages/workflow-meta/src/develop-workflow/moderator.ts +++ b/packages/workflow-meta/src/develop-workflow/moderator.ts @@ -1,7 +1,7 @@ -import { END } from "@uncaged/nerve-core"; -import type { Moderator } from "@uncaged/nerve-core"; import type { CommitterMeta } from "@uncaged/nerve-role-committer"; import type { ReviewerMeta } from "@uncaged/nerve-role-reviewer"; +import { END } from "@uncaged/workflow"; +import type { Moderator } from "@uncaged/workflow"; import type { CoderMeta } from "./roles/coder.js"; import type { PlannerMeta } from "./roles/planner.js"; import type { TesterMeta } from "./roles/tester.js"; diff --git a/packages/workflow-meta/src/develop-workflow/roles/coder.ts b/packages/workflow-meta/src/develop-workflow/roles/coder.ts index ec746c2..7a60bb9 100644 --- a/packages/workflow-meta/src/develop-workflow/roles/coder.ts +++ b/packages/workflow-meta/src/develop-workflow/roles/coder.ts @@ -1,6 +1,6 @@ -import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole } from "@uncaged/nerve-workflow-utils"; +import type { AgentFn, Role, ThreadContext } from "@uncaged/workflow"; import { z } from "zod"; export const coderMetaSchema = z.object({ diff --git a/packages/workflow-meta/src/develop-workflow/roles/planner.ts b/packages/workflow-meta/src/develop-workflow/roles/planner.ts index eadc893..00edbf5 100644 --- a/packages/workflow-meta/src/develop-workflow/roles/planner.ts +++ b/packages/workflow-meta/src/develop-workflow/roles/planner.ts @@ -1,6 +1,6 @@ -import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole } from "@uncaged/nerve-workflow-utils"; +import type { AgentFn, Role, ThreadContext } from "@uncaged/workflow"; import { z } from "zod"; export const plannerMetaSchema = z.object({ diff --git a/packages/workflow-meta/src/develop-workflow/roles/tester.ts b/packages/workflow-meta/src/develop-workflow/roles/tester.ts index 8902732..27af4a0 100644 --- a/packages/workflow-meta/src/develop-workflow/roles/tester.ts +++ b/packages/workflow-meta/src/develop-workflow/roles/tester.ts @@ -1,6 +1,6 @@ -import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core"; import type { LlmExtractorConfig } from "@uncaged/nerve-workflow-utils"; import { createRole } from "@uncaged/nerve-workflow-utils"; +import type { AgentFn, Role, ThreadContext } from "@uncaged/workflow"; import { z } from "zod"; export const testerMetaSchema = z.object({ diff --git a/packages/workflow-utils/package.json b/packages/workflow-utils/package.json index dca8c8c..faa7e64 100644 --- a/packages/workflow-utils/package.json +++ b/packages/workflow-utils/package.json @@ -24,6 +24,7 @@ "@uncaged/nerve-adapter-cursor": "workspace:*", "@uncaged/nerve-adapter-hermes": "workspace:*", "@uncaged/nerve-core": "workspace:*", + "@uncaged/workflow": "workspace:*", "zod": "^4.3.6" }, "devDependencies": { diff --git a/packages/workflow-utils/src/__tests__/create-llm-adapter.test.ts b/packages/workflow-utils/src/__tests__/create-llm-adapter.test.ts index f0d3a90..4fdbd15 100644 --- a/packages/workflow-utils/src/__tests__/create-llm-adapter.test.ts +++ b/packages/workflow-utils/src/__tests__/create-llm-adapter.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { START, type ThreadContext } from "@uncaged/nerve-core"; +import { START, type ThreadContext } from "@uncaged/workflow"; import { createLlmAdapter } from "../create-llm-adapter.js"; diff --git a/packages/workflow-utils/src/__tests__/create-role.test.ts b/packages/workflow-utils/src/__tests__/create-role.test.ts index c2aca98..06811fc 100644 --- a/packages/workflow-utils/src/__tests__/create-role.test.ts +++ b/packages/workflow-utils/src/__tests__/create-role.test.ts @@ -4,8 +4,8 @@ import type { RoleMeta, ThreadContext, WorkflowDefinition, -} from "@uncaged/nerve-core"; -import { END, START } from "@uncaged/nerve-core"; +} from "@uncaged/workflow"; +import { END, START } from "@uncaged/workflow"; import { afterEach, describe, expect, it, vi } from "vitest"; import { z } from "zod"; diff --git a/packages/workflow-utils/src/__tests__/role-decorators.test.ts b/packages/workflow-utils/src/__tests__/role-decorators.test.ts index 26aa03e..1e3a38f 100644 --- a/packages/workflow-utils/src/__tests__/role-decorators.test.ts +++ b/packages/workflow-utils/src/__tests__/role-decorators.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from "vitest"; -import type { Role, ThreadContext } from "@uncaged/nerve-core"; +import type { Role, ThreadContext } from "@uncaged/workflow"; -import { START } from "@uncaged/nerve-core"; +import { START } from "@uncaged/workflow"; import { decorateRole, onFail, withDryRun } from "../role-decorators.js"; type TestMeta = Record & { ok: boolean }; diff --git a/packages/workflow-utils/src/__tests__/role-factories.test.ts b/packages/workflow-utils/src/__tests__/role-factories.test.ts index 6494ca8..f601837 100644 --- a/packages/workflow-utils/src/__tests__/role-factories.test.ts +++ b/packages/workflow-utils/src/__tests__/role-factories.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { z } from "zod"; -import { START, type ThreadContext } from "@uncaged/nerve-core"; +import { START, type ThreadContext } from "@uncaged/workflow"; import { createCursorRole } from "../role-cursor.js"; import { createHermesRole } from "../role-hermes.js"; diff --git a/packages/workflow-utils/src/create-llm-adapter.ts b/packages/workflow-utils/src/create-llm-adapter.ts index bcab534..0e3c771 100644 --- a/packages/workflow-utils/src/create-llm-adapter.ts +++ b/packages/workflow-utils/src/create-llm-adapter.ts @@ -1,4 +1,4 @@ -import type { AgentFn, ThreadContext } from "@uncaged/nerve-core"; +import type { AgentFn, ThreadContext } from "@uncaged/workflow"; import { formatLlmError } from "./shared/format-error.js"; import { chatCompletionText } from "./shared/llm-chat.js"; diff --git a/packages/workflow-utils/src/create-role.ts b/packages/workflow-utils/src/create-role.ts index c3768a7..18648b3 100644 --- a/packages/workflow-utils/src/create-role.ts +++ b/packages/workflow-utils/src/create-role.ts @@ -1,4 +1,4 @@ -import type { AgentFn, Role, ThreadContext } from "@uncaged/nerve-core"; +import type { AgentFn, Role, ThreadContext } from "@uncaged/workflow"; import type { z } from "zod"; import { extractMetaOrThrow } from "./shared/extract-fn.js"; diff --git a/packages/workflow-utils/src/role-cursor.ts b/packages/workflow-utils/src/role-cursor.ts index 72673c5..33da265 100644 --- a/packages/workflow-utils/src/role-cursor.ts +++ b/packages/workflow-utils/src/role-cursor.ts @@ -1,5 +1,6 @@ import { type CursorAgentMode, cursorAgent } from "@uncaged/nerve-adapter-cursor"; -import type { Role, SpawnEnv } from "@uncaged/nerve-core"; +import type { SpawnEnv } from "@uncaged/nerve-core"; +import type { Role } from "@uncaged/workflow"; import type { CursorRoleDefaults, CursorRoleRequired } from "./role-types.js"; import { formatLlmError } from "./shared/format-error.js"; diff --git a/packages/workflow-utils/src/role-decorators.ts b/packages/workflow-utils/src/role-decorators.ts index b8155a0..3b2bfea 100644 --- a/packages/workflow-utils/src/role-decorators.ts +++ b/packages/workflow-utils/src/role-decorators.ts @@ -1,4 +1,4 @@ -import type { Role, ThreadContext } from "@uncaged/nerve-core"; +import type { Role, ThreadContext } from "@uncaged/workflow"; // --------------------------------------------------------------------------- // Decorator types diff --git a/packages/workflow-utils/src/role-hermes.ts b/packages/workflow-utils/src/role-hermes.ts index d23ee80..c2a4cd6 100644 --- a/packages/workflow-utils/src/role-hermes.ts +++ b/packages/workflow-utils/src/role-hermes.ts @@ -1,4 +1,4 @@ -import type { Role } from "@uncaged/nerve-core"; +import type { Role } from "@uncaged/workflow"; import type { HermesRoleDefaults, HermesRoleRequired } from "./role-types.js"; import { formatLlmError } from "./shared/format-error.js"; diff --git a/packages/workflow-utils/src/role-llm.ts b/packages/workflow-utils/src/role-llm.ts index 9ebb54e..cf66882 100644 --- a/packages/workflow-utils/src/role-llm.ts +++ b/packages/workflow-utils/src/role-llm.ts @@ -1,4 +1,4 @@ -import type { Role } from "@uncaged/nerve-core"; +import type { Role } from "@uncaged/workflow"; import type { LlmMessage, LlmRoleDefaults, LlmRoleRequired } from "./role-types.js"; import { formatLlmError } from "./shared/format-error.js"; diff --git a/packages/workflow-utils/src/role-react.ts b/packages/workflow-utils/src/role-react.ts index 579a90d..75ba8b1 100644 --- a/packages/workflow-utils/src/role-react.ts +++ b/packages/workflow-utils/src/role-react.ts @@ -1,4 +1,4 @@ -import type { Role } from "@uncaged/nerve-core"; +import type { Role } from "@uncaged/workflow"; import type { LlmMessage, ReActRoleDefaults, ReActRoleRequired } from "./role-types.js"; import { formatLlmError } from "./shared/format-error.js"; diff --git a/packages/workflow-utils/src/role-types.ts b/packages/workflow-utils/src/role-types.ts index be540c8..a060aba 100644 --- a/packages/workflow-utils/src/role-types.ts +++ b/packages/workflow-utils/src/role-types.ts @@ -1,4 +1,5 @@ -import type { SpawnEnv, ThreadContext } from "@uncaged/nerve-core"; +import type { SpawnEnv } from "@uncaged/nerve-core"; +import type { ThreadContext } from "@uncaged/workflow"; import type { z } from "zod"; import type { LlmProvider } from "./shared/llm-extract.js"; diff --git a/packages/workflow/package.json b/packages/workflow/package.json index 84fc580..531e20a 100644 --- a/packages/workflow/package.json +++ b/packages/workflow/package.json @@ -25,8 +25,8 @@ "build:public-types": "tsc -p tsconfig.public-types.json" }, "dependencies": { - "@uncaged/nerve-core": "workspace:*", - "@uncaged/nerve-store": "workspace:*" + "@uncaged/nerve-core": "^0.5.0", + "@uncaged/nerve-store": "^0.5.0" }, "devDependencies": { "@rslib/core": "^0.21.3", diff --git a/packages/workflow/src/ipc.ts b/packages/workflow/src/ipc.ts index 94da576..a216589 100644 --- a/packages/workflow/src/ipc.ts +++ b/packages/workflow/src/ipc.ts @@ -238,7 +238,9 @@ function parseWorkflowErrorMsg(obj: Record): Result): Result { +function parseThreadWorkflowMessageMsg( + obj: Record, +): Result { if (typeof obj.runId !== "string") { return err(new Error("Worker 'thread-workflow-message' missing string 'runId' field")); } @@ -297,9 +299,7 @@ export function parseWorkflowWorkerToParentMessage( } /** Parse messages from a workflow worker child (thread IPC + optional `ready`). */ -export function parseWorkflowChildMessage( - raw: unknown, -): Result { +export function parseWorkflowChildMessage(raw: unknown): Result { if (!isPlainRecord(raw)) { return err(new Error("Worker IPC message is not an object")); } diff --git a/packages/workflow/src/manager.ts b/packages/workflow/src/manager.ts index 1e55270..1f22044 100644 --- a/packages/workflow/src/manager.ts +++ b/packages/workflow/src/manager.ts @@ -6,17 +6,13 @@ * Concurrency and overflow (drop/queue) are enforced here in the parent process. */ -import type { NerveConfig, WorkflowConfig, WorkflowStatus } from "@uncaged/nerve-core"; +import type { NerveConfig, WorkflowStatus } from "@uncaged/nerve-core"; + +import type { WorkflowConfig } from "./config.js"; import type { LogStore } from "@uncaged/nerve-store"; import type { KillThreadMessage, StartThreadMessage, ThreadEventMessage } from "./ipc.js"; import { parseWorkflowChildMessage } from "./ipc.js"; -import { - createWorkerRuntime, - formatCapturedStderrTail, - formatChildExitSummary, -} from "./worker-runtime.js"; -import { WORKFLOW_WORKER_PATH } from "./paths.js"; import { DEFAULT_MAX_QUEUE, WORKER_SHUTDOWN_TIMEOUT_MS, @@ -27,6 +23,12 @@ import { extractExitCodeFromPayload, recoverThreadsFromStore, } from "./manager-support.js"; +import { WORKFLOW_WORKER_PATH } from "./paths.js"; +import { + createWorkerRuntime, + formatCapturedStderrTail, + formatChildExitSummary, +} from "./worker-runtime.js"; export type WorkflowLaunchParams = { prompt: string; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1032bbf..17d5c24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,10 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@uncaged/nerve-core': workspace:* + '@uncaged/nerve-store': workspace:* + importers: .: @@ -26,6 +30,9 @@ importers: '@uncaged/nerve-core': specifier: workspace:* version: link:../core + '@uncaged/workflow': + specifier: workspace:* + version: link:../workflow devDependencies: '@rslib/core': specifier: ^0.21.3 @@ -42,6 +49,9 @@ importers: '@uncaged/nerve-core': specifier: workspace:* version: link:../core + '@uncaged/workflow': + specifier: workspace:* + version: link:../workflow devDependencies: '@rslib/core': specifier: ^0.21.3 @@ -61,6 +71,9 @@ importers: '@uncaged/nerve-store': specifier: workspace:* version: link:../store + '@uncaged/workflow': + specifier: workspace:* + version: link:../workflow citty: specifier: ^0.1.6 version: 0.1.6 @@ -80,18 +93,12 @@ importers: '@uncaged/nerve-daemon': specifier: workspace:* version: link:../daemon - '@uncaged/workflow': - specifier: workspace:* - version: link:../workflow vitest: specifier: ^4.1.5 version: 4.1.5(@types/node@22.19.17)(vite@8.0.9(@types/node@22.19.17)(esbuild@0.27.7)(yaml@2.8.3)) packages/core: dependencies: - '@uncaged/workflow': - specifier: workspace:* - version: link:../workflow yaml: specifier: ^2.8.3 version: 2.8.3 @@ -170,6 +177,9 @@ importers: '@uncaged/nerve-workflow-utils': specifier: workspace:* version: link:../workflow-utils + '@uncaged/workflow': + specifier: workspace:* + version: link:../workflow zod: specifier: ^4.3.6 version: 4.3.6 @@ -192,6 +202,9 @@ importers: '@uncaged/nerve-workflow-utils': specifier: workspace:* version: link:../workflow-utils + '@uncaged/workflow': + specifier: workspace:* + version: link:../workflow zod: specifier: ^4.3.6 version: 4.3.6 @@ -254,6 +267,9 @@ importers: '@uncaged/nerve-workflow-utils': specifier: workspace:* version: link:../workflow-utils + '@uncaged/workflow': + specifier: workspace:* + version: link:../workflow zod: specifier: ^4.3.6 version: 4.3.6 @@ -279,6 +295,9 @@ importers: '@uncaged/nerve-core': specifier: workspace:* version: link:../core + '@uncaged/workflow': + specifier: workspace:* + version: link:../workflow zod: specifier: ^4.3.6 version: 4.3.6 -- 2.43.0