@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
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* The dashboard's pure logic: a run's events grouped into one block per stage
|
|
4
|
+
* execution, and the formats of durations and costs. Served as is to the browser.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** @typedef {import("../../engine/types.js").RunEvent} RunEvent */
|
|
8
|
+
/** @typedef {import("../../engine/types.js").AgentActivity} AgentActivity */
|
|
9
|
+
/** @typedef {import("../../engine/metrics.js").TokenCounts} TokenCounts */
|
|
10
|
+
/** @typedef {import("../../engine/metrics.js").AgentUsage} AgentUsage */
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @typedef {object} Call
|
|
14
|
+
* @property {string} [runtime]
|
|
15
|
+
* @property {string} [model]
|
|
16
|
+
* @property {string} [session]
|
|
17
|
+
* @property {"running" | "completed" | "failed"} status
|
|
18
|
+
* @property {AgentUsage} [usage] As the runtime announced it at the end of the call.
|
|
19
|
+
* @property {"call" | "session"} [scope] Whether `usage` is the call's own or its session's total.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @typedef {object} Tool
|
|
24
|
+
* @property {string} id
|
|
25
|
+
* @property {string} name
|
|
26
|
+
* @property {"running" | "completed" | "failed"} status
|
|
27
|
+
* @property {number} [durationMs]
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @typedef {object} Block
|
|
32
|
+
* @property {string} stage
|
|
33
|
+
* @property {string} kind
|
|
34
|
+
* @property {"running" | "passed" | "failed" | "completed"} status
|
|
35
|
+
* @property {string} startedAt
|
|
36
|
+
* @property {number | null} durationMs
|
|
37
|
+
* @property {{ verdict: string; reason: string; destination: string | null }} [decision]
|
|
38
|
+
* @property {{ key: string; count: number; max: number }} [retry]
|
|
39
|
+
* @property {string} [detail]
|
|
40
|
+
* @property {Call} [call]
|
|
41
|
+
* @property {Tool[]} tools
|
|
42
|
+
* @property {TokenCounts | null} tokens The call's responses so far.
|
|
43
|
+
* @property {boolean} provisional Whether `tokens` includes figures replaced at the end of the call.
|
|
44
|
+
* @property {string[]} warnings
|
|
45
|
+
* @property {{ at: string; message: string }[]} journal
|
|
46
|
+
*/
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} stage
|
|
50
|
+
* @param {string} kind
|
|
51
|
+
* @param {string} at
|
|
52
|
+
* @returns {Block}
|
|
53
|
+
*/
|
|
54
|
+
const block = (stage, kind, at) => ({
|
|
55
|
+
stage,
|
|
56
|
+
kind,
|
|
57
|
+
status: "running",
|
|
58
|
+
startedAt: at,
|
|
59
|
+
durationMs: null,
|
|
60
|
+
tools: [],
|
|
61
|
+
tokens: null,
|
|
62
|
+
provisional: false,
|
|
63
|
+
warnings: [],
|
|
64
|
+
journal: [],
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @param {Block} target
|
|
69
|
+
* @param {AgentActivity} activity
|
|
70
|
+
*/
|
|
71
|
+
function apply(target, activity) {
|
|
72
|
+
switch (activity.kind) {
|
|
73
|
+
case "call":
|
|
74
|
+
if (activity.status === "started") {
|
|
75
|
+
const { runtime, model, session } = activity;
|
|
76
|
+
target.call = { runtime, model, session, status: "running" };
|
|
77
|
+
} else
|
|
78
|
+
target.call = {
|
|
79
|
+
...target.call,
|
|
80
|
+
runtime: activity.runtime,
|
|
81
|
+
status: activity.status,
|
|
82
|
+
usage: activity.usage,
|
|
83
|
+
scope: activity.scope,
|
|
84
|
+
};
|
|
85
|
+
break;
|
|
86
|
+
case "tool": {
|
|
87
|
+
const tool = target.tools.find(({ id }) => id === activity.id);
|
|
88
|
+
const update =
|
|
89
|
+
activity.status === "started"
|
|
90
|
+
? { id: activity.id, name: activity.name, status: "running" }
|
|
91
|
+
: {
|
|
92
|
+
id: activity.id,
|
|
93
|
+
name: activity.name,
|
|
94
|
+
status: activity.status,
|
|
95
|
+
durationMs: activity.durationMs,
|
|
96
|
+
};
|
|
97
|
+
if (tool) Object.assign(tool, update);
|
|
98
|
+
else target.tools.push(/** @type {Tool} */ (update));
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
case "response": {
|
|
102
|
+
const sum = target.tokens ?? {
|
|
103
|
+
input: 0,
|
|
104
|
+
output: 0,
|
|
105
|
+
cacheRead: 0,
|
|
106
|
+
cacheWrite: 0,
|
|
107
|
+
};
|
|
108
|
+
for (const field of /** @type {const} */ ([
|
|
109
|
+
"input",
|
|
110
|
+
"output",
|
|
111
|
+
"cacheRead",
|
|
112
|
+
"cacheWrite",
|
|
113
|
+
]))
|
|
114
|
+
sum[field] += activity.tokens[field];
|
|
115
|
+
target.tokens = sum;
|
|
116
|
+
target.provisional ||= activity.provisional;
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
case "warning":
|
|
120
|
+
target.warnings.push(activity.message);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* One block per stage execution, in order: a stage's journal, agent activity
|
|
126
|
+
* and decision go to its latest block.
|
|
127
|
+
* @param {RunEvent[]} events
|
|
128
|
+
* @returns {Block[]}
|
|
129
|
+
*/
|
|
130
|
+
export function timeline(events) {
|
|
131
|
+
/** @type {Block[]} */
|
|
132
|
+
const blocks = [];
|
|
133
|
+
const latest = (/** @type {string} */ stage) =>
|
|
134
|
+
blocks.findLast((candidate) => candidate.stage === stage);
|
|
135
|
+
for (const event of events) {
|
|
136
|
+
if (event.type === "stage" && event.status === "started") {
|
|
137
|
+
blocks.push(block(event.stage, event.kind, event.at));
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
let target = latest(event.stage);
|
|
141
|
+
if (!target || (event.type === "stage" && target.status !== "running")) {
|
|
142
|
+
// A stage reported without a start, such as one failed by an interruption.
|
|
143
|
+
target = block(event.stage, event.kind, event.at);
|
|
144
|
+
blocks.push(target);
|
|
145
|
+
}
|
|
146
|
+
if (event.type === "stage") {
|
|
147
|
+
target.status = event.status === "started" ? "running" : event.status;
|
|
148
|
+
target.durationMs = Date.parse(event.at) - Date.parse(target.startedAt);
|
|
149
|
+
if (event.decision) target.decision = event.decision;
|
|
150
|
+
if (event.retry) target.retry = event.retry;
|
|
151
|
+
if (event.detail) target.detail = event.detail;
|
|
152
|
+
} else if (event.type === "log")
|
|
153
|
+
target.journal.push({ at: event.at, message: event.message });
|
|
154
|
+
else apply(target, event.activity);
|
|
155
|
+
}
|
|
156
|
+
return blocks;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* @param {number | null} ms
|
|
161
|
+
* @returns {string}
|
|
162
|
+
*/
|
|
163
|
+
export function duration(ms) {
|
|
164
|
+
if (ms === null) return "—";
|
|
165
|
+
const seconds = Math.floor(ms / 1000);
|
|
166
|
+
const hours = Math.floor(seconds / 3600);
|
|
167
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
168
|
+
return `${hours ? `${hours} h ` : ""}${minutes} min ${seconds % 60} s`;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* An estimated cost as the runtimes announced it; unknown without any measure.
|
|
173
|
+
* @param {number | null} usd
|
|
174
|
+
* @param {boolean} partial Some agent call announced no usage.
|
|
175
|
+
* @returns {string}
|
|
176
|
+
*/
|
|
177
|
+
export function cost(usd, partial) {
|
|
178
|
+
const value = usd === null ? "—" : `${usd.toFixed(4)} USD`;
|
|
179
|
+
return partial ? `${value} (partial)` : value;
|
|
180
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@clemrig/factory",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Write agent workflows in TypeScript inside any repository, run them in the terminal and watch them in a local dashboard",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/clementchesneau/clemrig-factory-v2.git"
|
|
9
|
+
},
|
|
10
|
+
"type": "module",
|
|
11
|
+
"bin": {
|
|
12
|
+
"factory": "dist/main.js"
|
|
13
|
+
},
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./cli": {
|
|
19
|
+
"default": "./dist/cli/application.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"templates"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=24"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json && node -e \"require('node:fs').cpSync('src/ui/static','dist/ui/static',{recursive:true})\"",
|
|
34
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.ui.json",
|
|
35
|
+
"lint": "biome lint --error-on-warnings .",
|
|
36
|
+
"format": "biome format .",
|
|
37
|
+
"format:write": "biome format --write .",
|
|
38
|
+
"test": "vitest run",
|
|
39
|
+
"test:coverage": "vitest run --coverage",
|
|
40
|
+
"check": "npm run lint && npm run format && npm run typecheck && npm run test:coverage && npm run build",
|
|
41
|
+
"prepack": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"zod": "4.6.5"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@biomejs/biome": "2.5.15",
|
|
48
|
+
"@types/node": "24.19.1",
|
|
49
|
+
"@vitest/coverage-v8": "5.0.3",
|
|
50
|
+
"tsx": "4.23.15",
|
|
51
|
+
"typescript": "7.0.2",
|
|
52
|
+
"vitest": "5.0.3"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2023",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"allowImportingTsExtensions": true,
|
|
9
|
+
"verbatimModuleSyntax": true,
|
|
10
|
+
"erasableSyntaxOnly": true,
|
|
11
|
+
"skipLibCheck": true
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
agent,
|
|
3
|
+
defineWorkflow,
|
|
4
|
+
END,
|
|
5
|
+
node,
|
|
6
|
+
pick,
|
|
7
|
+
retry,
|
|
8
|
+
runProcess,
|
|
9
|
+
schemaGate,
|
|
10
|
+
step,
|
|
11
|
+
z,
|
|
12
|
+
} from "@clemrig/factory";
|
|
13
|
+
|
|
14
|
+
const answer = z.strictObject({ answer: z.string().min(1) });
|
|
15
|
+
|
|
16
|
+
/** Answers a question about this repository from its recent commits. */
|
|
17
|
+
export default defineWorkflow({
|
|
18
|
+
name: "ask",
|
|
19
|
+
input: z.object({ question: z.string().min(1) }),
|
|
20
|
+
initial: "commits",
|
|
21
|
+
nodes: {
|
|
22
|
+
// CODE: deterministic work in the repository.
|
|
23
|
+
commits: node(
|
|
24
|
+
step(async ({ dir }) => {
|
|
25
|
+
const log = await runProcess(
|
|
26
|
+
["git", "log", "--oneline", "-n", "10"],
|
|
27
|
+
dir,
|
|
28
|
+
30_000,
|
|
29
|
+
);
|
|
30
|
+
if (log.exitCode !== 0)
|
|
31
|
+
throw new Error(`git log failed: ${log.stderr.trim()}`);
|
|
32
|
+
return { commits: log.stdout.trim().split("\n").filter(Boolean) };
|
|
33
|
+
}),
|
|
34
|
+
{ ok: "answer" },
|
|
35
|
+
),
|
|
36
|
+
// AGENT: prompts/answer.md, with the question and the commits as context.
|
|
37
|
+
// Read-only by default: its tools can read the repository, not change it.
|
|
38
|
+
answer: node(
|
|
39
|
+
agent({
|
|
40
|
+
profile: {
|
|
41
|
+
runtime: "claude-code",
|
|
42
|
+
model: "claude-sonnet-5-5",
|
|
43
|
+
thinking: "low",
|
|
44
|
+
tools: ["Read", "Grep", "Glob"],
|
|
45
|
+
},
|
|
46
|
+
schema: answer,
|
|
47
|
+
context: pick("input", "commits"),
|
|
48
|
+
}),
|
|
49
|
+
{ ok: "check" },
|
|
50
|
+
),
|
|
51
|
+
// GATE: an invalid answer goes back to the agent with feedback, at most 3 times.
|
|
52
|
+
check: node(schemaGate({ schema: answer, into: "answer" }), {
|
|
53
|
+
ok: END,
|
|
54
|
+
invalid: retry("answer", 3),
|
|
55
|
+
}),
|
|
56
|
+
},
|
|
57
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
You answer the operator's question about this repository.
|
|
2
|
+
|
|
3
|
+
- The context holds the question (`input.question`) and the ten most recent commits (`commits`).
|
|
4
|
+
- Read files with your tools when the commits are not enough.
|
|
5
|
+
- Answer in a few sentences, in the language of `input.question`, whatever language the rest of the context suggests.
|
|
6
|
+
- When the context holds `feedback`, your previous answer was refused: correct what it describes.
|
|
7
|
+
- Return `{"answer": "…"}`.
|