@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.
- package/README.md +462 -0
- package/README.zh-CN.md +464 -0
- package/dist/packages/adapters/opencode/src/opencode-session-client.d.ts +188 -0
- package/dist/packages/adapters/opencode/src/opencode-session-client.js +382 -0
- package/dist/packages/core/src/artifacts/artifact-evaluator.d.ts +17 -0
- package/dist/packages/core/src/artifacts/artifact-evaluator.js +1 -0
- package/dist/packages/core/src/artifacts/artifact.d.ts +7 -0
- package/dist/packages/core/src/artifacts/artifact.js +1 -0
- package/dist/packages/core/src/human-actions/human-action-record.d.ts +10 -0
- package/dist/packages/core/src/human-actions/human-action-record.js +1 -0
- package/dist/packages/core/src/human-actions/human-action.d.ts +13 -0
- package/dist/packages/core/src/human-actions/human-action.js +1 -0
- package/dist/packages/core/src/human-actions/question.d.ts +8 -0
- package/dist/packages/core/src/human-actions/question.js +1 -0
- package/dist/packages/core/src/state/phase.d.ts +2 -0
- package/dist/packages/core/src/state/phase.js +1 -0
- package/dist/packages/core/src/state/workflow-runtime-state.d.ts +14 -0
- package/dist/packages/core/src/state/workflow-runtime-state.js +1 -0
- package/dist/packages/core/src/state/workflow-state.d.ts +13 -0
- package/dist/packages/core/src/state/workflow-state.js +1 -0
- package/dist/packages/core/src/transitions/default-phase-transition.d.ts +5 -0
- package/dist/packages/core/src/transitions/default-phase-transition.js +195 -0
- package/dist/packages/core/src/transitions/phase-transition.d.ts +22 -0
- package/dist/packages/core/src/transitions/phase-transition.js +1 -0
- package/dist/packages/core/src/transitions/transition-action.d.ts +20 -0
- package/dist/packages/core/src/transitions/transition-action.js +1 -0
- package/dist/packages/runtime/src/artifacts/file-system-artifact-evaluator.d.ts +36 -0
- package/dist/packages/runtime/src/artifacts/file-system-artifact-evaluator.js +1213 -0
- package/dist/packages/runtime/src/attach/attach-service.d.ts +15 -0
- package/dist/packages/runtime/src/attach/attach-service.js +31 -0
- package/dist/packages/runtime/src/bootstrap/create-harness.d.ts +33 -0
- package/dist/packages/runtime/src/bootstrap/create-harness.js +79 -0
- package/dist/packages/runtime/src/bootstrap/initialize-workflow.d.ts +8 -0
- package/dist/packages/runtime/src/bootstrap/initialize-workflow.js +33 -0
- package/dist/packages/runtime/src/commands/create-opencode-workflow-commands.d.ts +12 -0
- package/dist/packages/runtime/src/commands/create-opencode-workflow-commands.js +24 -0
- package/dist/packages/runtime/src/commands/default-workflow-command-runner.d.ts +4 -0
- package/dist/packages/runtime/src/commands/default-workflow-command-runner.js +343 -0
- package/dist/packages/runtime/src/commands/opencode-plugin-command-adapter.d.ts +20 -0
- package/dist/packages/runtime/src/commands/opencode-plugin-command-adapter.js +22 -0
- package/dist/packages/runtime/src/commands/workflow-command-runner.d.ts +19 -0
- package/dist/packages/runtime/src/commands/workflow-command-runner.js +1 -0
- package/dist/packages/runtime/src/commands/workflow-open-request.d.ts +10 -0
- package/dist/packages/runtime/src/commands/workflow-open-request.js +220 -0
- package/dist/packages/runtime/src/config/skill-registry.d.ts +15 -0
- package/dist/packages/runtime/src/config/skill-registry.js +108 -0
- package/dist/packages/runtime/src/config/workflow-config.d.ts +17 -0
- package/dist/packages/runtime/src/config/workflow-config.js +51 -0
- package/dist/packages/runtime/src/diagnostics/workflow-diagnostics-format.d.ts +4 -0
- package/dist/packages/runtime/src/diagnostics/workflow-diagnostics-format.js +70 -0
- package/dist/packages/runtime/src/diagnostics/workflow-doctor.d.ts +23 -0
- package/dist/packages/runtime/src/diagnostics/workflow-doctor.js +120 -0
- package/dist/packages/runtime/src/engine/default-workflow-engine.d.ts +9 -0
- package/dist/packages/runtime/src/engine/default-workflow-engine.js +337 -0
- package/dist/packages/runtime/src/engine/workflow-engine.d.ts +28 -0
- package/dist/packages/runtime/src/engine/workflow-engine.js +1 -0
- package/dist/packages/runtime/src/events/file-system-workflow-event-store.d.ts +8 -0
- package/dist/packages/runtime/src/events/file-system-workflow-event-store.js +28 -0
- package/dist/packages/runtime/src/events/workflow-event-store.d.ts +10 -0
- package/dist/packages/runtime/src/events/workflow-event-store.js +1 -0
- package/dist/packages/runtime/src/index.d.ts +4 -0
- package/dist/packages/runtime/src/index.js +4 -0
- package/dist/packages/runtime/src/install/workflow-installer.d.ts +15 -0
- package/dist/packages/runtime/src/install/workflow-installer.js +111 -0
- package/dist/packages/runtime/src/plugin/workflow-plugin-entry.d.ts +167 -0
- package/dist/packages/runtime/src/plugin/workflow-plugin-entry.js +340 -0
- package/dist/packages/runtime/src/presentation/human-action-renderer.d.ts +13 -0
- package/dist/packages/runtime/src/presentation/human-action-renderer.js +161 -0
- package/dist/packages/runtime/src/presentation/watch-renderer.d.ts +12 -0
- package/dist/packages/runtime/src/presentation/watch-renderer.js +17 -0
- package/dist/packages/runtime/src/recovery/basic-recovery-classifier.d.ts +4 -0
- package/dist/packages/runtime/src/recovery/basic-recovery-classifier.js +12 -0
- package/dist/packages/runtime/src/recovery/recovery-classifier.d.ts +4 -0
- package/dist/packages/runtime/src/recovery/recovery-classifier.js +1 -0
- package/dist/packages/runtime/src/scheduling/immediate-tick-scheduler.d.ts +9 -0
- package/dist/packages/runtime/src/scheduling/immediate-tick-scheduler.js +28 -0
- package/dist/packages/runtime/src/scheduling/tick-scheduler.d.ts +3 -0
- package/dist/packages/runtime/src/scheduling/tick-scheduler.js +1 -0
- package/dist/packages/runtime/src/sessions/file-system-session-coordinator.d.ts +19 -0
- package/dist/packages/runtime/src/sessions/file-system-session-coordinator.js +132 -0
- package/dist/packages/runtime/src/sessions/session-activity-monitor.d.ts +22 -0
- package/dist/packages/runtime/src/sessions/session-activity-monitor.js +112 -0
- package/dist/packages/runtime/src/sessions/session-coordinator.d.ts +24 -0
- package/dist/packages/runtime/src/sessions/session-coordinator.js +1 -0
- package/dist/packages/runtime/src/shared/json-file.d.ts +2 -0
- package/dist/packages/runtime/src/shared/json-file.js +19 -0
- package/dist/packages/runtime/src/state/file-system-human-action-store.d.ts +15 -0
- package/dist/packages/runtime/src/state/file-system-human-action-store.js +69 -0
- package/dist/packages/runtime/src/state/file-system-workflow-state-store.d.ts +15 -0
- package/dist/packages/runtime/src/state/file-system-workflow-state-store.js +59 -0
- package/dist/packages/runtime/src/state/human-action-service.d.ts +21 -0
- package/dist/packages/runtime/src/state/human-action-service.js +80 -0
- package/dist/packages/runtime/src/state/human-action-store.d.ts +9 -0
- package/dist/packages/runtime/src/state/human-action-store.js +1 -0
- package/dist/packages/runtime/src/state/workflow-state-store.d.ts +11 -0
- package/dist/packages/runtime/src/state/workflow-state-store.js +1 -0
- package/dist/packages/runtime/src/subtasks/noop-subtask-tracker.d.ts +4 -0
- package/dist/packages/runtime/src/subtasks/noop-subtask-tracker.js +5 -0
- package/dist/packages/runtime/src/subtasks/subtask-tracker.d.ts +3 -0
- package/dist/packages/runtime/src/subtasks/subtask-tracker.js +1 -0
- package/dist/packages/runtime/src/workspace/workflow-workspace.d.ts +31 -0
- package/dist/packages/runtime/src/workspace/workflow-workspace.js +43 -0
- package/dist/plugin.d.ts +1 -0
- package/dist/plugin.js +1 -0
- package/dist/src/cli.d.ts +1 -0
- package/dist/src/cli.js +175 -0
- package/package.json +56 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { WorkflowPluginCommandDefinition } from "../commands/create-opencode-workflow-commands";
|
|
2
|
+
import { createHarness } from "../bootstrap/create-harness";
|
|
3
|
+
import { type PluginSdkClient } from "../../../adapters/opencode/src/opencode-session-client";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
export interface WorkflowPluginInputLike {
|
|
6
|
+
directory: string;
|
|
7
|
+
serverUrl?: string;
|
|
8
|
+
client?: PluginSdkClient;
|
|
9
|
+
}
|
|
10
|
+
type PluginEventInput = {
|
|
11
|
+
event: {
|
|
12
|
+
type: string;
|
|
13
|
+
properties?: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
type NativeAgentConfig = {
|
|
17
|
+
mode: "primary" | "subagent" | "all";
|
|
18
|
+
description: string;
|
|
19
|
+
model: string;
|
|
20
|
+
prompt: string;
|
|
21
|
+
tools: Record<string, boolean>;
|
|
22
|
+
};
|
|
23
|
+
type NativeHostConfig = {
|
|
24
|
+
agent?: Record<string, NativeAgentConfig>;
|
|
25
|
+
};
|
|
26
|
+
type WorkflowToolArgs = {
|
|
27
|
+
command: WorkflowPluginCommandDefinition["name"];
|
|
28
|
+
workflowId: string;
|
|
29
|
+
payload?: string;
|
|
30
|
+
};
|
|
31
|
+
type WorkflowToolContext = {
|
|
32
|
+
sessionID?: string;
|
|
33
|
+
};
|
|
34
|
+
export interface WorkflowPluginEntry {
|
|
35
|
+
name: string;
|
|
36
|
+
commands: WorkflowPluginCommandDefinition[];
|
|
37
|
+
}
|
|
38
|
+
type WorkflowPrimaryAgentMetadata = {
|
|
39
|
+
name: string;
|
|
40
|
+
mode: "primary";
|
|
41
|
+
description: string;
|
|
42
|
+
model: string;
|
|
43
|
+
tools: Record<string, boolean>;
|
|
44
|
+
promptFile: string;
|
|
45
|
+
manifestFile: string;
|
|
46
|
+
};
|
|
47
|
+
export interface WorkflowPluginEntryOptions {
|
|
48
|
+
baseDir: string;
|
|
49
|
+
harnessPromise?: Promise<Awaited<ReturnType<typeof createHarness>>>;
|
|
50
|
+
opencodeBaseUrl?: string;
|
|
51
|
+
opencodePassword?: string;
|
|
52
|
+
sessionClient?: Awaited<ReturnType<typeof createHarness>>["sessionClient"];
|
|
53
|
+
}
|
|
54
|
+
export declare function createWorkflowPluginEntry(options: WorkflowPluginEntryOptions): WorkflowPluginEntry;
|
|
55
|
+
export declare function workflowPlugin(input: WorkflowPluginInputLike): Promise<{
|
|
56
|
+
config: (cfg?: NativeHostConfig) => Promise<void>;
|
|
57
|
+
healthcheck: () => Promise<{
|
|
58
|
+
ok: boolean;
|
|
59
|
+
name: string;
|
|
60
|
+
commands: import("../commands/workflow-command-runner").WorkflowChannelCommand[];
|
|
61
|
+
primaryAgent: WorkflowPrimaryAgentMetadata;
|
|
62
|
+
}>;
|
|
63
|
+
event: (inputEvent: PluginEventInput) => Promise<void>;
|
|
64
|
+
tool: {
|
|
65
|
+
workflow_channel: {
|
|
66
|
+
description: string;
|
|
67
|
+
args: {
|
|
68
|
+
command: z.ZodEnum<{
|
|
69
|
+
"workflow-open": "workflow-open";
|
|
70
|
+
"workflow-attach": "workflow-attach";
|
|
71
|
+
"workflow-status": "workflow-status";
|
|
72
|
+
"workflow-answer": "workflow-answer";
|
|
73
|
+
"workflow-approve": "workflow-approve";
|
|
74
|
+
"workflow-resume": "workflow-resume";
|
|
75
|
+
"workflow-back": "workflow-back";
|
|
76
|
+
}>;
|
|
77
|
+
workflowId: z.ZodString;
|
|
78
|
+
payload: z.ZodOptional<z.ZodString>;
|
|
79
|
+
};
|
|
80
|
+
execute: (args: WorkflowToolArgs, context?: WorkflowToolContext) => Promise<string>;
|
|
81
|
+
};
|
|
82
|
+
workflow_open: {
|
|
83
|
+
description: string;
|
|
84
|
+
args: {
|
|
85
|
+
workflowId: z.ZodString;
|
|
86
|
+
payload: z.ZodOptional<z.ZodString>;
|
|
87
|
+
};
|
|
88
|
+
execute: (args: {
|
|
89
|
+
workflowId: string;
|
|
90
|
+
payload?: string;
|
|
91
|
+
}, context?: WorkflowToolContext) => Promise<string>;
|
|
92
|
+
};
|
|
93
|
+
workflow_attach: {
|
|
94
|
+
description: string;
|
|
95
|
+
args: {
|
|
96
|
+
workflowId: z.ZodString;
|
|
97
|
+
};
|
|
98
|
+
execute: (args: {
|
|
99
|
+
workflowId: string;
|
|
100
|
+
}, context?: WorkflowToolContext) => Promise<string>;
|
|
101
|
+
};
|
|
102
|
+
workflow_status: {
|
|
103
|
+
description: string;
|
|
104
|
+
args: {
|
|
105
|
+
workflowId: z.ZodString;
|
|
106
|
+
};
|
|
107
|
+
execute: (args: {
|
|
108
|
+
workflowId: string;
|
|
109
|
+
}, context?: WorkflowToolContext) => Promise<string>;
|
|
110
|
+
};
|
|
111
|
+
workflow_answer: {
|
|
112
|
+
description: string;
|
|
113
|
+
args: {
|
|
114
|
+
workflowId: z.ZodString;
|
|
115
|
+
payload: z.ZodString;
|
|
116
|
+
};
|
|
117
|
+
execute: (args: {
|
|
118
|
+
workflowId: string;
|
|
119
|
+
payload: string;
|
|
120
|
+
}, context?: WorkflowToolContext) => Promise<string>;
|
|
121
|
+
};
|
|
122
|
+
workflow_approve: {
|
|
123
|
+
description: string;
|
|
124
|
+
args: {
|
|
125
|
+
workflowId: z.ZodString;
|
|
126
|
+
};
|
|
127
|
+
execute: (args: {
|
|
128
|
+
workflowId: string;
|
|
129
|
+
}, context?: WorkflowToolContext) => Promise<string>;
|
|
130
|
+
};
|
|
131
|
+
workflow_resume: {
|
|
132
|
+
description: string;
|
|
133
|
+
args: {
|
|
134
|
+
workflowId: z.ZodString;
|
|
135
|
+
};
|
|
136
|
+
execute: (args: {
|
|
137
|
+
workflowId: string;
|
|
138
|
+
}, context?: WorkflowToolContext) => Promise<string>;
|
|
139
|
+
};
|
|
140
|
+
workflow_back: {
|
|
141
|
+
description: string;
|
|
142
|
+
args: {
|
|
143
|
+
workflowId: z.ZodString;
|
|
144
|
+
};
|
|
145
|
+
execute: (args: {
|
|
146
|
+
workflowId: string;
|
|
147
|
+
}, context?: WorkflowToolContext) => Promise<string>;
|
|
148
|
+
};
|
|
149
|
+
workflow_doctor: {
|
|
150
|
+
description: string;
|
|
151
|
+
args: {};
|
|
152
|
+
execute: () => Promise<string>;
|
|
153
|
+
};
|
|
154
|
+
workflow_install: {
|
|
155
|
+
description: string;
|
|
156
|
+
args: {};
|
|
157
|
+
execute: () => Promise<string>;
|
|
158
|
+
};
|
|
159
|
+
};
|
|
160
|
+
workflowCommands: WorkflowPluginCommandDefinition[];
|
|
161
|
+
workflow: {
|
|
162
|
+
name: string;
|
|
163
|
+
commands: WorkflowPluginCommandDefinition[];
|
|
164
|
+
primaryAgent: WorkflowPrimaryAgentMetadata;
|
|
165
|
+
};
|
|
166
|
+
}>;
|
|
167
|
+
export default workflowPlugin;
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
import { createOpencodeWorkflowCommands } from "../commands/create-opencode-workflow-commands";
|
|
2
|
+
import { DefaultWorkflowCommandRunner } from "../commands/default-workflow-command-runner";
|
|
3
|
+
import { DefaultWorkflowPluginCommandAdapter } from "../commands/opencode-plugin-command-adapter";
|
|
4
|
+
import { createHarness } from "../bootstrap/create-harness";
|
|
5
|
+
import { runWorkflowDoctor } from "../diagnostics/workflow-doctor";
|
|
6
|
+
import { runWorkflowInstall } from "../install/workflow-installer";
|
|
7
|
+
import { SdkOpencodeSessionClient, } from "../../../adapters/opencode/src/opencode-session-client";
|
|
8
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { join } from "node:path";
|
|
11
|
+
import { z } from "zod";
|
|
12
|
+
const WORKFLOW_PRIMARY_AGENT_PROMPT = `You are the workflow execution agent.
|
|
13
|
+
|
|
14
|
+
Responsibilities:
|
|
15
|
+
- Act as the main entry agent for the workflow lifecycle.
|
|
16
|
+
- Use workflow_open to start a workflow when none exists.
|
|
17
|
+
- Prefer workflow_attach to continue an existing workflow. Use workflow_status mainly when you need a fresh read without driving continuation.
|
|
18
|
+
- Follow runtime output strictly. If Recommended tool / payload is present, use it instead of inventing a phase jump.
|
|
19
|
+
- Use workflow_answer, workflow_approve, and workflow_resume only when the runtime asks for human action.
|
|
20
|
+
- If workflow_open returns a clarification question, STOP and ask the user that question. Do not call workflow_status or other workflow tools until the user answers.
|
|
21
|
+
- When a workflow tool returns a structured workflow block, preserve and show the full block to the user. Do not compress it into a one-line summary unless the user explicitly asks for a summary.
|
|
22
|
+
- If the workflow is in progress and the runtime output recommends workflow_attach, continue with workflow_attach instead of stopping at workflow_status.
|
|
23
|
+
|
|
24
|
+
Hard rules:
|
|
25
|
+
- Never skip phases manually.
|
|
26
|
+
- Never assume you can jump directly to develop/review/test.
|
|
27
|
+
- The runtime state machine is the only authority for phase progression.
|
|
28
|
+
- If the user provides natural language only, keep it as-is and let the workflow runtime + downstream AI refinement handle understanding and document selection.
|
|
29
|
+
`;
|
|
30
|
+
const WORKFLOW_PRIMARY_AGENT_DESCRIPTION = "Primary workflow agent that drives refine->plan->develop->review->test via workflow tools";
|
|
31
|
+
const WORKFLOW_PRIMARY_AGENT_MODEL = "ppchat-codex/gpt-5.4";
|
|
32
|
+
function buildPrimaryAgentMetadata(baseDir) {
|
|
33
|
+
return {
|
|
34
|
+
name: "workflow",
|
|
35
|
+
mode: "primary",
|
|
36
|
+
description: WORKFLOW_PRIMARY_AGENT_DESCRIPTION,
|
|
37
|
+
model: WORKFLOW_PRIMARY_AGENT_MODEL,
|
|
38
|
+
tools: {
|
|
39
|
+
workflow_open: true,
|
|
40
|
+
workflow_attach: true,
|
|
41
|
+
workflow_status: true,
|
|
42
|
+
workflow_answer: true,
|
|
43
|
+
workflow_approve: true,
|
|
44
|
+
workflow_resume: true,
|
|
45
|
+
workflow_back: true,
|
|
46
|
+
},
|
|
47
|
+
promptFile: `${baseDir}/workflow-primary-agent.prompt.md`,
|
|
48
|
+
manifestFile: `${baseDir}/workflow-primary-agent.manifest.json`,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
function buildWorkflowTodos(phase, status) {
|
|
52
|
+
const phases = [
|
|
53
|
+
{ key: "spec_refinement", title: "Workflow / Refinement" },
|
|
54
|
+
{ key: "plan", title: "Workflow / Plan" },
|
|
55
|
+
{ key: "develop", title: "Workflow / Develop" },
|
|
56
|
+
{ key: "review", title: "Workflow / Review" },
|
|
57
|
+
{ key: "test", title: "Workflow / Test" },
|
|
58
|
+
{ key: "done", title: "Workflow / Done" },
|
|
59
|
+
];
|
|
60
|
+
const currentIndex = phases.findIndex((item) => item.key === phase || (phase === "blocked" && item.key === "test"));
|
|
61
|
+
return phases.map((item, index) => ({
|
|
62
|
+
title: item.title,
|
|
63
|
+
completed: phase === "done"
|
|
64
|
+
? true
|
|
65
|
+
: currentIndex === -1
|
|
66
|
+
? false
|
|
67
|
+
: index < currentIndex,
|
|
68
|
+
}))
|
|
69
|
+
.map((item) => ({
|
|
70
|
+
...item,
|
|
71
|
+
completed: status === "completed" && item.title === "Workflow / Done" ? true : item.completed,
|
|
72
|
+
}));
|
|
73
|
+
}
|
|
74
|
+
async function syncHostTodos(args) {
|
|
75
|
+
const todoClient = args.client?.session?.todo;
|
|
76
|
+
if (!todoClient?.list || !todoClient.create || !todoClient.update) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const stateFile = join(args.baseDir, "workflows", args.workflowId, "workflow-state.json");
|
|
80
|
+
let workflow = null;
|
|
81
|
+
try {
|
|
82
|
+
workflow = JSON.parse(await readFile(stateFile, "utf8"));
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (!workflow?.activeSessionId || !workflow.phase || !workflow.status) {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const desired = buildWorkflowTodos(workflow.phase, workflow.status);
|
|
91
|
+
const existingResponse = await todoClient.list({ path: { id: workflow.activeSessionId } });
|
|
92
|
+
const existing = existingResponse.data ?? [];
|
|
93
|
+
for (const todo of desired) {
|
|
94
|
+
const current = existing.find((item) => item.title === todo.title);
|
|
95
|
+
if (!current) {
|
|
96
|
+
await todoClient.create({
|
|
97
|
+
path: { id: workflow.activeSessionId },
|
|
98
|
+
body: {
|
|
99
|
+
title: todo.title,
|
|
100
|
+
content: `workflowId=${workflow.workflowId}; phase=${workflow.phase}; status=${workflow.status}`,
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if ((current.completed ?? false) !== todo.completed) {
|
|
106
|
+
await todoClient.update({
|
|
107
|
+
path: { id: workflow.activeSessionId, todoId: current.id },
|
|
108
|
+
body: {
|
|
109
|
+
completed: todo.completed,
|
|
110
|
+
content: `workflowId=${workflow.workflowId}; phase=${workflow.phase}; status=${workflow.status}`,
|
|
111
|
+
},
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function findWorkflowBySessionId(args) {
|
|
117
|
+
const harness = await createHarness(args.baseDir);
|
|
118
|
+
const workflows = await harness.stateStore.listWorkflows?.();
|
|
119
|
+
const match = workflows?.find((workflow) => workflow.activeSessionId === args.sessionId);
|
|
120
|
+
if (!match) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
return {
|
|
124
|
+
workflowId: match.workflowId,
|
|
125
|
+
phase: match.phase,
|
|
126
|
+
status: match.status,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
export function createWorkflowPluginEntry(options) {
|
|
130
|
+
const harnessPromise = options.harnessPromise
|
|
131
|
+
?? createHarness(options.baseDir, {
|
|
132
|
+
...(options.sessionClient ? { sessionClient: options.sessionClient } : {}),
|
|
133
|
+
...(options.opencodeBaseUrl ? { opencodeBaseUrl: options.opencodeBaseUrl } : {}),
|
|
134
|
+
...(options.opencodePassword ? { opencodePassword: options.opencodePassword } : {}),
|
|
135
|
+
});
|
|
136
|
+
const adapter = new DefaultWorkflowPluginCommandAdapter(new DefaultWorkflowCommandRunner(), () => harnessPromise);
|
|
137
|
+
return {
|
|
138
|
+
name: "autopilot",
|
|
139
|
+
commands: createOpencodeWorkflowCommands(adapter),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
export async function workflowPlugin(input) {
|
|
143
|
+
const baseDir = `${input.directory}/.workflow-harness`;
|
|
144
|
+
const sdkSessionClient = input.client
|
|
145
|
+
? new SdkOpencodeSessionClient(input.client)
|
|
146
|
+
: undefined;
|
|
147
|
+
const harnessPromise = createHarness(baseDir, {
|
|
148
|
+
...(sdkSessionClient ? { sessionClient: sdkSessionClient } : {}),
|
|
149
|
+
...(input.serverUrl ? { opencodeBaseUrl: input.serverUrl } : {}),
|
|
150
|
+
});
|
|
151
|
+
const entry = createWorkflowPluginEntry({
|
|
152
|
+
baseDir,
|
|
153
|
+
harnessPromise,
|
|
154
|
+
});
|
|
155
|
+
const commandNames = entry.commands.map((command) => command.name);
|
|
156
|
+
const loadMessage = `Autopilot plugin loaded (${commandNames.length} commands)`;
|
|
157
|
+
const commandMap = new Map(entry.commands.map((command) => [command.name, command]));
|
|
158
|
+
const primaryAgent = buildPrimaryAgentMetadata(baseDir);
|
|
159
|
+
const invokeCommand = async (commandName, workflowId, payload, foregroundSessionId) => {
|
|
160
|
+
const command = commandMap.get(commandName);
|
|
161
|
+
if (!command) {
|
|
162
|
+
return `Unknown workflow command: ${commandName}`;
|
|
163
|
+
}
|
|
164
|
+
const result = await command.execute({
|
|
165
|
+
workflowId,
|
|
166
|
+
...(payload !== undefined ? { payload } : {}),
|
|
167
|
+
...(foregroundSessionId ? { foregroundSessionId } : {}),
|
|
168
|
+
});
|
|
169
|
+
await syncHostTodos({
|
|
170
|
+
baseDir,
|
|
171
|
+
workflowId,
|
|
172
|
+
...(input.client ? { client: input.client } : {}),
|
|
173
|
+
});
|
|
174
|
+
return result;
|
|
175
|
+
};
|
|
176
|
+
console.log(`[autopilot] ${loadMessage}`);
|
|
177
|
+
await mkdir(baseDir, { recursive: true });
|
|
178
|
+
await writeFile(primaryAgent.promptFile, `${WORKFLOW_PRIMARY_AGENT_PROMPT}\n`, "utf8");
|
|
179
|
+
await writeFile(primaryAgent.manifestFile, `${JSON.stringify(primaryAgent, null, 2)}\n`, "utf8");
|
|
180
|
+
await writeFile(`${baseDir}/plugin-load.json`, `${JSON.stringify({
|
|
181
|
+
ok: true,
|
|
182
|
+
name: entry.name,
|
|
183
|
+
loadedAt: new Date().toISOString(),
|
|
184
|
+
commands: commandNames,
|
|
185
|
+
}, null, 2)}\n`, "utf8");
|
|
186
|
+
return {
|
|
187
|
+
config: async (cfg) => {
|
|
188
|
+
if (!cfg) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
cfg.agent ??= {};
|
|
192
|
+
cfg.agent[primaryAgent.name] = {
|
|
193
|
+
mode: primaryAgent.mode,
|
|
194
|
+
description: primaryAgent.description,
|
|
195
|
+
model: primaryAgent.model,
|
|
196
|
+
prompt: WORKFLOW_PRIMARY_AGENT_PROMPT,
|
|
197
|
+
tools: primaryAgent.tools,
|
|
198
|
+
};
|
|
199
|
+
},
|
|
200
|
+
healthcheck: async () => ({
|
|
201
|
+
ok: true,
|
|
202
|
+
name: entry.name,
|
|
203
|
+
commands: commandNames,
|
|
204
|
+
primaryAgent,
|
|
205
|
+
}),
|
|
206
|
+
event: async (inputEvent) => {
|
|
207
|
+
const sessionId = typeof inputEvent.event.properties?.sessionID === "string"
|
|
208
|
+
? inputEvent.event.properties.sessionID
|
|
209
|
+
: undefined;
|
|
210
|
+
if (!sessionId) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
const workflow = await findWorkflowBySessionId({
|
|
214
|
+
baseDir,
|
|
215
|
+
sessionId,
|
|
216
|
+
});
|
|
217
|
+
if (!workflow) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const harness = await harnessPromise;
|
|
221
|
+
await syncHostTodos({
|
|
222
|
+
baseDir,
|
|
223
|
+
workflowId: workflow.workflowId,
|
|
224
|
+
...(input.client ? { client: input.client } : {}),
|
|
225
|
+
});
|
|
226
|
+
if (inputEvent.event.type === "session.idle") {
|
|
227
|
+
await harness.tickScheduler.requestTick(workflow.workflowId, "plugin observed session idle");
|
|
228
|
+
}
|
|
229
|
+
if (!input.client?.tui?.showToast) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
if (inputEvent.event.type === "session.idle") {
|
|
233
|
+
await input.client.tui.showToast({
|
|
234
|
+
body: {
|
|
235
|
+
message: `Workflow ${workflow.workflowId}: ${workflow.phase ?? "unknown"} / ${workflow.status ?? "unknown"}`,
|
|
236
|
+
variant: "info",
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
if (inputEvent.event.type === "session.error") {
|
|
241
|
+
await input.client.tui.showToast({
|
|
242
|
+
body: {
|
|
243
|
+
message: `Workflow ${workflow.workflowId} encountered a session error`,
|
|
244
|
+
variant: "error",
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
tool: {
|
|
250
|
+
workflow_channel: {
|
|
251
|
+
description: "Run workflow channel commands inside the workflow harness.",
|
|
252
|
+
args: {
|
|
253
|
+
command: z.enum(commandNames)
|
|
254
|
+
.describe("Workflow channel command to execute"),
|
|
255
|
+
workflowId: z.string().describe("Workflow identifier"),
|
|
256
|
+
payload: z.string().optional().describe("Optional JSON/string payload for answer-like commands"),
|
|
257
|
+
},
|
|
258
|
+
execute: async (args, context) => {
|
|
259
|
+
return invokeCommand(args.command, args.workflowId, args.payload, context?.sessionID);
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
workflow_open: {
|
|
263
|
+
description: "Open or initialize a workflow channel and attach to it.",
|
|
264
|
+
args: {
|
|
265
|
+
workflowId: z.string().describe("Workflow identifier"),
|
|
266
|
+
payload: z.string().optional().describe("Initial request. Supports plain text or JSON: { prompt, docPaths[], projectContext }"),
|
|
267
|
+
},
|
|
268
|
+
execute: async (args, context) => invokeCommand("workflow-open", args.workflowId, args.payload, context?.sessionID),
|
|
269
|
+
},
|
|
270
|
+
workflow_attach: {
|
|
271
|
+
description: "Attach to an existing workflow channel.",
|
|
272
|
+
args: {
|
|
273
|
+
workflowId: z.string().describe("Workflow identifier"),
|
|
274
|
+
},
|
|
275
|
+
execute: async (args, context) => invokeCommand("workflow-attach", args.workflowId, undefined, context?.sessionID),
|
|
276
|
+
},
|
|
277
|
+
workflow_status: {
|
|
278
|
+
description: "Render the current workflow status block.",
|
|
279
|
+
args: {
|
|
280
|
+
workflowId: z.string().describe("Workflow identifier"),
|
|
281
|
+
},
|
|
282
|
+
execute: async (args, context) => invokeCommand("workflow-status", args.workflowId, undefined, context?.sessionID),
|
|
283
|
+
},
|
|
284
|
+
workflow_answer: {
|
|
285
|
+
description: "Answer workflow clarification questions.",
|
|
286
|
+
args: {
|
|
287
|
+
workflowId: z.string().describe("Workflow identifier"),
|
|
288
|
+
payload: z.string().describe("JSON string payload for question answers"),
|
|
289
|
+
},
|
|
290
|
+
execute: async (args, context) => invokeCommand("workflow-answer", args.workflowId, args.payload, context?.sessionID),
|
|
291
|
+
},
|
|
292
|
+
workflow_approve: {
|
|
293
|
+
description: "Approve the current workflow plan or decision.",
|
|
294
|
+
args: {
|
|
295
|
+
workflowId: z.string().describe("Workflow identifier"),
|
|
296
|
+
},
|
|
297
|
+
execute: async (args, context) => invokeCommand("workflow-approve", args.workflowId, undefined, context?.sessionID),
|
|
298
|
+
},
|
|
299
|
+
workflow_resume: {
|
|
300
|
+
description: "Resume a blocked workflow.",
|
|
301
|
+
args: {
|
|
302
|
+
workflowId: z.string().describe("Workflow identifier"),
|
|
303
|
+
},
|
|
304
|
+
execute: async (args, context) => invokeCommand("workflow-resume", args.workflowId, undefined, context?.sessionID),
|
|
305
|
+
},
|
|
306
|
+
workflow_back: {
|
|
307
|
+
description: "Leave the workflow channel without stopping the workflow.",
|
|
308
|
+
args: {
|
|
309
|
+
workflowId: z.string().describe("Workflow identifier"),
|
|
310
|
+
},
|
|
311
|
+
execute: async (args, context) => invokeCommand("workflow-back", args.workflowId, undefined, context?.sessionID),
|
|
312
|
+
},
|
|
313
|
+
workflow_doctor: {
|
|
314
|
+
description: "Run workflow configuration and skill self-check diagnostics.",
|
|
315
|
+
args: {},
|
|
316
|
+
execute: async () => {
|
|
317
|
+
const harness = await harnessPromise;
|
|
318
|
+
return JSON.stringify(await runWorkflowDoctor(harness.workspace), null, 2);
|
|
319
|
+
},
|
|
320
|
+
},
|
|
321
|
+
workflow_install: {
|
|
322
|
+
description: "Generate workflow.json and safely register the plugin in OpenCode config.",
|
|
323
|
+
args: {},
|
|
324
|
+
execute: async () => {
|
|
325
|
+
return JSON.stringify(await runWorkflowInstall({
|
|
326
|
+
cwd: input.directory,
|
|
327
|
+
homeDir: homedir(),
|
|
328
|
+
}), null, 2);
|
|
329
|
+
},
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
workflowCommands: entry.commands,
|
|
333
|
+
workflow: {
|
|
334
|
+
name: entry.name,
|
|
335
|
+
commands: entry.commands,
|
|
336
|
+
primaryAgent,
|
|
337
|
+
},
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
export default workflowPlugin;
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
export declare function renderHumanActionBlock(args: {
|
|
5
|
+
workflow: WorkflowState;
|
|
6
|
+
runtime: WorkflowRuntimeState | null;
|
|
7
|
+
humanAction: HumanActionRecord | null;
|
|
8
|
+
clarification?: {
|
|
9
|
+
prompt: string;
|
|
10
|
+
options: string[];
|
|
11
|
+
} | null;
|
|
12
|
+
phaseDetails?: string[];
|
|
13
|
+
}): string;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
const divider = "=".repeat(64);
|
|
2
|
+
const phaseOrder = ["spec_refinement", "plan", "develop", "review", "test", "done"];
|
|
3
|
+
function renderProgressTodos(workflow) {
|
|
4
|
+
const currentIndex = phaseOrder.indexOf(workflow.phase);
|
|
5
|
+
if (currentIndex === -1) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
const labelByPhase = {
|
|
9
|
+
spec_refinement: "Refinement",
|
|
10
|
+
plan: "Plan",
|
|
11
|
+
develop: "Develop",
|
|
12
|
+
review: "Review",
|
|
13
|
+
test: "Test",
|
|
14
|
+
done: "Done",
|
|
15
|
+
};
|
|
16
|
+
return phaseOrder.map((phase, index) => {
|
|
17
|
+
const marker = workflow.phase === "blocked"
|
|
18
|
+
? index < currentIndex ? "[x]" : "[-]"
|
|
19
|
+
: index < currentIndex
|
|
20
|
+
? "[x]"
|
|
21
|
+
: index === currentIndex
|
|
22
|
+
? "[~]"
|
|
23
|
+
: "[ ]";
|
|
24
|
+
return `${marker} ${labelByPhase[phase]}`;
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function renderClarificationBlock(clarification) {
|
|
28
|
+
const lines = ["Clarification required:", clarification.prompt];
|
|
29
|
+
if (clarification.options.length > 0) {
|
|
30
|
+
lines.push(...clarification.options);
|
|
31
|
+
}
|
|
32
|
+
return lines;
|
|
33
|
+
}
|
|
34
|
+
function actionLabel(actionType) {
|
|
35
|
+
switch (actionType) {
|
|
36
|
+
case "need_answers":
|
|
37
|
+
return "Answer Required";
|
|
38
|
+
case "need_approval":
|
|
39
|
+
return "Approval Required";
|
|
40
|
+
case "blocked":
|
|
41
|
+
return "Manual Decision Required";
|
|
42
|
+
case "done":
|
|
43
|
+
return "Done";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function exactAction(record) {
|
|
47
|
+
if (record.action.type === "need_answers") {
|
|
48
|
+
const payload = recommendedPayload(record) ?? '{"your-question-id":"your answer"}';
|
|
49
|
+
return `Run: bun run src/cli.ts answer ${record.workflowId} '${payload}'`;
|
|
50
|
+
}
|
|
51
|
+
if (record.action.type === "need_approval") {
|
|
52
|
+
return `Run: bun run src/cli.ts approve ${record.workflowId}`;
|
|
53
|
+
}
|
|
54
|
+
if (record.action.type === "blocked") {
|
|
55
|
+
return `Run: bun run src/cli.ts resume ${record.workflowId}`;
|
|
56
|
+
}
|
|
57
|
+
return "No action required";
|
|
58
|
+
}
|
|
59
|
+
function recommendedTool(record) {
|
|
60
|
+
if (record.action.type === "need_answers") {
|
|
61
|
+
return "workflow_answer";
|
|
62
|
+
}
|
|
63
|
+
if (record.action.type === "need_approval") {
|
|
64
|
+
return "workflow_approve";
|
|
65
|
+
}
|
|
66
|
+
if (record.action.type === "blocked") {
|
|
67
|
+
return "workflow_resume";
|
|
68
|
+
}
|
|
69
|
+
return "workflow_status";
|
|
70
|
+
}
|
|
71
|
+
function recommendedPayload(record) {
|
|
72
|
+
if (record.action.type !== "need_answers") {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const payload = Object.fromEntries((record.action.questions ?? []).map((question) => [question.id, question.suggestedAnswer ?? "your answer"]));
|
|
76
|
+
return JSON.stringify(payload);
|
|
77
|
+
}
|
|
78
|
+
function renderQuestions(record) {
|
|
79
|
+
const questions = record.action.questions ?? [];
|
|
80
|
+
if (questions.length === 0) {
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
return [
|
|
84
|
+
"Questions:",
|
|
85
|
+
...questions.map((question, index) => {
|
|
86
|
+
const suggested = question.suggestedAnswer ? ` | suggested: ${question.suggestedAnswer}` : "";
|
|
87
|
+
return `${index + 1}. [${question.priority}] ${question.text}${suggested}`;
|
|
88
|
+
}),
|
|
89
|
+
];
|
|
90
|
+
}
|
|
91
|
+
export function renderHumanActionBlock(args) {
|
|
92
|
+
const { workflow, runtime, humanAction, clarification, phaseDetails = [] } = args;
|
|
93
|
+
const lines = [
|
|
94
|
+
divider,
|
|
95
|
+
`Workflow: ${workflow.workflowId}`,
|
|
96
|
+
`Phase: ${workflow.phase}`,
|
|
97
|
+
`Status: ${workflow.status}`,
|
|
98
|
+
`Iteration: ${workflow.iteration}/${workflow.maxIterations}`,
|
|
99
|
+
];
|
|
100
|
+
if (workflow.blockReason) {
|
|
101
|
+
lines.push(`Block reason: ${workflow.blockReason}`);
|
|
102
|
+
}
|
|
103
|
+
if (runtime?.recoveryState === "recovering") {
|
|
104
|
+
lines.push("Recovery: in progress");
|
|
105
|
+
}
|
|
106
|
+
if (clarification) {
|
|
107
|
+
lines.push("");
|
|
108
|
+
lines.push(...renderClarificationBlock(clarification));
|
|
109
|
+
lines.push(divider);
|
|
110
|
+
return lines.join("\n");
|
|
111
|
+
}
|
|
112
|
+
const progressTodos = renderProgressTodos(workflow);
|
|
113
|
+
if (progressTodos.length > 0) {
|
|
114
|
+
lines.push("");
|
|
115
|
+
lines.push("Progress:");
|
|
116
|
+
lines.push(...progressTodos);
|
|
117
|
+
}
|
|
118
|
+
if (phaseDetails.length > 0) {
|
|
119
|
+
lines.push("");
|
|
120
|
+
lines.push("Details:");
|
|
121
|
+
lines.push(...phaseDetails);
|
|
122
|
+
}
|
|
123
|
+
if (!humanAction || humanAction.status === "consumed") {
|
|
124
|
+
lines.push("Human action: none");
|
|
125
|
+
lines.push(`Recommended tool: ${workflow.status === "completed" ? "workflow_back" : workflow.status === "in_progress" ? "workflow_attach" : "workflow_status"}`);
|
|
126
|
+
lines.push(workflow.status === "completed"
|
|
127
|
+
? "Channel state: workflow completed"
|
|
128
|
+
: workflow.status === "in_progress"
|
|
129
|
+
? "Channel state: workflow is still running or waiting for the next attach-driven continuation"
|
|
130
|
+
: "Channel state: waiting for external progress or next attach");
|
|
131
|
+
if (workflow.status === "in_progress") {
|
|
132
|
+
lines.push(`Exact action: Re-run workflow_attach for ${workflow.workflowId} to continue the workflow loop.`);
|
|
133
|
+
lines.push("Agent hint: if you are the workflow main agent, do not stop here — continue with workflow_attach until a human action or terminal state appears.");
|
|
134
|
+
}
|
|
135
|
+
lines.push(divider);
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
}
|
|
138
|
+
lines.push("");
|
|
139
|
+
lines.push(`Human action: ${actionLabel(humanAction.action.type)}`);
|
|
140
|
+
lines.push(`Reason: ${humanAction.action.reason}`);
|
|
141
|
+
if (humanAction.action.summary) {
|
|
142
|
+
lines.push(`Summary: ${humanAction.action.summary}`);
|
|
143
|
+
}
|
|
144
|
+
if (humanAction.status) {
|
|
145
|
+
lines.push(`Action status: ${humanAction.status}`);
|
|
146
|
+
}
|
|
147
|
+
const questionLines = renderQuestions(humanAction);
|
|
148
|
+
if (questionLines.length > 0) {
|
|
149
|
+
lines.push("");
|
|
150
|
+
lines.push(...questionLines);
|
|
151
|
+
}
|
|
152
|
+
lines.push("");
|
|
153
|
+
lines.push(`Recommended tool: ${recommendedTool(humanAction)}`);
|
|
154
|
+
const payload = recommendedPayload(humanAction);
|
|
155
|
+
if (payload) {
|
|
156
|
+
lines.push(`Recommended payload: ${payload}`);
|
|
157
|
+
}
|
|
158
|
+
lines.push(`Exact action: ${exactAction(humanAction)}`);
|
|
159
|
+
lines.push(divider);
|
|
160
|
+
return lines.join("\n");
|
|
161
|
+
}
|