refactor(core): remove WorkflowTrigger from SenseTrigger — shell only
Senses trigger shell commands only. Workflows are invoked via CLI.
SenseTrigger is now { command: string } — no discriminated union.
Closes #318
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -212,10 +212,10 @@ extract:
|
||||
|
||||
### compute 函数签名
|
||||
|
||||
Sense 的 `compute` 接收当前状态,返回新状态和可选的 workflow trigger。状态以 JSON 文件持久化在 `data/senses/<name>.json`。
|
||||
Sense 的 `compute` 接收当前状态,返回新状态和可选的 shell trigger(`{ command: string }`)。状态以 JSON 文件持久化在 `data/senses/<name>.json`。Workflow 只能通过 CLI / daemon IPC 启动,不能从 sense 返回值直接启动。
|
||||
|
||||
```typescript
|
||||
import type { SenseComputeFn, WorkflowTrigger } from "@uncaged/nerve-core";
|
||||
import type { SenseComputeFn } from "@uncaged/nerve-core";
|
||||
|
||||
type MyState = {
|
||||
lastRun: number | null;
|
||||
@@ -226,7 +226,7 @@ export const initialState: MyState = { lastRun: null, count: 0 };
|
||||
|
||||
export async function compute(state: MyState): Promise<{
|
||||
state: MyState;
|
||||
trigger: WorkflowTrigger | null;
|
||||
trigger: { command: string } | null;
|
||||
}> {
|
||||
return {
|
||||
state: { lastRun: Date.now(), count: state.count + 1 },
|
||||
@@ -247,15 +247,9 @@ export async function compute(state: MyState): Promise<{
|
||||
### 返回值
|
||||
|
||||
```typescript
|
||||
// trigger: null → 不触发 workflow
|
||||
// trigger: WorkflowTrigger → 触发 workflow
|
||||
|
||||
type WorkflowTrigger = {
|
||||
name: string; // workflow 名称(对应 nerve.yaml 中的 key)
|
||||
maxRounds: number; // moderator 最大轮次
|
||||
prompt: string; // 初始 prompt
|
||||
dryRun: boolean; // 干跑模式
|
||||
};
|
||||
// trigger: null → 不执行 shell 命令
|
||||
// trigger: { command } → sense worker 在成功的 compute 后以 shell 执行该命令(cwd = nerve 根目录)
|
||||
// 启动 workflow:在 shell 中调用 `nerve workflow trigger ...`,或使用 daemon IPC / HTTP API
|
||||
```
|
||||
|
||||
### Sense 模块导出
|
||||
@@ -271,7 +265,7 @@ export const initialState: MyState = { ... };
|
||||
// 2. compute 函数
|
||||
export async function compute(state: MyState): Promise<{
|
||||
state: MyState;
|
||||
trigger: WorkflowTrigger | null;
|
||||
trigger: { command: string } | null;
|
||||
}> {
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -252,22 +252,7 @@ export async function compute(): Promise<ComputeResult<MySignalShape>> {
|
||||
|
||||
### 返回值
|
||||
|
||||
```typescript
|
||||
// 返回 null = 静默,不发 signal
|
||||
// 返回非 null = 发出 signal(并写入业务表),可选触发 workflow
|
||||
type ComputeResult<T> =
|
||||
| null
|
||||
| { signal: T; workflow: WorkflowTrigger | null };
|
||||
|
||||
type WorkflowTrigger = {
|
||||
name: string; // workflow 名称(对应 nerve.yaml 中的 key)
|
||||
maxRounds: number; // moderator 最大轮次
|
||||
prompt: string; // 初始 prompt
|
||||
dryRun: boolean; // 干跑模式
|
||||
};
|
||||
```
|
||||
|
||||
若返回值是普通对象且不含 `signal` 字段,内核会按 shorthand 视为 `{ signal: payload, workflow: null }`(见 core 的 `routeSenseComputeOutput`)。
|
||||
当前引擎:`compute(state)` 返回 `{ state, trigger }`,`trigger` 为 `null` 或 `{ command: string }`(shell 命令)。Workflow 仅通过 CLI / daemon IPC 启动;类型见 `@uncaged/nerve-core` 的 `SenseComputeFn` / `SenseTrigger`。
|
||||
|
||||
### Sense 模块导出
|
||||
|
||||
|
||||
@@ -245,22 +245,7 @@ export async function compute(): Promise<ComputeResult<MySignalShape>> {
|
||||
|
||||
### 返回值
|
||||
|
||||
```typescript
|
||||
// 返回 null = 静默,不发 signal
|
||||
// 返回非 null = 发出 signal(并写入业务表),可选触发 workflow
|
||||
type ComputeResult<T> =
|
||||
| null
|
||||
| { signal: T; trigger: WorkflowTrigger | null };
|
||||
|
||||
type WorkflowTrigger = {
|
||||
name: string; // workflow 名称(对应 nerve.yaml 中的 key)
|
||||
maxRounds: number; // moderator 最大轮次
|
||||
prompt: string; // 初始 prompt
|
||||
dryRun: boolean; // 干跑模式
|
||||
};
|
||||
```
|
||||
|
||||
若返回值是普通对象且不含 `signal` 字段,内核会按 shorthand 视为 `{ signal: payload, trigger: null }`(见 core 的 `routeSenseComputeOutput`)。
|
||||
当前引擎:`compute(state)` 返回 `{ state, trigger }`,其中 `trigger` 为 `null` 或 `{ command: string }`(sense worker 内 `shell: true` 执行)。Workflow 仅通过 CLI / daemon IPC 启动,类型见 `@uncaged/nerve-core` 的 `SenseComputeFn` / `SenseTrigger`。
|
||||
|
||||
### Sense 模块导出
|
||||
|
||||
|
||||
@@ -125,29 +125,6 @@ export async function compute(state) {
|
||||
}
|
||||
`;
|
||||
|
||||
/** First trigger launches local noop workflow; later triggers only advance idleTicks. */
|
||||
const counterIndexJsWithNoopWorkflow = `export const initialState = { launched: false, idleTicks: 0 };
|
||||
|
||||
export async function compute(state) {
|
||||
if (!state.launched) {
|
||||
return {
|
||||
state: { launched: true, idleTicks: state.idleTicks },
|
||||
trigger: {
|
||||
kind: "workflow",
|
||||
name: "noop",
|
||||
maxRounds: 3,
|
||||
prompt: "e2e-archive",
|
||||
dryRun: false,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
state: { launched: state.launched, idleTicks: state.idleTicks + 1 },
|
||||
trigger: null,
|
||||
};
|
||||
}
|
||||
`;
|
||||
|
||||
/** Minimal workflow: moderator ends immediately (no role rounds). */
|
||||
const noopWorkflowIndexJs = `const END = "__end__";
|
||||
export default {
|
||||
@@ -205,11 +182,7 @@ function writeWorkspaceLayout(nerveRoot: string, withNoopWorkflow: boolean): voi
|
||||
withNoopWorkflow ? nerveYamlWithNoopWorkflow : nerveYamlTemplate,
|
||||
"utf8",
|
||||
);
|
||||
writeFileSync(
|
||||
join(nerveRoot, "dist", "senses", "counter", "index.js"),
|
||||
withNoopWorkflow ? counterIndexJsWithNoopWorkflow : counterIndexJs,
|
||||
"utf8",
|
||||
);
|
||||
writeFileSync(join(nerveRoot, "dist", "senses", "counter", "index.js"), counterIndexJs, "utf8");
|
||||
writeFileSync(
|
||||
join(nerveRoot, "dist", "workflows", "echo", "index.js"),
|
||||
echoWorkflowIndexJs,
|
||||
@@ -235,8 +208,8 @@ export type TestDaemonHandle = {
|
||||
|
||||
export type StartTestDaemonOpts = {
|
||||
/**
|
||||
* When true, counter sense's first compute launches a local `noop` workflow (real
|
||||
* workflow-worker child). Requires built `workflow-worker.js` next to `sense-worker.js`.
|
||||
* When true, bundles a local `noop` workflow under `dist/workflows/noop` for tests that
|
||||
* start runs via `nerve workflow trigger` (real workflow-worker child).
|
||||
*/
|
||||
withNoopWorkflow: boolean;
|
||||
} | null;
|
||||
|
||||
@@ -46,8 +46,14 @@ describe("e2e store archive", () => {
|
||||
daemon = await startTestDaemon({ withNoopWorkflow: true });
|
||||
linkWorkspaceDaemonIntoNerveRoot(daemon.nerveRoot);
|
||||
|
||||
const triggerResult = await runCli(daemon, ["sense", "trigger", "counter"]);
|
||||
expect(triggerResult.exitCode).toBe(0);
|
||||
const wfTrigger = await runCli(daemon, [
|
||||
"workflow",
|
||||
"trigger",
|
||||
"noop",
|
||||
"--prompt",
|
||||
"e2e-archive",
|
||||
]);
|
||||
expect(wfTrigger.exitCode).toBe(0);
|
||||
|
||||
const logsDb = join(daemon.nerveRoot, "data", "logs.db");
|
||||
await pollUntil(() => {
|
||||
@@ -101,8 +107,14 @@ describe("e2e store archive", () => {
|
||||
daemon = await startTestDaemon({ withNoopWorkflow: true });
|
||||
linkWorkspaceDaemonIntoNerveRoot(daemon.nerveRoot);
|
||||
|
||||
const triggerResult = await runCli(daemon, ["sense", "trigger", "counter"]);
|
||||
expect(triggerResult.exitCode).toBe(0);
|
||||
const wfTrigger = await runCli(daemon, [
|
||||
"workflow",
|
||||
"trigger",
|
||||
"noop",
|
||||
"--prompt",
|
||||
"e2e-archive",
|
||||
]);
|
||||
expect(wfTrigger.exitCode).toBe(0);
|
||||
|
||||
const logsDb = join(daemon.nerveRoot, "data", "logs.db");
|
||||
await pollUntil(() => {
|
||||
|
||||
Reference in New Issue
Block a user