@mingchuno/agent-workflows 0.1.0 → 0.2.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 +32 -6
- package/dist/src/adapters/agents.js +6 -3
- package/dist/src/adapters/hosting.js +16 -10
- package/dist/src/adapters/sdk-protocol.d.ts +3 -3
- package/dist/src/adapters/sdk-protocol.js +9 -7
- package/dist/src/cli.d.ts +1 -1
- package/dist/src/cli.js +40 -19
- package/dist/src/config.d.ts +22 -22
- package/dist/src/config.js +31 -26
- package/dist/src/defaults.d.ts +2 -0
- package/dist/src/defaults.js +2 -0
- package/dist/src/domain.d.ts +24 -4
- package/dist/src/domain.js +10 -3
- package/dist/src/evidence.d.ts +54 -0
- package/dist/src/evidence.js +214 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/invocation.d.ts +24 -0
- package/dist/src/invocation.js +163 -0
- package/dist/src/operations.d.ts +7 -2
- package/dist/src/operations.js +76 -134
- package/dist/src/prompts.d.ts +28 -0
- package/dist/src/prompts.js +63 -0
- package/dist/src/recovery.d.ts +19 -0
- package/dist/src/recovery.js +99 -0
- package/dist/src/runner.d.ts +5 -0
- package/dist/src/runner.js +145 -18
- package/dist/src/runtime/process.d.ts +2 -0
- package/dist/src/runtime/process.js +41 -12
- package/dist/src/store.d.ts +21 -2
- package/dist/src/store.js +122 -1
- package/dist/src/tui/actions.d.ts +16 -0
- package/dist/src/tui/actions.js +23 -0
- package/dist/src/tui/constants.d.ts +6 -0
- package/dist/src/tui/constants.js +3 -0
- package/dist/src/{tui-data.d.ts → tui/data.d.ts} +8 -6
- package/dist/src/tui/data.js +141 -0
- package/dist/src/tui/dialogs.d.ts +17 -0
- package/dist/src/tui/dialogs.js +149 -0
- package/dist/src/tui/format.d.ts +7 -0
- package/dist/src/tui/format.js +62 -0
- package/dist/src/tui/index.d.ts +2 -0
- package/dist/src/tui/index.js +1 -0
- package/dist/src/tui/layout.d.ts +25 -0
- package/dist/src/tui/layout.js +36 -0
- package/dist/src/tui/log-file.d.ts +26 -0
- package/dist/src/tui/log-file.js +156 -0
- package/dist/src/tui/log.d.ts +11 -0
- package/dist/src/tui/log.js +90 -0
- package/dist/src/tui/monitor.d.ts +8 -0
- package/dist/src/tui/monitor.js +222 -0
- package/dist/src/tui/text.d.ts +3 -0
- package/dist/src/tui/text.js +10 -0
- package/dist/src/tui/use-log-controller.d.ts +27 -0
- package/dist/src/tui/use-log-controller.js +192 -0
- package/dist/src/tui/views.d.ts +17 -0
- package/dist/src/tui/views.js +97 -0
- package/docs/api.md +119 -6
- package/docs/architecture.md +21 -4
- package/docs/configuration.md +137 -5
- package/docs/database.md +7 -0
- package/docs/operations.md +117 -2
- package/docs/providers.md +58 -2
- package/docs/releases.md +34 -79
- package/examples/config.ts +2 -2
- package/package.json +4 -2
- package/dist/src/tui-data.js +0 -89
- package/dist/src/tui.d.ts +0 -5
- package/dist/src/tui.js +0 -69
- package/docs/acceptance.md +0 -35
package/README.md
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
1
|
# Agent Workflows
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
A local TypeScript SDK and CLI/TUI that turns labelled GitHub or GitLab issues into draft change requests with independent agent reviews. DBOS persists execution; each project uses its existing checkout, with one task at a time. Separate projects progress concurrently.
|
|
4
6
|
|
|
5
|
-
|
|
7
|
+
Supports Codex and Copilot SDKs, GitHub, and GitLab.com/self-hosted GitLab. Human review and merging remain separate.
|
|
8
|
+
|
|
9
|
+
## Why use this SDK over DBOS directly?
|
|
10
|
+
|
|
11
|
+
DBOS provides durable execution, checkpoints and queues. This SDK supplies the
|
|
12
|
+
coding-workflow behavior a client would otherwise build around it:
|
|
13
|
+
|
|
14
|
+
- **Ready-made operations:** issue intake, agent implementation, validation,
|
|
15
|
+
draft PR/MR publication and independent review, with Codex/Copilot and
|
|
16
|
+
GitHub/GitLab adapters.
|
|
17
|
+
- **Checkout and recovery rules:** exclusive checkout ownership, process
|
|
18
|
+
cancellation, preservation of unfinished work, and reconciliation of interrupted
|
|
19
|
+
commits, pushes and publication.
|
|
20
|
+
- **Application controls and evidence:** explicit run outcomes, safe retry
|
|
21
|
+
admission, session/log records, and a shared SDK, CLI and terminal monitor.
|
|
22
|
+
|
|
23
|
+
Clients configure providers and compose operations while DBOS remains responsible
|
|
24
|
+
for durability. [Custom workflows can call DBOS directly](docs/api.md#dbos-sdk-direct-usage).
|
|
25
|
+
Use this SDK when its existing-checkout, one-task-per-project model fits; use DBOS
|
|
26
|
+
directly when you need general-purpose workflows or want to own these policies.
|
|
6
27
|
|
|
7
28
|
## Quickstart
|
|
8
29
|
|
|
@@ -17,7 +38,7 @@ agent-workflows init
|
|
|
17
38
|
|
|
18
39
|
For SDK use, install locally with `npm install @mingchuno/agent-workflows`. Both interfaces ship in the same package.
|
|
19
40
|
|
|
20
|
-
Edit `agent-workflows.json`: set the checkout, hosting origin/repository, Git identity, agent stages and validation commands. Set the named hosting-token environment variable through your usual secret manager. Commit or ignore the configuration before running. Keep the state directory outside managed checkouts, or explicitly Git-ignore it.
|
|
41
|
+
Edit `agent-workflows.json`: set the checkout, hosting origin/repository, Git identity, agent stages and validation commands. Set the named hosting-token environment variable through your usual secret manager; for GitHub, follow the [PAT creation and required permissions](docs/providers.md#create-a-fine-grained-personal-access-token) guide before running. Commit or ignore the configuration before running. Keep the state directory outside managed checkouts, or explicitly Git-ignore it.
|
|
21
42
|
|
|
22
43
|
```sh
|
|
23
44
|
agent-workflows run
|
|
@@ -26,6 +47,10 @@ agent-workflows monitor
|
|
|
26
47
|
agent-workflows status --json
|
|
27
48
|
```
|
|
28
49
|
|
|
50
|
+
Alternatively, load database, hosting and application variables from one explicit
|
|
51
|
+
file: `agent-workflows --env-file ./runner.env run`. Existing shell values win,
|
|
52
|
+
including empty strings. See [environment file examples and boundaries](docs/configuration.md#cli-environment-files).
|
|
53
|
+
|
|
29
54
|
The runner fetches the configured base, creates a branch, implements an eligible issue, validates it, generates publication text, commits and pushes, creates a draft PR/MR, and publishes an independent review of its exact head. It never merges. Initial use should target a repository and issue you explicitly intend to automate; running the CLI authorizes these effects and agent usage.
|
|
30
55
|
|
|
31
56
|
## SDK
|
|
@@ -65,10 +90,11 @@ Use `pnpm start --help` to run the CLI from source. `pnpm build` emits the runti
|
|
|
65
90
|
Schema changes and database upgrades: [database maintenance](docs/database.md). Biome formats and lints supported source/configuration files; Markdown and YAML are maintained manually.
|
|
66
91
|
|
|
67
92
|
- [Configuration and profiles](docs/configuration.md)
|
|
93
|
+
- [Default stage prompts](docs/configuration.md#default-stage-prompts)
|
|
68
94
|
- [Public SDK API and composition](docs/api.md)
|
|
95
|
+
- [DBOS SDK direct usage](docs/api.md#dbos-sdk-direct-usage)
|
|
69
96
|
- [Authentication and provider capabilities](docs/providers.md)
|
|
70
97
|
- [CLI, TUI, observability and recovery](docs/operations.md)
|
|
71
|
-
- [
|
|
72
|
-
- [
|
|
73
|
-
- [
|
|
74
|
-
- [Specification](https://github.com/mingchuno/agent-workflows/issues/1)
|
|
98
|
+
- [Observability Landscape](docs/operations.md#observability-landscape)
|
|
99
|
+
- [Architecture](docs/architecture.md)
|
|
100
|
+
- [Releases](docs/releases.md)
|
|
@@ -2,6 +2,7 @@ import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
2
2
|
import { homedir, tmpdir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { defaultStageTimeoutMs } from "../defaults.js";
|
|
5
6
|
import { command } from "../runtime/process.js";
|
|
6
7
|
/** Runs each SDK invocation in an owned process group; no cross-stage session reuse. */
|
|
7
8
|
export class SDKAgent {
|
|
@@ -65,9 +66,9 @@ export class SDKAgent {
|
|
|
65
66
|
cwd: invocation.cwd,
|
|
66
67
|
prompt: invocation.prompt,
|
|
67
68
|
profile: invocation.profile,
|
|
68
|
-
|
|
69
|
+
outputSchema: invocation.outputSchema,
|
|
69
70
|
readOnly: invocation.readOnly,
|
|
70
|
-
timeoutMs: invocation.timeoutMs ??
|
|
71
|
+
timeoutMs: invocation.timeoutMs ?? defaultStageTimeoutMs,
|
|
71
72
|
}, invocation);
|
|
72
73
|
}
|
|
73
74
|
async worker(input, invocation, signal = invocation?.signal) {
|
|
@@ -97,7 +98,9 @@ export class SDKAgent {
|
|
|
97
98
|
]),
|
|
98
99
|
captureOutput: false,
|
|
99
100
|
processFile: invocation?.processFile,
|
|
100
|
-
timeoutMs: invocation
|
|
101
|
+
timeoutMs: invocation
|
|
102
|
+
? (invocation.timeoutMs ?? defaultStageTimeoutMs)
|
|
103
|
+
: 30_000,
|
|
101
104
|
onOutput: (chunk) => {
|
|
102
105
|
buffer += chunk;
|
|
103
106
|
for (;;) {
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { Gitlab } from "@gitbeaker/rest";
|
|
2
2
|
import { Octokit } from "@octokit/rest";
|
|
3
3
|
import { BlockedError, } from "../domain.js";
|
|
4
|
+
const hostingRequestTimeoutMs = 30_000;
|
|
5
|
+
const hostingPageSize = 100;
|
|
6
|
+
const minimumGitLabMajorVersion = 17;
|
|
7
|
+
const maximumGitLabMajorVersion = 19;
|
|
4
8
|
function origin(value) {
|
|
5
9
|
const url = new URL(value);
|
|
6
10
|
if (url.username || url.password || url.search || url.hash)
|
|
@@ -70,7 +74,7 @@ export class GitHubHosting {
|
|
|
70
74
|
baseUrl: host === "https://github.com"
|
|
71
75
|
? "https://api.github.com"
|
|
72
76
|
: `${host}/api/v3`,
|
|
73
|
-
request: { timeout:
|
|
77
|
+
request: { timeout: hostingRequestTimeoutMs, redirect: "error" },
|
|
74
78
|
});
|
|
75
79
|
}
|
|
76
80
|
async listIssues(labels) {
|
|
@@ -78,7 +82,7 @@ export class GitHubHosting {
|
|
|
78
82
|
...this.repo,
|
|
79
83
|
state: "open",
|
|
80
84
|
labels: labels.join(","),
|
|
81
|
-
per_page:
|
|
85
|
+
per_page: hostingPageSize,
|
|
82
86
|
});
|
|
83
87
|
return issues
|
|
84
88
|
.filter((issue) => !issue.pull_request)
|
|
@@ -112,7 +116,7 @@ export class GitHubHosting {
|
|
|
112
116
|
...this.repo,
|
|
113
117
|
head: `${this.repo.owner}:${branch}`,
|
|
114
118
|
state: "all",
|
|
115
|
-
per_page:
|
|
119
|
+
per_page: hostingPageSize,
|
|
116
120
|
});
|
|
117
121
|
if (changes.length > 1)
|
|
118
122
|
throw new BlockedError("Multiple change requests match branch");
|
|
@@ -139,7 +143,7 @@ export class GitHubHosting {
|
|
|
139
143
|
const reviews = await this.client.paginate(this.client.pulls.listReviews, {
|
|
140
144
|
...this.repo,
|
|
141
145
|
pull_number: input.change.id,
|
|
142
|
-
per_page:
|
|
146
|
+
per_page: hostingPageSize,
|
|
143
147
|
});
|
|
144
148
|
if (reviews.some((review) => review.body?.includes(marker(input.runId))))
|
|
145
149
|
return;
|
|
@@ -172,21 +176,23 @@ export class GitLabHosting {
|
|
|
172
176
|
this.client = new Gitlab({
|
|
173
177
|
host,
|
|
174
178
|
token: token(project),
|
|
175
|
-
queryTimeout:
|
|
179
|
+
queryTimeout: hostingRequestTimeoutMs,
|
|
176
180
|
});
|
|
177
181
|
}
|
|
178
182
|
async preflight() {
|
|
179
183
|
const metadata = await this.client.Metadata.show();
|
|
180
184
|
const major = Number(metadata.version.split(".")[0]);
|
|
181
|
-
if (!Number.isInteger(major) ||
|
|
182
|
-
|
|
185
|
+
if (!Number.isInteger(major) ||
|
|
186
|
+
major < minimumGitLabMajorVersion ||
|
|
187
|
+
major > maximumGitLabMajorVersion)
|
|
188
|
+
throw new Error(`Supported GitLab versions are ${minimumGitLabMajorVersion}.x–${maximumGitLabMajorVersion}.x; instance reports ${metadata.version}`);
|
|
183
189
|
}
|
|
184
190
|
async listIssues(labels) {
|
|
185
191
|
const issues = await this.client.Issues.all({
|
|
186
192
|
projectId: this.repository,
|
|
187
193
|
state: "opened",
|
|
188
194
|
labels: labels.join(","),
|
|
189
|
-
perPage:
|
|
195
|
+
perPage: hostingPageSize,
|
|
190
196
|
});
|
|
191
197
|
return issues.map((issue) => ({
|
|
192
198
|
id: String(issue.id),
|
|
@@ -216,7 +222,7 @@ export class GitLabHosting {
|
|
|
216
222
|
const changes = await this.client.MergeRequests.all({
|
|
217
223
|
projectId: this.repository,
|
|
218
224
|
sourceBranch: branch,
|
|
219
|
-
perPage:
|
|
225
|
+
perPage: hostingPageSize,
|
|
220
226
|
});
|
|
221
227
|
if (changes.length > 1)
|
|
222
228
|
throw new BlockedError("Multiple merge requests match branch");
|
|
@@ -237,7 +243,7 @@ export class GitLabHosting {
|
|
|
237
243
|
.sha;
|
|
238
244
|
}
|
|
239
245
|
async publishReview(input) {
|
|
240
|
-
const notes = await this.client.MergeRequestNotes.all(this.repository, input.change.id, { perPage:
|
|
246
|
+
const notes = await this.client.MergeRequestNotes.all(this.repository, input.change.id, { perPage: hostingPageSize });
|
|
241
247
|
if (notes.some((note) => note.body.includes(marker(input.runId))))
|
|
242
248
|
return;
|
|
243
249
|
const change = await this.client.MergeRequests.show(this.repository, input.change.id);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ModelInfo, SessionConfig } from "@github/copilot-sdk";
|
|
2
|
-
import type { ThreadEvent, ThreadOptions } from "@openai/codex-sdk";
|
|
2
|
+
import type { ThreadEvent, ThreadOptions, TurnOptions } from "@openai/codex-sdk";
|
|
3
3
|
import type { AgentProfile } from "../config.js";
|
|
4
4
|
export interface WorkerInput {
|
|
5
5
|
provider: "codex" | "copilot";
|
|
@@ -8,7 +8,7 @@ export interface WorkerInput {
|
|
|
8
8
|
cwd: string;
|
|
9
9
|
prompt: string;
|
|
10
10
|
profile: AgentProfile;
|
|
11
|
-
|
|
11
|
+
outputSchema?: unknown;
|
|
12
12
|
readOnly: boolean;
|
|
13
13
|
processFile?: string;
|
|
14
14
|
timeoutMs?: number;
|
|
@@ -16,7 +16,7 @@ export interface WorkerInput {
|
|
|
16
16
|
export type Emit = (type: string, value: unknown) => void;
|
|
17
17
|
export interface CodexClient {
|
|
18
18
|
startThread(options: ThreadOptions): {
|
|
19
|
-
runStreamed(prompt: string): Promise<{
|
|
19
|
+
runStreamed(prompt: string, options?: TurnOptions): Promise<{
|
|
20
20
|
events: AsyncIterable<ThreadEvent>;
|
|
21
21
|
}>;
|
|
22
22
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { defaultStageTimeoutMs } from "../defaults.js";
|
|
2
2
|
export async function runCodex(client, input, emit) {
|
|
3
3
|
const thread = client.startThread({
|
|
4
4
|
workingDirectory: input.cwd,
|
|
@@ -8,7 +8,9 @@ export async function runCodex(client, input, emit) {
|
|
|
8
8
|
sandboxMode: input.readOnly ? "read-only" : "workspace-write",
|
|
9
9
|
approvalPolicy: "never",
|
|
10
10
|
});
|
|
11
|
-
const turn = await thread.runStreamed(input.prompt
|
|
11
|
+
const turn = await thread.runStreamed(input.prompt, {
|
|
12
|
+
outputSchema: input.outputSchema,
|
|
13
|
+
});
|
|
12
14
|
let output = "";
|
|
13
15
|
for await (const event of turn.events) {
|
|
14
16
|
if (event.type === "thread.started")
|
|
@@ -39,17 +41,17 @@ export async function runCopilot(client, input, emit) {
|
|
|
39
41
|
reasoningEffort: input.profile
|
|
40
42
|
.reasoningEffort,
|
|
41
43
|
workingDirectory: input.cwd,
|
|
42
|
-
skillDirectories: input.skills.map(dirname),
|
|
43
44
|
infiniteSessions: input.profile.context
|
|
44
45
|
? { enabled: true, ...input.profile.context }
|
|
45
46
|
: undefined,
|
|
46
|
-
onPermissionRequest: async (request) =>
|
|
47
|
-
|
|
48
|
-
|
|
47
|
+
onPermissionRequest: async (request) => request.managedApprovalRequired ||
|
|
48
|
+
(input.readOnly && request.kind !== "read")
|
|
49
|
+
? { kind: "reject" }
|
|
50
|
+
: { kind: "approve-once" },
|
|
49
51
|
});
|
|
50
52
|
emit("session", session.sessionId);
|
|
51
53
|
session.on((event) => emit("event", event));
|
|
52
|
-
const response = await session.sendAndWait({ prompt: input.prompt }, input.timeoutMs ??
|
|
54
|
+
const response = await session.sendAndWait({ prompt: input.prompt }, input.timeoutMs ?? defaultStageTimeoutMs);
|
|
53
55
|
emit("result", response?.data.content ?? "");
|
|
54
56
|
await session.disconnect();
|
|
55
57
|
}
|
package/dist/src/cli.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env -S node --
|
|
2
2
|
export {};
|
package/dist/src/cli.js
CHANGED
|
@@ -1,19 +1,40 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env -S node --
|
|
2
2
|
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
import { resolve } from "node:path";
|
|
3
|
+
import { basename, dirname, resolve } from "node:path";
|
|
4
|
+
import { parseEnv } from "node:util";
|
|
4
5
|
import { Command } from "commander";
|
|
5
6
|
import { render } from "ink";
|
|
6
7
|
import React from "react";
|
|
7
8
|
import { createAgents } from "./adapters/agents.js";
|
|
8
9
|
import { createHosting } from "./adapters/hosting.js";
|
|
9
10
|
import { configSchema } from "./config.js";
|
|
11
|
+
import { defaultValidationTimeoutMs } from "./defaults.js";
|
|
10
12
|
import { Runner } from "./runner.js";
|
|
11
13
|
import { Store } from "./store.js";
|
|
12
|
-
import { Monitor } from "./tui.js";
|
|
14
|
+
import { Monitor } from "./tui/index.js";
|
|
15
|
+
const launchDirectory = process.cwd();
|
|
13
16
|
const program = new Command()
|
|
14
17
|
.name("agent-workflows")
|
|
15
18
|
.description("Local durable issue-to-review workflows")
|
|
16
|
-
.option("-c, --config <file>", "configuration path", "agent-workflows.json")
|
|
19
|
+
.option("-c, --config <file>", "configuration path", "agent-workflows.json")
|
|
20
|
+
.option("--env-file <path>", "load literal dotenv values; existing environment wins")
|
|
21
|
+
.hook("preAction", async () => {
|
|
22
|
+
const path = program.opts().envFile;
|
|
23
|
+
if (path === undefined)
|
|
24
|
+
return;
|
|
25
|
+
const file = resolve(launchDirectory, path);
|
|
26
|
+
let contents;
|
|
27
|
+
try {
|
|
28
|
+
contents = await readFile(file, "utf8");
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
throw new Error(`Cannot read environment file ${file} (${error.code ?? "read failed"})`);
|
|
32
|
+
}
|
|
33
|
+
for (const [name, value] of Object.entries(parseEnv(contents))) {
|
|
34
|
+
if (process.env[name] === undefined)
|
|
35
|
+
process.env[name] = value;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
17
38
|
async function configuration() {
|
|
18
39
|
return configSchema.parse(JSON.parse(await readFile(resolve(program.opts().config), "utf8")));
|
|
19
40
|
}
|
|
@@ -41,7 +62,7 @@ program
|
|
|
41
62
|
const config = {
|
|
42
63
|
id: "local",
|
|
43
64
|
databaseUrlEnv: "AGENT_WORKFLOWS_DATABASE_URL",
|
|
44
|
-
stateDirectory: resolve(".agent-workflows
|
|
65
|
+
stateDirectory: resolve("..", `${basename(process.cwd())}.agent-workflows`),
|
|
45
66
|
projects: [
|
|
46
67
|
{
|
|
47
68
|
id: "example",
|
|
@@ -57,18 +78,14 @@ program
|
|
|
57
78
|
branchTemplate: "agent/{issue}-{attempt}",
|
|
58
79
|
gitIdentity: { name: "YOUR NAME", email: "you@example.com" },
|
|
59
80
|
agent: { provider: "codex" },
|
|
60
|
-
validation: [
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
writing: {
|
|
66
|
-
prompt: "Describe the actual changes and validation accurately.",
|
|
81
|
+
validation: [
|
|
82
|
+
{
|
|
83
|
+
command: "pnpm",
|
|
84
|
+
args: ["test"],
|
|
85
|
+
timeoutMs: defaultValidationTimeoutMs,
|
|
67
86
|
},
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
},
|
|
71
|
-
},
|
|
87
|
+
],
|
|
88
|
+
stages: { implementation: {}, publication: {}, review: {} },
|
|
72
89
|
},
|
|
73
90
|
],
|
|
74
91
|
};
|
|
@@ -88,6 +105,7 @@ program
|
|
|
88
105
|
config.projects = config.projects.filter((project) => wanted.has(project.id));
|
|
89
106
|
}
|
|
90
107
|
const runner = new Runner({
|
|
108
|
+
promptBaseDirectory: dirname(resolve(program.opts().config)),
|
|
91
109
|
config,
|
|
92
110
|
databaseUrl: databaseUrl(config),
|
|
93
111
|
hosting: createHosting,
|
|
@@ -126,6 +144,7 @@ program
|
|
|
126
144
|
program.command("inspect <run>").action(async (run) => withStore(async (store) => console.log(JSON.stringify({
|
|
127
145
|
run: await store.run(run),
|
|
128
146
|
invocations: await store.invocations(run),
|
|
147
|
+
recovery: await store.recoveryPlan(run),
|
|
129
148
|
}, null, 2))));
|
|
130
149
|
program
|
|
131
150
|
.command("logs <run>")
|
|
@@ -149,7 +168,7 @@ program
|
|
|
149
168
|
}
|
|
150
169
|
}
|
|
151
170
|
}));
|
|
152
|
-
for (const kind of ["pause", "resume", "stop", "retry"])
|
|
171
|
+
for (const kind of ["pause", "resume", "stop", "retry", "recover"])
|
|
153
172
|
program
|
|
154
173
|
.command(`${kind} <target>`)
|
|
155
174
|
.description(`${kind} project or run through the active runner`)
|
|
@@ -160,10 +179,12 @@ for (const kind of ["pause", "resume", "stop", "retry"])
|
|
|
160
179
|
}));
|
|
161
180
|
}));
|
|
162
181
|
program.command("monitor").action(async () => {
|
|
163
|
-
if (!process.stdin.isTTY)
|
|
182
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
164
183
|
throw new Error("Monitor requires an interactive terminal; use status --json instead");
|
|
165
184
|
await withStore(async (store) => {
|
|
166
|
-
await render(React.createElement(Monitor, { source: store })
|
|
185
|
+
await render(React.createElement(Monitor, { source: store }), {
|
|
186
|
+
alternateScreen: true,
|
|
187
|
+
}).waitUntilExit();
|
|
167
188
|
});
|
|
168
189
|
});
|
|
169
190
|
try {
|
package/dist/src/config.d.ts
CHANGED
|
@@ -25,8 +25,8 @@ export declare const stageSchema: z.ZodObject<{
|
|
|
25
25
|
bufferExhaustionThreshold: z.ZodOptional<z.ZodNumber>;
|
|
26
26
|
}, z.core.$strict>>>;
|
|
27
27
|
}, z.core.$strict>>;
|
|
28
|
-
prompt: z.
|
|
29
|
-
|
|
28
|
+
prompt: z.ZodOptional<z.ZodString>;
|
|
29
|
+
promptFile: z.ZodOptional<z.ZodString>;
|
|
30
30
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
31
31
|
}, z.core.$strict>;
|
|
32
32
|
export declare const projectSchema: z.ZodObject<{
|
|
@@ -67,8 +67,8 @@ export declare const projectSchema: z.ZodObject<{
|
|
|
67
67
|
bufferExhaustionThreshold: z.ZodOptional<z.ZodNumber>;
|
|
68
68
|
}, z.core.$strict>>;
|
|
69
69
|
}, z.core.$strict>;
|
|
70
|
-
stages: z.
|
|
71
|
-
implementation: z.
|
|
70
|
+
stages: z.ZodPrefault<z.ZodObject<{
|
|
71
|
+
implementation: z.ZodPrefault<z.ZodObject<{
|
|
72
72
|
profile: z.ZodOptional<z.ZodObject<{
|
|
73
73
|
provider: z.ZodOptional<z.ZodEnum<{
|
|
74
74
|
codex: "codex";
|
|
@@ -81,11 +81,11 @@ export declare const projectSchema: z.ZodObject<{
|
|
|
81
81
|
bufferExhaustionThreshold: z.ZodOptional<z.ZodNumber>;
|
|
82
82
|
}, z.core.$strict>>>;
|
|
83
83
|
}, z.core.$strict>>;
|
|
84
|
-
prompt: z.
|
|
85
|
-
|
|
84
|
+
prompt: z.ZodOptional<z.ZodString>;
|
|
85
|
+
promptFile: z.ZodOptional<z.ZodString>;
|
|
86
86
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
87
87
|
}, z.core.$strict>>;
|
|
88
|
-
|
|
88
|
+
publication: z.ZodPrefault<z.ZodObject<{
|
|
89
89
|
profile: z.ZodOptional<z.ZodObject<{
|
|
90
90
|
provider: z.ZodOptional<z.ZodEnum<{
|
|
91
91
|
codex: "codex";
|
|
@@ -98,11 +98,11 @@ export declare const projectSchema: z.ZodObject<{
|
|
|
98
98
|
bufferExhaustionThreshold: z.ZodOptional<z.ZodNumber>;
|
|
99
99
|
}, z.core.$strict>>>;
|
|
100
100
|
}, z.core.$strict>>;
|
|
101
|
-
prompt: z.
|
|
102
|
-
|
|
101
|
+
prompt: z.ZodOptional<z.ZodString>;
|
|
102
|
+
promptFile: z.ZodOptional<z.ZodString>;
|
|
103
103
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
104
104
|
}, z.core.$strict>>;
|
|
105
|
-
review: z.
|
|
105
|
+
review: z.ZodPrefault<z.ZodObject<{
|
|
106
106
|
profile: z.ZodOptional<z.ZodObject<{
|
|
107
107
|
provider: z.ZodOptional<z.ZodEnum<{
|
|
108
108
|
codex: "codex";
|
|
@@ -115,8 +115,8 @@ export declare const projectSchema: z.ZodObject<{
|
|
|
115
115
|
bufferExhaustionThreshold: z.ZodOptional<z.ZodNumber>;
|
|
116
116
|
}, z.core.$strict>>>;
|
|
117
117
|
}, z.core.$strict>>;
|
|
118
|
-
prompt: z.
|
|
119
|
-
|
|
118
|
+
prompt: z.ZodOptional<z.ZodString>;
|
|
119
|
+
promptFile: z.ZodOptional<z.ZodString>;
|
|
120
120
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
121
121
|
}, z.core.$strict>>;
|
|
122
122
|
}, z.core.$strict>>;
|
|
@@ -163,8 +163,8 @@ export declare const configSchema: z.ZodObject<{
|
|
|
163
163
|
bufferExhaustionThreshold: z.ZodOptional<z.ZodNumber>;
|
|
164
164
|
}, z.core.$strict>>;
|
|
165
165
|
}, z.core.$strict>;
|
|
166
|
-
stages: z.
|
|
167
|
-
implementation: z.
|
|
166
|
+
stages: z.ZodPrefault<z.ZodObject<{
|
|
167
|
+
implementation: z.ZodPrefault<z.ZodObject<{
|
|
168
168
|
profile: z.ZodOptional<z.ZodObject<{
|
|
169
169
|
provider: z.ZodOptional<z.ZodEnum<{
|
|
170
170
|
codex: "codex";
|
|
@@ -177,11 +177,11 @@ export declare const configSchema: z.ZodObject<{
|
|
|
177
177
|
bufferExhaustionThreshold: z.ZodOptional<z.ZodNumber>;
|
|
178
178
|
}, z.core.$strict>>>;
|
|
179
179
|
}, z.core.$strict>>;
|
|
180
|
-
prompt: z.
|
|
181
|
-
|
|
180
|
+
prompt: z.ZodOptional<z.ZodString>;
|
|
181
|
+
promptFile: z.ZodOptional<z.ZodString>;
|
|
182
182
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
183
183
|
}, z.core.$strict>>;
|
|
184
|
-
|
|
184
|
+
publication: z.ZodPrefault<z.ZodObject<{
|
|
185
185
|
profile: z.ZodOptional<z.ZodObject<{
|
|
186
186
|
provider: z.ZodOptional<z.ZodEnum<{
|
|
187
187
|
codex: "codex";
|
|
@@ -194,11 +194,11 @@ export declare const configSchema: z.ZodObject<{
|
|
|
194
194
|
bufferExhaustionThreshold: z.ZodOptional<z.ZodNumber>;
|
|
195
195
|
}, z.core.$strict>>>;
|
|
196
196
|
}, z.core.$strict>>;
|
|
197
|
-
prompt: z.
|
|
198
|
-
|
|
197
|
+
prompt: z.ZodOptional<z.ZodString>;
|
|
198
|
+
promptFile: z.ZodOptional<z.ZodString>;
|
|
199
199
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
200
200
|
}, z.core.$strict>>;
|
|
201
|
-
review: z.
|
|
201
|
+
review: z.ZodPrefault<z.ZodObject<{
|
|
202
202
|
profile: z.ZodOptional<z.ZodObject<{
|
|
203
203
|
provider: z.ZodOptional<z.ZodEnum<{
|
|
204
204
|
codex: "codex";
|
|
@@ -211,8 +211,8 @@ export declare const configSchema: z.ZodObject<{
|
|
|
211
211
|
bufferExhaustionThreshold: z.ZodOptional<z.ZodNumber>;
|
|
212
212
|
}, z.core.$strict>>>;
|
|
213
213
|
}, z.core.$strict>>;
|
|
214
|
-
prompt: z.
|
|
215
|
-
|
|
214
|
+
prompt: z.ZodOptional<z.ZodString>;
|
|
215
|
+
promptFile: z.ZodOptional<z.ZodString>;
|
|
216
216
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
217
217
|
}, z.core.$strict>>;
|
|
218
218
|
}, z.core.$strict>>;
|
package/dist/src/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
+
import { defaultStageTimeoutMs, defaultValidationTimeoutMs, } from "./defaults.js";
|
|
2
3
|
export const profileSchema = z.strictObject({
|
|
3
4
|
provider: z.enum(["codex", "copilot"]),
|
|
4
5
|
model: z.string().min(1).optional(),
|
|
@@ -10,12 +11,24 @@ export const profileSchema = z.strictObject({
|
|
|
10
11
|
})
|
|
11
12
|
.optional(),
|
|
12
13
|
});
|
|
13
|
-
export const stageSchema = z
|
|
14
|
+
export const stageSchema = z
|
|
15
|
+
.strictObject({
|
|
14
16
|
profile: profileSchema.partial().optional(),
|
|
15
|
-
prompt: z
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
prompt: z
|
|
18
|
+
.string()
|
|
19
|
+
.refine((text) => text.trim().length > 0, "Prompt must be nonblank")
|
|
20
|
+
.optional(),
|
|
21
|
+
promptFile: z
|
|
22
|
+
.string()
|
|
23
|
+
.refine((text) => text.trim().length > 0, "Prompt file path must be nonblank")
|
|
24
|
+
.optional(),
|
|
25
|
+
timeoutMs: z.number().int().positive().default(defaultStageTimeoutMs),
|
|
26
|
+
}, {
|
|
27
|
+
error: (issue) => issue.code === "unrecognized_keys" && issue.keys.includes("skills")
|
|
28
|
+
? "Stage skills was removed; configure skills in your agent runtime and request them in prompt"
|
|
29
|
+
: undefined,
|
|
30
|
+
})
|
|
31
|
+
.refine((stage) => stage.prompt === undefined || stage.promptFile === undefined, "Specify either prompt or promptFile, never both");
|
|
19
32
|
export const projectSchema = z.strictObject({
|
|
20
33
|
id: z.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
21
34
|
checkout: z.string().min(1),
|
|
@@ -40,34 +53,26 @@ export const projectSchema = z.strictObject({
|
|
|
40
53
|
.array(z.strictObject({
|
|
41
54
|
command: z.string().min(1),
|
|
42
55
|
args: z.array(z.string()).default([]),
|
|
43
|
-
timeoutMs: z
|
|
56
|
+
timeoutMs: z
|
|
57
|
+
.number()
|
|
58
|
+
.int()
|
|
59
|
+
.positive()
|
|
60
|
+
.default(defaultValidationTimeoutMs),
|
|
44
61
|
}))
|
|
45
62
|
.default([]),
|
|
46
63
|
gitIdentity: z.strictObject({ name: z.string().min(1), email: z.email() }),
|
|
47
64
|
agent: profileSchema,
|
|
48
65
|
stages: z
|
|
49
66
|
.strictObject({
|
|
50
|
-
implementation: stageSchema.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
skills: [],
|
|
58
|
-
timeoutMs: 1_800_000,
|
|
59
|
-
})),
|
|
60
|
-
review: stageSchema.default(() => ({
|
|
61
|
-
prompt: "",
|
|
62
|
-
skills: [],
|
|
63
|
-
timeoutMs: 1_800_000,
|
|
64
|
-
})),
|
|
67
|
+
implementation: stageSchema.prefault({}),
|
|
68
|
+
publication: stageSchema.prefault({}),
|
|
69
|
+
review: stageSchema.prefault({}),
|
|
70
|
+
}, {
|
|
71
|
+
error: (issue) => issue.code === "unrecognized_keys" && issue.keys.includes("writing")
|
|
72
|
+
? "Stage writing was renamed to publication; update projects.stages.writing"
|
|
73
|
+
: undefined,
|
|
65
74
|
})
|
|
66
|
-
.
|
|
67
|
-
implementation: { prompt: "", skills: [], timeoutMs: 1_800_000 },
|
|
68
|
-
writing: { prompt: "", skills: [], timeoutMs: 1_800_000 },
|
|
69
|
-
review: { prompt: "", skills: [], timeoutMs: 1_800_000 },
|
|
70
|
-
})),
|
|
75
|
+
.prefault({}),
|
|
71
76
|
});
|
|
72
77
|
export const configSchema = z.strictObject({
|
|
73
78
|
id: z.string().regex(/^[a-zA-Z0-9_-]+$/),
|
package/dist/src/domain.d.ts
CHANGED
|
@@ -21,14 +21,16 @@ export declare const publicationSchema: z.ZodObject<{
|
|
|
21
21
|
}, z.core.$strict>;
|
|
22
22
|
export type Publication = z.infer<typeof publicationSchema>;
|
|
23
23
|
export declare const reviewSchema: z.ZodObject<{
|
|
24
|
+
complete: z.ZodBoolean;
|
|
25
|
+
limitations: z.ZodArray<z.ZodString>;
|
|
24
26
|
summary: z.ZodString;
|
|
25
27
|
findings: z.ZodArray<z.ZodObject<{
|
|
26
28
|
body: z.ZodString;
|
|
27
|
-
path: z.
|
|
28
|
-
line: z.
|
|
29
|
+
path: z.ZodDefault<z.ZodNullable<z.ZodString>>;
|
|
30
|
+
line: z.ZodDefault<z.ZodNullable<z.ZodNumber>>;
|
|
29
31
|
}, z.core.$strict>>;
|
|
30
32
|
}, z.core.$strict>;
|
|
31
|
-
export type Review = z.
|
|
33
|
+
export type Review = z.input<typeof reviewSchema>;
|
|
32
34
|
export interface ValidationResult {
|
|
33
35
|
command: string;
|
|
34
36
|
args: string[];
|
|
@@ -84,7 +86,7 @@ export interface AgentInvocation {
|
|
|
84
86
|
cwd: string;
|
|
85
87
|
prompt: string;
|
|
86
88
|
profile: AgentProfile;
|
|
87
|
-
|
|
89
|
+
outputSchema?: unknown;
|
|
88
90
|
processFile?: string;
|
|
89
91
|
readOnly: boolean;
|
|
90
92
|
signal: AbortSignal;
|
|
@@ -104,6 +106,22 @@ export interface AgentAdapter {
|
|
|
104
106
|
invoke(invocation: AgentInvocation): Promise<string>;
|
|
105
107
|
}
|
|
106
108
|
export type Outcome = "queued" | "running" | "completed" | "failed" | "blocked" | "cancelled" | "no-change" | "ineligible";
|
|
109
|
+
export interface ExecutionRecord {
|
|
110
|
+
/** DBOS workflow identity; publication markers continue to use the run ID. */
|
|
111
|
+
id: string;
|
|
112
|
+
recoveryOf?: string;
|
|
113
|
+
startStep?: number;
|
|
114
|
+
reusedSteps?: string[];
|
|
115
|
+
fingerprint: string;
|
|
116
|
+
recoverySupported: boolean;
|
|
117
|
+
createdAt: string;
|
|
118
|
+
startedAt?: string;
|
|
119
|
+
finishedAt?: string;
|
|
120
|
+
outcome: Outcome;
|
|
121
|
+
phase: string;
|
|
122
|
+
failedStep?: number;
|
|
123
|
+
error?: string;
|
|
124
|
+
}
|
|
107
125
|
export interface RunRecord {
|
|
108
126
|
id: string;
|
|
109
127
|
projectId: string;
|
|
@@ -126,6 +144,8 @@ export interface RunRecord {
|
|
|
126
144
|
review?: Review;
|
|
127
145
|
reviewHead?: string;
|
|
128
146
|
error?: string;
|
|
147
|
+
failedStep?: number;
|
|
148
|
+
executions?: ExecutionRecord[];
|
|
129
149
|
}
|
|
130
150
|
export declare class BlockedError extends Error {
|
|
131
151
|
constructor(message: string);
|