@clemrig/factory 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +198 -2
- package/dist/app/doctor.d.ts +20 -0
- package/dist/app/doctor.js +38 -0
- package/dist/app/doctor.js.map +1 -0
- package/dist/app/factory.d.ts +12 -0
- package/dist/app/factory.js +22 -0
- package/dist/app/factory.js.map +1 -0
- package/dist/app/init.d.ts +6 -0
- package/dist/app/init.js +15 -0
- package/dist/app/init.js.map +1 -0
- package/dist/app/run.d.ts +24 -0
- package/dist/app/run.js +100 -0
- package/dist/app/run.js.map +1 -0
- package/dist/app/runs.d.ts +36 -0
- package/dist/app/runs.js +66 -0
- package/dist/app/runs.js.map +1 -0
- package/dist/cli/application.d.ts +6 -0
- package/dist/cli/application.js +63 -0
- package/dist/cli/application.js.map +1 -0
- package/dist/cli/args.d.ts +12 -0
- package/dist/cli/args.js +45 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/doctor.d.ts +5 -0
- package/dist/cli/doctor.js +33 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/help.d.ts +1 -0
- package/dist/cli/help.js +17 -0
- package/dist/cli/help.js.map +1 -0
- package/dist/cli/init.d.ts +6 -0
- package/dist/cli/init.js +18 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/launcher.d.ts +5 -0
- package/dist/cli/launcher.js +55 -0
- package/dist/cli/launcher.js.map +1 -0
- package/dist/cli/reporter.d.ts +17 -0
- package/dist/cli/reporter.js +123 -0
- package/dist/cli/reporter.js.map +1 -0
- package/dist/cli/run.d.ts +9 -0
- package/dist/cli/run.js +39 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/status.d.ts +4 -0
- package/dist/cli/status.js +15 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/ui.d.ts +3 -0
- package/dist/cli/ui.js +18 -0
- package/dist/cli/ui.js.map +1 -0
- package/dist/engine/metrics.d.ts +45 -0
- package/dist/engine/metrics.js +60 -0
- package/dist/engine/metrics.js.map +1 -0
- package/dist/engine/primitives.d.ts +34 -0
- package/dist/engine/primitives.js +81 -0
- package/dist/engine/primitives.js.map +1 -0
- package/dist/engine/profiles.d.ts +53 -0
- package/dist/engine/profiles.js +117 -0
- package/dist/engine/profiles.js.map +1 -0
- package/dist/engine/runner.d.ts +13 -0
- package/dist/engine/runner.js +157 -0
- package/dist/engine/runner.js.map +1 -0
- package/dist/engine/types.d.ts +194 -0
- package/dist/engine/types.js +2 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/engine/workflow.d.ts +18 -0
- package/dist/engine/workflow.js +196 -0
- package/dist/engine/workflow.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +4 -0
- package/dist/main.js.map +1 -0
- package/dist/system/environment.d.ts +2 -0
- package/dist/system/environment.js +13 -0
- package/dist/system/environment.js.map +1 -0
- package/dist/system/factory.d.ts +16 -0
- package/dist/system/factory.js +62 -0
- package/dist/system/factory.js.map +1 -0
- package/dist/system/process.d.ts +21 -0
- package/dist/system/process.js +238 -0
- package/dist/system/process.js.map +1 -0
- package/dist/system/runtimes/adapter.d.ts +90 -0
- package/dist/system/runtimes/adapter.js +20 -0
- package/dist/system/runtimes/adapter.js.map +1 -0
- package/dist/system/runtimes/agent-runtime.d.ts +45 -0
- package/dist/system/runtimes/agent-runtime.js +184 -0
- package/dist/system/runtimes/agent-runtime.js.map +1 -0
- package/dist/system/runtimes/claude-code.d.ts +10 -0
- package/dist/system/runtimes/claude-code.js +383 -0
- package/dist/system/runtimes/claude-code.js.map +1 -0
- package/dist/system/runtimes/inspect.d.ts +25 -0
- package/dist/system/runtimes/inspect.js +129 -0
- package/dist/system/runtimes/inspect.js.map +1 -0
- package/dist/system/runtimes/pi.d.ts +7 -0
- package/dist/system/runtimes/pi.js +235 -0
- package/dist/system/runtimes/pi.js.map +1 -0
- package/dist/system/runtimes/protocol.d.ts +9 -0
- package/dist/system/runtimes/protocol.js +38 -0
- package/dist/system/runtimes/protocol.js.map +1 -0
- package/dist/system/runtimes/registry.d.ts +5 -0
- package/dist/system/runtimes/registry.js +5 -0
- package/dist/system/runtimes/registry.js.map +1 -0
- package/dist/system/store.d.ts +20 -0
- package/dist/system/store.js +126 -0
- package/dist/system/store.js.map +1 -0
- package/dist/system/templates.d.ts +8 -0
- package/dist/system/templates.js +30 -0
- package/dist/system/templates.js.map +1 -0
- package/dist/ui/server.d.ts +8 -0
- package/dist/ui/server.js +111 -0
- package/dist/ui/server.js.map +1 -0
- package/dist/ui/static/dashboard.js +266 -0
- package/dist/ui/static/follow.js +67 -0
- package/dist/ui/static/index.html +20 -0
- package/dist/ui/static/style.css +166 -0
- package/dist/ui/static/timeline.js +180 -0
- package/package.json +52 -4
- package/templates/init/factory.ts +4 -0
- package/templates/init/gitignore +3 -0
- package/templates/init/tsconfig.json +13 -0
- package/templates/init/workflows/ask/index.ts +57 -0
- package/templates/init/workflows/ask/prompts/answer.md +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Clément CHESNEAU
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,199 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @clemrig/factory
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Write agent workflows in TypeScript inside any repository, run them in the terminal and watch them in a local dashboard.
|
|
4
|
+
|
|
5
|
+
A workflow is an explicit graph of **CODE** stages (your code), **AGENT** stages (an agent run by [Claude Code](https://code.claude.com) or [Pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) and **GATE** stages that accept a result or send it back for correction, a bounded number of times. Workflows live in the repository's `.factory/` folder, are committed with it and run without a build step.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- macOS or Linux, Node.js 24 or later, npm.
|
|
10
|
+
- For agents run by Claude Code: the `claude` CLI 2.1.296 or later in the `PATH`, logged in with a claude.ai subscription (`claude`, then `/login`) or given an API key (`ANTHROPIC_API_KEY`).
|
|
11
|
+
- For agents run by Pi: the `pi` CLI (`@earendil-works/pi-coding-agent` 1.1.0 or later) in the `PATH`, connected to the provider each Pi profile names (`pi`, then `/login`, or the provider's API key).
|
|
12
|
+
|
|
13
|
+
The factory never reads or stores credentials: each runtime uses its own connection, from its login or from an API key in the environment or `.factory/.env`. Claude Code prefers an API key to its login; Pi prefers a login stored for the provider to an API key. `factory doctor` and `factory run` show which connection each runtime uses.
|
|
14
|
+
|
|
15
|
+
## Get started
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
npm install -g @clemrig/factory
|
|
19
|
+
cd my-repository
|
|
20
|
+
factory init
|
|
21
|
+
factory doctor
|
|
22
|
+
factory run ask --question "What changed recently?"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`factory init` creates `.factory/` in the current directory and installs it; it refuses to overwrite an existing `.factory/`. Commit `.factory/`: another machine only needs `npm ci --prefix .factory`.
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
.factory/
|
|
29
|
+
package.json pins the exact core version
|
|
30
|
+
factory.ts the repository's workflows
|
|
31
|
+
workflows/ask/
|
|
32
|
+
index.ts the example workflow
|
|
33
|
+
prompts/answer.md the prompt of its answer stage
|
|
34
|
+
tsconfig.json editor settings only
|
|
35
|
+
.gitignore node_modules/, runs/, .env
|
|
36
|
+
runs/ one folder per run (ignored)
|
|
37
|
+
.env optional secrets (ignored)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
The global `factory` command uses the nearest `.factory/` holding a `package.json`, in the current directory or a parent, and runs the core version installed there: every machine runs the version pinned in the repository. Only `factory init` and `factory help` run with the global version. Without `.factory/` the command asks for `factory init`; without installed dependencies it gives the `npm ci --prefix .factory` command to run.
|
|
41
|
+
|
|
42
|
+
The example workflow `ask` reads the last 10 commits with `git log` (a CODE stage), asks a read-only Claude Code agent (`claude-sonnet-5-5`, effort `low`, tools `Read`, `Grep`, `Glob`) to answer the question, and checks the answer with a gate that sends it back at most 3 times. Copy its folder to start a new workflow.
|
|
43
|
+
|
|
44
|
+
## Write a workflow
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
// .factory/workflows/changelog/index.ts
|
|
48
|
+
import {
|
|
49
|
+
agent,
|
|
50
|
+
defineWorkflow,
|
|
51
|
+
END,
|
|
52
|
+
node,
|
|
53
|
+
pick,
|
|
54
|
+
retry,
|
|
55
|
+
runProcess,
|
|
56
|
+
schemaGate,
|
|
57
|
+
step,
|
|
58
|
+
z,
|
|
59
|
+
} from "@clemrig/factory";
|
|
60
|
+
|
|
61
|
+
const notes = z.strictObject({ entries: z.array(z.string().min(1)).min(1) });
|
|
62
|
+
|
|
63
|
+
export default defineWorkflow({
|
|
64
|
+
name: "changelog", // also the folder name: .factory/workflows/changelog/
|
|
65
|
+
input: z.object({
|
|
66
|
+
since: z.string().min(1),
|
|
67
|
+
limit: z.coerce.number().int().positive().optional(), // options are strings: coerce numbers
|
|
68
|
+
}),
|
|
69
|
+
initial: "log",
|
|
70
|
+
nodes: {
|
|
71
|
+
log: node(
|
|
72
|
+
step(async ({ dir, state }) => {
|
|
73
|
+
const log = await runProcess(
|
|
74
|
+
["git", "log", "--oneline", `--since=${String(state.input.since)}`],
|
|
75
|
+
dir,
|
|
76
|
+
30_000,
|
|
77
|
+
);
|
|
78
|
+
if (log.exitCode !== 0) throw new Error(log.stderr);
|
|
79
|
+
return { commits: log.stdout.trim().split("\n") };
|
|
80
|
+
}),
|
|
81
|
+
{ ok: "write" },
|
|
82
|
+
),
|
|
83
|
+
// AGENT: prompt in prompts/write.md, after the stage.
|
|
84
|
+
write: node(
|
|
85
|
+
agent({
|
|
86
|
+
profile: {
|
|
87
|
+
runtime: "claude-code",
|
|
88
|
+
model: "claude-sonnet-5-5",
|
|
89
|
+
thinking: "low",
|
|
90
|
+
tools: ["Read", "Grep", "Glob"],
|
|
91
|
+
// access: "read" by default; "write" allows tools that change the repository
|
|
92
|
+
},
|
|
93
|
+
schema: notes,
|
|
94
|
+
context: pick("input", "commits"),
|
|
95
|
+
}),
|
|
96
|
+
{ ok: "check" },
|
|
97
|
+
),
|
|
98
|
+
check: node(
|
|
99
|
+
schemaGate({
|
|
100
|
+
schema: notes,
|
|
101
|
+
into: "notes",
|
|
102
|
+
check: ({ entries }) =>
|
|
103
|
+
entries.length > 20 ? "Keep at most 20 entries" : undefined,
|
|
104
|
+
}),
|
|
105
|
+
{ ok: END, invalid: retry("write", 3) },
|
|
106
|
+
),
|
|
107
|
+
},
|
|
108
|
+
// Optional: without it, the result shows the run's validated data as JSON.
|
|
109
|
+
presentResult: (state) =>
|
|
110
|
+
(state.data.notes as { entries: string[] }).entries.map((entry) => `- ${entry}`),
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
// .factory/factory.ts
|
|
116
|
+
import ask from "./workflows/ask/index.ts";
|
|
117
|
+
import changelog from "./workflows/changelog/index.ts";
|
|
118
|
+
|
|
119
|
+
export default [ask, changelog];
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Workflows are TypeScript run directly by Node: use erasable syntax only (no `enum`, no parameter properties) and relative imports with their `.ts` extension. Import the core only from `@clemrig/factory`, which also exports `z` (Zod), so that workflows and the core share one Zod instance.
|
|
123
|
+
|
|
124
|
+
**Graph.** Each stage declares its outcomes and the node routes every outcome to a stage, to `END` or to `retry(stage, max)`. A stage receives `{ state, agent, log, dir }`: the run (read-only), the agent runtime, the run journal and the repository directory. Stages and agents work in the repository (the folder that contains `.factory/`), which is also the process's working directory during a run, wherever the command was started.
|
|
125
|
+
|
|
126
|
+
| Primitive | Use |
|
|
127
|
+
|---|---|
|
|
128
|
+
| `step(run)`, `code(outcomes, run)` | CODE stage: returns data (merged into `state.data`) or a named outcome |
|
|
129
|
+
| `agent({ profile, schema, context, prompt?, session? })` | AGENT stage: one structured request run with its profile; its output waits in `state.output` for a gate |
|
|
130
|
+
| `gate(outcomes, run)`, `schemaGate({ schema, into, check? })` | GATE stage: `schemaGate` validates the agent output, runs `check`, then writes it into `state.data[into]` or sends a correction |
|
|
131
|
+
| `retry(stage, max)` | correction route, taken at most `max` times (1–100) per run, with the gate's reason as `feedback` for the next agent |
|
|
132
|
+
| `pick(...keys)` | agent context from `input` and data keys |
|
|
133
|
+
| `runProcess(argv, cwd, timeoutMs)` | runs a command without a shell; Ctrl-C stops it |
|
|
134
|
+
|
|
135
|
+
`defineWorkflow` refuses, when the workflow is loaded: a `.` in a stage or outcome name (a correction counter is named `<stage>.<outcome>`), an unrouted outcome, an unknown target, a retry maximum outside 1–100, a loop whose edges include no `retry` (with the loop's path), an invalid profile, a write tool in a read-only profile, a prompt or session name that cannot name a file, a `maxCostUsd` that is not a positive number, and an input that is not a `z.object` or uses the names `json`, `help` or `max-cost-usd`. Every run is therefore bounded.
|
|
136
|
+
|
|
137
|
+
**Inputs.** Each field of `input` becomes a `--<name>` option of `factory run`; an option given without a value is `true` and only a boolean field accepts it: `z.boolean()`, possibly with `.optional()`, `.nullable()` or `.default(…)` (a transformed boolean or one with `.catch` takes no option alone). A missing, invalid or unknown option is refused with the list of problems, before any run is created. A workflow without `input` accepts no input option.
|
|
138
|
+
|
|
139
|
+
**Budget.** `maxCostUsd` (optional) fails a run once the cost its agents' runtimes announced exceeds it; `factory run … --max-cost-usd 2` sets it for one run, instead of the workflow's. It is checked before and after each agent call, failed or not, on the runtime's figures: a running call is not stopped and can go past it; once it is exceeded, no further call starts and the run fails at its next stage boundary, even if workflow code catches the error. A call that announced no cost fails a run that has a budget, since the budget can no longer be checked. With a subscription, the cost is an estimate.
|
|
140
|
+
|
|
141
|
+
**Profiles.** Each agent stage declares its complete profile; no machine setting changes it. A profile is a plain object: share one between stages with a constant, typed with `satisfies AgentProfile` (`AgentProfile` is exported by `@clemrig/factory`).
|
|
142
|
+
|
|
143
|
+
| Field | Claude Code (`runtime: "claude-code"`) | Pi (`runtime: "pi"`) |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| `access` | `"read"` (default) or `"write"` | `"read"` (default) or `"write"` |
|
|
146
|
+
| `provider` | — | the Pi provider, required (`openai`, `openai-codex`…) |
|
|
147
|
+
| `model` | model name or alias | model name |
|
|
148
|
+
| `thinking` | effort: `low` … `max` | `off` … `max` |
|
|
149
|
+
| `tools` | exact names (`Read`, `Grep`, `Bash`…) and `mcp__<server>__<tool>` | exact names (`read`, `bash`…); other names need an extension |
|
|
150
|
+
| other | `mcpServers: { name: { command, args? } }` | `skills`, `extensions` (paths) |
|
|
151
|
+
| `timeoutMs` | default 15 minutes | default 15 minutes |
|
|
152
|
+
|
|
153
|
+
A read-only profile may use only `Read`, `Grep`, `Glob`, `WebFetch`, `WebSearch` and declared MCP tools on Claude Code, and no `bash`, `powershell`, `edit` or `write` on Pi. Relative skill, extension and MCP command paths are relative to `.factory/`; a bare command is looked up in the `PATH`. Each Claude Code session holds only the profile's tools and MCP servers, without the operator's settings, plugins, skills or memory: a session with another tool, MCP server or plugin is refused, and another customization (skills, commands, output style, memory) is journaled as a warning. Pi runs without the machine's extensions, skills, context files and MCP servers. Runtimes are not sandboxes: listed tools act with your rights.
|
|
154
|
+
|
|
155
|
+
**Prompts.** An agent's prompt is `.factory/workflows/<workflow>/prompts/<name>.md`, where the name is the stage's unless `agent({ prompt })` names another. It is appended to the runtime's system prompt; the request (context and output JSON Schema) follows on standard input. Prompts are read when a run starts: an edit applies to the next run, without building anything.
|
|
156
|
+
|
|
157
|
+
**Sessions.** An agent stage keeps one conversation per run: a correction sent back to it resumes the conversation, with the gate's feedback. `session: "name"` shares a conversation between stages of the same runtime (sharing one across runtimes is refused); `session: (state) => name` opens one per value. A run's stage and session names form one namespace per runtime: a name returned by a function joins the conversation of that name on the same runtime. Session names start and end with a letter or digit.
|
|
158
|
+
|
|
159
|
+
## Run and observe
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
factory run changelog --since 2026-10-01 [--json]
|
|
163
|
+
factory status [RUN_ID] [--json]
|
|
164
|
+
factory ui [--port 4321]
|
|
165
|
+
factory doctor [--json]
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Before creating a run, `factory run` validates the input, reads the prompts and checks, for each runtime the workflow uses, that its executable is in the `PATH`, at least the supported version, and connected; a missing piece fails the command with the stage, runtime and reason. The terminal then shows each runtime's version and connection. A model the account cannot use fails the run at its first call, with the runtime's message.
|
|
169
|
+
|
|
170
|
+
The run executes in the foreground. The terminal shows each stage's start and end, decisions and corrections with their counter (`[check.invalid 1/3]`), journal messages and, for an agent, the start and end of its call, each finished tool with its duration and isolation warnings; then the result, the duration, the agents' tokens and estimated cost, and the runtimes used. `--json` writes every event, then the result, as JSON lines on standard output. Exit codes: 0 completed, 1 error or failed run.
|
|
171
|
+
|
|
172
|
+
**Ctrl-C** stops the run's child processes and ends the run as `failed` with its cause before its next stage, or instead of completing; a second Ctrl-C kills the remaining child processes and quits at once (exit code 130, the run may then stay `running`). A failed or interrupted run is not resumed: start a new one.
|
|
173
|
+
|
|
174
|
+
`factory status` lists the repository's runs or shows one; while `factory.ts` cannot load, `status` and `ui` say so and still show the runs with the default rendering. `factory ui` serves a read-only dashboard of the repository's runs on `127.0.0.1` (port chosen by the system unless `--port` sets it, URL printed; Ctrl-C stops it): the runs with workflow, status, current stage, duration, estimated cost and last update, and for a run its runtimes, a timeline with one block per stage (status, duration, the agent's tools and tokens as they come, journal, decision), the correction counters and the result. It follows a running run as it goes. To watch a run on another machine, forward the same port through SSH: `ssh -L 4321:127.0.0.1:4321 machine`, run `factory ui --port 4321` there, and open `http://localhost:4321/`. `factory doctor` checks Node, loads the workflows, reads their prompts, lists each workflow's options and checks each runtime's executable, version and connection, without any provider request.
|
|
175
|
+
|
|
176
|
+
## Data
|
|
177
|
+
|
|
178
|
+
Each run keeps `state.json` and `events.jsonl` in `.factory/runs/<RUN_ID>/`, plus its Pi sessions and Claude Code session markers; Claude Code keeps its transcripts in its own storage (`~/.claude/projects/…`). Runs hold inputs, agent outputs and the journal: they stay private and are ignored by Git. `.factory/.env` is loaded automatically, without overriding variables already set in the shell; runtimes, MCP servers and commands inherit the environment. `factory run` executes the code of `.factory/`: trust it as you trust `npm run`. The dashboard shows the same data to any local process that can reach `127.0.0.1`.
|
|
179
|
+
|
|
180
|
+
Tokens and costs are the figures each runtime announces, never recomputed: Claude Code announces at the end of a call its session's total, earlier calls included, and its tokens during a call are provisional; Pi announces each response with its cost. A run's total is the sum of its sessions; a call without figures makes it partial. The estimated cost is the runtime's public-price estimate, not a bill.
|
|
181
|
+
|
|
182
|
+
## Limits
|
|
183
|
+
|
|
184
|
+
Not in this version: resuming a run, detached runs, logs `--follow` and stop, local settings or profile overrides, actions or graph drawing in the dashboard, runs of several repositories, `factory new`, a shared stage library, a test harness for workflows, CI, cron or Git hook triggers, notifications, Windows.
|
|
185
|
+
|
|
186
|
+
## Develop the core
|
|
187
|
+
|
|
188
|
+
```sh
|
|
189
|
+
npm ci
|
|
190
|
+
npm run check # lint, format, typecheck, tests with coverage, build
|
|
191
|
+
npm run build
|
|
192
|
+
npm pack # then, in a test repository: npm install --no-save --prefix .factory /path/to/clemrig-factory-0.1.0.tgz
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`npm link` in this repository provides a global `factory` built from it; a test repository still runs the core installed in its `.factory/`. See [AGENTS.md](AGENTS.md), [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md), [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) and the [decisions](docs/decisions/README.md) (in French).
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Runtime } from "../engine/profiles.js";
|
|
2
|
+
import type { Workflow } from "../engine/types.js";
|
|
3
|
+
import { type AgentProblem, type RuntimeStatus } from "../system/runtimes/inspect.js";
|
|
4
|
+
export type { AgentProblem, RuntimeStatus, } from "../system/runtimes/inspect.js";
|
|
5
|
+
/** What `factory doctor` found, without any provider request. */
|
|
6
|
+
export interface Diagnosis {
|
|
7
|
+
ok: boolean;
|
|
8
|
+
node: RuntimeStatus;
|
|
9
|
+
factory: RuntimeStatus;
|
|
10
|
+
/** Each loaded workflow, with the reason its prompts cannot be read. */
|
|
11
|
+
workflows: {
|
|
12
|
+
workflow: Workflow;
|
|
13
|
+
error?: string;
|
|
14
|
+
}[];
|
|
15
|
+
runtimes: Partial<Record<Runtime, RuntimeStatus>>;
|
|
16
|
+
/** Missing runtimes, by `<workflow>.<stage>`. */
|
|
17
|
+
problems: AgentProblem[];
|
|
18
|
+
}
|
|
19
|
+
/** Node, the factory and its workflows' prompts, then each runtime in use. */
|
|
20
|
+
export declare function diagnose(factoryDir: string): Promise<Diagnosis>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { loadLocalEnvironment } from "../system/environment.js";
|
|
2
|
+
import { loadFactory, loadPrompts } from "../system/factory.js";
|
|
3
|
+
import { inspectNode, inspectRuntimes, } from "../system/runtimes/inspect.js";
|
|
4
|
+
const message = (error) => error instanceof Error ? error.message : String(error);
|
|
5
|
+
/** Node, the factory and its workflows' prompts, then each runtime in use. */
|
|
6
|
+
export async function diagnose(factoryDir) {
|
|
7
|
+
loadLocalEnvironment(factoryDir);
|
|
8
|
+
const node = inspectNode();
|
|
9
|
+
let factory;
|
|
10
|
+
let loaded;
|
|
11
|
+
try {
|
|
12
|
+
factory = await loadFactory(factoryDir);
|
|
13
|
+
loaded = { ok: true, detail: `Factory ${factoryDir}` };
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
loaded = { ok: false, detail: `Factory: ${message(error)}` };
|
|
17
|
+
}
|
|
18
|
+
const workflows = [];
|
|
19
|
+
const agents = {};
|
|
20
|
+
for (const workflow of factory?.workflows.values() ?? []) {
|
|
21
|
+
try {
|
|
22
|
+
await loadPrompts(factoryDir, workflow);
|
|
23
|
+
workflows.push({ workflow });
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
workflows.push({ workflow, error: message(error) });
|
|
27
|
+
}
|
|
28
|
+
for (const [stage, declared] of Object.entries(workflow.agents))
|
|
29
|
+
agents[`${workflow.name}.${stage}`] = declared;
|
|
30
|
+
}
|
|
31
|
+
const { runtimes, problems } = await inspectRuntimes(agents);
|
|
32
|
+
const ok = node.ok &&
|
|
33
|
+
loaded.ok &&
|
|
34
|
+
workflows.every(({ error }) => error === undefined) &&
|
|
35
|
+
problems.length === 0;
|
|
36
|
+
return { ok, node, factory: loaded, workflows, runtimes, problems };
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/app/doctor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAgB,WAAW,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC9E,OAAO,EAEL,WAAW,EACX,eAAe,GAEhB,MAAM,+BAA+B,CAAC;AAmBvC,MAAM,OAAO,GAAG,CAAC,KAAc,EAAE,EAAE,CACjC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAEzD,8EAA8E;AAC9E,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,UAAkB;IAC/C,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACjC,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;IAC3B,IAAI,OAA4B,CAAC;IACjC,IAAI,MAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;QACxC,MAAM,GAAG,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,WAAW,UAAU,EAAE,EAAE,CAAC;IACzD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,GAAG,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,OAAO,CAAC,KAAK,CAAC,EAAE,EAAE,CAAC;IAC/D,CAAC;IACD,MAAM,SAAS,GAA2B,EAAE,CAAC;IAC7C,MAAM,MAAM,GAA+B,EAAE,CAAC;IAC9C,KAAK,MAAM,QAAQ,IAAI,OAAO,EAAE,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CAAC;QACzD,IAAI,CAAC;YACH,MAAM,WAAW,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;YACxC,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC/B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,KAAK,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;YAC7D,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,IAAI,KAAK,EAAE,CAAC,GAAG,QAAQ,CAAC;IACnD,CAAC;IACD,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,CAAC;IAC7D,MAAM,EAAE,GACN,IAAI,CAAC,EAAE;QACP,MAAM,CAAC,EAAE;QACT,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;QACnD,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC;IACxB,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;AACtE,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type Factory } from "../system/factory.js";
|
|
2
|
+
export type { Factory } from "../system/factory.js";
|
|
3
|
+
/** Loads `.factory/.env` without overriding the shell, then the repository's workflows. */
|
|
4
|
+
export declare function openFactory(factoryDir: string): Promise<Factory>;
|
|
5
|
+
/**
|
|
6
|
+
* The factory to read runs with: while `factory.ts` cannot load, its runs stay
|
|
7
|
+
* readable with the default rendering, and `error` says why.
|
|
8
|
+
*/
|
|
9
|
+
export declare function openFactoryForReading(factoryDir: string): Promise<{
|
|
10
|
+
factory: Factory;
|
|
11
|
+
error?: string;
|
|
12
|
+
}>;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { loadLocalEnvironment } from "../system/environment.js";
|
|
2
|
+
import { emptyFactory, loadFactory } from "../system/factory.js";
|
|
3
|
+
const message = (error) => error instanceof Error ? error.message : String(error);
|
|
4
|
+
/** Loads `.factory/.env` without overriding the shell, then the repository's workflows. */
|
|
5
|
+
export async function openFactory(factoryDir) {
|
|
6
|
+
loadLocalEnvironment(factoryDir);
|
|
7
|
+
return loadFactory(factoryDir);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The factory to read runs with: while `factory.ts` cannot load, its runs stay
|
|
11
|
+
* readable with the default rendering, and `error` says why.
|
|
12
|
+
*/
|
|
13
|
+
export async function openFactoryForReading(factoryDir) {
|
|
14
|
+
loadLocalEnvironment(factoryDir);
|
|
15
|
+
try {
|
|
16
|
+
return { factory: await loadFactory(factoryDir) };
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return { factory: emptyFactory(factoryDir), error: message(error) };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=factory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"factory.js","sourceRoot":"","sources":["../../src/app/factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAChE,OAAO,EAAE,YAAY,EAAgB,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAI/E,MAAM,OAAO,GAAG,CAAC,KAAc,EAAE,EAAE,CACjC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAEzD,2FAA2F;AAC3F,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,UAAkB;IAClD,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACjC,OAAO,WAAW,CAAC,UAAU,CAAC,CAAC;AACjC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,UAAkB;IAElB,oBAAoB,CAAC,UAAU,CAAC,CAAC;IACjC,IAAI,CAAC;QACH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC,UAAU,CAAC,EAAE,CAAC;IACpD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,EAAE,OAAO,EAAE,YAAY,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;IACtE,CAAC;AACH,CAAC"}
|
package/dist/app/init.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { runProcess } from "../system/process.js";
|
|
2
|
+
export { createFromTemplates as createFactory } from "../system/templates.js";
|
|
3
|
+
/** Installs `.factory/`'s dependencies; `output` explains a failure. */
|
|
4
|
+
export async function installFactory(factoryDir) {
|
|
5
|
+
const install = await runProcess(["npm", "install", "--no-audit", "--no-fund"], factoryDir, 600_000).catch((error) => ({
|
|
6
|
+
exitCode: 1,
|
|
7
|
+
stdout: "",
|
|
8
|
+
stderr: error instanceof Error ? error.message : String(error),
|
|
9
|
+
}));
|
|
10
|
+
return {
|
|
11
|
+
ok: install.exitCode === 0,
|
|
12
|
+
output: install.stderr || install.stdout,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/app/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAE,mBAAmB,IAAI,aAAa,EAAE,MAAM,wBAAwB,CAAC;AAE9E,wEAAwE;AACxE,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,UAAkB;IAElB,MAAM,OAAO,GAAG,MAAM,UAAU,CAC9B,CAAC,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,CAAC,EAC7C,UAAU,EACV,OAAO,CACR,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CAAC,CAAC;QAC3B,QAAQ,EAAE,CAAC;QACX,MAAM,EAAE,EAAE;QACV,MAAM,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;KAC/D,CAAC,CAAC,CAAC;IACJ,OAAO;QACL,EAAE,EAAE,OAAO,CAAC,QAAQ,KAAK,CAAC;QAC1B,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM;KACzC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { RunEvent, RunState } from "../engine/types.js";
|
|
2
|
+
import { type Factory } from "../system/factory.js";
|
|
3
|
+
import { type RuntimeStatus } from "../system/runtimes/inspect.js";
|
|
4
|
+
export interface RunHooks {
|
|
5
|
+
/** Each stage, journal and agent event, once recorded. */
|
|
6
|
+
onEvent(event: RunEvent): void;
|
|
7
|
+
}
|
|
8
|
+
/** A created run, executing in the foreground. */
|
|
9
|
+
export interface StartedRun {
|
|
10
|
+
state: Readonly<RunState>;
|
|
11
|
+
/** Each runtime the run uses: version, executable and connection. */
|
|
12
|
+
runtimes: RuntimeStatus[];
|
|
13
|
+
/** Settles with the run once it completed or failed. */
|
|
14
|
+
done: Promise<RunState>;
|
|
15
|
+
/** Fails the run with `reason` before its next stage. */
|
|
16
|
+
stop(reason: Error): void;
|
|
17
|
+
/** Kills the child process groups still running, before quitting at once. */
|
|
18
|
+
kill(): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Checks input, budget, prompts and runtimes before creating the run, then runs
|
|
22
|
+
* it in the repository that contains `.factory/`, whatever the working directory.
|
|
23
|
+
*/
|
|
24
|
+
export declare function startRun(factory: Factory, name: string, options: Record<string, string | boolean>, hooks: RunHooks): Promise<StartedRun>;
|
package/dist/app/run.js
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { budgetProblem, emptyMetrics, recordUsage } from "../engine/metrics.js";
|
|
2
|
+
import { executeWorkflow } from "../engine/runner.js";
|
|
3
|
+
import { parseInput } from "../engine/workflow.js";
|
|
4
|
+
import { loadPrompts, workflowOf } from "../system/factory.js";
|
|
5
|
+
import { inDirectory, killProcessGroups } from "../system/process.js";
|
|
6
|
+
import { AgentRuntime } from "../system/runtimes/agent-runtime.js";
|
|
7
|
+
import { inspectRuntimes, } from "../system/runtimes/inspect.js";
|
|
8
|
+
import { FileRunStore, newRunId } from "../system/store.js";
|
|
9
|
+
/** `--max-cost-usd`: the run's budget instead of the workflow's. */
|
|
10
|
+
function parseBudget(value) {
|
|
11
|
+
const budget = Number(value);
|
|
12
|
+
if (typeof value !== "string" || !Number.isFinite(budget) || budget <= 0)
|
|
13
|
+
throw new Error(`Invalid --max-cost-usd ${String(value)}: expected a positive number of USD`);
|
|
14
|
+
return budget;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Checks input, budget, prompts and runtimes before creating the run, then runs
|
|
18
|
+
* it in the repository that contains `.factory/`, whatever the working directory.
|
|
19
|
+
*/
|
|
20
|
+
export async function startRun(factory, name, options, hooks) {
|
|
21
|
+
const workflow = workflowOf(factory, name);
|
|
22
|
+
const { "max-cost-usd": budget, ...inputOptions } = options;
|
|
23
|
+
const maxCostUsd = budget === undefined ? workflow.maxCostUsd : parseBudget(budget);
|
|
24
|
+
const input = parseInput(workflow, inputOptions);
|
|
25
|
+
const prompts = await loadPrompts(factory.factoryDir, workflow);
|
|
26
|
+
const { runtimes, problems, executables } = await inspectRuntimes(workflow.agents);
|
|
27
|
+
if (problems.length)
|
|
28
|
+
throw new Error(`Missing prerequisites: ${problems.map(({ stage, runtime, reason }) => `stage ${stage} (${runtime}): ${reason}`).join("; ")}`);
|
|
29
|
+
const now = new Date().toISOString();
|
|
30
|
+
const state = {
|
|
31
|
+
version: 4,
|
|
32
|
+
id: newRunId(),
|
|
33
|
+
workflow: name,
|
|
34
|
+
input,
|
|
35
|
+
stage: workflow.initial,
|
|
36
|
+
status: "running",
|
|
37
|
+
data: {},
|
|
38
|
+
createdAt: now,
|
|
39
|
+
updatedAt: now,
|
|
40
|
+
metrics: emptyMetrics(),
|
|
41
|
+
runtimes: Object.fromEntries(Object.entries(runtimes).map(([runtime, { detail }]) => [
|
|
42
|
+
runtime,
|
|
43
|
+
detail,
|
|
44
|
+
])),
|
|
45
|
+
};
|
|
46
|
+
const store = new FileRunStore(factory.runsDir, state.id);
|
|
47
|
+
const agent = new AgentRuntime({
|
|
48
|
+
dir: factory.dir,
|
|
49
|
+
factoryDir: factory.factoryDir,
|
|
50
|
+
prompts,
|
|
51
|
+
onUsage: async (report) => {
|
|
52
|
+
if (state.metrics)
|
|
53
|
+
recordUsage(state.metrics, report);
|
|
54
|
+
await store.save(state);
|
|
55
|
+
},
|
|
56
|
+
run: { id: state.id, directory: store.directory },
|
|
57
|
+
executables,
|
|
58
|
+
});
|
|
59
|
+
await store.save(state);
|
|
60
|
+
const controller = new AbortController();
|
|
61
|
+
/**
|
|
62
|
+
* Past its budget, the run stops: the abort fails it at its next stage
|
|
63
|
+
* boundary even if workflow code catches the error, and no call starts.
|
|
64
|
+
*/
|
|
65
|
+
const overBudget = () => {
|
|
66
|
+
const problem = maxCostUsd === undefined || !state.metrics
|
|
67
|
+
? undefined
|
|
68
|
+
: budgetProblem(state.metrics, maxCostUsd);
|
|
69
|
+
if (problem)
|
|
70
|
+
controller.abort(new Error(problem));
|
|
71
|
+
return problem;
|
|
72
|
+
};
|
|
73
|
+
// Checked before each agent call and after it, failed or not, on the figures its runtime announced.
|
|
74
|
+
const budgeted = {
|
|
75
|
+
async execute(request, record) {
|
|
76
|
+
const refused = overBudget();
|
|
77
|
+
if (refused)
|
|
78
|
+
throw new Error(refused);
|
|
79
|
+
const value = await agent
|
|
80
|
+
.execute(request, record)
|
|
81
|
+
.finally(() => overBudget());
|
|
82
|
+
const exceeded = overBudget();
|
|
83
|
+
if (exceeded)
|
|
84
|
+
throw new Error(exceeded);
|
|
85
|
+
return value;
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
const done = inDirectory(factory.dir, () => executeWorkflow(workflow, state, { agent: budgeted, dir: factory.dir }, store, {
|
|
89
|
+
onEvent: hooks.onEvent,
|
|
90
|
+
signal: controller.signal,
|
|
91
|
+
}));
|
|
92
|
+
return {
|
|
93
|
+
state,
|
|
94
|
+
runtimes: Object.values(runtimes),
|
|
95
|
+
done,
|
|
96
|
+
stop: (reason) => controller.abort(reason),
|
|
97
|
+
kill: killProcessGroups,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/app/run.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { RunEvent, RunState } from "../engine/types.js";
|
|
2
|
+
import type { Factory } from "../system/factory.js";
|
|
3
|
+
export { UnknownRunError } from "../system/store.js";
|
|
4
|
+
/** A run, or why it cannot be read. */
|
|
5
|
+
export type RunEntry = {
|
|
6
|
+
id: string;
|
|
7
|
+
state: RunState;
|
|
8
|
+
} | {
|
|
9
|
+
id: string;
|
|
10
|
+
error: string;
|
|
11
|
+
};
|
|
12
|
+
/** The repository's runs in creation order; an unreadable run comes with its error. */
|
|
13
|
+
export declare function listRuns(factory: Factory): Promise<RunEntry[]>;
|
|
14
|
+
/** A run as a list shows it; a cost no agent announced is unknown (`null`), not zero. */
|
|
15
|
+
export declare function summarizeRun(state: RunState): {
|
|
16
|
+
id: string;
|
|
17
|
+
workflow: string;
|
|
18
|
+
status: "completed" | "failed" | "running";
|
|
19
|
+
stage: string;
|
|
20
|
+
durationMs: number | null;
|
|
21
|
+
costUsd: number | null;
|
|
22
|
+
/** Some agent call announced no usage. */
|
|
23
|
+
partial: boolean;
|
|
24
|
+
updatedAt: string;
|
|
25
|
+
};
|
|
26
|
+
/** One run's state; an unknown ID is refused. */
|
|
27
|
+
export declare function loadRun(factory: Factory, id: string): Promise<RunState>;
|
|
28
|
+
/** One run's stage and journal events, in order. */
|
|
29
|
+
export declare function loadRunEvents(factory: Factory, id: string): Promise<RunEvent[]>;
|
|
30
|
+
/**
|
|
31
|
+
* A run's result lines: its workflow's rendering, or its validated data when the
|
|
32
|
+
* workflow left `factory.ts`. A failing rendering becomes the explanation.
|
|
33
|
+
*/
|
|
34
|
+
export declare function presentResult(factory: Factory, state: Readonly<RunState>): string[];
|
|
35
|
+
/** Each correction route's maximum, keyed `<stage>.<outcome>` like a run's counters. */
|
|
36
|
+
export declare function retryMaximums(factory: Factory, workflow: string): Map<string, number>;
|
package/dist/app/runs.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { elapsedMs } from "../engine/metrics.js";
|
|
2
|
+
import { presentData } from "../engine/workflow.js";
|
|
3
|
+
import { FileRunStore } from "../system/store.js";
|
|
4
|
+
export { UnknownRunError } from "../system/store.js";
|
|
5
|
+
const message = (error) => error instanceof Error ? error.message : String(error);
|
|
6
|
+
/** The repository's runs in creation order; an unreadable run comes with its error. */
|
|
7
|
+
export async function listRuns(factory) {
|
|
8
|
+
const entries = [];
|
|
9
|
+
for (const id of await FileRunStore.list(factory.runsDir))
|
|
10
|
+
try {
|
|
11
|
+
entries.push({
|
|
12
|
+
id,
|
|
13
|
+
state: await new FileRunStore(factory.runsDir, id).load(),
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
catch (error) {
|
|
17
|
+
entries.push({ id, error: message(error) });
|
|
18
|
+
}
|
|
19
|
+
return entries;
|
|
20
|
+
}
|
|
21
|
+
/** A run as a list shows it; a cost no agent announced is unknown (`null`), not zero. */
|
|
22
|
+
export function summarizeRun(state) {
|
|
23
|
+
const usage = state.metrics?.usage;
|
|
24
|
+
return {
|
|
25
|
+
id: state.id,
|
|
26
|
+
workflow: state.workflow,
|
|
27
|
+
status: state.status,
|
|
28
|
+
stage: state.stage,
|
|
29
|
+
durationMs: elapsedMs(state),
|
|
30
|
+
costUsd: usage?.responses ? usage.costUsd : null,
|
|
31
|
+
/** Some agent call announced no usage. */
|
|
32
|
+
partial: Boolean(usage?.missing),
|
|
33
|
+
updatedAt: state.updatedAt,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/** One run's state; an unknown ID is refused. */
|
|
37
|
+
export async function loadRun(factory, id) {
|
|
38
|
+
return new FileRunStore(factory.runsDir, id).load();
|
|
39
|
+
}
|
|
40
|
+
/** One run's stage and journal events, in order. */
|
|
41
|
+
export async function loadRunEvents(factory, id) {
|
|
42
|
+
return new FileRunStore(factory.runsDir, id).events();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A run's result lines: its workflow's rendering, or its validated data when the
|
|
46
|
+
* workflow left `factory.ts`. A failing rendering becomes the explanation.
|
|
47
|
+
*/
|
|
48
|
+
export function presentResult(factory, state) {
|
|
49
|
+
try {
|
|
50
|
+
const workflow = factory.workflows.get(state.workflow);
|
|
51
|
+
return workflow ? workflow.presentResult(state) : presentData(state);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
return [`Result unavailable: ${message(error)}`];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Each correction route's maximum, keyed `<stage>.<outcome>` like a run's counters. */
|
|
58
|
+
export function retryMaximums(factory, workflow) {
|
|
59
|
+
const found = new Map();
|
|
60
|
+
for (const [name, node] of Object.entries(factory.workflows.get(workflow)?.nodes ?? {}))
|
|
61
|
+
for (const [outcome, edge] of Object.entries(node.on))
|
|
62
|
+
if (edge && typeof edge === "object")
|
|
63
|
+
found.set(`${name}.${outcome}`, edge.max);
|
|
64
|
+
return found;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=runs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/app/runs.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The command run by the core installed in `.factory/`: the global `factory`
|
|
3
|
+
* calls it with the arguments it received and the `.factory/` it found. This
|
|
4
|
+
* contract only changes with a major version.
|
|
5
|
+
*/
|
|
6
|
+
export declare function main(argv: string[], directory: string): Promise<number>;
|