@agent-compose/sdk 0.8.1 → 0.8.3
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/dist/agent/__tests__/perf-sampler.test.d.ts +10 -0
- package/dist/agent/agent-context.d.ts +1 -1
- package/dist/agent/agent-loop.d.ts +5 -1
- package/dist/agent/desktop-open.d.ts +184 -0
- package/dist/agent/perf-sampler.d.ts +99 -0
- package/dist/agent/services-manifest.d.ts +88 -0
- package/dist/agent/services-restore.d.ts +58 -0
- package/dist/client.d.ts +189 -15
- package/dist/display.d.ts +17 -0
- package/dist/index.d.ts +14 -5
- package/dist/index.js +1625 -120
- package/dist/runtimes/_cli-agent.d.ts +372 -2
- package/dist/runtimes/claude-code.d.ts +12 -0
- package/dist/runtimes/codex.buildcommand.test.d.ts +9 -0
- package/dist/runtimes/codex.d.ts +8 -0
- package/dist/runtimes/openai-desktop.js +1555 -120
- package/dist/runtimes/session-env.test.d.ts +14 -0
- package/dist/sandbox/sizes.d.ts +120 -30
- package/dist/sandbox.d.ts +1 -1
- package/dist/types/api-conversations.d.ts +476 -1
- package/dist/types/api-factory.d.ts +164 -7
- package/dist/types/api-runs.d.ts +23 -1
- package/dist/types/protocol.d.ts +32 -1
- package/dist/types/runtime.d.ts +120 -0
- package/dist/types/workflow-metadata.d.ts +6 -5
- package/package.json +1 -1
- package/src/agent/agent-context.ts +128 -28
- package/src/agent/agent-loop.ts +10 -3
- package/src/agent/desktop-open.ts +418 -0
- package/src/agent/perf-sampler.ts +202 -0
- package/src/agent/services-manifest.ts +356 -0
- package/src/agent/services-restore.ts +195 -0
- package/src/client.ts +384 -32
- package/src/display.ts +44 -1
- package/src/index.ts +74 -7
- package/src/runtimes/_cli-agent.ts +1160 -67
- package/src/runtimes/claude-code.ts +187 -12
- package/src/runtimes/codex.ts +65 -2
- package/src/sandbox/providers/e2b.ts +8 -4
- package/src/sandbox/providers/local.ts +16 -4
- package/src/sandbox/sizes.ts +127 -44
- package/src/sandbox.ts +8 -0
- package/src/types/api-conversations.ts +461 -2
- package/src/types/api-factory.ts +165 -7
- package/src/types/api-runs.ts +25 -1
- package/src/types/protocol.ts +30 -1
- package/src/types/runtime.ts +122 -0
- package/src/types/workflow-metadata.ts +6 -5
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Restore-script generation for the durable services manifest.
|
|
3
|
+
*
|
|
4
|
+
* Pure string composition — nothing here touches a sandbox (the desktop-open
|
|
5
|
+
* posture). The server's fresh-boot ensure and `agentc services restore` both
|
|
6
|
+
* feed a parsed manifest through these generators, so the launch semantics are
|
|
7
|
+
* one implementation:
|
|
8
|
+
*
|
|
9
|
+
* - every service is launched DETACHED (`setsid sh -c … </dev/null &`) with a
|
|
10
|
+
* durable log and pidfile under /tmp/ac-services/ — the same contract the
|
|
11
|
+
* background-work doctrine teaches, so a restored service freezes while the
|
|
12
|
+
* machine parks and resumes on wake exactly like an agent-launched job;
|
|
13
|
+
* - `setup` and `data.restore` run first, sequentially, in the service's cwd;
|
|
14
|
+
* - health is gated with a bounded poll, never an unbounded wait;
|
|
15
|
+
* - every outcome is reported as a machine-readable status line
|
|
16
|
+
* (`##ac-service <name> <ok|unchecked|failed|dumped|dump-failed> [detail]`)
|
|
17
|
+
* — per-service honesty, never a fatal abort of the whole restore.
|
|
18
|
+
*
|
|
19
|
+
* The generated script is POSIX sh, installed via base64 + `sh -n` (the
|
|
20
|
+
* desktop-open install idiom) so a syntax regression fails loudly at install
|
|
21
|
+
* time, not silently at boot.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { ServiceEntry, ServicesManifest } from "./services-manifest.js";
|
|
25
|
+
|
|
26
|
+
/** Durable per-service logs + pidfiles live here (machine-local, recreated each boot). */
|
|
27
|
+
export const SERVICES_STATE_DIR = "/tmp/ac-services";
|
|
28
|
+
|
|
29
|
+
/** Marker prefix for machine-readable per-service outcome lines. */
|
|
30
|
+
export const SERVICE_STATUS_PREFIX = "##ac-service";
|
|
31
|
+
|
|
32
|
+
/** Bound on each service's health poll (1s beats). */
|
|
33
|
+
export const SERVICE_HEALTH_TIMEOUT_S = 45;
|
|
34
|
+
|
|
35
|
+
export type ServiceRestoreStatus = "ok" | "unchecked" | "failed" | "dumped" | "dump-failed";
|
|
36
|
+
|
|
37
|
+
export interface ServiceRestoreOutcome {
|
|
38
|
+
name: string;
|
|
39
|
+
status: ServiceRestoreStatus;
|
|
40
|
+
detail?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** POSIX single-quote escaping: ' → '\'' */
|
|
44
|
+
function shq(s: string): string {
|
|
45
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** `sh -c <cmd>` fragment with the command safely single-quoted. */
|
|
49
|
+
function shc(cmd: string): string {
|
|
50
|
+
return `sh -c ${shq(cmd)}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function serviceCwd(driveRoot: string, svc: ServiceEntry): string {
|
|
54
|
+
return svc.cwd ? `${driveRoot.replace(/\/+$/, "")}/${svc.cwd}` : driveRoot;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function restoreOneService(driveRoot: string, svc: ServiceEntry): string[] {
|
|
58
|
+
const n = svc.name;
|
|
59
|
+
const cwd = serviceCwd(driveRoot, svc);
|
|
60
|
+
const lines: string[] = [
|
|
61
|
+
``,
|
|
62
|
+
`# --- ${n} ---`,
|
|
63
|
+
`(`,
|
|
64
|
+
` cd ${shq(cwd)} || { status ${shq(n)} failed cwd-missing; exit 0; }`,
|
|
65
|
+
];
|
|
66
|
+
if (svc.setup) {
|
|
67
|
+
lines.push(
|
|
68
|
+
` if ! ${shc(svc.setup)} >> "$D/${n}.setup.log" 2>&1; then`,
|
|
69
|
+
` status ${shq(n)} failed setup; exit 0`,
|
|
70
|
+
` fi`,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
if (svc.data?.restore) {
|
|
74
|
+
lines.push(
|
|
75
|
+
` if ! ${shc(svc.data.restore)} >> "$D/${n}.setup.log" 2>&1; then`,
|
|
76
|
+
` status ${shq(n)} failed data-restore; exit 0`,
|
|
77
|
+
` fi`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
lines.push(
|
|
81
|
+
` $SETSID ${shc(svc.command)} >> "$D/${n}.log" 2>&1 < /dev/null &`,
|
|
82
|
+
` echo $! > "$D/${n}.pid"`,
|
|
83
|
+
);
|
|
84
|
+
const health = svc.health?.cmd
|
|
85
|
+
? shc(svc.health.cmd)
|
|
86
|
+
: svc.health?.http
|
|
87
|
+
? `curl -fsS -m 2 -o /dev/null ${shq(svc.health.http)}`
|
|
88
|
+
: null;
|
|
89
|
+
if (health) {
|
|
90
|
+
lines.push(
|
|
91
|
+
` i=0`,
|
|
92
|
+
` while [ "$i" -lt ${SERVICE_HEALTH_TIMEOUT_S} ]; do`,
|
|
93
|
+
` if ${health} >/dev/null 2>&1; then status ${shq(n)} ok ''; exit 0; fi`,
|
|
94
|
+
` kill -0 "$(cat "$D/${n}.pid")" 2>/dev/null || { status ${shq(n)} failed exited; exit 0; }`,
|
|
95
|
+
` i=$((i+1)); sleep 1`,
|
|
96
|
+
` done`,
|
|
97
|
+
` status ${shq(n)} failed health-timeout`,
|
|
98
|
+
);
|
|
99
|
+
} else {
|
|
100
|
+
lines.push(
|
|
101
|
+
` sleep 1`,
|
|
102
|
+
` if kill -0 "$(cat "$D/${n}.pid")" 2>/dev/null; then status ${shq(n)} unchecked ''; else status ${shq(n)} failed exited; fi`,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
lines.push(`)`);
|
|
106
|
+
return lines;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The auto-restore script: sequential, per-service honest, never exits nonzero
|
|
111
|
+
* because one service failed (the caller reads status lines, not the exit code).
|
|
112
|
+
*
|
|
113
|
+
* @param driveRoot absolute session working dir (the drive root, e.g. /factory/files)
|
|
114
|
+
*/
|
|
115
|
+
export function servicesRestoreScript(manifest: ServicesManifest, driveRoot: string): string {
|
|
116
|
+
const head = [
|
|
117
|
+
`#!/bin/sh`,
|
|
118
|
+
`# generated by agent-compose — restores services from ${driveRoot}/.ac/services.yml`,
|
|
119
|
+
`# logs + pidfiles: ${SERVICES_STATE_DIR}/<name>.{log,pid}`,
|
|
120
|
+
`D=${shq(SERVICES_STATE_DIR)}`,
|
|
121
|
+
`mkdir -p "$D"`,
|
|
122
|
+
`status() { printf '%s %s %s %s\\n' ${shq(SERVICE_STATUS_PREFIX)} "$1" "$2" "$3"; }`,
|
|
123
|
+
`# setsid detaches from the boot shell's session (Linux guests); on hosts`,
|
|
124
|
+
`# without it (macOS running \`agentc services restore\`) plain & still works.`,
|
|
125
|
+
`SETSID=""`,
|
|
126
|
+
`command -v setsid >/dev/null 2>&1 && SETSID=setsid`,
|
|
127
|
+
];
|
|
128
|
+
const body = manifest.services.flatMap((svc) => restoreOneService(driveRoot, svc));
|
|
129
|
+
return [...head, ...body, ``].join("\n");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* The pre-recycle dump script: runs each service's `data.dump` hook in its cwd,
|
|
134
|
+
* bounded by the caller's command timeout. Only DELIBERATE recycles (resize)
|
|
135
|
+
* get to run this — an evicted or dead machine cannot.
|
|
136
|
+
*/
|
|
137
|
+
export function servicesDumpScript(manifest: ServicesManifest, driveRoot: string): string | null {
|
|
138
|
+
const withDump = manifest.services.filter((svc) => svc.data?.dump);
|
|
139
|
+
if (withDump.length === 0) return null;
|
|
140
|
+
const lines = [
|
|
141
|
+
`#!/bin/sh`,
|
|
142
|
+
`# generated by agent-compose — pre-recycle data dumps from .ac/services.yml`,
|
|
143
|
+
`status() { printf '%s %s %s %s\\n' ${shq(SERVICE_STATUS_PREFIX)} "$1" "$2" "$3"; }`,
|
|
144
|
+
];
|
|
145
|
+
for (const svc of withDump) {
|
|
146
|
+
const cwd = serviceCwd(driveRoot, svc);
|
|
147
|
+
lines.push(
|
|
148
|
+
``,
|
|
149
|
+
`# --- ${svc.name} ---`,
|
|
150
|
+
`(`,
|
|
151
|
+
` cd ${shq(cwd)} || { status ${shq(svc.name)} dump-failed cwd-missing; exit 0; }`,
|
|
152
|
+
` if ${shc(svc.data!.dump!)} > /dev/null 2>&1; then`,
|
|
153
|
+
` status ${shq(svc.name)} dumped ''`,
|
|
154
|
+
` else`,
|
|
155
|
+
` status ${shq(svc.name)} dump-failed ''`,
|
|
156
|
+
` fi`,
|
|
157
|
+
`)`,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
lines.push(``);
|
|
161
|
+
return lines.join("\n");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Parse the status lines out of a restore/dump run's stdout. Non-status lines
|
|
166
|
+
* are ignored; malformed status lines are dropped rather than guessed at.
|
|
167
|
+
*/
|
|
168
|
+
export function parseServicesRestoreOutput(stdout: string): ServiceRestoreOutcome[] {
|
|
169
|
+
const out: ServiceRestoreOutcome[] = [];
|
|
170
|
+
for (const raw of stdout.split(/\r?\n/)) {
|
|
171
|
+
const line = raw.trim();
|
|
172
|
+
if (!line.startsWith(`${SERVICE_STATUS_PREFIX} `)) continue;
|
|
173
|
+
const rest = line.slice(SERVICE_STATUS_PREFIX.length + 1).trim();
|
|
174
|
+
const [name, status, ...detail] = rest.split(/\s+/);
|
|
175
|
+
if (!name || !status) continue;
|
|
176
|
+
if (!["ok", "unchecked", "failed", "dumped", "dump-failed"].includes(status)) continue;
|
|
177
|
+
const d = detail.join(" ").trim();
|
|
178
|
+
out.push({ name, status: status as ServiceRestoreStatus, ...(d ? { detail: d } : {}) });
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* One human line for the conversation notice, e.g.
|
|
185
|
+
* `postgres ✓ redis ✓ web ✗ (health-timeout — see /tmp/ac-services/web.log)`.
|
|
186
|
+
*/
|
|
187
|
+
export function formatServicesRestoreSummary(outcomes: ServiceRestoreOutcome[]): string {
|
|
188
|
+
return outcomes
|
|
189
|
+
.map((o) => {
|
|
190
|
+
if (o.status === "ok" || o.status === "unchecked" || o.status === "dumped") return `${o.name} ✓`;
|
|
191
|
+
const why = o.detail ? `${o.detail} — see ${SERVICES_STATE_DIR}/${o.name}.log` : `see ${SERVICES_STATE_DIR}/${o.name}.log`;
|
|
192
|
+
return `${o.name} ✗ (${why})`;
|
|
193
|
+
})
|
|
194
|
+
.join(" ");
|
|
195
|
+
}
|