@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,235 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { runtimeLabels } from "../../engine/profiles.js";
|
|
3
|
+
import { parseEvent, } from "./adapter.js";
|
|
4
|
+
import { agentSessionName } from "./protocol.js";
|
|
5
|
+
const label = runtimeLabels.pi;
|
|
6
|
+
const anyEvent = z.looseObject({ type: z.string() });
|
|
7
|
+
const message = z.looseObject({
|
|
8
|
+
role: z.string(),
|
|
9
|
+
// Text content is one text part; a text part holds its text.
|
|
10
|
+
content: z
|
|
11
|
+
.preprocess((content) => typeof content === "string"
|
|
12
|
+
? [{ type: "text", text: content }]
|
|
13
|
+
: content, z.array(z
|
|
14
|
+
.looseObject({ type: z.string(), text: z.unknown().optional() })
|
|
15
|
+
.superRefine((part, context) => {
|
|
16
|
+
if (part.type === "text" && typeof part.text !== "string")
|
|
17
|
+
context.addIssue({
|
|
18
|
+
code: "custom",
|
|
19
|
+
path: ["text"],
|
|
20
|
+
message: "expected a string",
|
|
21
|
+
});
|
|
22
|
+
})))
|
|
23
|
+
.optional(),
|
|
24
|
+
stopReason: z.string().optional(),
|
|
25
|
+
/** Only explains a failure: its shape never hides it. */
|
|
26
|
+
errorMessage: z.unknown().optional(),
|
|
27
|
+
usage: z.unknown().optional(),
|
|
28
|
+
});
|
|
29
|
+
const messageEnd = z.looseObject({ message });
|
|
30
|
+
const compactionEnd = z.looseObject({
|
|
31
|
+
aborted: z.boolean().optional(),
|
|
32
|
+
result: z
|
|
33
|
+
.looseObject({ usage: z.unknown().optional() })
|
|
34
|
+
.nullable()
|
|
35
|
+
.optional(),
|
|
36
|
+
});
|
|
37
|
+
const toolStart = z.looseObject({
|
|
38
|
+
toolCallId: z.string(),
|
|
39
|
+
toolName: z.string(),
|
|
40
|
+
});
|
|
41
|
+
const toolEnd = toolStart.extend({
|
|
42
|
+
isError: z.boolean(),
|
|
43
|
+
result: z
|
|
44
|
+
.looseObject({ usage: z.unknown().optional() })
|
|
45
|
+
.nullable()
|
|
46
|
+
.optional(),
|
|
47
|
+
});
|
|
48
|
+
const agentEnd = z.looseObject({ messages: z.array(message) });
|
|
49
|
+
const agentSettled = z.looseObject({ aborted: z.boolean() });
|
|
50
|
+
/** Pi's usage of one response; measured on a best-effort basis. */
|
|
51
|
+
const usage = z.looseObject({
|
|
52
|
+
input: z.number().nonnegative(),
|
|
53
|
+
output: z.number().nonnegative(),
|
|
54
|
+
cacheRead: z.number().nonnegative(),
|
|
55
|
+
cacheWrite: z.number().nonnegative(),
|
|
56
|
+
cost: z.looseObject({ total: z.number().nonnegative() }),
|
|
57
|
+
});
|
|
58
|
+
function measure(value) {
|
|
59
|
+
const parsed = usage.safeParse(value);
|
|
60
|
+
const measured = parsed.success
|
|
61
|
+
? {
|
|
62
|
+
input: parsed.data.input,
|
|
63
|
+
output: parsed.data.output,
|
|
64
|
+
cacheRead: parsed.data.cacheRead,
|
|
65
|
+
cacheWrite: parsed.data.cacheWrite,
|
|
66
|
+
costUsd: parsed.data.cost.total,
|
|
67
|
+
}
|
|
68
|
+
: undefined;
|
|
69
|
+
return { type: "usage", scope: "response", usage: measured };
|
|
70
|
+
}
|
|
71
|
+
const textOf = (response) => (response.content ?? [])
|
|
72
|
+
.map((part) => (part.type === "text" ? String(part.text) : ""))
|
|
73
|
+
.join("");
|
|
74
|
+
/** The final response once Pi has settled: its text, or why there is none. */
|
|
75
|
+
function finalEvent(response) {
|
|
76
|
+
if (!response)
|
|
77
|
+
return {
|
|
78
|
+
type: "failure",
|
|
79
|
+
message: `${label} completed without an assistant response`,
|
|
80
|
+
};
|
|
81
|
+
if (response.stopReason === "error" || response.stopReason === "aborted")
|
|
82
|
+
return {
|
|
83
|
+
type: "failure",
|
|
84
|
+
message: `${label} provider ${response.stopReason}: ${typeof response.errorMessage === "string" ? response.errorMessage : "no detail provided"}`,
|
|
85
|
+
};
|
|
86
|
+
if (response.stopReason !== "stop")
|
|
87
|
+
return {
|
|
88
|
+
type: "failure",
|
|
89
|
+
message: `${label} final response ended with stop reason ${response.stopReason ?? "missing"}`,
|
|
90
|
+
};
|
|
91
|
+
return { type: "result", text: textOf(response) };
|
|
92
|
+
}
|
|
93
|
+
/** `pi auth check --json`: whether a provider's credentials are ready, never the credentials. */
|
|
94
|
+
const authCheck = z.looseObject({
|
|
95
|
+
status: z.string(),
|
|
96
|
+
provider: z.string().optional(),
|
|
97
|
+
authType: z.string().optional(),
|
|
98
|
+
reason: z.string().optional(),
|
|
99
|
+
});
|
|
100
|
+
const authTypes = {
|
|
101
|
+
oauth: "OAuth",
|
|
102
|
+
api_key: "API key",
|
|
103
|
+
};
|
|
104
|
+
function readAuthCheck(provider, stdout) {
|
|
105
|
+
let check;
|
|
106
|
+
try {
|
|
107
|
+
check = authCheck.parse(JSON.parse(stdout));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return {
|
|
111
|
+
connected: false,
|
|
112
|
+
detail: `${label} connection check is unreadable`,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
return check.status === "ready"
|
|
116
|
+
? {
|
|
117
|
+
connected: true,
|
|
118
|
+
detail: `${provider} ${authTypes[check.authType ?? ""] ?? check.authType ?? "credentials"}`,
|
|
119
|
+
}
|
|
120
|
+
: {
|
|
121
|
+
connected: false,
|
|
122
|
+
detail: `${label} is not connected to ${provider}: ${check.reason ?? check.status}`,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
function profileArgs(profile, systemPrompt) {
|
|
126
|
+
return [
|
|
127
|
+
"--provider",
|
|
128
|
+
profile.provider,
|
|
129
|
+
"--model",
|
|
130
|
+
profile.model,
|
|
131
|
+
"--thinking",
|
|
132
|
+
profile.thinking,
|
|
133
|
+
"--append-system-prompt",
|
|
134
|
+
systemPrompt,
|
|
135
|
+
// Nothing from the machine: only the profile's tools, skills and extensions.
|
|
136
|
+
"--no-extensions",
|
|
137
|
+
"--no-skills",
|
|
138
|
+
"--no-context-files",
|
|
139
|
+
"--no-mcp",
|
|
140
|
+
...(profile.tools.length
|
|
141
|
+
? ["--tools", profile.tools.join(",")]
|
|
142
|
+
: ["--no-tools"]),
|
|
143
|
+
...profile.skills.flatMap((skill) => ["--skill", skill]),
|
|
144
|
+
...profile.extensions.flatMap((extension) => ["--extension", extension]),
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Pi in JSON mode (its documented event stream), its session named after the run.
|
|
149
|
+
* Every response is measured, retried ones included; the answer is the last
|
|
150
|
+
* response once Pi has settled, after its own retries.
|
|
151
|
+
*/
|
|
152
|
+
export const pi = {
|
|
153
|
+
name: "pi",
|
|
154
|
+
label,
|
|
155
|
+
executable: "pi",
|
|
156
|
+
minimumVersion: "1.1.0",
|
|
157
|
+
// --no-refresh: an expired token is reported, never refreshed.
|
|
158
|
+
connection: ({ provider }) => ({
|
|
159
|
+
args: ["auth", "check", "--provider", provider, "--json", "--no-refresh"],
|
|
160
|
+
read: (stdout) => readAuthCheck(provider, stdout),
|
|
161
|
+
}),
|
|
162
|
+
model: (profile) => `${profile.provider}/${profile.model}`,
|
|
163
|
+
async start({ profile, systemPrompt, session, sessions }) {
|
|
164
|
+
let final;
|
|
165
|
+
return {
|
|
166
|
+
args: [
|
|
167
|
+
"--mode",
|
|
168
|
+
"json",
|
|
169
|
+
"--print",
|
|
170
|
+
...profileArgs(profile, systemPrompt),
|
|
171
|
+
"--session-id",
|
|
172
|
+
agentSessionName(sessions.runId, session),
|
|
173
|
+
"--session-dir",
|
|
174
|
+
sessions.directory,
|
|
175
|
+
"--name",
|
|
176
|
+
`${sessions.runId}:${session}`,
|
|
177
|
+
"--",
|
|
178
|
+
"Read and obey the complete factory request supplied through standard input. Return only the required JSON.",
|
|
179
|
+
],
|
|
180
|
+
read(raw) {
|
|
181
|
+
const { type } = parseEvent(label, anyEvent, raw);
|
|
182
|
+
switch (type) {
|
|
183
|
+
case "message_end": {
|
|
184
|
+
const ended = parseEvent(label, messageEnd, raw).message;
|
|
185
|
+
return ended.role === "assistant" ? [measure(ended.usage)] : [];
|
|
186
|
+
}
|
|
187
|
+
case "compaction_end": {
|
|
188
|
+
const compaction = parseEvent(label, compactionEnd, raw);
|
|
189
|
+
return compaction.aborted
|
|
190
|
+
? []
|
|
191
|
+
: [measure(compaction.result?.usage)];
|
|
192
|
+
}
|
|
193
|
+
case "tool_execution_start": {
|
|
194
|
+
const tool = parseEvent(label, toolStart, raw);
|
|
195
|
+
return [
|
|
196
|
+
{
|
|
197
|
+
type: "tool",
|
|
198
|
+
id: tool.toolCallId,
|
|
199
|
+
name: tool.toolName,
|
|
200
|
+
status: "started",
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
case "tool_execution_end": {
|
|
205
|
+
const tool = parseEvent(label, toolEnd, raw);
|
|
206
|
+
// A tool that called a model reports that usage with its result.
|
|
207
|
+
return [
|
|
208
|
+
...(tool.result?.usage === undefined
|
|
209
|
+
? []
|
|
210
|
+
: [measure(tool.result.usage)]),
|
|
211
|
+
{
|
|
212
|
+
type: "tool",
|
|
213
|
+
id: tool.toolCallId,
|
|
214
|
+
name: tool.toolName,
|
|
215
|
+
status: tool.isError ? "failed" : "completed",
|
|
216
|
+
},
|
|
217
|
+
];
|
|
218
|
+
}
|
|
219
|
+
case "agent_end": {
|
|
220
|
+
// After its own retries, Pi settles on the last run's response.
|
|
221
|
+
final = parseEvent(label, agentEnd, raw).messages.findLast(({ role }) => role === "assistant");
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
case "agent_settled":
|
|
225
|
+
return parseEvent(label, agentSettled, raw).aborted
|
|
226
|
+
? [{ type: "failure", message: `${label} run was aborted` }]
|
|
227
|
+
: [finalEvent(final)];
|
|
228
|
+
default:
|
|
229
|
+
return [];
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
};
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
//# sourceMappingURL=pi.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pi.js","sourceRoot":"","sources":["../../../src/system/runtimes/pi.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/** The complete request every runtime receives through standard input. */
|
|
2
|
+
export declare function agentPrompt(request: {
|
|
3
|
+
context: unknown;
|
|
4
|
+
schema: Record<string, unknown>;
|
|
5
|
+
}): string;
|
|
6
|
+
/** Accepts exact JSON or a single fenced JSON block, never prose surrounding a result. */
|
|
7
|
+
export declare function parseAgentJson(text: string): unknown;
|
|
8
|
+
/** One session per run and session name: `<run>.<session>`. */
|
|
9
|
+
export declare function agentSessionName(runId: string, session: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Output protocol shared by every runtime, sent before the factory request. */
|
|
2
|
+
const outputProtocol = `# Response format
|
|
3
|
+
|
|
4
|
+
- Return exactly one JSON value matching the requested schema.
|
|
5
|
+
- Do not use Markdown or add prose.`;
|
|
6
|
+
/** The complete request every runtime receives through standard input. */
|
|
7
|
+
export function agentPrompt(request) {
|
|
8
|
+
return [
|
|
9
|
+
outputProtocol,
|
|
10
|
+
"Factory request:",
|
|
11
|
+
JSON.stringify({ context: request.context, outputSchema: request.schema }),
|
|
12
|
+
].join("\n\n");
|
|
13
|
+
}
|
|
14
|
+
/** Accepts exact JSON or a single fenced JSON block, never prose surrounding a result. */
|
|
15
|
+
export function parseAgentJson(text) {
|
|
16
|
+
const trimmed = text.trim();
|
|
17
|
+
const fenced = /^```(?:json)?\s*\n([\s\S]*?)\n```$/i.exec(trimmed);
|
|
18
|
+
const candidate = fenced ? (fenced[1] ?? "").trim() : trimmed;
|
|
19
|
+
if (!candidate)
|
|
20
|
+
throw new Error("Agent returned an empty final response");
|
|
21
|
+
try {
|
|
22
|
+
return JSON.parse(candidate);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw new Error(`Agent final response is not a single JSON value: ${error instanceof Error ? error.message : String(error)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/** A session name segment: never contains the `.` separator, starts and ends alphanumeric. */
|
|
29
|
+
const segment = /^[a-zA-Z0-9](?:[a-zA-Z0-9_-]*[a-zA-Z0-9])?$/;
|
|
30
|
+
/** One session per run and session name: `<run>.<session>`. */
|
|
31
|
+
export function agentSessionName(runId, session) {
|
|
32
|
+
if (!segment.test(runId))
|
|
33
|
+
throw new Error("Invalid run ID for agent session");
|
|
34
|
+
if (typeof session !== "string" || !segment.test(session))
|
|
35
|
+
throw new Error(`Invalid agent session name: ${String(session)}`);
|
|
36
|
+
return `${runId}.${session}`;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../../src/system/runtimes/protocol.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,cAAc,GAAG;;;oCAGa,CAAC;AAErC,0EAA0E;AAC1E,MAAM,UAAU,WAAW,CAAC,OAG3B;IACC,OAAO;QACL,cAAc;QACd,kBAAkB;QAClB,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,YAAY,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC;KAC3E,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AACjB,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,qCAAqC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACnE,MAAM,SAAS,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAC9D,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;IAC1E,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAC/B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CACb,oDAAoD,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAC7G,CAAC;IACJ,CAAC;AACH,CAAC;AAED,8FAA8F;AAC9F,MAAM,OAAO,GAAG,6CAA6C,CAAC;AAE9D,+DAA+D;AAC/D,MAAM,UAAU,gBAAgB,CAAC,KAAa,EAAE,OAAe;IAC7D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IAC9E,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC;QACvD,MAAM,IAAI,KAAK,CAAC,+BAA+B,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACpE,OAAO,GAAG,KAAK,IAAI,OAAO,EAAE,CAAC;AAC/B,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../../src/system/runtimes/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAE7B,mDAAmD;AACnD,MAAM,CAAC,MAAM,QAAQ,GAAG,EAAE,aAAa,EAAE,UAAU,EAAE,EAAE,EAAW,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RunEvent, RunState, RunStore } from "../engine/types.js";
|
|
2
|
+
/** No run has this ID, or none can: unlike a read error, asking again will not find it. */
|
|
3
|
+
export declare class UnknownRunError extends Error {
|
|
4
|
+
constructor(message: string);
|
|
5
|
+
}
|
|
6
|
+
/** A new run ID: its creation time, then a random suffix, so that IDs sort in creation order. */
|
|
7
|
+
export declare function newRunId(): string;
|
|
8
|
+
/** One run's directory: `state.json` and its `events.jsonl` journal. */
|
|
9
|
+
export declare class FileRunStore implements RunStore {
|
|
10
|
+
readonly directory: string;
|
|
11
|
+
readonly runId: string;
|
|
12
|
+
constructor(runsDir: string, runId: string);
|
|
13
|
+
/** Run IDs in order: they start with their creation time. */
|
|
14
|
+
static list(runsDir: string): Promise<string[]>;
|
|
15
|
+
save(state: RunState): Promise<void>;
|
|
16
|
+
load(): Promise<RunState>;
|
|
17
|
+
event(event: RunEvent): Promise<void>;
|
|
18
|
+
/** Recorded events; a last line still being written is ignored. */
|
|
19
|
+
events(): Promise<RunEvent[]>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { appendFile, mkdir, readdir, readFile, rename, rm, writeFile, } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
const isRecord = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
function isRetryCounts(value) {
|
|
6
|
+
return (isRecord(value) &&
|
|
7
|
+
Object.values(value).every((count) => Number.isSafeInteger(count) && count >= 1));
|
|
8
|
+
}
|
|
9
|
+
const finiteNonnegative = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0;
|
|
10
|
+
const isUsage = (value) => isRecord(value) &&
|
|
11
|
+
["input", "output", "cacheRead", "cacheWrite", "costUsd"].every((key) => finiteNonnegative(value[key]));
|
|
12
|
+
function isRunMetrics(value) {
|
|
13
|
+
if (!isRecord(value) || !isUsage(value.usage))
|
|
14
|
+
return false;
|
|
15
|
+
const counts = value.usage;
|
|
16
|
+
return (["responses", "missing"].every((key) => Number.isSafeInteger(counts[key]) && finiteNonnegative(counts[key])) &&
|
|
17
|
+
(value.sessions === undefined ||
|
|
18
|
+
(isRecord(value.sessions) &&
|
|
19
|
+
Object.values(value.sessions).every(isUsage))));
|
|
20
|
+
}
|
|
21
|
+
/** Each runtime's version and connection, as text. */
|
|
22
|
+
const isRuntimes = (value) => isRecord(value) &&
|
|
23
|
+
Object.values(value).every((detail) => typeof detail === "string");
|
|
24
|
+
/** No run has this ID, or none can: unlike a read error, asking again will not find it. */
|
|
25
|
+
export class UnknownRunError extends Error {
|
|
26
|
+
constructor(message) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "UnknownRunError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/** A new run ID: its creation time, then a random suffix, so that IDs sort in creation order. */
|
|
32
|
+
export function newRunId() {
|
|
33
|
+
return `${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
34
|
+
}
|
|
35
|
+
/** One run's directory: `state.json` and its `events.jsonl` journal. */
|
|
36
|
+
export class FileRunStore {
|
|
37
|
+
directory;
|
|
38
|
+
runId;
|
|
39
|
+
constructor(runsDir, runId) {
|
|
40
|
+
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]{0,127}$/.test(runId))
|
|
41
|
+
throw new UnknownRunError("Invalid run ID");
|
|
42
|
+
this.runId = runId;
|
|
43
|
+
this.directory = join(runsDir, runId);
|
|
44
|
+
}
|
|
45
|
+
/** Run IDs in order: they start with their creation time. */
|
|
46
|
+
static async list(runsDir) {
|
|
47
|
+
try {
|
|
48
|
+
return (await readdir(runsDir, { withFileTypes: true }))
|
|
49
|
+
.filter((entry) => entry.isDirectory())
|
|
50
|
+
.map((entry) => entry.name)
|
|
51
|
+
.sort();
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error.code === "ENOENT")
|
|
55
|
+
return [];
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async save(state) {
|
|
60
|
+
if (state.id !== this.runId)
|
|
61
|
+
throw new Error("Run state ID does not match store");
|
|
62
|
+
await mkdir(this.directory, { recursive: true });
|
|
63
|
+
const temporary = join(this.directory, `state-${randomUUID()}.tmp`);
|
|
64
|
+
try {
|
|
65
|
+
await writeFile(temporary, `${JSON.stringify(state, null, 2)}\n`, {
|
|
66
|
+
mode: 0o600,
|
|
67
|
+
});
|
|
68
|
+
await rename(temporary, join(this.directory, "state.json"));
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
await rm(temporary, { force: true });
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
async load() {
|
|
75
|
+
let text;
|
|
76
|
+
try {
|
|
77
|
+
text = await readFile(join(this.directory, "state.json"), "utf8");
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (error.code === "ENOENT")
|
|
81
|
+
throw new UnknownRunError(`Unknown run ${this.runId}`);
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
const state = JSON.parse(text);
|
|
85
|
+
if (state?.version !== 4 ||
|
|
86
|
+
state.id !== this.runId ||
|
|
87
|
+
typeof state.workflow !== "string" ||
|
|
88
|
+
!state.workflow.trim() ||
|
|
89
|
+
!["running", "completed", "failed"].includes(state.status) ||
|
|
90
|
+
typeof state.stage !== "string" ||
|
|
91
|
+
typeof state.createdAt !== "string" ||
|
|
92
|
+
typeof state.updatedAt !== "string" ||
|
|
93
|
+
!isRecord(state.input) ||
|
|
94
|
+
!isRecord(state.data) ||
|
|
95
|
+
(state.retries !== undefined && !isRetryCounts(state.retries)) ||
|
|
96
|
+
(state.metrics !== undefined && !isRunMetrics(state.metrics)) ||
|
|
97
|
+
(state.runtimes !== undefined && !isRuntimes(state.runtimes)))
|
|
98
|
+
throw new Error("Unsupported or mismatched persisted run; runs created by earlier factory versions are not readable");
|
|
99
|
+
return state;
|
|
100
|
+
}
|
|
101
|
+
async event(event) {
|
|
102
|
+
if (event.runId !== this.runId)
|
|
103
|
+
throw new Error("Event run ID does not match store");
|
|
104
|
+
await mkdir(this.directory, { recursive: true });
|
|
105
|
+
await appendFile(join(this.directory, "events.jsonl"), `${JSON.stringify(event)}\n`, { mode: 0o600 });
|
|
106
|
+
}
|
|
107
|
+
/** Recorded events; a last line still being written is ignored. */
|
|
108
|
+
async events() {
|
|
109
|
+
let text;
|
|
110
|
+
try {
|
|
111
|
+
text = await readFile(join(this.directory, "events.jsonl"), "utf8");
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error.code === "ENOENT")
|
|
115
|
+
return [];
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
const lines = text.split("\n");
|
|
119
|
+
// Only lines ended by a newline are complete.
|
|
120
|
+
lines.pop();
|
|
121
|
+
return lines
|
|
122
|
+
.filter((line) => line.trim())
|
|
123
|
+
.map((line) => JSON.parse(line));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/system/store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates `.factory/` in `cwd` from the package's templates, its `package.json`
|
|
3
|
+
* pinned to this core version. An existing `.factory/` is refused untouched.
|
|
4
|
+
*/
|
|
5
|
+
export declare function createFromTemplates(cwd: string): Promise<{
|
|
6
|
+
factoryDir: string;
|
|
7
|
+
version: string;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { cp, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
/** Both `src/system/` and `dist/system/` sit two levels below the package root. */
|
|
4
|
+
const root = new URL("../../", import.meta.url);
|
|
5
|
+
/**
|
|
6
|
+
* Creates `.factory/` in `cwd` from the package's templates, its `package.json`
|
|
7
|
+
* pinned to this core version. An existing `.factory/` is refused untouched.
|
|
8
|
+
*/
|
|
9
|
+
export async function createFromTemplates(cwd) {
|
|
10
|
+
const factoryDir = join(cwd, ".factory");
|
|
11
|
+
try {
|
|
12
|
+
await mkdir(factoryDir);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error.code === "EEXIST")
|
|
16
|
+
throw new Error(`${factoryDir} already exists`);
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
const { version } = JSON.parse(await readFile(new URL("package.json", root), "utf8"));
|
|
20
|
+
await cp(new URL("templates/init/", root), factoryDir, { recursive: true });
|
|
21
|
+
// npm pack drops files named .gitignore: the template has no dot.
|
|
22
|
+
await rename(join(factoryDir, "gitignore"), join(factoryDir, ".gitignore"));
|
|
23
|
+
await writeFile(join(factoryDir, "package.json"), `${JSON.stringify({
|
|
24
|
+
private: true,
|
|
25
|
+
type: "module",
|
|
26
|
+
dependencies: { "@clemrig/factory": version },
|
|
27
|
+
}, null, 2)}\n`);
|
|
28
|
+
return { factoryDir, version };
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=templates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates.js","sourceRoot":"","sources":["../../src/system/templates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,mFAAmF;AACnF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAEhD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,GAAW;IAEX,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;IACzC,IAAI,CAAC;QACH,MAAM,KAAK,CAAC,UAAU,CAAC,CAAC;IAC1B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YACpD,MAAM,IAAI,KAAK,CAAC,GAAG,UAAU,iBAAiB,CAAC,CAAC;QAClD,MAAM,KAAK,CAAC;IACd,CAAC;IACD,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAC5B,MAAM,QAAQ,CAAC,IAAI,GAAG,CAAC,cAAc,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAC/B,CAAC;IACzB,MAAM,EAAE,CAAC,IAAI,GAAG,CAAC,iBAAiB,EAAE,IAAI,CAAC,EAAE,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5E,kEAAkE;IAClE,MAAM,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC,CAAC;IAC5E,MAAM,SAAS,CACb,IAAI,CAAC,UAAU,EAAE,cAAc,CAAC,EAChC,GAAG,IAAI,CAAC,SAAS,CACf;QACE,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,QAAQ;QACd,YAAY,EAAE,EAAE,kBAAkB,EAAE,OAAO,EAAE;KAC9C,EACD,IAAI,EACJ,CAAC,CACF,IAAI,CACN,CAAC;IACF,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC;AACjC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Server } from "node:http";
|
|
2
|
+
import type { Factory } from "../app/factory.js";
|
|
3
|
+
/** A read-only view of the repository's runs on 127.0.0.1, on `port` or one chosen by the system. */
|
|
4
|
+
export declare function startDashboard(factory: Factory, listen?: number): Promise<{
|
|
5
|
+
url: string;
|
|
6
|
+
server: Server;
|
|
7
|
+
close(): Promise<void>;
|
|
8
|
+
}>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import { createServer, } from "node:http";
|
|
3
|
+
import { basename } from "node:path";
|
|
4
|
+
import { listRuns, loadRun, loadRunEvents, presentResult, retryMaximums, summarizeRun, UnknownRunError, } from "../app/runs.js";
|
|
5
|
+
/** The runs, newest first; an unreadable run with its error. */
|
|
6
|
+
async function runs(factory) {
|
|
7
|
+
return {
|
|
8
|
+
repository: basename(factory.dir),
|
|
9
|
+
runs: (await listRuns(factory))
|
|
10
|
+
.reverse()
|
|
11
|
+
.map((entry) => ("state" in entry ? summarizeRun(entry.state) : entry)),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/** A run with its events from the `after`-th: the page asks only for what it has not received. */
|
|
15
|
+
async function detail(factory, id, after) {
|
|
16
|
+
const state = await loadRun(factory, id);
|
|
17
|
+
const events = await loadRunEvents(factory, id);
|
|
18
|
+
const max = retryMaximums(factory, state.workflow);
|
|
19
|
+
return {
|
|
20
|
+
run: summarizeRun(state),
|
|
21
|
+
runtimes: state.runtimes ?? {},
|
|
22
|
+
error: state.error ?? null,
|
|
23
|
+
events: events.slice(after),
|
|
24
|
+
next: events.length,
|
|
25
|
+
corrections: Object.entries(state.retries ?? {}).map(([key, count]) => ({
|
|
26
|
+
key,
|
|
27
|
+
count,
|
|
28
|
+
max: max.get(key) ?? null,
|
|
29
|
+
})),
|
|
30
|
+
result: presentResult(factory, state),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
/** The page's files, served as they are: no script or style is ever inline. */
|
|
34
|
+
const pageFiles = {
|
|
35
|
+
"/": ["index.html", "text/html; charset=utf-8"],
|
|
36
|
+
"/style.css": ["style.css", "text/css; charset=utf-8"],
|
|
37
|
+
"/dashboard.js": ["dashboard.js", "text/javascript; charset=utf-8"],
|
|
38
|
+
"/timeline.js": ["timeline.js", "text/javascript; charset=utf-8"],
|
|
39
|
+
"/follow.js": ["follow.js", "text/javascript; charset=utf-8"],
|
|
40
|
+
};
|
|
41
|
+
async function loadPage() {
|
|
42
|
+
const page = new Map();
|
|
43
|
+
for (const [path, [file, type]] of Object.entries(pageFiles))
|
|
44
|
+
page.set(path, {
|
|
45
|
+
body: await readFile(new URL(`./static/${file}`, import.meta.url), "utf8"),
|
|
46
|
+
type,
|
|
47
|
+
});
|
|
48
|
+
return page;
|
|
49
|
+
}
|
|
50
|
+
function send(response, status, body, type = "text/plain; charset=utf-8") {
|
|
51
|
+
response.writeHead(status, {
|
|
52
|
+
"content-type": type,
|
|
53
|
+
"cache-control": "no-store",
|
|
54
|
+
"x-content-type-options": "nosniff",
|
|
55
|
+
"content-security-policy": "default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'",
|
|
56
|
+
});
|
|
57
|
+
response.end(body);
|
|
58
|
+
}
|
|
59
|
+
/** A read-only view of the repository's runs on 127.0.0.1, on `port` or one chosen by the system. */
|
|
60
|
+
export async function startDashboard(factory, listen = 0) {
|
|
61
|
+
let port = 0;
|
|
62
|
+
const page = await loadPage();
|
|
63
|
+
const handle = async (request, response) => {
|
|
64
|
+
// Another Host means another site reached this port (DNS rebinding).
|
|
65
|
+
if (request.headers.host !== `127.0.0.1:${port}` &&
|
|
66
|
+
request.headers.host !== `localhost:${port}`)
|
|
67
|
+
return send(response, 403, "Forbidden");
|
|
68
|
+
if (request.method !== "GET")
|
|
69
|
+
return send(response, 405, "Method Not Allowed");
|
|
70
|
+
const url = new URL(request.url ?? "/", "http://127.0.0.1");
|
|
71
|
+
const file = page.get(url.pathname);
|
|
72
|
+
if (file)
|
|
73
|
+
return send(response, 200, file.body, file.type);
|
|
74
|
+
if (url.pathname === "/api/runs")
|
|
75
|
+
return send(response, 200, JSON.stringify(await runs(factory)), "application/json");
|
|
76
|
+
const id = /^\/api\/runs\/([^/]+)$/.exec(url.pathname)?.[1];
|
|
77
|
+
if (id === undefined)
|
|
78
|
+
return send(response, 404, "Not Found");
|
|
79
|
+
const after = url.searchParams.get("after") ?? "0";
|
|
80
|
+
if (!/^\d+$/.test(after))
|
|
81
|
+
return send(response, 400, "Bad Request");
|
|
82
|
+
let body;
|
|
83
|
+
try {
|
|
84
|
+
body = await detail(factory, decodeURIComponent(id), Number(after));
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
// A run that cannot be read now may be read at the next request.
|
|
88
|
+
return error instanceof UnknownRunError
|
|
89
|
+
? send(response, 404, "Not Found")
|
|
90
|
+
: send(response, 500, error instanceof Error ? error.message : String(error));
|
|
91
|
+
}
|
|
92
|
+
return send(response, 200, JSON.stringify(body), "application/json");
|
|
93
|
+
};
|
|
94
|
+
const server = createServer((request, response) => {
|
|
95
|
+
handle(request, response).catch((error) => send(response, 500, error instanceof Error ? error.message : String(error)));
|
|
96
|
+
});
|
|
97
|
+
await new Promise((resolve, reject) => {
|
|
98
|
+
server.once("error", reject);
|
|
99
|
+
server.listen(listen, "127.0.0.1", () => resolve());
|
|
100
|
+
});
|
|
101
|
+
port = server.address().port;
|
|
102
|
+
return {
|
|
103
|
+
url: `http://127.0.0.1:${port}/`,
|
|
104
|
+
server,
|
|
105
|
+
close: () => new Promise((resolve, reject) => {
|
|
106
|
+
server.close((error) => (error ? reject(error) : resolve()));
|
|
107
|
+
server.closeAllConnections();
|
|
108
|
+
}),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=server.js.map
|