smol-symphony 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/AGENTS.md +73 -0
- package/DESIGN.md +453 -0
- package/LICENSE +21 -0
- package/PRODUCT.md +106 -0
- package/README.md +232 -0
- package/SPEC.md +2169 -0
- package/WORKFLOW.md +269 -0
- package/WORKFLOW.template.md +307 -0
- package/dist/agent/acp.js +304 -0
- package/dist/agent/acp.js.map +1 -0
- package/dist/agent/adapters.js +275 -0
- package/dist/agent/adapters.js.map +1 -0
- package/dist/agent/codex.js +439 -0
- package/dist/agent/codex.js.map +1 -0
- package/dist/agent/runner.js +394 -0
- package/dist/agent/runner.js.map +1 -0
- package/dist/agent/smolvm.js +174 -0
- package/dist/agent/smolvm.js.map +1 -0
- package/dist/bin/symphony.js +205 -0
- package/dist/bin/symphony.js.map +1 -0
- package/dist/http.js +1189 -0
- package/dist/http.js.map +1 -0
- package/dist/logging.js +45 -0
- package/dist/logging.js.map +1 -0
- package/dist/mcp.js +478 -0
- package/dist/mcp.js.map +1 -0
- package/dist/orchestrator.js +683 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/prompt.js +65 -0
- package/dist/prompt.js.map +1 -0
- package/dist/trackers/local.js +344 -0
- package/dist/trackers/local.js.map +1 -0
- package/dist/trackers/types.js +10 -0
- package/dist/trackers/types.js.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/workflow.js +385 -0
- package/dist/workflow.js.map +1 -0
- package/dist/workspace.js +196 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +68 -0
- package/scripts/build-vm.sh +67 -0
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
// ACP (Agent Client Protocol) client wrapper.
|
|
2
|
+
//
|
|
3
|
+
// Symphony talks the Zed Agent Client Protocol (https://agentclientprotocol.com) so that a
|
|
4
|
+
// single integration covers Claude (`claude-agent-acp`), Codex (`codex-acp`), and OpenCode
|
|
5
|
+
// (`opencode acp`). The protocol is JSON-RPC over stdio.
|
|
6
|
+
//
|
|
7
|
+
// This wrapper:
|
|
8
|
+
// * spawns the adapter command inside a smolvm machine (the child process stdio is the
|
|
9
|
+
// transport; nothing local is mutated outside the VM)
|
|
10
|
+
// * bridges Node child process stdio to the SDK's WHATWG streams via Readable.toWeb / etc.
|
|
11
|
+
// * implements the small Client surface ACP requires — session update streaming,
|
|
12
|
+
// permission requests, and (optionally) fs/terminal — under a documented high-trust
|
|
13
|
+
// posture (auto-approve, no client-side fs writes; the agent uses its own tools in-VM)
|
|
14
|
+
// * exposes high-level methods `initSession()` and `runPrompt()` that the agent runner
|
|
15
|
+
// uses to drive one turn of work.
|
|
16
|
+
import { Readable, Writable } from 'node:stream';
|
|
17
|
+
import { ClientSideConnection, ndJsonStream, PROTOCOL_VERSION, } from '@agentclientprotocol/sdk';
|
|
18
|
+
import { log } from '../logging.js';
|
|
19
|
+
export class AcpProtocolError extends Error {
|
|
20
|
+
code;
|
|
21
|
+
constructor(code, message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.name = 'AcpProtocolError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function nowIso() {
|
|
28
|
+
return new Date().toISOString();
|
|
29
|
+
}
|
|
30
|
+
function summarize(raw, max = 240) {
|
|
31
|
+
let s;
|
|
32
|
+
try {
|
|
33
|
+
s = typeof raw === 'string' ? raw : JSON.stringify(raw);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
s = String(raw);
|
|
37
|
+
}
|
|
38
|
+
if (s.length > max)
|
|
39
|
+
s = s.slice(0, max) + '…';
|
|
40
|
+
return s;
|
|
41
|
+
}
|
|
42
|
+
function extractTextContent(content) {
|
|
43
|
+
if (!content || typeof content !== 'object')
|
|
44
|
+
return '';
|
|
45
|
+
const c = content;
|
|
46
|
+
if (c.type === 'text' && typeof c.text === 'string')
|
|
47
|
+
return c.text;
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
// One open ACP session bound to a single child process. The adapter is expected to be
|
|
51
|
+
// already running; `initSession()` performs `initialize` + `session/new`, and `runPrompt()`
|
|
52
|
+
// drives one prompt-to-stop_reason cycle.
|
|
53
|
+
export class AcpClient {
|
|
54
|
+
opts;
|
|
55
|
+
conn;
|
|
56
|
+
sessionId = null;
|
|
57
|
+
closed = false;
|
|
58
|
+
cancelled = false;
|
|
59
|
+
// Tracked here so the runner snapshot picks up the most recent assistant text per turn.
|
|
60
|
+
lastAssistantText = '';
|
|
61
|
+
constructor(opts) {
|
|
62
|
+
this.opts = opts;
|
|
63
|
+
// Bridge child stdio to the WHATWG streams the SDK speaks. `ndJsonStream` expects raw
|
|
64
|
+
// bytes; we use Readable.toWeb / Writable.toWeb for the conversion.
|
|
65
|
+
const input = Readable.toWeb(opts.stdout);
|
|
66
|
+
const output = Writable.toWeb(opts.stdin);
|
|
67
|
+
const stream = ndJsonStream(output, input);
|
|
68
|
+
this.conn = new ClientSideConnection((_agent) => this.makeClient(), stream);
|
|
69
|
+
opts.stderr.setEncoding('utf8');
|
|
70
|
+
opts.stderr.on('data', (chunk) => {
|
|
71
|
+
const text = chunk.trim();
|
|
72
|
+
if (text.length > 0) {
|
|
73
|
+
this.emit('agent_stderr', summarize(text));
|
|
74
|
+
// Adapters shouldn't normally write to stderr; when they do it's a real
|
|
75
|
+
// condition (a failed in-VM cp, a missing binary, an SDK warning). Log
|
|
76
|
+
// at info so it lands in the symphony log alongside other lifecycle
|
|
77
|
+
// events instead of disappearing into the per-issue event ring buffer.
|
|
78
|
+
log.info('agent stderr', { text: text.slice(0, 500) });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
opts.stdout.on('close', () => this.handleTransportClose('stdout_closed'));
|
|
82
|
+
opts.stdin.on('error', () => {
|
|
83
|
+
/* surface through transport close */
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// The Client interface implementation that the adapter calls back into. Every method is
|
|
87
|
+
// implemented because we run the agent inside a smolvm; ACP requires the client to handle
|
|
88
|
+
// requests it advertises, and the SDK's Client interface lists them all.
|
|
89
|
+
makeClient() {
|
|
90
|
+
const self = this;
|
|
91
|
+
return {
|
|
92
|
+
async sessionUpdate(params) {
|
|
93
|
+
return self.onSessionUpdate(params);
|
|
94
|
+
},
|
|
95
|
+
async requestPermission(params) {
|
|
96
|
+
return self.onPermissionRequest(params);
|
|
97
|
+
},
|
|
98
|
+
async readTextFile(_params) {
|
|
99
|
+
// §10.5 "high-trust" posture: the agent has direct workspace access inside the VM,
|
|
100
|
+
// so client-mediated reads are unsupported. Returning an error keeps the session
|
|
101
|
+
// alive (per §10.5 "unsupported dynamic tool calls return failure without stall").
|
|
102
|
+
throw new AcpProtocolError('client_capability_not_implemented', 'client fs read not supported');
|
|
103
|
+
},
|
|
104
|
+
async writeTextFile(_params) {
|
|
105
|
+
throw new AcpProtocolError('client_capability_not_implemented', 'client fs write not supported');
|
|
106
|
+
},
|
|
107
|
+
async createTerminal(_params) {
|
|
108
|
+
throw new AcpProtocolError('client_capability_not_implemented', 'client terminal not supported');
|
|
109
|
+
},
|
|
110
|
+
async terminalOutput(_params) {
|
|
111
|
+
throw new AcpProtocolError('client_capability_not_implemented', 'client terminal not supported');
|
|
112
|
+
},
|
|
113
|
+
async waitForTerminalExit(_params) {
|
|
114
|
+
throw new AcpProtocolError('client_capability_not_implemented', 'client terminal not supported');
|
|
115
|
+
},
|
|
116
|
+
async killTerminal(_params) {
|
|
117
|
+
throw new AcpProtocolError('client_capability_not_implemented', 'client terminal not supported');
|
|
118
|
+
},
|
|
119
|
+
async releaseTerminal(_params) {
|
|
120
|
+
throw new AcpProtocolError('client_capability_not_implemented', 'client terminal not supported');
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
emit(event, message) {
|
|
125
|
+
this.opts.onEvent({ at: nowIso(), event, message });
|
|
126
|
+
}
|
|
127
|
+
onSessionUpdate(params) {
|
|
128
|
+
const update = params.update;
|
|
129
|
+
switch (update.sessionUpdate) {
|
|
130
|
+
case 'agent_message_chunk': {
|
|
131
|
+
const text = extractTextContent(update.content);
|
|
132
|
+
if (text) {
|
|
133
|
+
this.lastAssistantText += text;
|
|
134
|
+
this.emit('agent_message_chunk', text.length > 80 ? text.slice(0, 80) + '…' : text);
|
|
135
|
+
}
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
case 'agent_thought_chunk': {
|
|
139
|
+
const text = extractTextContent(update.content);
|
|
140
|
+
if (text)
|
|
141
|
+
this.emit('agent_thought_chunk', text.length > 80 ? text.slice(0, 80) + '…' : text);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
case 'tool_call': {
|
|
145
|
+
const name = typeof update.title === 'string' ? update.title : String(update.toolCallId ?? '?');
|
|
146
|
+
this.emit('tool_call', `${name}: ${summarize(update)}`);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
case 'tool_call_update': {
|
|
150
|
+
const status = typeof update.status === 'string' ? update.status : '';
|
|
151
|
+
this.emit('tool_call_update', `${status}: ${summarize(update)}`);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
case 'plan': {
|
|
155
|
+
this.emit('plan', summarize(update.entries ?? update));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
case 'usage_update': {
|
|
159
|
+
// ACP usage is "context-window used / size", not cumulative I/O tokens. We map it
|
|
160
|
+
// into total_tokens so the existing orchestrator accounting stays meaningful; the
|
|
161
|
+
// I/O split is recorded as zero because ACP does not expose it.
|
|
162
|
+
const used = Number(update.used ?? 0);
|
|
163
|
+
const size = Number(update.size ?? 0);
|
|
164
|
+
this.opts.onTokenUsage({ input_tokens: 0, output_tokens: 0, total_tokens: used });
|
|
165
|
+
this.emit('usage_update', `used=${used}/${size}`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
default:
|
|
169
|
+
this.emit('session_update', `${update.sessionUpdate}: ${summarize(update)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
onPermissionRequest(params) {
|
|
173
|
+
// §10.5 high-trust posture: auto-approve every prompt with "allow_always" so the
|
|
174
|
+
// agent doesn't ask twice in the same session. Falls back to the first listed option
|
|
175
|
+
// if no "allow_*" kind is present, which keeps the session alive in degraded mode.
|
|
176
|
+
const preferred = params.options.find((o) => o.kind === 'allow_always') ??
|
|
177
|
+
params.options.find((o) => o.kind === 'allow_once') ??
|
|
178
|
+
params.options[0];
|
|
179
|
+
const optionId = preferred?.optionId ?? '';
|
|
180
|
+
this.emit('approval_auto_approved', `${optionId || 'unknown'}: ${summarize(params.toolCall)}`);
|
|
181
|
+
return { outcome: { outcome: 'selected', optionId } };
|
|
182
|
+
}
|
|
183
|
+
handleTransportClose(reason) {
|
|
184
|
+
if (this.closed)
|
|
185
|
+
return;
|
|
186
|
+
this.closed = true;
|
|
187
|
+
this.emit('subprocess_exit', reason);
|
|
188
|
+
}
|
|
189
|
+
// Negotiate protocol + open a session. Throws on either failure.
|
|
190
|
+
async initSession() {
|
|
191
|
+
const init = await withTimeout(this.conn.initialize({
|
|
192
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
193
|
+
clientInfo: { name: 'smol-symphony', version: '0.1.0' },
|
|
194
|
+
clientCapabilities: {
|
|
195
|
+
// Advertise the minimum: the agent can read/write the workspace itself inside
|
|
196
|
+
// the VM, so we do not offer fs/terminal capabilities.
|
|
197
|
+
fs: { readTextFile: false, writeTextFile: false },
|
|
198
|
+
terminal: false,
|
|
199
|
+
},
|
|
200
|
+
}), this.opts.readTimeoutMs, 'initialize');
|
|
201
|
+
this.emit('session_init', `protocolVersion=${init.protocolVersion}`);
|
|
202
|
+
// MCP is required for symphony operations: if the agent doesn't advertise support
|
|
203
|
+
// for a requested transport, we refuse to start the session rather than silently
|
|
204
|
+
// dropping the entry and running a degraded agent that has no mark_done /
|
|
205
|
+
// request_human_steering tools. ACP's mcpCapabilities is optional and advertises
|
|
206
|
+
// `http`/`sse`/`acp` as booleans; missing/undefined means "not supported." Stdio is
|
|
207
|
+
// implicit (the spec treats it as the baseline) and has no `type` discriminator.
|
|
208
|
+
const requested = this.opts.mcpServers ?? [];
|
|
209
|
+
const caps = (init.agentCapabilities ?? {});
|
|
210
|
+
const mcpCaps = caps.mcpCapabilities ?? {};
|
|
211
|
+
const supportsKind = (kind) => {
|
|
212
|
+
if (kind === 'stdio')
|
|
213
|
+
return true;
|
|
214
|
+
if (kind === 'http')
|
|
215
|
+
return mcpCaps.http === true;
|
|
216
|
+
if (kind === 'sse')
|
|
217
|
+
return mcpCaps.sse === true;
|
|
218
|
+
if (kind === 'acp')
|
|
219
|
+
return mcpCaps.acp === true;
|
|
220
|
+
return false;
|
|
221
|
+
};
|
|
222
|
+
const unsupported = requested.filter((s) => {
|
|
223
|
+
const kind = (s.type ?? 'stdio');
|
|
224
|
+
return !supportsKind(kind);
|
|
225
|
+
});
|
|
226
|
+
if (unsupported.length > 0) {
|
|
227
|
+
const kinds = unsupported
|
|
228
|
+
.map((s) => s.type ?? 'stdio')
|
|
229
|
+
.join(', ');
|
|
230
|
+
this.emit('mcp_capability_mismatch', `unsupported=${kinds} adapter_caps=${JSON.stringify(mcpCaps)}`);
|
|
231
|
+
throw new AcpProtocolError('mcp_capability_mismatch', `agent does not support required MCP transport(s): ${kinds}. Adapter caps: ${JSON.stringify(mcpCaps)}`);
|
|
232
|
+
}
|
|
233
|
+
const session = await withTimeout(this.conn.newSession({ cwd: this.opts.cwd, mcpServers: requested }), this.opts.readTimeoutMs, 'session/new');
|
|
234
|
+
this.sessionId = session.sessionId;
|
|
235
|
+
this.emit('session_started', `sessionId=${session.sessionId} cwd=${this.opts.cwd} mcp_servers=${requested.length}`);
|
|
236
|
+
return { sessionId: session.sessionId };
|
|
237
|
+
}
|
|
238
|
+
async runPrompt(promptText) {
|
|
239
|
+
if (this.closed)
|
|
240
|
+
return { reason: 'subprocess_exit', message: 'adapter already closed' };
|
|
241
|
+
if (!this.sessionId)
|
|
242
|
+
return { reason: 'startup_failed', message: 'no active session' };
|
|
243
|
+
this.lastAssistantText = '';
|
|
244
|
+
const request = {
|
|
245
|
+
sessionId: this.sessionId,
|
|
246
|
+
prompt: [{ type: 'text', text: promptText }],
|
|
247
|
+
};
|
|
248
|
+
let resp;
|
|
249
|
+
try {
|
|
250
|
+
resp = await withTimeout(this.conn.prompt(request), this.opts.promptTimeoutMs, 'session/prompt');
|
|
251
|
+
}
|
|
252
|
+
catch (err) {
|
|
253
|
+
if (err instanceof TimeoutError) {
|
|
254
|
+
// Best-effort cancel so the adapter doesn't keep working in the background.
|
|
255
|
+
this.cancel().catch(() => undefined);
|
|
256
|
+
return { reason: 'prompt_timeout', message: err.message };
|
|
257
|
+
}
|
|
258
|
+
if (this.closed)
|
|
259
|
+
return { reason: 'subprocess_exit', message: err.message };
|
|
260
|
+
return { reason: 'refusal', message: err.message };
|
|
261
|
+
}
|
|
262
|
+
return mapStopReason(resp.stopReason, this.lastAssistantText);
|
|
263
|
+
}
|
|
264
|
+
async cancel() {
|
|
265
|
+
if (!this.sessionId || this.closed || this.cancelled)
|
|
266
|
+
return;
|
|
267
|
+
this.cancelled = true;
|
|
268
|
+
const note = { sessionId: this.sessionId };
|
|
269
|
+
try {
|
|
270
|
+
await this.conn.cancel(note);
|
|
271
|
+
}
|
|
272
|
+
catch (err) {
|
|
273
|
+
log.debug('acp cancel failed', { error: err.message });
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
function mapStopReason(stopReason, lastText) {
|
|
278
|
+
switch (stopReason) {
|
|
279
|
+
case 'end_turn':
|
|
280
|
+
return { reason: 'end_turn', message: summarize(lastText) };
|
|
281
|
+
case 'max_tokens':
|
|
282
|
+
case 'max_turn_requests':
|
|
283
|
+
case 'refusal':
|
|
284
|
+
case 'cancelled':
|
|
285
|
+
return { reason: stopReason, message: summarize(lastText) };
|
|
286
|
+
default:
|
|
287
|
+
return { reason: 'refusal', message: `unknown stop_reason ${stopReason}` };
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
class TimeoutError extends Error {
|
|
291
|
+
}
|
|
292
|
+
function withTimeout(p, ms, label) {
|
|
293
|
+
return new Promise((resolve, reject) => {
|
|
294
|
+
const t = setTimeout(() => reject(new TimeoutError(`${label} timed out after ${ms}ms`)), ms);
|
|
295
|
+
p.then((v) => {
|
|
296
|
+
clearTimeout(t);
|
|
297
|
+
resolve(v);
|
|
298
|
+
}, (e) => {
|
|
299
|
+
clearTimeout(t);
|
|
300
|
+
reject(e);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
//# sourceMappingURL=acp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acp.js","sourceRoot":"","sources":["../../src/agent/acp.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
// Registry of ACP adapters symphony knows how to launch end-to-end.
|
|
2
|
+
//
|
|
3
|
+
// Each profile encodes everything symphony needs to ship a working adapter into the
|
|
4
|
+
// per-issue smolvm without per-workflow bash boilerplate:
|
|
5
|
+
//
|
|
6
|
+
// - hostCredentialPath : the file on the host (relative to $HOME) that authenticates
|
|
7
|
+
// the adapter. Symphony reads this and stages it into the
|
|
8
|
+
// workspace before VM boot.
|
|
9
|
+
// - guestCredentialPath: the absolute path inside the VM where the adapter expects
|
|
10
|
+
// to find that file. Symphony's auto-generated acp launch
|
|
11
|
+
// command copies the staged file here at startup.
|
|
12
|
+
// - binary : the executable inside the VM that speaks ACP.
|
|
13
|
+
//
|
|
14
|
+
// To add a new adapter (e.g. opencode), populate the profile and add it to ADAPTERS.
|
|
15
|
+
// Until an adapter has a verified profile, workflows can still target it by setting
|
|
16
|
+
// `acp.command` explicitly — that opts out of symphony's auto-staging and the operator
|
|
17
|
+
// becomes responsible for getting credentials to the adapter themselves.
|
|
18
|
+
//
|
|
19
|
+
// IMPORTANT: every host path here is treated as private. Symphony reads the file,
|
|
20
|
+
// stages a copy into the workspace, and chmods the copy to 0600. The original host
|
|
21
|
+
// file is never exposed via a bind mount.
|
|
22
|
+
import { constants as fsConstants } from 'node:fs';
|
|
23
|
+
import { access, copyFile, mkdir, chmod, lstat, rm, realpath } from 'node:fs/promises';
|
|
24
|
+
import path from 'node:path';
|
|
25
|
+
import os from 'node:os';
|
|
26
|
+
export const ADAPTERS = {
|
|
27
|
+
claude: {
|
|
28
|
+
id: 'claude',
|
|
29
|
+
hostCredentialPath: '.claude/.credentials.json',
|
|
30
|
+
guestCredentialPath: '/root/.claude/.credentials.json',
|
|
31
|
+
binary: ['claude-agent-acp'],
|
|
32
|
+
},
|
|
33
|
+
codex: {
|
|
34
|
+
id: 'codex',
|
|
35
|
+
hostCredentialPath: '.codex/auth.json',
|
|
36
|
+
guestCredentialPath: '/root/.codex/auth.json',
|
|
37
|
+
binary: ['codex-acp'],
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
export function isKnownAdapter(id) {
|
|
41
|
+
return id === 'claude' || id === 'codex';
|
|
42
|
+
}
|
|
43
|
+
export function profileFor(id) {
|
|
44
|
+
return ADAPTERS[id];
|
|
45
|
+
}
|
|
46
|
+
/** Absolute path on the host where the adapter's credential file lives. */
|
|
47
|
+
export function hostCredentialAbsPath(profile) {
|
|
48
|
+
return path.join(os.homedir(), profile.hostCredentialPath);
|
|
49
|
+
}
|
|
50
|
+
/** Verify the host credential file exists and is readable. Used at startup validation. */
|
|
51
|
+
export async function assertHostCredentialReadable(profile) {
|
|
52
|
+
const p = hostCredentialAbsPath(profile);
|
|
53
|
+
try {
|
|
54
|
+
await access(p, fsConstants.R_OK);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
throw new Error(`adapter "${profile.id}" requires a host credential at ${p}, but it is missing or unreadable: ${err.message}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Copy the host's credential file into the workspace's runtime staging area, chmod 600.
|
|
62
|
+
* Re-runs every attempt so a host-side token rotation is picked up automatically.
|
|
63
|
+
*
|
|
64
|
+
* Defense layers, in order:
|
|
65
|
+
* 1. `resolveStagingLocation` picks a path OUTSIDE the working tree when git is
|
|
66
|
+
* present (`.git/symphony-runtime/...`) so `git add -A`, a tracked file at
|
|
67
|
+
* `.symphony-runtime/`, or a `.gitignore` negation cannot expose the secret.
|
|
68
|
+
* Linked worktrees are refused (operator must override acp.command).
|
|
69
|
+
* 2. `ensureRealDir` walks each parent we materialize and refuses if any exists
|
|
70
|
+
* as a symlink or non-directory.
|
|
71
|
+
* 3. `rm + copyFile(COPYFILE_EXCL)` closes the local race on the leaf path:
|
|
72
|
+
* after explicit cleanup, the create is strict and fails if anything raced
|
|
73
|
+
* back in.
|
|
74
|
+
* 4. Post-write `realpath` check confirms the file landed at the expected
|
|
75
|
+
* absolute path. On mismatch (a parent was swapped for a symlink between
|
|
76
|
+
* ensureRealDir and copyFile), we unlink the staging path itself — which
|
|
77
|
+
* removes a symlink without following it — and refuse. We deliberately do
|
|
78
|
+
* NOT touch whatever the symlink resolved to, since that file is by
|
|
79
|
+
* definition attacker-chosen.
|
|
80
|
+
*/
|
|
81
|
+
export async function stageCredential(workspacePath, profile) {
|
|
82
|
+
const src = hostCredentialAbsPath(profile);
|
|
83
|
+
const { stagingRootAbs, stagingRootRel } = await resolveStagingLocation(workspacePath);
|
|
84
|
+
const credsDir = path.join(stagingRootAbs, 'credentials');
|
|
85
|
+
await ensureRealDir(stagingRootAbs);
|
|
86
|
+
await ensureRealDir(credsDir);
|
|
87
|
+
const dst = path.join(credsDir, profile.id);
|
|
88
|
+
await rm(dst, { force: true, recursive: false });
|
|
89
|
+
await copyFile(src, dst, fsConstants.COPYFILE_EXCL);
|
|
90
|
+
await chmod(dst, 0o600);
|
|
91
|
+
const expectedReal = path.join(await realpath(workspacePath), stagingRootRel, 'credentials', profile.id);
|
|
92
|
+
const actualReal = await realpath(dst);
|
|
93
|
+
if (actualReal !== expectedReal) {
|
|
94
|
+
// Unlinking dst removes the symlink itself (does not follow). Leaked
|
|
95
|
+
// credential at actualReal is left in place because we cannot prove
|
|
96
|
+
// ownership; operator must inspect.
|
|
97
|
+
await rm(dst, { force: true }).catch(() => undefined);
|
|
98
|
+
throw new Error(`staging path redirected: wrote to ${actualReal}, expected ${expectedReal}. ` +
|
|
99
|
+
`A symlink raced in during staging; manually inspect and remove any leaked ` +
|
|
100
|
+
`credential at the actual path before retrying.`);
|
|
101
|
+
}
|
|
102
|
+
const relPath = path.posix.join(stagingRootRel, 'credentials', profile.id);
|
|
103
|
+
return { absPath: dst, relPath };
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Decide where to stage credentials based on workspace shape. See StagedCredentialPaths
|
|
107
|
+
* for the policy. Returns absolute + relative (POSIX, relative to workspacePath)
|
|
108
|
+
* paths to the staging root directory; the caller composes `credentials/<id>` under it.
|
|
109
|
+
*/
|
|
110
|
+
async function resolveStagingLocation(workspacePath) {
|
|
111
|
+
const gitPath = path.join(workspacePath, '.git');
|
|
112
|
+
let gitSt;
|
|
113
|
+
try {
|
|
114
|
+
gitSt = await lstat(gitPath);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
if (err.code === 'ENOENT') {
|
|
118
|
+
// No `.git` in the workspace itself. The workspace-root fallback is only
|
|
119
|
+
// safe when the workspace is also NOT inside an ancestor git repo — git's
|
|
120
|
+
// repository discovery walks up the directory tree, so a workspace at e.g.
|
|
121
|
+
// `<parent>/.symphony/workspaces/X` with no own `.git` ends up inside
|
|
122
|
+
// `<parent>/.git`. A `git add -A` (from a host-side hook, or from any
|
|
123
|
+
// process with the host filesystem visible) would then stage the
|
|
124
|
+
// credential in the parent's index. Detect that and refuse.
|
|
125
|
+
//
|
|
126
|
+
// Resolve symlinks first: git's discovery follows the resolved path, so
|
|
127
|
+
// walking the lexical `workspacePath` would miss an ancestor `.git` that
|
|
128
|
+
// sits above the symlink's real target (e.g. `/home/user/repo-link`
|
|
129
|
+
// pointing at `/srv/repo` where `/srv/.git` exists). A broken symlink at
|
|
130
|
+
// workspacePath itself is treated as a refusal: we cannot prove the
|
|
131
|
+
// workspace isn't inside someone's working tree, so don't stage.
|
|
132
|
+
let resolvedWorkspace;
|
|
133
|
+
try {
|
|
134
|
+
resolvedWorkspace = await realpath(workspacePath);
|
|
135
|
+
}
|
|
136
|
+
catch (resolveErr) {
|
|
137
|
+
throw new Error(`cannot auto-stage credentials: workspace ${workspacePath} could not be resolved ` +
|
|
138
|
+
`(realpath failed: ${resolveErr.message}). Refusing to stage without a ` +
|
|
139
|
+
`canonical path; fix the workspace or set acp.command explicitly.`);
|
|
140
|
+
}
|
|
141
|
+
const ancestor = await findAncestorGit(resolvedWorkspace);
|
|
142
|
+
if (ancestor !== null) {
|
|
143
|
+
throw new Error(`cannot auto-stage credentials: workspace ${workspacePath} has no .git of its own ` +
|
|
144
|
+
`but is inside an ancestor git repo at ${ancestor}. Either create a nested clone ` +
|
|
145
|
+
`(e.g. via hooks.after_create) or set acp.command explicitly.`);
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
stagingRootAbs: path.join(workspacePath, '.symphony-runtime'),
|
|
149
|
+
stagingRootRel: '.symphony-runtime',
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
throw err;
|
|
153
|
+
}
|
|
154
|
+
if (gitSt.isDirectory()) {
|
|
155
|
+
// Normal clone: stage inside `.git/`, which is outside the working tree.
|
|
156
|
+
// Git never recurses into `.git/` for `add`/`status`, so the credential is
|
|
157
|
+
// structurally untrackable — no `info/exclude` games needed, and a tracked
|
|
158
|
+
// `.symphony-runtime/credentials/<id>` at workspace root cannot interfere.
|
|
159
|
+
return {
|
|
160
|
+
stagingRootAbs: path.join(workspacePath, '.git', 'symphony-runtime'),
|
|
161
|
+
stagingRootRel: path.posix.join('.git', 'symphony-runtime'),
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (gitSt.isFile()) {
|
|
165
|
+
// Linked worktree: .git is a pointer file at /<worktree>/.git -> per-worktree
|
|
166
|
+
// gitdir (typically /<main>/.git/worktrees/<name>/). The per-worktree gitdir
|
|
167
|
+
// lives outside the workspace mount, so symphony cannot place the credential
|
|
168
|
+
// there and still have the in-VM agent reach it. Any worktree-internal path
|
|
169
|
+
// is inside the working tree, where `.gitignore` negation or a tracked file
|
|
170
|
+
// could still expose the secret to `git add -A`. Refuse loudly; operators
|
|
171
|
+
// who need a worktree workspace must set acp.command and manage credentials
|
|
172
|
+
// themselves.
|
|
173
|
+
throw new Error(`cannot auto-stage credentials in a linked worktree (.git at ${gitPath} is a file). ` +
|
|
174
|
+
`Set acp.command explicitly and manage credentials in that command.`);
|
|
175
|
+
}
|
|
176
|
+
throw new Error(`cannot auto-stage credentials: ${gitPath} is neither a directory nor a file ` +
|
|
177
|
+
`(symlink, device, or other unexpected entry).`);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Walk up from `workspacePath` looking for an ancestor `.git` entry (file or dir).
|
|
181
|
+
* Returns the absolute path of the first one found, or null when the walk reaches
|
|
182
|
+
* the filesystem root without hitting one. Used to detect workspaces that, despite
|
|
183
|
+
* having no `.git` of their own, are still inside the working tree of an ancestor
|
|
184
|
+
* git repo.
|
|
185
|
+
*
|
|
186
|
+
* The caller is expected to pass an already-canonical (realpath-resolved) path:
|
|
187
|
+
* git discovery follows the resolved path, so a lexical walk on a symlinked input
|
|
188
|
+
* would miss the real ancestor. Once the input is canonical, every `path.dirname`
|
|
189
|
+
* step stays canonical (a directory's parent is itself a directory component,
|
|
190
|
+
* never a symlink), so no per-level re-resolve is needed.
|
|
191
|
+
*/
|
|
192
|
+
async function findAncestorGit(workspacePath) {
|
|
193
|
+
const root = path.parse(workspacePath).root;
|
|
194
|
+
let current = path.dirname(workspacePath);
|
|
195
|
+
while (current && current !== root) {
|
|
196
|
+
const candidate = path.join(current, '.git');
|
|
197
|
+
try {
|
|
198
|
+
await lstat(candidate);
|
|
199
|
+
return candidate;
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
if (err.code !== 'ENOENT')
|
|
203
|
+
throw err;
|
|
204
|
+
}
|
|
205
|
+
const next = path.dirname(current);
|
|
206
|
+
if (next === current)
|
|
207
|
+
break;
|
|
208
|
+
current = next;
|
|
209
|
+
}
|
|
210
|
+
// Final check at the root itself.
|
|
211
|
+
try {
|
|
212
|
+
await lstat(path.join(root, '.git'));
|
|
213
|
+
return path.join(root, '.git');
|
|
214
|
+
}
|
|
215
|
+
catch (err) {
|
|
216
|
+
if (err.code !== 'ENOENT')
|
|
217
|
+
throw err;
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Ensure `p` is a real directory we own. Creates it if missing; refuses if it exists
|
|
223
|
+
* as a symlink (defense against the staging-via-symlink attack) or as a non-directory.
|
|
224
|
+
*/
|
|
225
|
+
async function ensureRealDir(p) {
|
|
226
|
+
let st;
|
|
227
|
+
try {
|
|
228
|
+
st = await lstat(p);
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
if (err.code === 'ENOENT') {
|
|
232
|
+
await mkdir(p, { recursive: false });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
if (st.isSymbolicLink()) {
|
|
238
|
+
throw new Error(`refusing to write symphony-runtime through a symlink at ${p}`);
|
|
239
|
+
}
|
|
240
|
+
if (!st.isDirectory()) {
|
|
241
|
+
throw new Error(`expected a directory at ${p}, found a non-directory`);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Build the bash command symphony will exec inside the VM. Copies the staged credential
|
|
246
|
+
* (located at `stagedRelPath`, a POSIX path relative to the in-VM cwd which equals the
|
|
247
|
+
* workspace mount) to the adapter's expected location, chmods it, and exec's the
|
|
248
|
+
* adapter binary.
|
|
249
|
+
*
|
|
250
|
+
* Every interpolated value (binary tokens, paths) is run through shQuote so future
|
|
251
|
+
* profiles with spaces or metacharacters compose safely. `stagedRelPath` comes from
|
|
252
|
+
* `stageCredential`, which picks a path outside the working tree when git is present.
|
|
253
|
+
*/
|
|
254
|
+
export function deriveAcpCommand(profile, stagedRelPath) {
|
|
255
|
+
if (profile.binary.length === 0) {
|
|
256
|
+
throw new Error(`adapter "${profile.id}" has an empty binary launch vector`);
|
|
257
|
+
}
|
|
258
|
+
const guestDir = path.posix.dirname(profile.guestCredentialPath);
|
|
259
|
+
const guestPath = profile.guestCredentialPath;
|
|
260
|
+
const execCmd = profile.binary.map(shQuote).join(' ');
|
|
261
|
+
return [
|
|
262
|
+
`mkdir -p ${shQuote(guestDir)}`,
|
|
263
|
+
`cp ${shQuote(stagedRelPath)} ${shQuote(guestPath)}`,
|
|
264
|
+
`chmod 600 ${shQuote(guestPath)}`,
|
|
265
|
+
`exec ${execCmd}`,
|
|
266
|
+
].join(' && ');
|
|
267
|
+
}
|
|
268
|
+
// Minimal POSIX-shell single-quoting. None of the paths we emit contain `'`, but be
|
|
269
|
+
// defensive in case a future binary name or path changes.
|
|
270
|
+
function shQuote(s) {
|
|
271
|
+
if (/^[A-Za-z0-9_\-./]+$/.test(s))
|
|
272
|
+
return s;
|
|
273
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=adapters.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapters.js","sourceRoot":"","sources":["../../src/agent/adapters.ts"],"names":[],"mappings":"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"}
|