@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.
Files changed (70) hide show
  1. package/README.md +32 -6
  2. package/dist/src/adapters/agents.js +6 -3
  3. package/dist/src/adapters/hosting.js +16 -10
  4. package/dist/src/adapters/sdk-protocol.d.ts +3 -3
  5. package/dist/src/adapters/sdk-protocol.js +9 -7
  6. package/dist/src/cli.d.ts +1 -1
  7. package/dist/src/cli.js +40 -19
  8. package/dist/src/config.d.ts +22 -22
  9. package/dist/src/config.js +31 -26
  10. package/dist/src/defaults.d.ts +2 -0
  11. package/dist/src/defaults.js +2 -0
  12. package/dist/src/domain.d.ts +24 -4
  13. package/dist/src/domain.js +10 -3
  14. package/dist/src/evidence.d.ts +54 -0
  15. package/dist/src/evidence.js +214 -0
  16. package/dist/src/index.d.ts +1 -0
  17. package/dist/src/index.js +1 -0
  18. package/dist/src/invocation.d.ts +24 -0
  19. package/dist/src/invocation.js +163 -0
  20. package/dist/src/operations.d.ts +7 -2
  21. package/dist/src/operations.js +76 -134
  22. package/dist/src/prompts.d.ts +28 -0
  23. package/dist/src/prompts.js +63 -0
  24. package/dist/src/recovery.d.ts +19 -0
  25. package/dist/src/recovery.js +99 -0
  26. package/dist/src/runner.d.ts +5 -0
  27. package/dist/src/runner.js +145 -18
  28. package/dist/src/runtime/process.d.ts +2 -0
  29. package/dist/src/runtime/process.js +41 -12
  30. package/dist/src/store.d.ts +21 -2
  31. package/dist/src/store.js +122 -1
  32. package/dist/src/tui/actions.d.ts +16 -0
  33. package/dist/src/tui/actions.js +23 -0
  34. package/dist/src/tui/constants.d.ts +6 -0
  35. package/dist/src/tui/constants.js +3 -0
  36. package/dist/src/{tui-data.d.ts → tui/data.d.ts} +8 -6
  37. package/dist/src/tui/data.js +141 -0
  38. package/dist/src/tui/dialogs.d.ts +17 -0
  39. package/dist/src/tui/dialogs.js +149 -0
  40. package/dist/src/tui/format.d.ts +7 -0
  41. package/dist/src/tui/format.js +62 -0
  42. package/dist/src/tui/index.d.ts +2 -0
  43. package/dist/src/tui/index.js +1 -0
  44. package/dist/src/tui/layout.d.ts +25 -0
  45. package/dist/src/tui/layout.js +36 -0
  46. package/dist/src/tui/log-file.d.ts +26 -0
  47. package/dist/src/tui/log-file.js +156 -0
  48. package/dist/src/tui/log.d.ts +11 -0
  49. package/dist/src/tui/log.js +90 -0
  50. package/dist/src/tui/monitor.d.ts +8 -0
  51. package/dist/src/tui/monitor.js +222 -0
  52. package/dist/src/tui/text.d.ts +3 -0
  53. package/dist/src/tui/text.js +10 -0
  54. package/dist/src/tui/use-log-controller.d.ts +27 -0
  55. package/dist/src/tui/use-log-controller.js +192 -0
  56. package/dist/src/tui/views.d.ts +17 -0
  57. package/dist/src/tui/views.js +97 -0
  58. package/docs/api.md +119 -6
  59. package/docs/architecture.md +21 -4
  60. package/docs/configuration.md +137 -5
  61. package/docs/database.md +7 -0
  62. package/docs/operations.md +117 -2
  63. package/docs/providers.md +58 -2
  64. package/docs/releases.md +34 -79
  65. package/examples/config.ts +2 -2
  66. package/package.json +4 -2
  67. package/dist/src/tui-data.js +0 -89
  68. package/dist/src/tui.d.ts +0 -5
  69. package/dist/src/tui.js +0 -69
  70. package/docs/acceptance.md +0 -35
package/README.md CHANGED
@@ -1,8 +1,29 @@
1
1
  # Agent Workflows
