@dna113p/machines 0.1.0 → 0.3.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/README.md +120 -4
- package/claude/statusline.mjs +244 -0
- package/dist/examples/agy.d.ts +1 -0
- package/dist/examples/agy.js +30 -0
- package/dist/examples/chatgpt-web.d.ts +1 -0
- package/dist/examples/chatgpt-web.js +36 -0
- package/dist/examples/claude.d.ts +1 -0
- package/dist/examples/claude.js +30 -0
- package/dist/examples/codex.d.ts +1 -0
- package/dist/examples/codex.js +30 -0
- package/dist/examples/decision.d.ts +1 -0
- package/dist/examples/decision.js +66 -0
- package/dist/examples/deepseek.d.ts +1 -0
- package/dist/examples/deepseek.js +27 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +72 -10
- package/dist/pi-extension/index.js +16 -9
- package/dist/src/acp.js +4 -41
- package/dist/src/agent-bindings.d.ts +6 -0
- package/dist/src/agent-bindings.js +35 -5
- package/dist/src/agent-limit.d.ts +25 -0
- package/dist/src/agent-limit.js +34 -0
- package/dist/src/agent-limits.d.ts +37 -0
- package/dist/src/agent-limits.js +212 -0
- package/dist/src/agent-protocol.d.ts +6 -0
- package/dist/src/agent-protocol.js +40 -0
- package/dist/src/agy.d.ts +11 -0
- package/dist/src/agy.js +168 -0
- package/dist/src/catalog.d.ts +3 -0
- package/dist/src/catalog.js +94 -12
- package/dist/src/chatgpt-web.d.ts +143 -0
- package/dist/src/chatgpt-web.js +946 -0
- package/dist/src/claude.d.ts +35 -0
- package/dist/src/claude.js +294 -0
- package/dist/src/cli.js +194 -16
- package/dist/src/codex.d.ts +16 -0
- package/dist/src/codex.js +176 -0
- package/dist/src/conversation.d.ts +74 -0
- package/dist/src/conversation.js +46 -0
- package/dist/src/coordinator-guidance.d.ts +3 -0
- package/dist/src/coordinator-guidance.js +17 -0
- package/dist/src/decision.d.ts +29 -0
- package/dist/src/decision.js +67 -0
- package/dist/src/deepseek.d.ts +12 -0
- package/dist/src/deepseek.js +16 -0
- package/dist/src/host.d.ts +3 -0
- package/dist/src/host.js +68 -7
- package/dist/src/index.d.ts +39 -2
- package/dist/src/index.js +62 -5
- package/dist/src/jev.d.ts +15 -0
- package/dist/src/jev.js +90 -0
- package/dist/src/json.d.ts +6 -0
- package/dist/src/json.js +28 -0
- package/dist/src/launcher.d.ts +11 -1
- package/dist/src/launcher.js +72 -3
- package/dist/src/laya.d.ts +15 -0
- package/dist/src/laya.js +13 -0
- package/dist/src/openrouter.d.ts +16 -0
- package/dist/src/openrouter.js +97 -0
- package/dist/src/run-inbox.d.ts +49 -0
- package/dist/src/run-inbox.js +229 -0
- package/dist/src/run-status.d.ts +97 -0
- package/dist/src/run-status.js +285 -0
- package/dist/src/session.d.ts +13 -0
- package/dist/src/session.js +41 -3
- package/dist/src/system-one-http.d.ts +25 -0
- package/dist/src/system-one-http.js +146 -0
- package/dist/src/terminal-human.d.ts +8 -1
- package/dist/src/terminal-human.js +36 -8
- package/dist/src/von.d.ts +15 -0
- package/dist/src/von.js +13 -0
- package/docs/architecture.md +69 -2
- package/docs/authoring.md +849 -2
- package/docs/integrations.md +243 -3
- package/docs/local-decisions.md +212 -0
- package/package.json +23 -4
- package/services/laya/server.py +230 -0
- package/skills/machine-builder/SKILL.md +56 -6
- package/skills/machine-delegation/SKILL.md +123 -0
- package/skills/machine-delegation/references/delegation-machine.md +90 -0
- package/skills/machine-project-setup/SKILL.md +181 -0
- package/skills/machine-project-setup/references/project-workflows.md +50 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { AgentLimitError, agentLimitOf } from "./agent-limit.js";
|
|
2
|
+
export const defaultProbeMs = 15 * 60_000;
|
|
3
|
+
// A provider's reset time is when its window ends, not when it accepts requests again.
|
|
4
|
+
const resetMarginMs = 60_000;
|
|
5
|
+
// Short sleeps measured against the clock notice a reset that passed while the computer was suspended.
|
|
6
|
+
const tickMs = 60_000;
|
|
7
|
+
const handoffNote = [
|
|
8
|
+
"Note: a previous Agent working on this assignment was interrupted by a usage limit and may have",
|
|
9
|
+
"left partial work in the workspace. Inspect the current state and continue the same assignment",
|
|
10
|
+
"rather than starting over.",
|
|
11
|
+
].join(" ");
|
|
12
|
+
export function createLimitMemory() {
|
|
13
|
+
return new Map();
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Runs one role's request on the first candidate that is not at a usage limit.
|
|
17
|
+
* A limited candidate hands the same request to the next one; when all are
|
|
18
|
+
* limited the request waits for a reset within the policy, or fails.
|
|
19
|
+
* A request with `limits: "report"` is tried on the bound candidate alone, and
|
|
20
|
+
* its limit is thrown at once.
|
|
21
|
+
*/
|
|
22
|
+
export function limitAwareRunner(role, candidates, memory, policy) {
|
|
23
|
+
const bound = candidates[0];
|
|
24
|
+
if (bound === undefined)
|
|
25
|
+
throw new Error(`Agent role "${role}" needs at least one preset`);
|
|
26
|
+
const now = policy.now ?? Date.now;
|
|
27
|
+
const sleep = policy.sleep ?? timerSleep;
|
|
28
|
+
const named = (name) => name === role ? name : `${name} (role ${role})`;
|
|
29
|
+
const limited = (name, retrying) => {
|
|
30
|
+
const remembered = memory.get(name);
|
|
31
|
+
return remembered !== undefined && remembered.until > now() && retrying?.has(remembered) !== true;
|
|
32
|
+
};
|
|
33
|
+
// The state decides what its limit means, so nothing is swapped, waited for, or reported as an update.
|
|
34
|
+
const reported = async (request, report) => {
|
|
35
|
+
const known = memory.get(bound.name);
|
|
36
|
+
if (known !== undefined && known.until > now()) {
|
|
37
|
+
throw new AgentLimitError(`${named(bound.name)} is at its usage limit (${resetText(known)})`, known.limit);
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const event = await bound.runner(request, report);
|
|
41
|
+
if (known !== undefined && memory.get(bound.name) === known)
|
|
42
|
+
memory.delete(bound.name);
|
|
43
|
+
return event;
|
|
44
|
+
}
|
|
45
|
+
catch (cause) {
|
|
46
|
+
const limit = agentLimitOf(cause);
|
|
47
|
+
if (limit === undefined)
|
|
48
|
+
throw cause;
|
|
49
|
+
// The run's other roles skip this preset, and the limit names it.
|
|
50
|
+
throw new AgentLimitError(cause instanceof Error ? cause.message : String(cause), remember(bound, limit).limit, { cause });
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
return async (request, report) => {
|
|
54
|
+
if (request.limits === "report")
|
|
55
|
+
return reported(request, report);
|
|
56
|
+
// A state is announced after its Agent starts, in the same tick. Observers
|
|
57
|
+
// attach an update to the announced state, so none is reported before then.
|
|
58
|
+
const tell = async (update) => {
|
|
59
|
+
await Promise.resolve();
|
|
60
|
+
report?.(update);
|
|
61
|
+
};
|
|
62
|
+
let interrupted = false;
|
|
63
|
+
let lastFailure;
|
|
64
|
+
let deadline;
|
|
65
|
+
// A wait is reported once, and lasts until a candidate gets past its limit.
|
|
66
|
+
let waiting = false;
|
|
67
|
+
let retrying = new Set();
|
|
68
|
+
for (;;) {
|
|
69
|
+
let passedOver;
|
|
70
|
+
for (const [index, candidate] of candidates.entries()) {
|
|
71
|
+
if (limited(candidate.name, retrying)) {
|
|
72
|
+
passedOver ??= candidate;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (passedOver !== undefined && !interrupted && !waiting) {
|
|
76
|
+
// Another role of this run found the limit, so this one names the preset it runs on instead.
|
|
77
|
+
// After a wait the preset is named when the Agent resumes.
|
|
78
|
+
await tell(swapped(named(passedOver.name), "is at", memory.get(passedOver.name), passedOver, candidate));
|
|
79
|
+
}
|
|
80
|
+
passedOver = undefined;
|
|
81
|
+
const resume = () => {
|
|
82
|
+
if (!waiting)
|
|
83
|
+
return;
|
|
84
|
+
waiting = false;
|
|
85
|
+
report?.({
|
|
86
|
+
type: "limit",
|
|
87
|
+
status: "resumed",
|
|
88
|
+
agent: bound.name,
|
|
89
|
+
...(candidate === bound ? {} : { to: candidate.name }),
|
|
90
|
+
text: `${named(bound.name)} waited for a usage limit to reset; continuing${candidate === bound ? "" : ` with ${candidate.name}`}`,
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
const tried = memory.get(candidate.name);
|
|
94
|
+
let limit;
|
|
95
|
+
try {
|
|
96
|
+
const event = await candidate.runner(interrupted ? withHandoffNote(request) : request,
|
|
97
|
+
// A limited turn can still report its identity and the provider's message; a tool call is work.
|
|
98
|
+
!waiting ? report : (update) => {
|
|
99
|
+
if (update.type === "tool")
|
|
100
|
+
resume();
|
|
101
|
+
report?.(update);
|
|
102
|
+
});
|
|
103
|
+
resume();
|
|
104
|
+
// What was remembered for a preset that answers is out of date, also for the run's other roles.
|
|
105
|
+
if (tried !== undefined && memory.get(candidate.name) === tried)
|
|
106
|
+
memory.delete(candidate.name);
|
|
107
|
+
return event;
|
|
108
|
+
}
|
|
109
|
+
catch (cause) {
|
|
110
|
+
limit = agentLimitOf(cause);
|
|
111
|
+
if (limit === undefined)
|
|
112
|
+
throw cause;
|
|
113
|
+
lastFailure = cause;
|
|
114
|
+
}
|
|
115
|
+
interrupted = true;
|
|
116
|
+
const remembered = remember(candidate, limit);
|
|
117
|
+
const next = candidates.slice(index + 1).find((later) => !limited(later.name, retrying));
|
|
118
|
+
// Also while a wait is reported: each probe that moves on to another preset is a swap.
|
|
119
|
+
if (next !== undefined)
|
|
120
|
+
await tell(swapped(named(candidate.name), "reached", remembered, candidate, next));
|
|
121
|
+
}
|
|
122
|
+
const limits = candidates.flatMap((candidate) => {
|
|
123
|
+
const remembered = memory.get(candidate.name);
|
|
124
|
+
return remembered === undefined ? [] : [{ candidate, remembered }];
|
|
125
|
+
});
|
|
126
|
+
const started = now();
|
|
127
|
+
deadline ??= started + policy.maxWaitMs;
|
|
128
|
+
const closes = deadline;
|
|
129
|
+
const wakes = policy.maxWaitMs <= 0 ? [] : limits.flatMap(({ remembered }) => {
|
|
130
|
+
if (remembered.known) {
|
|
131
|
+
return remembered.until <= closes ? [{ remembered, at: remembered.until + resetMarginMs }] : [];
|
|
132
|
+
}
|
|
133
|
+
// The last probe of a limit with no stated reset is at the deadline.
|
|
134
|
+
return started < closes ? [{ remembered, at: Math.min(remembered.until, closes) }] : [];
|
|
135
|
+
});
|
|
136
|
+
if (wakes.length === 0)
|
|
137
|
+
throw exhausted(role, limits, policy.maxWaitMs, lastFailure);
|
|
138
|
+
const wakeAt = Math.min(...wakes.map((wake) => wake.at));
|
|
139
|
+
// A reset that passed while a later candidate was working needs no wait.
|
|
140
|
+
if (!waiting && wakeAt > now()) {
|
|
141
|
+
waiting = true;
|
|
142
|
+
const resets = wakes.filter((wake) => wake.remembered.known).map((wake) => wake.remembered.until);
|
|
143
|
+
const until = timestamp(resets.length === 0 ? closes : Math.min(...resets));
|
|
144
|
+
const others = candidates.slice(1).map((candidate) => candidate.name);
|
|
145
|
+
await tell({
|
|
146
|
+
type: "limit",
|
|
147
|
+
status: "waiting",
|
|
148
|
+
agent: bound.name,
|
|
149
|
+
until,
|
|
150
|
+
text: `${named(bound.name)}${others.length === 0
|
|
151
|
+
? " reached its usage limit"
|
|
152
|
+
: ` and its fallback${others.length === 1 ? "" : "s"} (${others.join(", ")}) reached their usage limits`}; ${resets.length === 0 ? `no reset is stated, retrying until ${until}` : `waiting for the reset at ${until}`}`,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
for (let remaining = wakeAt - now(); remaining > 0; remaining = wakeAt - now()) {
|
|
156
|
+
await sleep(Math.min(remaining, tickMs));
|
|
157
|
+
}
|
|
158
|
+
retrying = new Set(wakes.filter((wake) => wake.at <= wakeAt).map((wake) => wake.remembered));
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
function remember(candidate, limit) {
|
|
162
|
+
const at = now();
|
|
163
|
+
// A reset that has already passed says nothing about when to try again.
|
|
164
|
+
const known = limit.resetsAt !== undefined && limit.resetsAt > at;
|
|
165
|
+
const remembered = {
|
|
166
|
+
limit: { ...limit, agent: candidate.name },
|
|
167
|
+
until: known ? limit.resetsAt : at + policy.probeMs,
|
|
168
|
+
known,
|
|
169
|
+
};
|
|
170
|
+
memory.set(candidate.name, remembered);
|
|
171
|
+
return remembered;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
function withHandoffNote(request) {
|
|
175
|
+
return { ...request, prompt: `${request.prompt}\n\n${handoffNote}` };
|
|
176
|
+
}
|
|
177
|
+
function swapped(subject, verb, remembered, from, to) {
|
|
178
|
+
return {
|
|
179
|
+
type: "limit",
|
|
180
|
+
status: "swapped",
|
|
181
|
+
agent: from.name,
|
|
182
|
+
to: to.name,
|
|
183
|
+
text: `${subject} ${verb} its usage limit (${resetText(remembered)}); continuing with ${to.name}`,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function exhausted(role, limits, maxWaitMs, cause) {
|
|
187
|
+
const described = limits.map(({ candidate, remembered }) => {
|
|
188
|
+
const details = [candidate.harness ?? remembered.limit.harness, remembered.limit.kind, resetText(remembered)];
|
|
189
|
+
return `${candidate.name} (${details.filter((detail) => detail !== undefined).join(", ")})`;
|
|
190
|
+
});
|
|
191
|
+
const reason = maxWaitMs <= 0
|
|
192
|
+
? "waiting for a reset is off"
|
|
193
|
+
: `none is available within the ${formatMinutes(maxWaitMs)} wait limit`;
|
|
194
|
+
const known = limits.filter(({ remembered }) => remembered.known)
|
|
195
|
+
.sort((left, right) => left.remembered.until - right.remembered.until);
|
|
196
|
+
const earliest = (known[0] ?? limits[0])?.remembered.limit ?? { harness: "unknown" };
|
|
197
|
+
return new AgentLimitError(`Agent role "${role}" is at a usage limit on every allowed preset: ${described.join("; ")}; ${reason}`, earliest, { limits: limits.map(({ remembered }) => remembered.limit), ...(cause === undefined ? {} : { cause }) });
|
|
198
|
+
}
|
|
199
|
+
function resetText(remembered) {
|
|
200
|
+
return remembered?.known === true ? `resets ${timestamp(remembered.until)}` : "reset unknown";
|
|
201
|
+
}
|
|
202
|
+
function timestamp(time) {
|
|
203
|
+
return new Date(time).toISOString().replace(/\.\d{3}Z$/u, "Z");
|
|
204
|
+
}
|
|
205
|
+
function formatMinutes(milliseconds) {
|
|
206
|
+
const minutes = Math.round(milliseconds / 6_000) / 10;
|
|
207
|
+
return `${minutes}-minute`;
|
|
208
|
+
}
|
|
209
|
+
// The timer stays referenced: a waiting run is working, and must keep its process alive.
|
|
210
|
+
function timerSleep(milliseconds) {
|
|
211
|
+
return new Promise((resolve) => { setTimeout(resolve, milliseconds); });
|
|
212
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export function agentPrompt(request) {
|
|
2
|
+
return [
|
|
3
|
+
request.prompt,
|
|
4
|
+
"",
|
|
5
|
+
"You are already executing one Agent state inside a Machine. Complete this assigned state using the available tools.",
|
|
6
|
+
"Do not re-delegate this assignment or start the parent workflow again unless the assignment explicitly requires bounded nested work.",
|
|
7
|
+
"Return evidence and an allowed outcome to the owning Machine; it controls verification, transitions, and Human decisions.",
|
|
8
|
+
`Allowed outcome types: ${request.outcomes.join(", ")}.`,
|
|
9
|
+
"When finished, end your final response with exactly one line in this form:",
|
|
10
|
+
'MACHINES_EVENT {"type":"completed"}',
|
|
11
|
+
"Replace completed with exactly one allowed outcome type.",
|
|
12
|
+
"Include additional JSON fields only when the task asks for them.",
|
|
13
|
+
].join("\n");
|
|
14
|
+
}
|
|
15
|
+
export function readAgentEvent(output, options) {
|
|
16
|
+
const matches = [...output.matchAll(/^MACHINES_EVENT (.+)$/gmu)];
|
|
17
|
+
const encoded = matches.at(-1)?.[1];
|
|
18
|
+
if (encoded === undefined) {
|
|
19
|
+
throw new Error(`${options.adapter} Agent finished without returning a Machines event`);
|
|
20
|
+
}
|
|
21
|
+
let event;
|
|
22
|
+
try {
|
|
23
|
+
event = JSON.parse(encoded);
|
|
24
|
+
}
|
|
25
|
+
catch (cause) {
|
|
26
|
+
throw new Error(`${options.adapter} Agent returned invalid event JSON`, { cause });
|
|
27
|
+
}
|
|
28
|
+
if (!isEvent(event)) {
|
|
29
|
+
throw new Error(`${options.adapter} Agent returned an invalid Machines event`);
|
|
30
|
+
}
|
|
31
|
+
const message = output.replace(/^MACHINES_EVENT .+$(?:\r?\n)?/gmu, "").trim();
|
|
32
|
+
return options.includeMessage && message !== "" ? { ...event, message } : event;
|
|
33
|
+
}
|
|
34
|
+
function isEvent(value) {
|
|
35
|
+
return value !== null
|
|
36
|
+
&& typeof value === "object"
|
|
37
|
+
&& !Array.isArray(value)
|
|
38
|
+
&& "type" in value
|
|
39
|
+
&& typeof value.type === "string";
|
|
40
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AgentRunner } from "./index.ts";
|
|
2
|
+
export interface AgyAgentOptions {
|
|
3
|
+
readonly env?: Readonly<Record<string, string>>;
|
|
4
|
+
readonly harness?: string;
|
|
5
|
+
readonly model?: string;
|
|
6
|
+
readonly effort?: "low" | "medium" | "high";
|
|
7
|
+
/** Explicitly opt in to auto-approving all AGY tool permission requests. */
|
|
8
|
+
readonly dangerouslySkipPermissions?: boolean;
|
|
9
|
+
readonly output?: "capture" | "stream";
|
|
10
|
+
}
|
|
11
|
+
export declare function agyAgent(command?: string, args?: readonly string[], options?: AgyAgentOptions): AgentRunner;
|
package/dist/src/agy.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { createInterface } from "node:readline";
|
|
3
|
+
import * as v from "valibot";
|
|
4
|
+
import { agentPrompt, readAgentEvent } from "./agent-protocol.js";
|
|
5
|
+
export function agyAgent(command = "agy", args = [], options = {}) {
|
|
6
|
+
return async (request, report) => {
|
|
7
|
+
if (request.cwd === undefined) {
|
|
8
|
+
throw new Error("agy Agent requires a working directory");
|
|
9
|
+
}
|
|
10
|
+
if (request.outcomes.length === 0) {
|
|
11
|
+
throw new Error("agy Agent requires at least one allowed outcome");
|
|
12
|
+
}
|
|
13
|
+
const harness = options.harness ?? (command === "agy" ? "agy" : command);
|
|
14
|
+
const env = { ...process.env, ...options.env };
|
|
15
|
+
const model = options.model ?? env.AGY_MODEL ?? env.MACHINES_AGENT_MODEL;
|
|
16
|
+
const effort = options.effort ?? env.AGY_EFFORT ?? env.MACHINES_AGENT_EFFORT;
|
|
17
|
+
const outputMode = options.output ?? "stream";
|
|
18
|
+
report?.({
|
|
19
|
+
type: "identity",
|
|
20
|
+
harness,
|
|
21
|
+
...(model === undefined ? {} : { model }),
|
|
22
|
+
...(effort === undefined ? {} : { thinking: effort }),
|
|
23
|
+
});
|
|
24
|
+
const cliArgs = [
|
|
25
|
+
...args,
|
|
26
|
+
"--print",
|
|
27
|
+
agentPrompt(request),
|
|
28
|
+
"--output-format",
|
|
29
|
+
"stream-json",
|
|
30
|
+
"--disable-slash-commands",
|
|
31
|
+
...(options.dangerouslySkipPermissions === true ? ["--dangerously-skip-permissions"] : []),
|
|
32
|
+
...(model === undefined ? [] : ["--model", model]),
|
|
33
|
+
...(effort === undefined ? [] : ["--effort", effort]),
|
|
34
|
+
];
|
|
35
|
+
const child = spawn(command, cliArgs, {
|
|
36
|
+
cwd: request.cwd,
|
|
37
|
+
env,
|
|
38
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
39
|
+
});
|
|
40
|
+
let stderr = "";
|
|
41
|
+
child.stderr.setEncoding("utf8");
|
|
42
|
+
child.stderr.on("data", (chunk) => {
|
|
43
|
+
stderr += chunk;
|
|
44
|
+
if (outputMode === "stream")
|
|
45
|
+
process.stderr.write(chunk);
|
|
46
|
+
});
|
|
47
|
+
try {
|
|
48
|
+
await waitForSpawn(child);
|
|
49
|
+
return await runSession(child, outputMode, report);
|
|
50
|
+
}
|
|
51
|
+
catch (cause) {
|
|
52
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
53
|
+
const detail = stderr.trim();
|
|
54
|
+
throw new Error(`agy Agent "${command}" failed in "${request.cwd}": ${message}${detail === "" ? "" : `\n${detail}`}`, { cause });
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
child.kill();
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
async function runSession(child, outputMode, report) {
|
|
62
|
+
const exited = new Promise((resolve, reject) => {
|
|
63
|
+
child.once("close", (code, signal) => {
|
|
64
|
+
if (code === 0)
|
|
65
|
+
resolve();
|
|
66
|
+
else
|
|
67
|
+
reject(new Error(signal === null
|
|
68
|
+
? `Process exited with code ${code}`
|
|
69
|
+
: `Process terminated by ${signal}`));
|
|
70
|
+
});
|
|
71
|
+
child.once("error", reject);
|
|
72
|
+
});
|
|
73
|
+
// Observe failures immediately, while stdout may still be draining.
|
|
74
|
+
void exited.catch(() => { });
|
|
75
|
+
const lines = createInterface({ input: child.stdout });
|
|
76
|
+
let displayedOutput = "";
|
|
77
|
+
let finalResponse;
|
|
78
|
+
const emitOutput = (text) => {
|
|
79
|
+
if (text === "")
|
|
80
|
+
return;
|
|
81
|
+
displayedOutput += text;
|
|
82
|
+
report?.({ type: "output", text });
|
|
83
|
+
if (outputMode === "stream")
|
|
84
|
+
process.stdout.write(text);
|
|
85
|
+
};
|
|
86
|
+
try {
|
|
87
|
+
for await (const line of lines) {
|
|
88
|
+
const record = decodeRecord(line);
|
|
89
|
+
if (record === undefined)
|
|
90
|
+
continue;
|
|
91
|
+
if (record.event === "result") {
|
|
92
|
+
finalResponse = record.result.response;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const step = record.step_update;
|
|
96
|
+
if (step.step_type === "agent_response") {
|
|
97
|
+
emitOutput(step.text_delta);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const id = String(step.step_index ?? step.tool_name ?? "tool");
|
|
101
|
+
const title = step.tool_name ?? step.tool_info?.name ?? "tool";
|
|
102
|
+
const status = step.state === "ACTIVE"
|
|
103
|
+
? "in_progress"
|
|
104
|
+
: (step.state === "DONE" ? "completed" : undefined);
|
|
105
|
+
report?.({ type: "tool", id, title, ...(status === undefined ? {} : { status }) });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
await exited;
|
|
110
|
+
// The final response owns the outcome; earlier deltas may contain progress
|
|
111
|
+
// or a preliminary event. Fall back only when no final response was sent.
|
|
112
|
+
const response = finalResponse ?? displayedOutput;
|
|
113
|
+
if (finalResponse !== undefined && !displayedOutput.endsWith(finalResponse)) {
|
|
114
|
+
if (finalResponse.startsWith(displayedOutput)) {
|
|
115
|
+
emitOutput(finalResponse.slice(displayedOutput.length));
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
const separator = displayedOutput !== "" && !displayedOutput.endsWith("\n") ? "\n" : "";
|
|
119
|
+
emitOutput(separator + finalResponse);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (outputMode === "stream" && displayedOutput !== "" && !displayedOutput.endsWith("\n")) {
|
|
123
|
+
process.stdout.write("\n");
|
|
124
|
+
}
|
|
125
|
+
return readAgentEvent(response, { adapter: "agy", includeMessage: outputMode === "capture" });
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
lines.close();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
// Unknown events and malformed records are ignored at the transport boundary.
|
|
132
|
+
// Only these validated fields can reach the harness-neutral AgentUpdate API.
|
|
133
|
+
const recordSchema = v.variant("event", [
|
|
134
|
+
v.object({
|
|
135
|
+
event: v.literal("step_update"),
|
|
136
|
+
step_update: v.variant("step_type", [
|
|
137
|
+
v.object({ step_type: v.literal("agent_response"), text_delta: v.string() }),
|
|
138
|
+
v.object({
|
|
139
|
+
step_type: v.literal("tool"),
|
|
140
|
+
step_index: v.nullish(v.union([v.string(), v.number()])),
|
|
141
|
+
tool_name: v.nullish(v.string()),
|
|
142
|
+
tool_info: v.nullish(v.object({ name: v.nullish(v.string()) })),
|
|
143
|
+
state: v.nullish(v.string()),
|
|
144
|
+
}),
|
|
145
|
+
]),
|
|
146
|
+
}),
|
|
147
|
+
v.object({
|
|
148
|
+
event: v.literal("result"),
|
|
149
|
+
result: v.object({ response: v.string() }),
|
|
150
|
+
}),
|
|
151
|
+
]);
|
|
152
|
+
function decodeRecord(line) {
|
|
153
|
+
let value;
|
|
154
|
+
try {
|
|
155
|
+
value = JSON.parse(line);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
return undefined;
|
|
159
|
+
}
|
|
160
|
+
const result = v.safeParse(recordSchema, value);
|
|
161
|
+
return result.success ? result.output : undefined;
|
|
162
|
+
}
|
|
163
|
+
function waitForSpawn(child) {
|
|
164
|
+
return new Promise((resolve, reject) => {
|
|
165
|
+
child.once("spawn", resolve);
|
|
166
|
+
child.once("error", reject);
|
|
167
|
+
});
|
|
168
|
+
}
|
package/dist/src/catalog.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface AgentPresetSummary {
|
|
|
14
14
|
readonly harness?: string;
|
|
15
15
|
readonly model?: string;
|
|
16
16
|
readonly thinking?: string;
|
|
17
|
+
/** The presets that take over at a usage limit, in order; absent when none is configured. */
|
|
18
|
+
readonly fallback?: readonly string[];
|
|
17
19
|
readonly source: string;
|
|
18
20
|
}
|
|
19
21
|
export interface LauncherLocation {
|
|
@@ -24,6 +26,7 @@ interface ResolvedAgentPresets {
|
|
|
24
26
|
readonly agents: Readonly<Record<string, AgentRunner>>;
|
|
25
27
|
readonly presets: Readonly<Record<string, AgentPreset>>;
|
|
26
28
|
readonly sources: Readonly<Record<string, string>>;
|
|
29
|
+
readonly fallback?: readonly string[];
|
|
27
30
|
}
|
|
28
31
|
export declare function inspectMachines(location?: LauncherLocation): Promise<readonly MachineSummary[]>;
|
|
29
32
|
export declare function inspectAgentPresets(location?: LauncherLocation): Promise<readonly AgentPresetSummary[]>;
|
package/dist/src/catalog.js
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
|
-
import { loadAgentBindings } from "./agent-bindings.js";
|
|
1
|
+
import { fallbackPresets, loadAgentBindings } from "./agent-bindings.js";
|
|
2
2
|
import { acpAgent } from "./acp.js";
|
|
3
|
+
import { AgentLimitError } from "./agent-limit.js";
|
|
4
|
+
import { agyAgent } from "./agy.js";
|
|
5
|
+
import { chatGptWebAgent } from "./chatgpt-web.js";
|
|
6
|
+
import { claudeAgent } from "./claude.js";
|
|
7
|
+
import { codexAgent } from "./codex.js";
|
|
8
|
+
import { deepseekAgent } from "./deepseek.js";
|
|
9
|
+
import { decisionAgent } from "./decision.js";
|
|
10
|
+
import { jevAgent, jevProvider } from "./jev.js";
|
|
11
|
+
import { layaAgent, layaProvider } from "./laya.js";
|
|
12
|
+
import { vonAgent, vonProvider } from "./von.js";
|
|
13
|
+
import { openRouterDecisionAgent, openRouterDecisionProvider } from "./openrouter.js";
|
|
3
14
|
import { discoverMachines, machinesUserHome } from "./discovery.js";
|
|
4
15
|
import { loadMachineModule, readAgentRoles, requireMachineDescription } from "./machine-module.js";
|
|
5
16
|
export async function inspectMachines(location = {}) {
|
|
@@ -34,33 +45,104 @@ export async function inspectAgentPresets(location = {}) {
|
|
|
34
45
|
const home = location.home ?? machinesUserHome();
|
|
35
46
|
const configured = await configuredAgentPresets(cwd, home);
|
|
36
47
|
return Object.entries(configured.presets)
|
|
37
|
-
.map(([name, preset]) =>
|
|
38
|
-
name,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
48
|
+
.map(([name, preset]) => {
|
|
49
|
+
const fallback = fallbackPresets(name, preset, configured.fallback);
|
|
50
|
+
return {
|
|
51
|
+
name,
|
|
52
|
+
description: preset.description,
|
|
53
|
+
...(preset.harness === undefined ? {} : { harness: preset.harness }),
|
|
54
|
+
...(preset.model === undefined ? {} : { model: preset.model }),
|
|
55
|
+
...(preset.thinking === undefined ? {} : { thinking: preset.thinking }),
|
|
56
|
+
...(fallback === undefined ? {} : { fallback }),
|
|
57
|
+
source: configured.sources[name] ?? "unknown",
|
|
58
|
+
};
|
|
59
|
+
})
|
|
45
60
|
.sort((left, right) => left.name.localeCompare(right.name));
|
|
46
61
|
}
|
|
47
62
|
export async function configuredAgentPresets(cwd, home) {
|
|
48
|
-
const configured = await loadAgentBindings(cwd, home, {
|
|
63
|
+
const configured = await loadAgentBindings(cwd, home, {
|
|
64
|
+
acpAgent, AgentLimitError, agyAgent, chatGptWebAgent, claudeAgent, codexAgent, deepseekAgent, decisionAgent, jevAgent, jevProvider,
|
|
65
|
+
openRouterDecisionAgent, openRouterDecisionProvider, layaAgent, layaProvider, vonAgent, vonProvider,
|
|
66
|
+
});
|
|
49
67
|
const defaultRunner = acpAgent("npx", ["-y", "pi-acp"], {
|
|
50
68
|
harness: "pi-acp",
|
|
51
69
|
output: "capture",
|
|
52
70
|
});
|
|
71
|
+
const agyRunner = agyAgent("agy", [], {
|
|
72
|
+
harness: "agy",
|
|
73
|
+
output: "capture",
|
|
74
|
+
});
|
|
75
|
+
const claudeRunner = claudeAgent("claude", [], { output: "capture" });
|
|
76
|
+
const codexRunner = codexAgent("codex", [], { output: "capture" });
|
|
77
|
+
const chatGptWebRunner = chatGptWebAgent({ output: "capture" });
|
|
78
|
+
const deepseekRunner = deepseekAgent("dsh", [], { output: "capture" });
|
|
79
|
+
const jevRunner = jevAgent();
|
|
80
|
+
const openRouterRunner = openRouterDecisionAgent();
|
|
81
|
+
const layaRunner = layaAgent();
|
|
82
|
+
const vonRunner = vonAgent();
|
|
53
83
|
return {
|
|
54
|
-
agents: {
|
|
84
|
+
agents: {
|
|
85
|
+
default: defaultRunner, agy: agyRunner, claude: claudeRunner, codex: codexRunner, "chatgpt-web": chatGptWebRunner, deepseek: deepseekRunner, jev: jevRunner,
|
|
86
|
+
"openrouter-decision": openRouterRunner, laya: layaRunner, von: vonRunner, ...configured.agents,
|
|
87
|
+
},
|
|
55
88
|
presets: {
|
|
56
89
|
default: {
|
|
57
90
|
description: "Runs the current Pi Agent through ACP",
|
|
58
91
|
harness: "pi-acp",
|
|
59
92
|
runner: defaultRunner,
|
|
60
93
|
},
|
|
94
|
+
agy: {
|
|
95
|
+
description: "Runs the Antigravity CLI (agy)",
|
|
96
|
+
harness: "agy",
|
|
97
|
+
runner: agyRunner,
|
|
98
|
+
},
|
|
99
|
+
claude: {
|
|
100
|
+
description: "Runs the Claude Code CLI headlessly with its own login and permission rules",
|
|
101
|
+
harness: "claude",
|
|
102
|
+
runner: claudeRunner,
|
|
103
|
+
},
|
|
104
|
+
codex: {
|
|
105
|
+
description: "Runs the Codex CLI in a read-only sandbox",
|
|
106
|
+
harness: "codex",
|
|
107
|
+
runner: codexRunner,
|
|
108
|
+
},
|
|
109
|
+
"chatgpt-web": {
|
|
110
|
+
description: "Runs a new ChatGPT web conversation that works locally through rig-bridge",
|
|
111
|
+
harness: "chatgpt-web",
|
|
112
|
+
runner: chatGptWebRunner,
|
|
113
|
+
},
|
|
114
|
+
deepseek: {
|
|
115
|
+
description: "Runs DeepSeek Harness (dsh) through ACP",
|
|
116
|
+
harness: "dsh",
|
|
117
|
+
runner: deepseekRunner,
|
|
118
|
+
},
|
|
119
|
+
jev: {
|
|
120
|
+
description: "Classifies supplied evidence with Jev; does not execute tools",
|
|
121
|
+
harness: "jev",
|
|
122
|
+
runner: jevRunner,
|
|
123
|
+
},
|
|
124
|
+
"openrouter-decision": {
|
|
125
|
+
description: "Classifies supplied evidence through OpenRouter Decisions; does not execute tools",
|
|
126
|
+
harness: "openrouter-decision",
|
|
127
|
+
runner: openRouterRunner,
|
|
128
|
+
},
|
|
129
|
+
laya: {
|
|
130
|
+
description: "Classifies supplied evidence through a self-hosted Laya bridge; does not execute tools",
|
|
131
|
+
harness: "laya",
|
|
132
|
+
runner: layaRunner,
|
|
133
|
+
},
|
|
134
|
+
von: {
|
|
135
|
+
description: "Classifies supplied evidence through a self-hosted Von server; does not execute tools",
|
|
136
|
+
harness: "von",
|
|
137
|
+
runner: vonRunner,
|
|
138
|
+
},
|
|
61
139
|
...configured.presets,
|
|
62
140
|
},
|
|
63
|
-
sources: {
|
|
141
|
+
sources: {
|
|
142
|
+
default: "built in", agy: "built in", claude: "built in", codex: "built in", "chatgpt-web": "built in", deepseek: "built in", jev: "built in",
|
|
143
|
+
"openrouter-decision": "built in", laya: "built in", von: "built in", ...configured.sources,
|
|
144
|
+
},
|
|
145
|
+
...(configured.fallback === undefined ? {} : { fallback: configured.fallback }),
|
|
64
146
|
};
|
|
65
147
|
}
|
|
66
148
|
function errorMessage(cause) {
|