@dna113p/machines 0.3.3 → 0.3.5
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/README.md +12 -2
- package/claude/statusline.mjs +5 -2
- package/dist/mcp/server.js +2 -2
- package/dist/src/cli.js +163 -20
- package/dist/src/codex.d.ts +2 -0
- package/dist/src/codex.js +2 -0
- package/dist/src/host.js +6 -2
- package/dist/src/launcher.d.ts +3 -0
- package/dist/src/launcher.js +3 -0
- package/dist/src/run-graph.d.ts +39 -0
- package/dist/src/run-graph.js +91 -0
- package/dist/src/run-status.d.ts +19 -3
- package/dist/src/run-status.js +64 -5
- package/dist/src/run-ui-page.d.ts +6 -0
- package/dist/src/run-ui-page.js +690 -0
- package/dist/src/run-ui.d.ts +62 -0
- package/dist/src/run-ui.js +258 -0
- package/dist/src/run-viewer.d.ts +57 -0
- package/dist/src/run-viewer.js +66 -0
- package/dist/src/run-watch.d.ts +3 -1
- package/dist/src/run-watch.js +10 -6
- package/docs/architecture.md +10 -0
- package/docs/authoring.md +13 -0
- package/docs/integrations.md +186 -10
- package/package.json +88 -20
- package/skills/machine-builder/SKILL.md +31 -0
- package/skills/machine-delegation/SKILL.md +23 -0
- package/skills/machine-project-setup/SKILL.md +23 -5
- package/skills/machine-project-setup/references/project-workflows.md +4 -0
package/dist/src/run-status.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { StateValue } from "xstate";
|
|
2
2
|
import type { AgentUpdate } from "./index.ts";
|
|
3
|
+
import { type RunGraph, type RunTrailStep } from "./run-graph.ts";
|
|
3
4
|
/** One published run, stored as `<id>.json` in the run-status directory. */
|
|
4
5
|
export interface RunStatusRecord {
|
|
5
6
|
/** Version 1 records carry only `human.prompt`. */
|
|
@@ -30,6 +31,12 @@ export interface RunStatusRecord {
|
|
|
30
31
|
/** Names the inbox responses the run took lately, so that each sender can tell; never a response. */
|
|
31
32
|
readonly deliveries?: readonly string[];
|
|
32
33
|
readonly error?: string;
|
|
34
|
+
/** The finished run's summary, at most 2000 characters including any ellipsis. */
|
|
35
|
+
readonly note?: string;
|
|
36
|
+
/** The Machine's states and transitions, when it is small enough to draw. */
|
|
37
|
+
readonly graph?: RunGraph;
|
|
38
|
+
/** The states the run entered, oldest first: the latest `trailLimit` of them. */
|
|
39
|
+
readonly trail?: readonly RunTrailStep[];
|
|
33
40
|
readonly startedAt: string;
|
|
34
41
|
readonly updatedAt: string;
|
|
35
42
|
}
|
|
@@ -63,6 +70,7 @@ export interface RunStatusRun {
|
|
|
63
70
|
/** Replaces `MACHINES_RUN_LABEL`, and is shortened as it is. */
|
|
64
71
|
readonly label?: string;
|
|
65
72
|
readonly answeredBy?: "host";
|
|
73
|
+
readonly graph?: RunGraph;
|
|
66
74
|
}
|
|
67
75
|
/** A present key replaces its field; a present key holding `undefined` clears it. */
|
|
68
76
|
export interface RunStatusChange {
|
|
@@ -77,6 +85,7 @@ export interface RunStatusChange {
|
|
|
77
85
|
}
|
|
78
86
|
export type RunStatusOutcome = {
|
|
79
87
|
readonly state?: StateValue;
|
|
88
|
+
readonly note?: string;
|
|
80
89
|
} | {
|
|
81
90
|
readonly error: string;
|
|
82
91
|
};
|
|
@@ -89,6 +98,8 @@ export interface RunStatusPublisher {
|
|
|
89
98
|
update(change: RunStatusChange): void;
|
|
90
99
|
finish(outcome: RunStatusOutcome): void;
|
|
91
100
|
}
|
|
101
|
+
/** How many entered states a record keeps: enough for a long run's repairs, small enough to rewrite at every step. */
|
|
102
|
+
export declare const trailLimit = 200;
|
|
92
103
|
/** How long a record goes on naming a response its run took: longer than a sender waits to hear of it. */
|
|
93
104
|
export declare const deliveryRetentionMilliseconds = 60000;
|
|
94
105
|
/** How long a session's watch list is kept after its last change: sessions end without saying so. */
|
|
@@ -115,8 +126,9 @@ export declare function runDeliveryPath(directory: string, id: string, delivery:
|
|
|
115
126
|
/**
|
|
116
127
|
* Publishes one run for hosts that can only read files, such as a status line,
|
|
117
128
|
* in `runStatusDirectory`. Publication is presentation: it never throws, and it
|
|
118
|
-
* records
|
|
119
|
-
* written, as in a read-only home, is not
|
|
129
|
+
* records no Machine input and only the supplied summary of its output.
|
|
130
|
+
* A run whose first record cannot be written, as in a read-only home, is not
|
|
131
|
+
* published at all.
|
|
120
132
|
*/
|
|
121
133
|
export declare function createRunStatusPublisher(run: RunStatusRun, environment?: NodeJS.ProcessEnv): RunStatusPublisher;
|
|
122
134
|
/**
|
|
@@ -129,7 +141,9 @@ export declare function runStatusObserver(update: (change: RunStatusChange) => v
|
|
|
129
141
|
};
|
|
130
142
|
/**
|
|
131
143
|
* The session a process belongs to: the owner of the runs it starts, and the
|
|
132
|
-
* session whose watch list `machine watch` changes.
|
|
144
|
+
* session whose watch list `machine watch` changes. `MACHINES_RUN_OWNER` is how
|
|
145
|
+
* any host names it; the others are what Claude Code, Codex, and Pi give the
|
|
146
|
+
* commands they start.
|
|
133
147
|
*/
|
|
134
148
|
export declare function runSessionId(environment?: NodeJS.ProcessEnv): string | undefined;
|
|
135
149
|
/** Returns the valid records in a directory. Unreadable or malformed files are skipped. */
|
|
@@ -137,3 +151,5 @@ export declare function readRunStatuses(directory: string): RunStatusRecord[];
|
|
|
137
151
|
/** The id becomes a file name, so it must not be able to leave the directory. */
|
|
138
152
|
export declare function isFileSafeId(id: string): boolean;
|
|
139
153
|
export declare function isProcessAlive(pid: number): boolean;
|
|
154
|
+
/** Only an object's non-empty summary is published from a Machine's output. */
|
|
155
|
+
export declare function runOutputSummary(output: unknown): string | undefined;
|
package/dist/src/run-status.js
CHANGED
|
@@ -2,7 +2,10 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { basename, isAbsolute, join, resolve } from "node:path";
|
|
4
4
|
import { machinesUserHome } from "./discovery.js";
|
|
5
|
+
import { statePaths } from "./run-graph.js";
|
|
5
6
|
const staleAfterMilliseconds = 60 * 60 * 1_000;
|
|
7
|
+
/** How many entered states a record keeps: enough for a long run's repairs, small enough to rewrite at every step. */
|
|
8
|
+
export const trailLimit = 200;
|
|
6
9
|
/** How long a record goes on naming a response its run took: longer than a sender waits to hear of it. */
|
|
7
10
|
export const deliveryRetentionMilliseconds = 60_000;
|
|
8
11
|
/** How long a session's watch list is kept after its last change: sessions end without saying so. */
|
|
@@ -59,8 +62,9 @@ export function runDeliveryPath(directory, id, delivery) {
|
|
|
59
62
|
/**
|
|
60
63
|
* Publishes one run for hosts that can only read files, such as a status line,
|
|
61
64
|
* in `runStatusDirectory`. Publication is presentation: it never throws, and it
|
|
62
|
-
* records
|
|
63
|
-
* written, as in a read-only home, is not
|
|
65
|
+
* records no Machine input and only the supplied summary of its output.
|
|
66
|
+
* A run whose first record cannot be written, as in a read-only home, is not
|
|
67
|
+
* published at all.
|
|
64
68
|
*/
|
|
65
69
|
export function createRunStatusPublisher(run, environment = process.env) {
|
|
66
70
|
const directory = runStatusDirectory(environment);
|
|
@@ -79,6 +83,14 @@ export function createRunStatusPublisher(run, environment = process.env) {
|
|
|
79
83
|
let human;
|
|
80
84
|
let deliveries = [];
|
|
81
85
|
let error;
|
|
86
|
+
let note;
|
|
87
|
+
let trail = [];
|
|
88
|
+
const enter = (entered, at) => {
|
|
89
|
+
const path = entered === undefined ? "" : statePaths(entered).join(", ");
|
|
90
|
+
if (path === "" || trail.at(-1)?.state === path)
|
|
91
|
+
return;
|
|
92
|
+
trail = [...trail, { state: path, at }].slice(-trailLimit);
|
|
93
|
+
};
|
|
82
94
|
const publish = () => {
|
|
83
95
|
const record = {
|
|
84
96
|
schemaVersion: 2,
|
|
@@ -99,6 +111,9 @@ export function createRunStatusPublisher(run, environment = process.env) {
|
|
|
99
111
|
...(run.answeredBy === undefined ? {} : { answeredBy: run.answeredBy }),
|
|
100
112
|
...(deliveries.length === 0 ? {} : { deliveries: deliveries.map((delivery) => delivery.id) }),
|
|
101
113
|
...(error === undefined ? {} : { error }),
|
|
114
|
+
...(note === undefined ? {} : { note }),
|
|
115
|
+
...(run.graph === undefined ? {} : { graph: run.graph }),
|
|
116
|
+
...(trail.length === 0 ? {} : { trail }),
|
|
102
117
|
startedAt,
|
|
103
118
|
updatedAt: new Date().toISOString(),
|
|
104
119
|
};
|
|
@@ -153,8 +168,10 @@ export function createRunStatusPublisher(run, environment = process.env) {
|
|
|
153
168
|
// Repeated notifications for an unchanged run do not need another write.
|
|
154
169
|
if (JSON.stringify(next) === JSON.stringify({ status, state, agent, limit, human, deliveries }))
|
|
155
170
|
return;
|
|
156
|
-
if (next.state !== state)
|
|
171
|
+
if (next.state !== state) {
|
|
157
172
|
stateSince = new Date().toISOString();
|
|
173
|
+
enter(next.state, stateSince);
|
|
174
|
+
}
|
|
158
175
|
({ status, state, agent, limit, human, deliveries } = next);
|
|
159
176
|
publish();
|
|
160
177
|
},
|
|
@@ -168,6 +185,8 @@ export function createRunStatusPublisher(run, environment = process.env) {
|
|
|
168
185
|
else {
|
|
169
186
|
status = "completed";
|
|
170
187
|
state = formatState(outcome.state) ?? state;
|
|
188
|
+
note = formatNote(outcome.note);
|
|
189
|
+
enter(state, new Date().toISOString());
|
|
171
190
|
}
|
|
172
191
|
stateSince = undefined;
|
|
173
192
|
agent = undefined;
|
|
@@ -228,10 +247,16 @@ export function runStatusObserver(update) {
|
|
|
228
247
|
}
|
|
229
248
|
/**
|
|
230
249
|
* The session a process belongs to: the owner of the runs it starts, and the
|
|
231
|
-
* session whose watch list `machine watch` changes.
|
|
250
|
+
* session whose watch list `machine watch` changes. `MACHINES_RUN_OWNER` is how
|
|
251
|
+
* any host names it; the others are what Claude Code, Codex, and Pi give the
|
|
252
|
+
* commands they start.
|
|
232
253
|
*/
|
|
233
254
|
export function runSessionId(environment = process.env) {
|
|
234
|
-
return environment.MACHINES_RUN_OWNER
|
|
255
|
+
return environment.MACHINES_RUN_OWNER
|
|
256
|
+
|| environment.CLAUDE_CODE_SESSION_ID
|
|
257
|
+
|| environment.CODEX_THREAD_ID
|
|
258
|
+
|| environment.PI_SESSION_ID
|
|
259
|
+
|| undefined;
|
|
235
260
|
}
|
|
236
261
|
/** Returns the valid records in a directory. Unreadable or malformed files are skipped. */
|
|
237
262
|
export function readRunStatuses(directory) {
|
|
@@ -347,9 +372,23 @@ function isRunStatusRecord(value) {
|
|
|
347
372
|
&& (record.answeredBy === undefined || record.answeredBy === "host")
|
|
348
373
|
&& isOptionalStrings(record.deliveries)
|
|
349
374
|
&& isOptionalString(record.error)
|
|
375
|
+
&& isOptionalString(record.note)
|
|
376
|
+
&& (record.graph === undefined || isGraph(record.graph))
|
|
377
|
+
&& (record.trail === undefined || (Array.isArray(record.trail) && record.trail.every((step) => step !== null && typeof step === "object" && typeof step.state === "string" && isTimestamp(step.at))))
|
|
350
378
|
&& isTimestamp(record.startedAt)
|
|
351
379
|
&& isTimestamp(record.updatedAt);
|
|
352
380
|
}
|
|
381
|
+
function isGraph(value) {
|
|
382
|
+
if (value === null || typeof value !== "object")
|
|
383
|
+
return false;
|
|
384
|
+
const graph = value;
|
|
385
|
+
return isOptionalString(graph.initial)
|
|
386
|
+
&& Array.isArray(graph.states) && graph.states.every((state) => state !== null && typeof state === "object"
|
|
387
|
+
&& typeof state.id === "string" && typeof state.kind === "string"
|
|
388
|
+
&& isOptionalString(state.parent) && isOptionalString(state.role))
|
|
389
|
+
&& Array.isArray(graph.edges) && graph.edges.every((edge) => edge !== null && typeof edge === "object"
|
|
390
|
+
&& typeof edge.from === "string" && typeof edge.to === "string" && typeof edge.event === "string");
|
|
391
|
+
}
|
|
353
392
|
function isOptionalString(value) {
|
|
354
393
|
return value === undefined || typeof value === "string";
|
|
355
394
|
}
|
|
@@ -378,6 +417,26 @@ function formatState(state) {
|
|
|
378
417
|
return undefined;
|
|
379
418
|
return typeof state === "string" ? state : JSON.stringify(state);
|
|
380
419
|
}
|
|
420
|
+
/** Only an object's non-empty summary is published from a Machine's output. */
|
|
421
|
+
export function runOutputSummary(output) {
|
|
422
|
+
if (output === null || typeof output !== "object" || Array.isArray(output))
|
|
423
|
+
return undefined;
|
|
424
|
+
const summary = output.summary;
|
|
425
|
+
return typeof summary === "string" && summary.trim() !== "" ? summary : undefined;
|
|
426
|
+
}
|
|
427
|
+
function formatNote(note) {
|
|
428
|
+
const text = note?.trim();
|
|
429
|
+
if (!text)
|
|
430
|
+
return undefined;
|
|
431
|
+
if (text.length <= 2000)
|
|
432
|
+
return text;
|
|
433
|
+
// Reserve one character for the ellipsis, keeping the last whole word that fits.
|
|
434
|
+
let end = 1999;
|
|
435
|
+
while (end > 0 && !/\s/u.test(text[end]))
|
|
436
|
+
end -= 1;
|
|
437
|
+
// A single word longer than the limit still has to fit in the record.
|
|
438
|
+
return `${text.slice(0, end || 1999).trimEnd()}…`;
|
|
439
|
+
}
|
|
381
440
|
/** One short line: a label is a caption, not a place for task text. */
|
|
382
441
|
function formatLabel(label) {
|
|
383
442
|
const line = label?.split(/\r?\n/u).map((part) => part.trim()).find((part) => part !== "");
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page `machine ui` serves: one self-contained document, with no outside
|
|
3
|
+
* scripts, styles, or fonts, so it works offline and loads nothing but its own
|
|
4
|
+
* data. The token lets only this page answer a run.
|
|
5
|
+
*/
|
|
6
|
+
export declare function runUiPage(token: string): string;
|