@mingchuno/agent-workflows 0.3.0 → 0.5.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 +25 -14
- package/dist/src/adapters/agents.js +9 -0
- package/dist/src/cli-config.d.ts +2 -0
- package/dist/src/cli-config.js +35 -0
- package/dist/src/cli.js +22 -24
- package/dist/src/config.d.ts +10 -0
- package/dist/src/config.js +9 -11
- package/dist/src/domain.d.ts +6 -0
- package/dist/src/invocation.d.ts +2 -4
- package/dist/src/invocation.js +204 -107
- package/dist/src/operations.d.ts +1 -2
- package/dist/src/operations.js +18 -33
- package/dist/src/recovery.d.ts +20 -2
- package/dist/src/recovery.js +49 -1
- package/dist/src/runner.d.ts +6 -2
- package/dist/src/runner.js +81 -83
- package/dist/src/runtime/process.d.ts +1 -0
- package/dist/src/runtime/process.js +6 -2
- package/dist/src/store.d.ts +4 -2
- package/dist/src/store.js +69 -51
- package/dist/src/tui/data.d.ts +1 -1
- package/dist/src/tui/dialogs.js +1 -0
- package/dist/src/tui/monitor-navigation.d.ts +76 -0
- package/dist/src/tui/monitor-navigation.js +187 -0
- package/dist/src/tui/monitor.js +70 -183
- package/dist/src/tui/projection.d.ts +22 -0
- package/dist/src/tui/projection.js +49 -0
- package/dist/src/validation-selection.d.ts +6 -0
- package/dist/src/validation-selection.js +29 -0
- package/dist/src/workspace.js +3 -2
- package/docs/api.md +11 -7
- package/docs/configuration.md +61 -18
- package/docs/database.md +2 -18
- package/docs/operations.md +21 -28
- package/docs/providers.md +2 -2
- package/package.json +1 -1
- package/dist/src/tui/actions.d.ts +0 -16
- package/dist/src/tui/actions.js +0 -23
- package/docs/architecture.md +0 -41
package/README.md
CHANGED
|
@@ -52,9 +52,10 @@ agent-workflows monitor
|
|
|
52
52
|
agent-workflows status --json
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
-
Alternatively,
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
Alternatively, set `"envFile": "./runner.env"` at the top level of
|
|
56
|
+
`agent-workflows.json` to load database, hosting and application variables from
|
|
57
|
+
one explicit file. Existing process values win, including empty strings. See
|
|
58
|
+
[environment file examples and boundaries](docs/configuration.md#cli-environment-files).
|
|
58
59
|
|
|
59
60
|
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.
|
|
60
61
|
|
|
@@ -76,6 +77,24 @@ await runner.shutdown();
|
|
|
76
77
|
|
|
77
78
|
[Custom workflow](examples/custom-workflow.ts), [complete runner](examples/run.ts), [configuration](examples/config.ts), and [inspection](examples/observe.ts) examples are type-checked with the library. The custom workflow is also exercised using controlled providers.
|
|
78
79
|
|
|
80
|
+
## Documentation
|
|
81
|
+
|
|
82
|
+
### Using the package
|
|
83
|
+
|
|
84
|
+
- [Configuration and profiles](docs/configuration.md)
|
|
85
|
+
- [Default stage prompts](docs/configuration.md#default-stage-prompts)
|
|
86
|
+
- [Public SDK API and composition](docs/api.md)
|
|
87
|
+
- [DBOS SDK direct usage](docs/api.md#dbos-sdk-direct-usage)
|
|
88
|
+
- [Authentication and provider capabilities](docs/providers.md)
|
|
89
|
+
- [CLI, TUI, observability, and recovery](docs/operations.md)
|
|
90
|
+
- [Observability landscape](docs/operations.md#observability-landscape)
|
|
91
|
+
|
|
92
|
+
### Maintaining the project
|
|
93
|
+
|
|
94
|
+
- [Architecture decisions](docs/adr/README.md)
|
|
95
|
+
- [Database maintenance](docs/database.md)
|
|
96
|
+
- [Release process](docs/releases.md)
|
|
97
|
+
|
|
79
98
|
## Development and review
|
|
80
99
|
|
|
81
100
|
[mise](https://mise.jdx.dev/getting-started.html) pins the development Node and pnpm versions. Activate it in your shell or prefix commands with `mise exec --`.
|
|
@@ -92,14 +111,6 @@ Use `pnpm start --help` to run the CLI from source. `pnpm build` emits the runti
|
|
|
92
111
|
|
|
93
112
|
`pnpm test` builds the application, then starts and removes a disposable real PostgreSQL database using `initdb`, `pg_ctl`, and `createdb` on PATH. Alternatively, set `TEST_DATABASE_URL` to a disposable database whose role can create test databases. Tests use real temporary Git repositories and controlled adapters/HTTP servers; they make no paid agent calls or writes to real hosting providers.
|
|
94
113
|
|
|
95
|
-
Schema changes and database upgrades: [database maintenance](docs/database.md).
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
- [Default stage prompts](docs/configuration.md#default-stage-prompts)
|
|
99
|
-
- [Public SDK API and composition](docs/api.md)
|
|
100
|
-
- [DBOS SDK direct usage](docs/api.md#dbos-sdk-direct-usage)
|
|
101
|
-
- [Authentication and provider capabilities](docs/providers.md)
|
|
102
|
-
- [CLI, TUI, observability and recovery](docs/operations.md)
|
|
103
|
-
- [Observability Landscape](docs/operations.md#observability-landscape)
|
|
104
|
-
- [Architecture](docs/architecture.md)
|
|
105
|
-
- [Releases](docs/releases.md)
|
|
114
|
+
Schema changes and database upgrades: [database maintenance](docs/database.md).
|
|
115
|
+
Biome formats and lints supported source/configuration files; Markdown and YAML
|
|
116
|
+
are maintained manually.
|
|
@@ -84,6 +84,7 @@ export class SDKAgent {
|
|
|
84
84
|
const worker = join(dirname(ownPath), `agent-worker.${source ? "ts" : "js"}`);
|
|
85
85
|
let buffer = "", output = "", events = Promise.resolve();
|
|
86
86
|
let eventError;
|
|
87
|
+
let stderr = "";
|
|
87
88
|
const persistenceFailure = new AbortController();
|
|
88
89
|
try {
|
|
89
90
|
await command(process.execPath, [
|
|
@@ -97,6 +98,9 @@ export class SDKAgent {
|
|
|
97
98
|
...(signal ? [signal] : []),
|
|
98
99
|
]),
|
|
99
100
|
captureOutput: false,
|
|
101
|
+
onStderr: (chunk) => {
|
|
102
|
+
stderr = (stderr + chunk).slice(-16_384);
|
|
103
|
+
},
|
|
100
104
|
processFile: invocation?.processFile,
|
|
101
105
|
timeoutMs: invocation
|
|
102
106
|
? (invocation.timeoutMs ?? defaultStageTimeoutMs)
|
|
@@ -128,6 +132,11 @@ export class SDKAgent {
|
|
|
128
132
|
});
|
|
129
133
|
}
|
|
130
134
|
},
|
|
135
|
+
}).catch((error) => {
|
|
136
|
+
const detail = stderr.trim();
|
|
137
|
+
throw detail
|
|
138
|
+
? new Error(`${String(error)}\nWorker stderr (last 16 KiB):\n${detail}`)
|
|
139
|
+
: error;
|
|
131
140
|
});
|
|
132
141
|
await events;
|
|
133
142
|
if (eventError)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { parseEnv } from "node:util";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { configSchema } from "./config.js";
|
|
5
|
+
const cliConfigurationSchema = configSchema.extend({
|
|
6
|
+
envFile: z
|
|
7
|
+
.string()
|
|
8
|
+
.refine((path) => path.trim().length > 0, "Environment file path must be nonblank")
|
|
9
|
+
.optional(),
|
|
10
|
+
});
|
|
11
|
+
export async function readCliConfiguration(path, resolveEnvironmentFile) {
|
|
12
|
+
const { envFile, ...configuration } = cliConfigurationSchema.parse(JSON.parse(await readFile(path, "utf8")));
|
|
13
|
+
if (envFile !== undefined)
|
|
14
|
+
await loadEnvironmentFile(resolveEnvironmentFile(envFile));
|
|
15
|
+
return configuration;
|
|
16
|
+
}
|
|
17
|
+
async function loadEnvironmentFile(path) {
|
|
18
|
+
let contents;
|
|
19
|
+
try {
|
|
20
|
+
contents = await readFile(path, "utf8");
|
|
21
|
+
}
|
|
22
|
+
catch (error) {
|
|
23
|
+
throw new Error(`Cannot read environment file ${path} (${error.code ?? "read failed"})`);
|
|
24
|
+
}
|
|
25
|
+
let values;
|
|
26
|
+
try {
|
|
27
|
+
values = parseEnv(contents);
|
|
28
|
+
}
|
|
29
|
+
catch (error) {
|
|
30
|
+
throw new Error(`Cannot parse environment file ${path}`, { cause: error });
|
|
31
|
+
}
|
|
32
|
+
for (const [name, value] of Object.entries(values))
|
|
33
|
+
if (value !== undefined && process.env[name] === undefined)
|
|
34
|
+
process.env[name] = value;
|
|
35
|
+
}
|
package/dist/src/cli.js
CHANGED
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
import { realpathSync, statSync } from "node:fs";
|
|
3
3
|
import { readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, relative, resolve } from "node:path";
|
|
5
|
-
import { parseEnv } from "node:util";
|
|
6
5
|
import { Command } from "commander";
|
|
7
6
|
import { render } from "ink";
|
|
8
7
|
import React from "react";
|
|
9
8
|
import { createAgents } from "./adapters/agents.js";
|
|
10
9
|
import { createHosting } from "./adapters/hosting.js";
|
|
11
|
-
import {
|
|
10
|
+
import { readCliConfiguration } from "./cli-config.js";
|
|
12
11
|
import { defaultValidationTimeoutMs } from "./defaults.js";
|
|
13
12
|
import { Runner } from "./runner.js";
|
|
14
13
|
import { Store } from "./store.js";
|
|
@@ -18,27 +17,9 @@ const program = new Command()
|
|
|
18
17
|
.name("agent-workflows")
|
|
19
18
|
.description("Local durable issue-to-review workflows")
|
|
20
19
|
.option("-c, --config <file>", "configuration path", "agent-workflows.json")
|
|
21
|
-
.option("--config-base-directory <directory>", "base directory for paths contained in configuration")
|
|
22
|
-
.option("--env-file <path>", "load literal dotenv values; existing environment wins")
|
|
23
|
-
.hook("preAction", async () => {
|
|
24
|
-
const path = program.opts().envFile;
|
|
25
|
-
if (path === undefined)
|
|
26
|
-
return;
|
|
27
|
-
const file = resolve(launchDirectory, path);
|
|
28
|
-
let contents;
|
|
29
|
-
try {
|
|
30
|
-
contents = await readFile(file, "utf8");
|
|
31
|
-
}
|
|
32
|
-
catch (error) {
|
|
33
|
-
throw new Error(`Cannot read environment file ${file} (${error.code ?? "read failed"})`);
|
|
34
|
-
}
|
|
35
|
-
for (const [name, value] of Object.entries(parseEnv(contents))) {
|
|
36
|
-
if (process.env[name] === undefined)
|
|
37
|
-
process.env[name] = value;
|
|
38
|
-
}
|
|
39
|
-
});
|
|
20
|
+
.option("--config-base-directory <directory>", "base directory for paths contained in configuration");
|
|
40
21
|
async function configuration() {
|
|
41
|
-
return
|
|
22
|
+
return readCliConfiguration(configPath(), (path) => resolve(configBaseDirectory(), path));
|
|
42
23
|
}
|
|
43
24
|
function configPath() {
|
|
44
25
|
return resolve(launchDirectory, program.opts().config);
|
|
@@ -67,6 +48,9 @@ function databaseUrl(config) {
|
|
|
67
48
|
}
|
|
68
49
|
async function withStore(action) {
|
|
69
50
|
const config = await configuration();
|
|
51
|
+
await withConfiguredStore(config, action);
|
|
52
|
+
}
|
|
53
|
+
async function withConfiguredStore(config, action) {
|
|
70
54
|
const store = new Store(databaseUrl(config), config.id);
|
|
71
55
|
try {
|
|
72
56
|
await store.initialize();
|
|
@@ -181,6 +165,9 @@ program
|
|
|
181
165
|
const record = await store.run(run);
|
|
182
166
|
const invocations = (await store.invocations(run)).filter((item) => !options.invocation || item.id === options.invocation);
|
|
183
167
|
const paths = [
|
|
168
|
+
...(!options.invocation
|
|
169
|
+
? (record.stageLogs?.map((item) => item.path) ?? [])
|
|
170
|
+
: []),
|
|
184
171
|
...invocations.map((item) => item.log),
|
|
185
172
|
...(!options.invocation
|
|
186
173
|
? (record.validation?.map((check) => check.log) ?? [])
|
|
@@ -196,7 +183,7 @@ program
|
|
|
196
183
|
}
|
|
197
184
|
}
|
|
198
185
|
}));
|
|
199
|
-
for (const kind of ["pause", "resume", "stop", "
|
|
186
|
+
for (const kind of ["pause", "resume", "stop", "recover"])
|
|
200
187
|
program
|
|
201
188
|
.command(`${kind} <target>`)
|
|
202
189
|
.description(`${kind} project or run through the active runner`)
|
|
@@ -206,13 +193,24 @@ for (const kind of ["pause", "resume", "stop", "retry", "recover"])
|
|
|
206
193
|
status: "pending",
|
|
207
194
|
}));
|
|
208
195
|
}));
|
|
196
|
+
program
|
|
197
|
+
.command("retry <target>")
|
|
198
|
+
.description("retry a run through the active runner")
|
|
199
|
+
.option("--refresh-issue", "use the current hosted issue for the new run")
|
|
200
|
+
.action(async (target, options) => withStore(async (store) => {
|
|
201
|
+
console.log(JSON.stringify({
|
|
202
|
+
commandId: await store.request(options.refreshIssue ? "retry-refresh" : "retry", target),
|
|
203
|
+
status: "pending",
|
|
204
|
+
}));
|
|
205
|
+
}));
|
|
209
206
|
program
|
|
210
207
|
.command("monitor")
|
|
211
208
|
.option("--notify", "notify when an observed execution reaches an outcome")
|
|
212
209
|
.action(async (options) => {
|
|
210
|
+
const config = await configuration();
|
|
213
211
|
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
214
212
|
throw new Error("Monitor requires an interactive terminal; use status --json instead");
|
|
215
|
-
await
|
|
213
|
+
await withConfiguredStore(config, async (store) => {
|
|
216
214
|
await render(React.createElement(Monitor, {
|
|
217
215
|
source: store,
|
|
218
216
|
notificationWriter: options.notify
|
package/dist/src/config.d.ts
CHANGED
|
@@ -51,6 +51,11 @@ export declare const projectSchema: z.ZodObject<{
|
|
|
51
51
|
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
52
52
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
53
53
|
}, z.core.$strict>>>;
|
|
54
|
+
validationProfiles: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
55
|
+
command: z.ZodString;
|
|
56
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
57
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
58
|
+
}, z.core.$strict>>>>;
|
|
54
59
|
includeAgentCoAuthors: z.ZodDefault<z.ZodBoolean>;
|
|
55
60
|
agent: z.ZodObject<{
|
|
56
61
|
provider: z.ZodEnum<{
|
|
@@ -144,6 +149,11 @@ export declare const configSchema: z.ZodObject<{
|
|
|
144
149
|
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
145
150
|
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
146
151
|
}, z.core.$strict>>>;
|
|
152
|
+
validationProfiles: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
153
|
+
command: z.ZodString;
|
|
154
|
+
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
155
|
+
timeoutMs: z.ZodDefault<z.ZodNumber>;
|
|
156
|
+
}, z.core.$strict>>>>;
|
|
147
157
|
includeAgentCoAuthors: z.ZodDefault<z.ZodBoolean>;
|
|
148
158
|
agent: z.ZodObject<{
|
|
149
159
|
provider: z.ZodEnum<{
|
package/dist/src/config.js
CHANGED
|
@@ -29,6 +29,11 @@ export const stageSchema = z
|
|
|
29
29
|
: undefined,
|
|
30
30
|
})
|
|
31
31
|
.refine((stage) => stage.prompt === undefined || stage.promptFile === undefined, "Specify either prompt or promptFile, never both");
|
|
32
|
+
const validationCommandSchema = z.strictObject({
|
|
33
|
+
command: z.string().min(1),
|
|
34
|
+
args: z.array(z.string()).default([]),
|
|
35
|
+
timeoutMs: z.number().int().positive().default(defaultValidationTimeoutMs),
|
|
36
|
+
});
|
|
32
37
|
export const projectSchema = z.strictObject({
|
|
33
38
|
id: z.string().regex(/^[a-zA-Z0-9_-]+$/),
|
|
34
39
|
checkout: z.string().min(1),
|
|
@@ -49,17 +54,10 @@ export const projectSchema = z.strictObject({
|
|
|
49
54
|
.includes("{issue}")
|
|
50
55
|
.default("agent/{issue}-{attempt}"),
|
|
51
56
|
pollIntervalMs: z.number().int().min(100).default(30_000),
|
|
52
|
-
validation: z
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
timeoutMs: z
|
|
57
|
-
.number()
|
|
58
|
-
.int()
|
|
59
|
-
.positive()
|
|
60
|
-
.default(defaultValidationTimeoutMs),
|
|
61
|
-
}))
|
|
62
|
-
.default([]),
|
|
57
|
+
validation: z.array(validationCommandSchema).default([]),
|
|
58
|
+
validationProfiles: z
|
|
59
|
+
.record(z.string().regex(/^[a-zA-Z0-9_-]+$/), z.array(validationCommandSchema).min(1))
|
|
60
|
+
.default({}),
|
|
63
61
|
includeAgentCoAuthors: z.boolean().default(true),
|
|
64
62
|
agent: profileSchema,
|
|
65
63
|
stages: z
|
package/dist/src/domain.d.ts
CHANGED
|
@@ -135,6 +135,7 @@ export interface RunRecord {
|
|
|
135
135
|
attempt: number;
|
|
136
136
|
retryOf?: string;
|
|
137
137
|
issue: Issue;
|
|
138
|
+
validationProfile?: string;
|
|
138
139
|
outcome: Outcome;
|
|
139
140
|
phase: string;
|
|
140
141
|
createdAt: string;
|
|
@@ -152,6 +153,11 @@ export interface RunRecord {
|
|
|
152
153
|
reviewHead?: string;
|
|
153
154
|
error?: string;
|
|
154
155
|
failedStep?: number;
|
|
156
|
+
stageLogs?: Array<{
|
|
157
|
+
executionId: string;
|
|
158
|
+
step: string;
|
|
159
|
+
path: string;
|
|
160
|
+
}>;
|
|
155
161
|
executions?: ExecutionRecord[];
|
|
156
162
|
}
|
|
157
163
|
export declare class BlockedError extends Error {
|
package/dist/src/invocation.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { type Stage } from "./config.js";
|
|
3
|
-
import { type
|
|
3
|
+
import { type RunRecord } from "./domain.js";
|
|
4
4
|
import { type ChangeEvidence } from "./evidence.js";
|
|
5
5
|
import type { OperationDependencies } from "./operations.js";
|
|
6
6
|
export interface InvocationTask {
|
|
@@ -17,9 +17,7 @@ interface StageExecution {
|
|
|
17
17
|
task: InvocationTask;
|
|
18
18
|
stepId: number;
|
|
19
19
|
dependencies: OperationDependencies;
|
|
20
|
-
saveImplementationSnapshot: (runId: string, snapshot: Snapshot, provider: string) => Promise<ContributionCandidate | undefined>;
|
|
21
|
-
acceptContribution: (runId: string, candidate: ContributionCandidate) => Promise<void>;
|
|
22
20
|
}
|
|
23
21
|
/** One logical stage; only returned format errors admit a second response attempt. */
|
|
24
|
-
export declare function invokeStage(
|
|
22
|
+
export declare function invokeStage(execution: StageExecution): Promise<string>;
|
|
25
23
|
export {};
|