@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.
Files changed (48) hide show
  1. package/dist/agent/__tests__/perf-sampler.test.d.ts +10 -0
  2. package/dist/agent/agent-context.d.ts +1 -1
  3. package/dist/agent/agent-loop.d.ts +5 -1
  4. package/dist/agent/desktop-open.d.ts +184 -0
  5. package/dist/agent/perf-sampler.d.ts +99 -0
  6. package/dist/agent/services-manifest.d.ts +88 -0
  7. package/dist/agent/services-restore.d.ts +58 -0
  8. package/dist/client.d.ts +189 -15
  9. package/dist/display.d.ts +17 -0
  10. package/dist/index.d.ts +14 -5
  11. package/dist/index.js +1625 -120
  12. package/dist/runtimes/_cli-agent.d.ts +372 -2
  13. package/dist/runtimes/claude-code.d.ts +12 -0
  14. package/dist/runtimes/codex.buildcommand.test.d.ts +9 -0
  15. package/dist/runtimes/codex.d.ts +8 -0
  16. package/dist/runtimes/openai-desktop.js +1555 -120
  17. package/dist/runtimes/session-env.test.d.ts +14 -0
  18. package/dist/sandbox/sizes.d.ts +120 -30
  19. package/dist/sandbox.d.ts +1 -1
  20. package/dist/types/api-conversations.d.ts +476 -1
  21. package/dist/types/api-factory.d.ts +164 -7
  22. package/dist/types/api-runs.d.ts +23 -1
  23. package/dist/types/protocol.d.ts +32 -1
  24. package/dist/types/runtime.d.ts +120 -0
  25. package/dist/types/workflow-metadata.d.ts +6 -5
  26. package/package.json +1 -1
  27. package/src/agent/agent-context.ts +128 -28
  28. package/src/agent/agent-loop.ts +10 -3
  29. package/src/agent/desktop-open.ts +418 -0
  30. package/src/agent/perf-sampler.ts +202 -0
  31. package/src/agent/services-manifest.ts +356 -0
  32. package/src/agent/services-restore.ts +195 -0
  33. package/src/client.ts +384 -32
  34. package/src/display.ts +44 -1
  35. package/src/index.ts +74 -7
  36. package/src/runtimes/_cli-agent.ts +1160 -67
  37. package/src/runtimes/claude-code.ts +187 -12
  38. package/src/runtimes/codex.ts +65 -2
  39. package/src/sandbox/providers/e2b.ts +8 -4
  40. package/src/sandbox/providers/local.ts +16 -4
  41. package/src/sandbox/sizes.ts +127 -44
  42. package/src/sandbox.ts +8 -0
  43. package/src/types/api-conversations.ts +461 -2
  44. package/src/types/api-factory.ts +165 -7
  45. package/src/types/api-runs.ts +25 -1
  46. package/src/types/protocol.ts +30 -1
  47. package/src/types/runtime.ts +122 -0
  48. 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
+ }