feat(core,daemon,cli): add dryRun thread-level parameter to StartSignal

- StartSignal.meta gains dryRun: boolean (alongside maxRounds)
- DaemonIpcTriggerWorkflowRequest includes dryRun, parsed with default false
- CLI parses dryRun from --payload JSON, passes through daemon client
- workflow-worker/workflow-manager propagate dryRun through full IPC chain
- Sense-triggered workflows default to dryRun: false
- workflow-utils exports isDryRun(start) helper
- All tests updated, 376 pass

Fixes #101
This commit is contained in:
2026-04-24 23:45:29 +00:00
parent a7ce8401ce
commit c4dc707eb0
18 changed files with 212 additions and 93 deletions
+36 -9
View File
@@ -242,6 +242,41 @@ function runInTransaction<T>(db: DatabaseSync, fn: () => T): T {
}
}
function launchShapeFromRecord(rec: Record<string, unknown>): {
prompt: string;
maxRounds: number;
dryRun: boolean;
} | null {
if (typeof rec.prompt !== "string" || typeof rec.maxRounds !== "number") return null;
return {
prompt: rec.prompt,
maxRounds: rec.maxRounds,
dryRun: typeof rec.dryRun === "boolean" ? rec.dryRun : false,
};
}
/** Parse JSON from a workflow `started` log row into a trigger / launch payload for crash recovery. */
function triggerPayloadFromStartedLogJson(payload: string): unknown | null {
let parsed: unknown;
try {
parsed = JSON.parse(payload);
} catch {
return null;
}
if (!isPlainRecord(parsed)) return null;
const direct = launchShapeFromRecord(parsed);
if (direct !== null) return direct;
const inner = parsed.triggerPayload;
if (inner !== null && isPlainRecord(inner)) {
const fromInner = launchShapeFromRecord(inner);
if (fromInner !== null) return fromInner;
return inner;
}
return null;
}
function runOptionalVacuum(sqlite: DatabaseSync, vacuum?: boolean): boolean {
if (vacuum !== true) return false;
sqlite.exec("VACUUM");
@@ -513,15 +548,7 @@ export function createLogStore(dbPath: string): LogStore {
function getTriggerPayload(runId: string): unknown {
const row = getTriggerPayloadStmt.get(runId) as { payload: string | null } | undefined;
if (row === undefined || row.payload === null) return null;
try {
const parsed: unknown = JSON.parse(row.payload);
if (isPlainRecord(parsed)) {
return parsed.triggerPayload ?? null;
}
} catch {
// malformed
}
return null;
return triggerPayloadFromStartedLogJson(row.payload);
}
function getThreadEvents(runId: string): Array<{ type: string; [key: string]: unknown }> {