@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.
Files changed (39) hide show
  1. package/README.md +25 -14
  2. package/dist/src/adapters/agents.js +9 -0
  3. package/dist/src/cli-config.d.ts +2 -0
  4. package/dist/src/cli-config.js +35 -0
  5. package/dist/src/cli.js +22 -24
  6. package/dist/src/config.d.ts +10 -0
  7. package/dist/src/config.js +9 -11
  8. package/dist/src/domain.d.ts +6 -0
  9. package/dist/src/invocation.d.ts +2 -4
  10. package/dist/src/invocation.js +204 -107
  11. package/dist/src/operations.d.ts +1 -2
  12. package/dist/src/operations.js +18 -33
  13. package/dist/src/recovery.d.ts +20 -2
  14. package/dist/src/recovery.js +49 -1
  15. package/dist/src/runner.d.ts +6 -2
  16. package/dist/src/runner.js +81 -83
  17. package/dist/src/runtime/process.d.ts +1 -0
  18. package/dist/src/runtime/process.js +6 -2
  19. package/dist/src/store.d.ts +4 -2
  20. package/dist/src/store.js +69 -51
  21. package/dist/src/tui/data.d.ts +1 -1
  22. package/dist/src/tui/dialogs.js +1 -0
  23. package/dist/src/tui/monitor-navigation.d.ts +76 -0
  24. package/dist/src/tui/monitor-navigation.js +187 -0
  25. package/dist/src/tui/monitor.js +70 -183
  26. package/dist/src/tui/projection.d.ts +22 -0
  27. package/dist/src/tui/projection.js +49 -0
  28. package/dist/src/validation-selection.d.ts +6 -0
  29. package/dist/src/validation-selection.js +29 -0
  30. package/dist/src/workspace.js +3 -2
  31. package/docs/api.md +11 -7
  32. package/docs/configuration.md +61 -18
  33. package/docs/database.md +2 -18
  34. package/docs/operations.md +21 -28
  35. package/docs/providers.md +2 -2
  36. package/package.json +1 -1
  37. package/dist/src/tui/actions.d.ts +0 -16
  38. package/dist/src/tui/actions.js +0 -23
  39. 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, load database, hosting and application variables from one explicit
56
- file: `agent-workflows --env-file ./runner.env run`. Existing shell values win,
57
- including empty strings. See [environment file examples and boundaries](docs/configuration.md#cli-environment-files).
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). Biome formats and lints supported source/configuration files; Markdown and YAML are maintained manually.
96
-
97
- - [Configuration and profiles](docs/configuration.md)
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,2 @@
1
+ import { type Configuration } from "./config.js";
2
+ export declare function readCliConfiguration(path: string, resolveEnvironmentFile: (path: string) => string): Promise<Configuration>;
@@ -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 { configSchema } from "./config.js";
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 configSchema.parse(JSON.parse(await readFile(configPath(), "utf8")));
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", "retry", "recover"])
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 withStore(async (store) => {
213
+ await withConfiguredStore(config, async (store) => {
216
214
  await render(React.createElement(Monitor, {
217
215
  source: store,
218
216
  notificationWriter: options.notify
@@ -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<{
@@ -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
- .array(z.strictObject({
54
- command: z.string().min(1),
55
- args: z.array(z.string()).default([]),
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
@@ -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 {
@@ -1,6 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { type Stage } from "./config.js";
3
- import { type ContributionCandidate, type RunRecord, type Snapshot } from "./domain.js";
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({ run, name, stage, task, stepId, dependencies, saveImplementationSnapshot, acceptContribution, }: StageExecution): Promise<string>;
22
+ export declare function invokeStage(execution: StageExecution): Promise<string>;
25
23
  export {};