2
2
 
3
+ ![Print-shop workbench with a job sheet, a worked proof, and an inspected copy awaiting human approval.](docs/assets/hero.png)
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
- Phase 1 includes Codex and Copilot SDK adapters, GitHub and GitLab.com/self-hosted GitLab adapters, durable publication reconciliation, session observability and explicit operator controls. Human review and merging remain separate.
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
- - [Architecture and review evidence](docs/architecture.md)
72
- - [Phase 1 acceptance](docs/acceptance.md)
73
- - [Release setup, first publication and recovery](docs/releases.md)
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
- skills: invocation.skills,
69
+ outputSchema: invocation.outputSchema,
69
70
  readOnly: invocation.readOnly,
70
- timeoutMs: invocation.timeoutMs ?? 1_800_000,
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 ? (invocation.timeoutMs ?? 1_800_000) : 30_000,
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: 30000, redirect: "error" },
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: 100,
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: 100,
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: 100,
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: 30000,
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) || major < 17 || major > 19)
182
- throw new Error(`Supported GitLab versions are 17.x–19.x; instance reports ${metadata.version}`);
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: 100,
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: 100,
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: 100 });
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
- skills: string[];
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 { dirname } from "node:path";
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) => input.readOnly && request.kind !== "read"
47
- ? { kind: "denied-no-approval-rule-and-could-not-request-from-user" }
48
- : { kind: "approved" },
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 ?? 1_800_000);
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: [{ command: "pnpm", args: ["test"], timeoutMs: 300000 }],
61
- stages: {
62
- implementation: {
63
- prompt: "Implement the issue and preserve repository conventions.",
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
- review: {
69
- prompt: "Review correctness, safety and the issue acceptance criteria.",
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 })).waitUntilExit();
185
+ await render(React.createElement(Monitor, { source: store }), {
186
+ alternateScreen: true,
187
+ }).waitUntilExit();
167
188
  });
168
189
  });
169
190
  try {
@@ -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.ZodDefault<z.ZodString>;
29
- skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
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.ZodDefault<z.ZodObject<{
71
- implementation: z.ZodDefault<z.ZodObject<{
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.ZodDefault<z.ZodString>;
85
- skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
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
- writing: z.ZodDefault<z.ZodObject<{
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.ZodDefault<z.ZodString>;
102
- skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
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.ZodDefault<z.ZodObject<{
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.ZodDefault<z.ZodString>;
119
- skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
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.ZodDefault<z.ZodObject<{
167
- implementation: z.ZodDefault<z.ZodObject<{
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.ZodDefault<z.ZodString>;
181
- skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
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
- writing: z.ZodDefault<z.ZodObject<{
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.ZodDefault<z.ZodString>;
198
- skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
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.ZodDefault<z.ZodObject<{
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.ZodDefault<z.ZodString>;
215
- skills: z.ZodDefault<z.ZodArray<z.ZodString>>;
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>>;
@@ -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.strictObject({
14
+ export const stageSchema = z
15
+ .strictObject({
14
16
  profile: profileSchema.partial().optional(),
15
- prompt: z.string().default(""),
16
- skills: z.array(z.string()).default([]),
17
- timeoutMs: z.number().int().positive().default(1_800_000),
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.number().int().positive().default(300_000),
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.default(() => ({
51
- prompt: "",
52
- skills: [],
53
- timeoutMs: 1_800_000,
54
- })),
55
- writing: stageSchema.default(() => ({
56
- prompt: "",
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
- .default(() => ({
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_-]+$/),
@@ -0,0 +1,2 @@
1
+ export declare const defaultStageTimeoutMs = 1800000;
2
+ export declare const defaultValidationTimeoutMs = 300000;
@@ -0,0 +1,2 @@
1
+ export const defaultStageTimeoutMs = 1_800_000;
2
+ export const defaultValidationTimeoutMs = 300_000;
@@ -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.ZodOptional<z.ZodString>;
28
- line: z.ZodOptional<z.ZodNumber>;
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.infer<typeof reviewSchema>;
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
- skills: string[];
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);