@fkqfkq123/opencode-autopilot 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (107) hide show
  1. package/README.md +462 -0
  2. package/README.zh-CN.md +464 -0
  3. package/dist/packages/adapters/opencode/src/opencode-session-client.d.ts +188 -0
  4. package/dist/packages/adapters/opencode/src/opencode-session-client.js +382 -0
  5. package/dist/packages/core/src/artifacts/artifact-evaluator.d.ts +17 -0
  6. package/dist/packages/core/src/artifacts/artifact-evaluator.js +1 -0
  7. package/dist/packages/core/src/artifacts/artifact.d.ts +7 -0
  8. package/dist/packages/core/src/artifacts/artifact.js +1 -0
  9. package/dist/packages/core/src/human-actions/human-action-record.d.ts +10 -0
  10. package/dist/packages/core/src/human-actions/human-action-record.js +1 -0
  11. package/dist/packages/core/src/human-actions/human-action.d.ts +13 -0
  12. package/dist/packages/core/src/human-actions/human-action.js +1 -0
  13. package/dist/packages/core/src/human-actions/question.d.ts +8 -0
  14. package/dist/packages/core/src/human-actions/question.js +1 -0
  15. package/dist/packages/core/src/state/phase.d.ts +2 -0
  16. package/dist/packages/core/src/state/phase.js +1 -0
  17. package/dist/packages/core/src/state/workflow-runtime-state.d.ts +14 -0
  18. package/dist/packages/core/src/state/workflow-runtime-state.js +1 -0
  19. package/dist/packages/core/src/state/workflow-state.d.ts +13 -0
  20. package/dist/packages/core/src/state/workflow-state.js +1 -0
  21. package/dist/packages/core/src/transitions/default-phase-transition.d.ts +5 -0
  22. package/dist/packages/core/src/transitions/default-phase-transition.js +195 -0
  23. package/dist/packages/core/src/transitions/phase-transition.d.ts +22 -0
  24. package/dist/packages/core/src/transitions/phase-transition.js +1 -0
  25. package/dist/packages/core/src/transitions/transition-action.d.ts +20 -0
  26. package/dist/packages/core/src/transitions/transition-action.js +1 -0
  27. package/dist/packages/runtime/src/artifacts/file-system-artifact-evaluator.d.ts +36 -0
  28. package/dist/packages/runtime/src/artifacts/file-system-artifact-evaluator.js +1213 -0
  29. package/dist/packages/runtime/src/attach/attach-service.d.ts +15 -0
  30. package/dist/packages/runtime/src/attach/attach-service.js +31 -0
  31. package/dist/packages/runtime/src/bootstrap/create-harness.d.ts +33 -0
  32. package/dist/packages/runtime/src/bootstrap/create-harness.js +79 -0
  33. package/dist/packages/runtime/src/bootstrap/initialize-workflow.d.ts +8 -0
  34. package/dist/packages/runtime/src/bootstrap/initialize-workflow.js +33 -0
  35. package/dist/packages/runtime/src/commands/create-opencode-workflow-commands.d.ts +12 -0
  36. package/dist/packages/runtime/src/commands/create-opencode-workflow-commands.js +24 -0
  37. package/dist/packages/runtime/src/commands/default-workflow-command-runner.d.ts +4 -0
  38. package/dist/packages/runtime/src/commands/default-workflow-command-runner.js +343 -0
  39. package/dist/packages/runtime/src/commands/opencode-plugin-command-adapter.d.ts +20 -0
  40. package/dist/packages/runtime/src/commands/opencode-plugin-command-adapter.js +22 -0
  41. package/dist/packages/runtime/src/commands/workflow-command-runner.d.ts +19 -0
  42. package/dist/packages/runtime/src/commands/workflow-command-runner.js +1 -0
  43. package/dist/packages/runtime/src/commands/workflow-open-request.d.ts +10 -0
  44. package/dist/packages/runtime/src/commands/workflow-open-request.js +220 -0
  45. package/dist/packages/runtime/src/config/skill-registry.d.ts +15 -0
  46. package/dist/packages/runtime/src/config/skill-registry.js +108 -0
  47. package/dist/packages/runtime/src/config/workflow-config.d.ts +17 -0
  48. package/dist/packages/runtime/src/config/workflow-config.js +51 -0
  49. package/dist/packages/runtime/src/diagnostics/workflow-diagnostics-format.d.ts +4 -0
  50. package/dist/packages/runtime/src/diagnostics/workflow-diagnostics-format.js +70 -0
  51. package/dist/packages/runtime/src/diagnostics/workflow-doctor.d.ts +23 -0
  52. package/dist/packages/runtime/src/diagnostics/workflow-doctor.js +120 -0
  53. package/dist/packages/runtime/src/engine/default-workflow-engine.d.ts +9 -0
  54. package/dist/packages/runtime/src/engine/default-workflow-engine.js +337 -0
  55. package/dist/packages/runtime/src/engine/workflow-engine.d.ts +28 -0
  56. package/dist/packages/runtime/src/engine/workflow-engine.js +1 -0
  57. package/dist/packages/runtime/src/events/file-system-workflow-event-store.d.ts +8 -0
  58. package/dist/packages/runtime/src/events/file-system-workflow-event-store.js +28 -0
  59. package/dist/packages/runtime/src/events/workflow-event-store.d.ts +10 -0
  60. package/dist/packages/runtime/src/events/workflow-event-store.js +1 -0
  61. package/dist/packages/runtime/src/index.d.ts +4 -0
  62. package/dist/packages/runtime/src/index.js +4 -0
  63. package/dist/packages/runtime/src/install/workflow-installer.d.ts +15 -0
  64. package/dist/packages/runtime/src/install/workflow-installer.js +111 -0
  65. package/dist/packages/runtime/src/plugin/workflow-plugin-entry.d.ts +167 -0
  66. package/dist/packages/runtime/src/plugin/workflow-plugin-entry.js +340 -0
  67. package/dist/packages/runtime/src/presentation/human-action-renderer.d.ts +13 -0
  68. package/dist/packages/runtime/src/presentation/human-action-renderer.js +161 -0
  69. package/dist/packages/runtime/src/presentation/watch-renderer.d.ts +12 -0
  70. package/dist/packages/runtime/src/presentation/watch-renderer.js +17 -0
  71. package/dist/packages/runtime/src/recovery/basic-recovery-classifier.d.ts +4 -0
  72. package/dist/packages/runtime/src/recovery/basic-recovery-classifier.js +12 -0
  73. package/dist/packages/runtime/src/recovery/recovery-classifier.d.ts +4 -0
  74. package/dist/packages/runtime/src/recovery/recovery-classifier.js +1 -0
  75. package/dist/packages/runtime/src/scheduling/immediate-tick-scheduler.d.ts +9 -0
  76. package/dist/packages/runtime/src/scheduling/immediate-tick-scheduler.js +28 -0
  77. package/dist/packages/runtime/src/scheduling/tick-scheduler.d.ts +3 -0
  78. package/dist/packages/runtime/src/scheduling/tick-scheduler.js +1 -0
  79. package/dist/packages/runtime/src/sessions/file-system-session-coordinator.d.ts +19 -0
  80. package/dist/packages/runtime/src/sessions/file-system-session-coordinator.js +132 -0
  81. package/dist/packages/runtime/src/sessions/session-activity-monitor.d.ts +22 -0
  82. package/dist/packages/runtime/src/sessions/session-activity-monitor.js +112 -0
  83. package/dist/packages/runtime/src/sessions/session-coordinator.d.ts +24 -0
  84. package/dist/packages/runtime/src/sessions/session-coordinator.js +1 -0
  85. package/dist/packages/runtime/src/shared/json-file.d.ts +2 -0
  86. package/dist/packages/runtime/src/shared/json-file.js +19 -0
  87. package/dist/packages/runtime/src/state/file-system-human-action-store.d.ts +15 -0
  88. package/dist/packages/runtime/src/state/file-system-human-action-store.js +69 -0
  89. package/dist/packages/runtime/src/state/file-system-workflow-state-store.d.ts +15 -0
  90. package/dist/packages/runtime/src/state/file-system-workflow-state-store.js +59 -0
  91. package/dist/packages/runtime/src/state/human-action-service.d.ts +21 -0
  92. package/dist/packages/runtime/src/state/human-action-service.js +80 -0
  93. package/dist/packages/runtime/src/state/human-action-store.d.ts +9 -0
  94. package/dist/packages/runtime/src/state/human-action-store.js +1 -0
  95. package/dist/packages/runtime/src/state/workflow-state-store.d.ts +11 -0
  96. package/dist/packages/runtime/src/state/workflow-state-store.js +1 -0
  97. package/dist/packages/runtime/src/subtasks/noop-subtask-tracker.d.ts +4 -0
  98. package/dist/packages/runtime/src/subtasks/noop-subtask-tracker.js +5 -0
  99. package/dist/packages/runtime/src/subtasks/subtask-tracker.d.ts +3 -0
  100. package/dist/packages/runtime/src/subtasks/subtask-tracker.js +1 -0
  101. package/dist/packages/runtime/src/workspace/workflow-workspace.d.ts +31 -0
  102. package/dist/packages/runtime/src/workspace/workflow-workspace.js +43 -0
  103. package/dist/plugin.d.ts +1 -0
  104. package/dist/plugin.js +1 -0
  105. package/dist/src/cli.d.ts +1 -0
  106. package/dist/src/cli.js +175 -0
  107. package/package.json +56 -0
@@ -0,0 +1,12 @@
1
+ import type { HumanActionRecord } from "../../../core/src/human-actions/human-action-record";
2
+ import type { WorkflowRuntimeState } from "../../../core/src/state/workflow-runtime-state";
3
+ import type { WorkflowState } from "../../../core/src/state/workflow-state";
4
+ import type { WorkflowEventRecord } from "../events/workflow-event-store";
5
+ export declare function renderWatchFrame(args: {
6
+ workflow: WorkflowState;
7
+ runtime: WorkflowRuntimeState | null;
8
+ humanAction: HumanActionRecord | null;
9
+ recentEvents: WorkflowEventRecord[];
10
+ attached: boolean;
11
+ modeLabel?: string;
12
+ }): string;
@@ -0,0 +1,17 @@
1
+ import { renderHumanActionBlock } from "./human-action-renderer";
2
+ export function renderWatchFrame(args) {
3
+ const { workflow, runtime, humanAction, recentEvents, attached, modeLabel = "Autopilot" } = args;
4
+ const lines = [
5
+ `Mode: ${modeLabel}`,
6
+ `Attached: ${attached ? "yes" : "no"}`,
7
+ renderHumanActionBlock({ workflow, runtime, humanAction }),
8
+ ];
9
+ if (recentEvents.length > 0) {
10
+ lines.push("");
11
+ lines.push("Recent events:");
12
+ for (const event of recentEvents.slice(-5)) {
13
+ lines.push(`- ${event.at} ${event.type}`);
14
+ }
15
+ }
16
+ return lines.join("\n");
17
+ }
@@ -0,0 +1,4 @@
1
+ import type { RecoveryClassifier, RecoveryDisposition } from "./recovery-classifier";
2
+ export declare class BasicRecoveryClassifier implements RecoveryClassifier {
3
+ classify(error: unknown): RecoveryDisposition;
4
+ }
@@ -0,0 +1,12 @@
1
+ export class BasicRecoveryClassifier {
2
+ classify(error) {
3
+ const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
4
+ if (message.includes("timeout") || message.includes("temporary")) {
5
+ return "retryable";
6
+ }
7
+ if (message.includes("missing") || message.includes("stale")) {
8
+ return "recoverable";
9
+ }
10
+ return "terminal";
11
+ }
12
+ }
@@ -0,0 +1,4 @@
1
+ export type RecoveryDisposition = "retryable" | "recoverable" | "terminal";
2
+ export interface RecoveryClassifier {
3
+ classify(error: unknown): RecoveryDisposition;
4
+ }
@@ -0,0 +1,9 @@
1
+ import type { TickScheduler } from "./tick-scheduler";
2
+ export type TickHandler = (workflowId: string) => Promise<void>;
3
+ export declare class ImmediateTickScheduler implements TickScheduler {
4
+ private readonly active;
5
+ private readonly queued;
6
+ private handler;
7
+ setHandler(handler: TickHandler): void;
8
+ requestTick(workflowId: string, _reason: string): Promise<void>;
9
+ }
@@ -0,0 +1,28 @@
1
+ export class ImmediateTickScheduler {
2
+ active = new Set();
3
+ queued = new Set();
4
+ handler = null;
5
+ setHandler(handler) {
6
+ this.handler = handler;
7
+ }
8
+ async requestTick(workflowId, _reason) {
9
+ if (!this.handler) {
10
+ throw new Error("Tick handler has not been registered");
11
+ }
12
+ if (this.active.has(workflowId)) {
13
+ this.queued.add(workflowId);
14
+ return;
15
+ }
16
+ this.active.add(workflowId);
17
+ try {
18
+ await this.handler(workflowId);
19
+ while (this.queued.has(workflowId)) {
20
+ this.queued.delete(workflowId);
21
+ await this.handler(workflowId);
22
+ }
23
+ }
24
+ finally {
25
+ this.active.delete(workflowId);
26
+ }
27
+ }
28
+ }
@@ -0,0 +1,3 @@
1
+ export interface TickScheduler {
2
+ requestTick(workflowId: string, reason: string): Promise<void>;
3
+ }
@@ -0,0 +1,19 @@
1
+ import type { Phase } from "../../../core/src/state/phase";
2
+ import type { SessionEvent } from "../../../adapters/opencode/src/opencode-session-client";
3
+ import type { RelevantSessionState } from "../../../core/src/transitions/phase-transition";
4
+ import type { OpencodeSessionClient } from "../../../adapters/opencode/src/opencode-session-client";
5
+ import type { WorkflowWorkspace } from "../workspace/workflow-workspace";
6
+ import type { SessionCoordinator, SessionDescriptor } from "./session-coordinator";
7
+ export declare class FileSystemSessionCoordinator implements SessionCoordinator {
8
+ private readonly workspace;
9
+ private readonly sessionClient;
10
+ constructor(workspace: WorkflowWorkspace, sessionClient: OpencodeSessionClient);
11
+ getRelevantSession(workflowId: string): Promise<RelevantSessionState>;
12
+ ensureSession(workflowId: string, phase: Phase, preferredSessionId?: string | null): Promise<string>;
13
+ inject(workflowId: string, sessionId: string, prompt: string): Promise<void>;
14
+ archiveIrrelevantSessions(workflowId: string, phase: Phase): Promise<void>;
15
+ getStoredSession(workflowId: string, sessionId: string): Promise<SessionDescriptor | null>;
16
+ streamEvents(session: SessionDescriptor): AsyncIterable<SessionEvent>;
17
+ private loadSessions;
18
+ private saveSessions;
19
+ }
@@ -0,0 +1,132 @@
1
+ import { readJsonFile, writeJsonFile } from "../shared/json-file";
2
+ export class FileSystemSessionCoordinator {
3
+ workspace;
4
+ sessionClient;
5
+ constructor(workspace, sessionClient) {
6
+ this.workspace = workspace;
7
+ this.sessionClient = sessionClient;
8
+ }
9
+ async getRelevantSession(workflowId) {
10
+ const sessions = await this.loadSessions(workflowId);
11
+ const relevant = [...sessions]
12
+ .reverse()
13
+ .find((session) => !session.archived);
14
+ if (!relevant) {
15
+ return {
16
+ sessionId: null,
17
+ relevant: false,
18
+ status: "missing",
19
+ phaseMatches: false,
20
+ };
21
+ }
22
+ const status = await this.sessionClient.getSessionStatus(relevant.sessionId);
23
+ return {
24
+ sessionId: relevant.sessionId,
25
+ relevant: true,
26
+ status,
27
+ phaseMatches: true,
28
+ };
29
+ }
30
+ async ensureSession(workflowId, phase, preferredSessionId) {
31
+ const sessions = await this.loadSessions(workflowId);
32
+ if (preferredSessionId && (phase === "develop" || phase === "review" || phase === "test")) {
33
+ const foreground = sessions.find((session) => session.sessionId === preferredSessionId);
34
+ if (foreground) {
35
+ const next = sessions.map((session) => session.sessionId === preferredSessionId
36
+ ? {
37
+ ...session,
38
+ workflowId,
39
+ phase,
40
+ archived: false,
41
+ isForegroundPreferred: true,
42
+ }
43
+ : session);
44
+ await this.saveSessions(workflowId, next);
45
+ return preferredSessionId;
46
+ }
47
+ const createdForeground = {
48
+ sessionId: preferredSessionId,
49
+ workflowId,
50
+ phase,
51
+ createdAt: new Date().toISOString(),
52
+ isForegroundPreferred: true,
53
+ status: "idle",
54
+ title: `${workflowId}:${phase}`,
55
+ };
56
+ sessions.push(createdForeground);
57
+ await this.saveSessions(workflowId, sessions);
58
+ return preferredSessionId;
59
+ }
60
+ const existing = [...sessions]
61
+ .reverse()
62
+ .find((session) => !session.archived && session.phase === phase);
63
+ if (existing) {
64
+ return existing.sessionId;
65
+ }
66
+ const sessionTitle = `${workflowId}:${phase}`;
67
+ const created = await this.sessionClient.createSession({
68
+ workflowId,
69
+ phase,
70
+ title: sessionTitle,
71
+ });
72
+ const next = {
73
+ sessionId: created.sessionId,
74
+ workflowId,
75
+ phase,
76
+ createdAt: new Date().toISOString(),
77
+ status: "idle",
78
+ title: sessionTitle,
79
+ };
80
+ sessions.push(next);
81
+ await this.saveSessions(workflowId, sessions);
82
+ return next.sessionId;
83
+ }
84
+ async inject(workflowId, sessionId, prompt) {
85
+ const stored = await this.getStoredSession(workflowId, sessionId);
86
+ if (stored) {
87
+ await this.sessionClient.ensureSessionReady(sessionId, stored.title || `${workflowId}:${stored.phase}`);
88
+ }
89
+ const injectResult = await this.sessionClient.injectPrompt({
90
+ sessionId,
91
+ prompt,
92
+ });
93
+ const sessions = await this.loadSessions(workflowId);
94
+ const next = sessions.map((session) => {
95
+ if (session.sessionId !== sessionId) {
96
+ return session;
97
+ }
98
+ return {
99
+ ...session,
100
+ status: injectResult.statusBefore === "failed" ? "failed" : "running",
101
+ lastPrompt: prompt,
102
+ lastDispatchMode: injectResult.dispatchMode,
103
+ lastStatusBeforeDispatch: injectResult.statusBefore,
104
+ };
105
+ });
106
+ await this.saveSessions(workflowId, next);
107
+ }
108
+ async archiveIrrelevantSessions(workflowId, phase) {
109
+ const sessions = await this.loadSessions(workflowId);
110
+ const next = sessions.map((session) => ({
111
+ ...session,
112
+ archived: session.phase !== phase,
113
+ }));
114
+ await this.saveSessions(workflowId, next);
115
+ }
116
+ async getStoredSession(workflowId, sessionId) {
117
+ const sessions = await this.loadSessions(workflowId);
118
+ return sessions.find((session) => session.sessionId === sessionId) ?? null;
119
+ }
120
+ async *streamEvents(session) {
121
+ for await (const event of this.sessionClient.streamEvents(session.sessionId)) {
122
+ yield event;
123
+ }
124
+ }
125
+ async loadSessions(workflowId) {
126
+ const data = await readJsonFile(this.workspace.sessionsFile(workflowId));
127
+ return data?.sessions ?? [];
128
+ }
129
+ async saveSessions(workflowId, sessions) {
130
+ await writeJsonFile(this.workspace.sessionsFile(workflowId), { sessions });
131
+ }
132
+ }
@@ -0,0 +1,22 @@
1
+ import type { TickScheduler } from "../scheduling/tick-scheduler";
2
+ import type { WorkflowStateStore } from "../state/workflow-state-store";
3
+ import type { SessionCoordinator } from "./session-coordinator";
4
+ export interface SessionActivityMonitor {
5
+ start(workflowId: string): Promise<void>;
6
+ stop(workflowId: string): Promise<void>;
7
+ }
8
+ export declare class DefaultSessionActivityMonitor implements SessionActivityMonitor {
9
+ private readonly scopeKey;
10
+ private readonly stateStore;
11
+ private readonly sessionCoordinator;
12
+ private readonly tickScheduler;
13
+ private static readonly activeControllers;
14
+ private readonly running;
15
+ constructor(scopeKey: string, stateStore: WorkflowStateStore, sessionCoordinator: SessionCoordinator, tickScheduler: TickScheduler);
16
+ start(workflowId: string): Promise<void>;
17
+ stop(workflowId: string): Promise<void>;
18
+ private runLoop;
19
+ private keyFor;
20
+ private sleep;
21
+ private requestTickSafely;
22
+ }
@@ -0,0 +1,112 @@
1
+ export class DefaultSessionActivityMonitor {
2
+ scopeKey;
3
+ stateStore;
4
+ sessionCoordinator;
5
+ tickScheduler;
6
+ static activeControllers = new Map();
7
+ running = new Map();
8
+ constructor(scopeKey, stateStore, sessionCoordinator, tickScheduler) {
9
+ this.scopeKey = scopeKey;
10
+ this.stateStore = stateStore;
11
+ this.sessionCoordinator = sessionCoordinator;
12
+ this.tickScheduler = tickScheduler;
13
+ }
14
+ async start(workflowId) {
15
+ const monitorKey = this.keyFor(workflowId);
16
+ if (this.running.has(monitorKey)) {
17
+ return;
18
+ }
19
+ const active = DefaultSessionActivityMonitor.activeControllers.get(monitorKey);
20
+ if (active) {
21
+ active.abort();
22
+ }
23
+ const controller = new AbortController();
24
+ this.running.set(monitorKey, controller);
25
+ DefaultSessionActivityMonitor.activeControllers.set(monitorKey, controller);
26
+ void this.runLoop(workflowId, monitorKey, controller.signal);
27
+ }
28
+ async stop(workflowId) {
29
+ const monitorKey = this.keyFor(workflowId);
30
+ const controller = this.running.get(monitorKey);
31
+ if (!controller) {
32
+ return;
33
+ }
34
+ controller.abort();
35
+ this.running.delete(monitorKey);
36
+ if (DefaultSessionActivityMonitor.activeControllers.get(monitorKey) === controller) {
37
+ DefaultSessionActivityMonitor.activeControllers.delete(monitorKey);
38
+ }
39
+ }
40
+ async runLoop(workflowId, monitorKey, signal) {
41
+ try {
42
+ while (!signal.aborted) {
43
+ const workflow = await this.stateStore.getWorkflow(workflowId);
44
+ if (!workflow?.activeSessionId) {
45
+ await this.sleep(100, signal);
46
+ continue;
47
+ }
48
+ const coordinator = this.sessionCoordinator;
49
+ const stored = coordinator.getStoredSession
50
+ ? await coordinator.getStoredSession(workflowId, workflow.activeSessionId)
51
+ : null;
52
+ if (!stored) {
53
+ await this.sleep(100, signal);
54
+ continue;
55
+ }
56
+ for await (const event of this.sessionCoordinator.streamEvents(stored)) {
57
+ if (signal.aborted) {
58
+ return;
59
+ }
60
+ if (event.type === "session.idle") {
61
+ const shouldStop = await this.requestTickSafely(workflowId, "session idle");
62
+ if (shouldStop) {
63
+ return;
64
+ }
65
+ }
66
+ if (event.type === "session.error") {
67
+ const shouldStop = await this.requestTickSafely(workflowId, "session failed");
68
+ if (shouldStop) {
69
+ return;
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ finally {
76
+ this.running.delete(monitorKey);
77
+ const active = DefaultSessionActivityMonitor.activeControllers.get(monitorKey);
78
+ if (active && active.signal === signal) {
79
+ DefaultSessionActivityMonitor.activeControllers.delete(monitorKey);
80
+ }
81
+ }
82
+ }
83
+ keyFor(workflowId) {
84
+ return `${this.scopeKey}::${workflowId}`;
85
+ }
86
+ async sleep(ms, signal) {
87
+ await new Promise((resolve) => {
88
+ const timeout = setTimeout(() => resolve(), ms);
89
+ signal.addEventListener("abort", () => {
90
+ clearTimeout(timeout);
91
+ resolve();
92
+ }, { once: true });
93
+ });
94
+ }
95
+ async requestTickSafely(workflowId, reason) {
96
+ try {
97
+ await this.tickScheduler.requestTick(workflowId, reason);
98
+ return false;
99
+ }
100
+ catch (error) {
101
+ const message = error instanceof Error ? error.message : String(error);
102
+ if (message.includes("Workflow not found")
103
+ || message.includes("Workflow state not found")
104
+ || message.includes("Runtime state not found")
105
+ || message.includes("ENOENT")
106
+ || message.includes("EINVAL")) {
107
+ return true;
108
+ }
109
+ throw error;
110
+ }
111
+ }
112
+ }
@@ -0,0 +1,24 @@
1
+ import type { Phase } from "../../../core/src/state/phase";
2
+ import type { RelevantSessionState } from "../../../core/src/transitions/phase-transition";
3
+ import type { SessionEvent } from "../../../adapters/opencode/src/opencode-session-client";
4
+ export interface SessionDescriptor {
5
+ sessionId: string;
6
+ workflowId: string;
7
+ phase: Phase;
8
+ createdAt: string;
9
+ archived?: boolean;
10
+ isForegroundPreferred?: boolean;
11
+ status: "running" | "idle" | "failed";
12
+ title?: string;
13
+ lastPrompt?: string;
14
+ lastDispatchMode?: string;
15
+ lastStatusBeforeDispatch?: "running" | "idle" | "failed" | "missing";
16
+ }
17
+ export interface SessionCoordinator {
18
+ getRelevantSession(workflowId: string): Promise<RelevantSessionState>;
19
+ ensureSession(workflowId: string, phase: Phase, preferredSessionId?: string | null): Promise<string>;
20
+ inject(workflowId: string, sessionId: string, prompt: string): Promise<void>;
21
+ archiveIrrelevantSessions(workflowId: string, phase: Phase): Promise<void>;
22
+ getStoredSession(workflowId: string, sessionId: string): Promise<SessionDescriptor | null>;
23
+ streamEvents(session: SessionDescriptor): AsyncIterable<SessionEvent>;
24
+ }
@@ -0,0 +1,2 @@
1
+ export declare function readJsonFile<T>(filePath: string): Promise<T | null>;
2
+ export declare function writeJsonFile(filePath: string, value: unknown): Promise<void>;
@@ -0,0 +1,19 @@
1
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
2
+ import { dirname } from "node:path";
3
+ export async function readJsonFile(filePath) {
4
+ try {
5
+ const raw = await readFile(filePath, "utf8");
6
+ return JSON.parse(raw);
7
+ }
8
+ catch (error) {
9
+ const message = error instanceof Error ? error.message : String(error);
10
+ if (message.includes("ENOENT")) {
11
+ return null;
12
+ }
13
+ throw error;
14
+ }
15
+ }
16
+ export async function writeJsonFile(filePath, value) {
17
+ await mkdir(dirname(filePath), { recursive: true });
18
+ await writeFile(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
19
+ }
@@ -0,0 +1,15 @@
1
+ import type { HumanAction } from "../../../core/src/human-actions/human-action";
2
+ import type { HumanActionRecord } from "../../../core/src/human-actions/human-action-record";
3
+ import type { WorkflowWorkspace } from "../workspace/workflow-workspace";
4
+ import type { HumanActionStore } from "./human-action-store";
5
+ export declare class FileSystemHumanActionStore implements HumanActionStore {
6
+ private readonly workspace;
7
+ constructor(workspace: WorkflowWorkspace);
8
+ create(action: HumanAction): Promise<HumanActionRecord>;
9
+ getCurrent(workflowId: string): Promise<HumanActionRecord | null>;
10
+ private updateStatus;
11
+ markPresented(actionId: string): Promise<void>;
12
+ markResponded(actionId: string): Promise<void>;
13
+ markConsumed(actionId: string): Promise<void>;
14
+ private findByActionId;
15
+ }
@@ -0,0 +1,69 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { randomUUID } from "node:crypto";
3
+ import { readJsonFile, writeJsonFile } from "../shared/json-file";
4
+ export class FileSystemHumanActionStore {
5
+ workspace;
6
+ constructor(workspace) {
7
+ this.workspace = workspace;
8
+ }
9
+ async create(action) {
10
+ const record = {
11
+ id: randomUUID(),
12
+ workflowId: action.workflowId,
13
+ action,
14
+ status: "pending",
15
+ createdAt: new Date().toISOString(),
16
+ };
17
+ await writeJsonFile(this.workspace.humanActionFile(action.workflowId), record);
18
+ return record;
19
+ }
20
+ async getCurrent(workflowId) {
21
+ return readJsonFile(this.workspace.humanActionFile(workflowId));
22
+ }
23
+ async updateStatus(actionId, status) {
24
+ const current = await this.findByActionId(actionId);
25
+ if (!current) {
26
+ throw new Error(`Human action not found: ${actionId}`);
27
+ }
28
+ const next = {
29
+ ...current,
30
+ status,
31
+ };
32
+ if (status === "responded" || status === "consumed") {
33
+ next.respondedAt = new Date().toISOString();
34
+ }
35
+ else if (current.respondedAt) {
36
+ next.respondedAt = current.respondedAt;
37
+ }
38
+ await writeJsonFile(this.workspace.humanActionFile(current.workflowId), next);
39
+ }
40
+ async markPresented(actionId) {
41
+ await this.updateStatus(actionId, "presented");
42
+ }
43
+ async markResponded(actionId) {
44
+ await this.updateStatus(actionId, "responded");
45
+ }
46
+ async markConsumed(actionId) {
47
+ await this.updateStatus(actionId, "consumed");
48
+ }
49
+ async findByActionId(actionId) {
50
+ const workflowsDir = this.workspace.workflowsRoot();
51
+ let workflowIds = [];
52
+ try {
53
+ workflowIds = await readdir(workflowsDir);
54
+ }
55
+ catch (error) {
56
+ const message = error instanceof Error ? error.message : String(error);
57
+ if (!message.includes("ENOENT")) {
58
+ throw error;
59
+ }
60
+ }
61
+ for (const workflowId of workflowIds) {
62
+ const current = await this.getCurrent(workflowId);
63
+ if (current?.id === actionId) {
64
+ return current;
65
+ }
66
+ }
67
+ return null;
68
+ }
69
+ }
@@ -0,0 +1,15 @@
1
+ import type { WorkflowRuntimeState } from "../../../core/src/state/workflow-runtime-state";
2
+ import type { WorkflowState } from "../../../core/src/state/workflow-state";
3
+ import type { WorkflowWorkspace } from "../workspace/workflow-workspace";
4
+ import type { WorkflowStateStore } from "./workflow-state-store";
5
+ export declare class FileSystemWorkflowStateStore implements WorkflowStateStore {
6
+ private readonly workspace;
7
+ constructor(workspace: WorkflowWorkspace);
8
+ listWorkflows(): Promise<WorkflowState[]>;
9
+ getWorkflow(workflowId: string): Promise<WorkflowState | null>;
10
+ saveWorkflow(state: WorkflowState): Promise<void>;
11
+ updateWorkflow(workflowId: string, patch: Partial<WorkflowState>): Promise<WorkflowState>;
12
+ getRuntime(workflowId: string): Promise<WorkflowRuntimeState | null>;
13
+ saveRuntime(state: WorkflowRuntimeState): Promise<void>;
14
+ updateRuntime(workflowId: string, patch: Partial<WorkflowRuntimeState>): Promise<WorkflowRuntimeState>;
15
+ }
@@ -0,0 +1,59 @@
1
+ import { readdir } from "node:fs/promises";
2
+ import { readJsonFile, writeJsonFile } from "../shared/json-file";
3
+ export class FileSystemWorkflowStateStore {
4
+ workspace;
5
+ constructor(workspace) {
6
+ this.workspace = workspace;
7
+ }
8
+ async listWorkflows() {
9
+ try {
10
+ const workflowIds = await readdir(this.workspace.workflowsRoot());
11
+ const items = await Promise.all(workflowIds.map((workflowId) => this.getWorkflow(workflowId)));
12
+ return items.filter((item) => item !== null);
13
+ }
14
+ catch (error) {
15
+ const message = error instanceof Error ? error.message : String(error);
16
+ if (message.includes("ENOENT")) {
17
+ return [];
18
+ }
19
+ throw error;
20
+ }
21
+ }
22
+ async getWorkflow(workflowId) {
23
+ return readJsonFile(this.workspace.workflowStateFile(workflowId));
24
+ }
25
+ async saveWorkflow(state) {
26
+ await writeJsonFile(this.workspace.workflowStateFile(state.workflowId), state);
27
+ }
28
+ async updateWorkflow(workflowId, patch) {
29
+ const current = await this.getWorkflow(workflowId);
30
+ if (!current) {
31
+ throw new Error(`Workflow state not found: ${workflowId}`);
32
+ }
33
+ const next = {
34
+ ...current,
35
+ ...patch,
36
+ updatedAt: new Date().toISOString(),
37
+ };
38
+ await this.saveWorkflow(next);
39
+ return next;
40
+ }
41
+ async getRuntime(workflowId) {
42
+ return readJsonFile(this.workspace.workflowRuntimeStateFile(workflowId));
43
+ }
44
+ async saveRuntime(state) {
45
+ await writeJsonFile(this.workspace.workflowRuntimeStateFile(state.workflowId), state);
46
+ }
47
+ async updateRuntime(workflowId, patch) {
48
+ const current = await this.getRuntime(workflowId);
49
+ if (!current) {
50
+ throw new Error(`Workflow runtime state not found: ${workflowId}`);
51
+ }
52
+ const next = {
53
+ ...current,
54
+ ...patch,
55
+ };
56
+ await this.saveRuntime(next);
57
+ return next;
58
+ }
59
+ }
@@ -0,0 +1,21 @@
1
+ import type { FileSystemArtifactEvaluator } from "../artifacts/file-system-artifact-evaluator";
2
+ import type { WorkflowEventStore } from "../events/workflow-event-store";
3
+ import type { TickScheduler } from "../scheduling/tick-scheduler";
4
+ import type { HumanActionStore } from "./human-action-store";
5
+ import type { WorkflowStateStore } from "./workflow-state-store";
6
+ export interface HumanActionService {
7
+ answer(workflowId: string, answers: Record<string, string>): Promise<void>;
8
+ approve(workflowId: string): Promise<void>;
9
+ resume(workflowId: string): Promise<void>;
10
+ }
11
+ export declare class DefaultHumanActionService implements HumanActionService {
12
+ private readonly humanActionStore;
13
+ private readonly stateStore;
14
+ private readonly artifactEvaluator;
15
+ private readonly tickScheduler;
16
+ private readonly eventStore;
17
+ constructor(humanActionStore: HumanActionStore, stateStore: WorkflowStateStore, artifactEvaluator: FileSystemArtifactEvaluator, tickScheduler: TickScheduler, eventStore: WorkflowEventStore);
18
+ answer(workflowId: string, answers: Record<string, string>): Promise<void>;
19
+ approve(workflowId: string): Promise<void>;
20
+ resume(workflowId: string): Promise<void>;
21
+ }