@commonlyai/cli 0.1.88 → 0.1.92
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/package.json +1 -1
- package/src/commands/agent.js +217 -14
- package/src/commands/daemon.js +2 -2
- package/src/lib/adapters/claude.js +2 -1
- package/src/lib/adapters/index.js +2 -0
- package/src/lib/adapters/opencode.js +923 -0
- package/src/lib/daemon-supervisor.js +34 -12
- package/src/lib/declared-mcp-guard.js +1 -1
- package/src/lib/default-environment.js +13 -5
- package/src/lib/enforcement.js +42 -7
- package/src/lib/environment.js +93 -3
- package/src/lib/grant-broker-guard.js +9 -9
- package/src/lib/sandbox/seatbelt.js +49 -30
package/package.json
CHANGED
package/src/commands/agent.js
CHANGED
|
@@ -39,8 +39,18 @@ import { readLongTerm, syncBack } from '../lib/memory-bridge.js';
|
|
|
39
39
|
import { pollRetryPolicy } from '../lib/poll-retry.js';
|
|
40
40
|
import { detectMemorySources, composeImport, importMemory } from '../lib/memory-import.js';
|
|
41
41
|
import { detectSkills, importSkills } from '../lib/skills-import.js';
|
|
42
|
-
import {
|
|
43
|
-
|
|
42
|
+
import {
|
|
43
|
+
environmentForServer,
|
|
44
|
+
normalizeSandboxTrust,
|
|
45
|
+
parseEnvironmentFile,
|
|
46
|
+
resolveWorkspace,
|
|
47
|
+
validateEnvironmentSpec,
|
|
48
|
+
} from '../lib/environment.js';
|
|
49
|
+
import {
|
|
50
|
+
ADAPTERS_WITH_DEFAULT_MCP,
|
|
51
|
+
ADAPTERS_WITH_GRANT_BROKER,
|
|
52
|
+
defaultMcpServers,
|
|
53
|
+
} from '../lib/default-environment.js';
|
|
44
54
|
import { withholdGrantBroker } from '../lib/grant-broker-guard.js';
|
|
45
55
|
import {
|
|
46
56
|
FOCUS_FRAME_MAX_CODE_POINTS,
|
|
@@ -125,8 +135,114 @@ export const deleteAgentToken = (name) => {
|
|
|
125
135
|
// podId) or is a local fact (which CLI binary to wrap). Returns a record ready
|
|
126
136
|
// for saveAgentToken, or null when COMMONLY_AGENT_TOKEN isn't set (caller
|
|
127
137
|
// falls back to the attach hint).
|
|
138
|
+
// OpenCode is opt-in: unlike the established wrappers it must not be selected
|
|
139
|
+
// just because its binary happens to be installed on the operator's machine.
|
|
128
140
|
export const BOOTSTRAP_ADAPTER_DETECT_ORDER = ['claude', 'codex', 'pi'];
|
|
129
141
|
|
|
142
|
+
/** Return the adapter binding resolved for this exact installation by the server. */
|
|
143
|
+
export const runtimeAdapterForInstallation = ({ installations, podId, instanceId } = {}) => {
|
|
144
|
+
if (!Array.isArray(installations) || !podId) return null;
|
|
145
|
+
const expectedPodId = String(podId);
|
|
146
|
+
const expectedInstanceId = String(instanceId || 'default');
|
|
147
|
+
const installation = installations.find((row) => (
|
|
148
|
+
row?.type === 'installation'
|
|
149
|
+
&& String(row.podId || '') === expectedPodId
|
|
150
|
+
&& String(row.instanceId || 'default') === expectedInstanceId
|
|
151
|
+
));
|
|
152
|
+
const adapter = String(installation?.runtimeAdapter || '').trim().toLowerCase();
|
|
153
|
+
return adapter || null;
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/** Non-broker adapters need a server-resolved binding on env-token seats. */
|
|
157
|
+
export const adapterRequiresServerBinding = (adapterName) => {
|
|
158
|
+
const adapter = String(adapterName || '').trim().toLowerCase();
|
|
159
|
+
return Boolean(adapter) && adapter !== 'stub' && !ADAPTERS_WITH_GRANT_BROKER.has(adapter);
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const serverRuntimeAdapterProjectionState = (installations) => {
|
|
163
|
+
if (!Array.isArray(installations)) return 'unknown';
|
|
164
|
+
const installationRows = installations.filter((row) => row?.type === 'installation');
|
|
165
|
+
if (installationRows.length === 0) return 'unknown';
|
|
166
|
+
return installationRows.some((row) => Object.prototype.hasOwnProperty.call(row, 'runtimeAdapter'))
|
|
167
|
+
? 'present'
|
|
168
|
+
: 'legacy';
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
/** Return the refusal message unless this installation declares the same adapter. */
|
|
172
|
+
export const serverAdapterBindingRefusal = ({
|
|
173
|
+
adapterName,
|
|
174
|
+
agentName,
|
|
175
|
+
installations,
|
|
176
|
+
podId,
|
|
177
|
+
instanceId,
|
|
178
|
+
includeAttachGuidance = true,
|
|
179
|
+
} = {}) => {
|
|
180
|
+
const adapter = String(adapterName || '').trim().toLowerCase();
|
|
181
|
+
if (!adapterRequiresServerBinding(adapter)) return null;
|
|
182
|
+
const projectionState = serverRuntimeAdapterProjectionState(installations);
|
|
183
|
+
if (projectionState === 'unknown') {
|
|
184
|
+
return 'Could not verify the server adapter binding because the installations response is empty or missing. '
|
|
185
|
+
+ 'Retry after the server returns this installation.';
|
|
186
|
+
}
|
|
187
|
+
if (projectionState === 'legacy') {
|
|
188
|
+
return `This Commonly server predates runtime adapter bindings; upgrade the server before running adapter '${adapter}'.`;
|
|
189
|
+
}
|
|
190
|
+
if (runtimeAdapterForInstallation({ installations, podId, instanceId }) === adapter) return null;
|
|
191
|
+
const refusal = `Adapter '${adapter}' is not declared for this Commonly installation.`;
|
|
192
|
+
if (!includeAttachGuidance) return refusal;
|
|
193
|
+
return `${refusal} A pod owner must sign in with commonly login, then run commonly agent attach ${adapter}`
|
|
194
|
+
+ ` --pod ${podId || '<pod-id>'} --name ${agentName || '<agent-name>'}`
|
|
195
|
+
+ ` [--env <environment.yaml>] before this seat can use adapter '${adapter}'.`;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/** Verify a newly attached adapter before telling the operator how to run it. */
|
|
199
|
+
export const checkAttachedAdapterBinding = async ({
|
|
200
|
+
adapterName,
|
|
201
|
+
agentName,
|
|
202
|
+
podId,
|
|
203
|
+
instanceId,
|
|
204
|
+
instanceUrl,
|
|
205
|
+
runtimeToken,
|
|
206
|
+
clientFactory = createClient,
|
|
207
|
+
} = {}) => {
|
|
208
|
+
if (!adapterRequiresServerBinding(adapterName)) return { canRun: true };
|
|
209
|
+
|
|
210
|
+
let identity;
|
|
211
|
+
try {
|
|
212
|
+
identity = await clientFactory({ instance: instanceUrl, token: runtimeToken })
|
|
213
|
+
.get('/api/agents/runtime/installations');
|
|
214
|
+
} catch (error) {
|
|
215
|
+
return {
|
|
216
|
+
canRun: false,
|
|
217
|
+
warning: `Could not verify the server adapter binding after attach: ${error.message}. `
|
|
218
|
+
+ 'The installation was saved, but do not expect agent run to work until verification succeeds.',
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const refusal = serverAdapterBindingRefusal({
|
|
223
|
+
adapterName,
|
|
224
|
+
agentName,
|
|
225
|
+
installations: identity?.installations,
|
|
226
|
+
podId,
|
|
227
|
+
instanceId,
|
|
228
|
+
includeAttachGuidance: false,
|
|
229
|
+
});
|
|
230
|
+
return refusal ? { canRun: false, warning: refusal } : { canRun: true };
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
export const printAttachRunGuidance = ({
|
|
234
|
+
agentName,
|
|
235
|
+
binding,
|
|
236
|
+
log = console.log,
|
|
237
|
+
warn = console.warn,
|
|
238
|
+
} = {}) => {
|
|
239
|
+
if (binding?.canRun) {
|
|
240
|
+
log(`\n Run with: commonly agent run ${agentName}`);
|
|
241
|
+
} else {
|
|
242
|
+
warn(`\n WARNING: ${binding?.warning || 'The server did not confirm this adapter binding.'}`);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
|
|
130
246
|
// ── `agent run --adapter` against an existing token file (#2098) ────────────
|
|
131
247
|
// The flag picks the CLI once, at first-run bootstrap; after that the token
|
|
132
248
|
// file's adapter is what runs. A different `--adapter` used to be dropped
|
|
@@ -213,6 +329,15 @@ export const bootstrapAgentRecordFromEnv = async ({
|
|
|
213
329
|
log(`token belongs to '${agentName}', not '${name}' — using the token's identity`);
|
|
214
330
|
}
|
|
215
331
|
|
|
332
|
+
const bindingRefusal = serverAdapterBindingRefusal({
|
|
333
|
+
adapterName,
|
|
334
|
+
agentName,
|
|
335
|
+
installations: installs,
|
|
336
|
+
podId: primary?.podId,
|
|
337
|
+
instanceId: primary?.instanceId,
|
|
338
|
+
});
|
|
339
|
+
if (bindingRefusal) throw new Error(bindingRefusal);
|
|
340
|
+
|
|
216
341
|
return {
|
|
217
342
|
agentName,
|
|
218
343
|
displayName: identity?.displayName || primary?.displayName || agentName,
|
|
@@ -495,7 +620,11 @@ export const updateAgentConfiguration = async ({
|
|
|
495
620
|
if (!environment && Object.keys(environmentRuntime).length) {
|
|
496
621
|
environment = { ...environmentRuntime };
|
|
497
622
|
}
|
|
498
|
-
if (environment
|
|
623
|
+
if (environment?.provider !== undefined
|
|
624
|
+
&& String(runtime.adapter || record.adapter || '').trim().toLowerCase() !== 'opencode') {
|
|
625
|
+
throw new Error('environment.provider is supported only by the opencode adapter');
|
|
626
|
+
}
|
|
627
|
+
if (environment) config.environment = environmentForServer(environment);
|
|
499
628
|
if (!Object.keys(config).length) {
|
|
500
629
|
throw new Error('provide at least one of --adapter, --model, --effort, or --env');
|
|
501
630
|
}
|
|
@@ -571,10 +700,10 @@ export const resolveAttachSandbox = ({
|
|
|
571
700
|
// pure — a mode and an adapter name — so it belongs with the derivation, and
|
|
572
701
|
// its witnesses live beside it.
|
|
573
702
|
if ((mode === 'workspace' || mode === 'read-only')
|
|
574
|
-
&& (trust !== 'public' || !['codex', 'claude'].includes(adapterName))) {
|
|
703
|
+
&& (trust !== 'public' || !['codex', 'claude', 'opencode'].includes(adapterName))) {
|
|
575
704
|
throw new Error(
|
|
576
705
|
`sandbox.mode=${mode} is currently implemented only for public `
|
|
577
|
-
+ '
|
|
706
|
+
+ 'Claude, Codex, or OpenCode adapters',
|
|
578
707
|
);
|
|
579
708
|
}
|
|
580
709
|
return { mode, trust };
|
|
@@ -593,11 +722,12 @@ export const performAttach = async ({
|
|
|
593
722
|
envPath = null,
|
|
594
723
|
wakeOnMessage = false,
|
|
595
724
|
log = () => {},
|
|
725
|
+
adapterRegistry = { getAdapter, listAdapterNames },
|
|
596
726
|
}) => {
|
|
597
|
-
const adapter = getAdapter(adapterName);
|
|
727
|
+
const adapter = adapterRegistry.getAdapter(adapterName);
|
|
598
728
|
if (!adapter) {
|
|
599
729
|
throw new Error(
|
|
600
|
-
`Unknown adapter "${adapterName}". Known: ${listAdapterNames().join(', ')}`,
|
|
730
|
+
`Unknown adapter "${adapterName}". Known: ${adapterRegistry.listAdapterNames().join(', ')}`,
|
|
601
731
|
);
|
|
602
732
|
}
|
|
603
733
|
|
|
@@ -613,7 +743,13 @@ export const performAttach = async ({
|
|
|
613
743
|
let workspace = null;
|
|
614
744
|
if (envPath) {
|
|
615
745
|
environment = await parseEnvironmentFile(envPath);
|
|
746
|
+
if (environment?.provider !== undefined && adapterName !== 'opencode') {
|
|
747
|
+
throw new Error('environment.provider is supported only by the opencode adapter');
|
|
748
|
+
}
|
|
616
749
|
workspace = await resolveWorkspace(environment, agentName, dirname(envPath));
|
|
750
|
+
if (typeof adapter.validateEnvironment === 'function') {
|
|
751
|
+
await adapter.validateEnvironment(environment, workspace.path);
|
|
752
|
+
}
|
|
617
753
|
log(`workspace: ${workspace.path}${workspace.created ? ' (created)' : ''}`);
|
|
618
754
|
|
|
619
755
|
const { mode: sandboxMode } = resolveAttachSandbox({ environment, adapterName });
|
|
@@ -648,15 +784,16 @@ export const performAttach = async ({
|
|
|
648
784
|
+ `${seatbelt.error}. On Linux use sandbox.mode=bwrap.`,
|
|
649
785
|
);
|
|
650
786
|
}
|
|
787
|
+
const adapterLabel = adapterName === 'opencode' ? 'OpenCode' : 'Claude';
|
|
651
788
|
log(
|
|
652
789
|
`sandbox: public ${sandboxMode} via macOS Seatbelt `
|
|
653
|
-
+
|
|
790
|
+
+ `(deny-by-default host filesystem; ${adapterLabel}/MCP network retained)`,
|
|
654
791
|
);
|
|
655
792
|
}
|
|
656
793
|
} else if (sandboxMode !== 'none' && sandboxMode !== undefined) {
|
|
657
794
|
throw new Error(
|
|
658
795
|
`sandbox.mode=${sandboxMode} is not yet implemented in the local-CLI driver. `
|
|
659
|
-
+ `Supported locally: none, bwrap, and public
|
|
796
|
+
+ `Supported locally: none, bwrap, and public Codex/Claude/OpenCode workspace/read-only.`,
|
|
660
797
|
);
|
|
661
798
|
}
|
|
662
799
|
} else {
|
|
@@ -719,9 +856,10 @@ export const performAttach = async ({
|
|
|
719
856
|
config: {
|
|
720
857
|
runtime: {
|
|
721
858
|
runtimeType,
|
|
859
|
+
adapter: adapterName,
|
|
722
860
|
host: 'byo',
|
|
723
861
|
},
|
|
724
|
-
...(environment ? { environment } : {}),
|
|
862
|
+
...(environment ? { environment: environmentForServer(environment) } : {}),
|
|
725
863
|
// ADR-018: ambient wake is a per-install opt-in read by
|
|
726
864
|
// agentMentionService (`config.wakeOnMessage.enabled === true`, default
|
|
727
865
|
// OFF). Until now the only way to set it for a BYO seat was a DB write.
|
|
@@ -968,6 +1106,9 @@ export const performRun = ({
|
|
|
968
1106
|
revokeOrphansImpl = revokeOrphanedSpawnCredentials,
|
|
969
1107
|
sleepImpl = (ms) => new Promise((resolve) => { setTimeout(resolve, ms); }),
|
|
970
1108
|
}) => {
|
|
1109
|
+
if (environment?.provider !== undefined && adapter?.name !== 'opencode') {
|
|
1110
|
+
throw new Error('environment.provider is supported only by the opencode adapter');
|
|
1111
|
+
}
|
|
971
1112
|
const client = createClient({ instance: instanceUrl, token });
|
|
972
1113
|
|
|
973
1114
|
// THE BOOT SWEEP IS THE SECOND NET, AND IT HAPPENS ONCE PER PROCESS. A seat
|
|
@@ -1594,7 +1735,35 @@ export const performRun = ({
|
|
|
1594
1735
|
uploadName: `${agentName}-reply-${event._id}.md`,
|
|
1595
1736
|
log: (line) => log(`[${event.type}] ${line}`),
|
|
1596
1737
|
});
|
|
1597
|
-
if (delivery.
|
|
1738
|
+
if (delivery.skipped) {
|
|
1739
|
+
// The server answered 200 and created nothing: a duplicate inside its
|
|
1740
|
+
// dedupe window, a silent body, a heartbeat it keeps. Nothing reached
|
|
1741
|
+
// the pod, so this is not a delivery and must not read as one in the
|
|
1742
|
+
// log — "posted 151 bytes as 1 message" over a skip is how a silent
|
|
1743
|
+
// seat looked like a talking one (hq-support, 2026-10-09 08:10Z). Ack
|
|
1744
|
+
// it like a refusal: re-posting the same text inside the window would
|
|
1745
|
+
// only be skipped again.
|
|
1746
|
+
deliveryRefusal = delivery;
|
|
1747
|
+
const windowMinutes = delivery.duplicate?.dedupeWindowMinutes;
|
|
1748
|
+
const detail = [
|
|
1749
|
+
windowMinutes ? `${windowMinutes} min window` : null,
|
|
1750
|
+
delivery.duplicate?.id ? `duplicate of ${delivery.duplicate.id}` : null,
|
|
1751
|
+
delivery.attemptedMessages > 1
|
|
1752
|
+
? `after ${delivery.messages}/${delivery.attemptedMessages} messages`
|
|
1753
|
+
: null,
|
|
1754
|
+
].filter(Boolean).join(', ');
|
|
1755
|
+
log(`[${event.type}] server skipped the post: ${delivery.reason}${detail ? ` (${detail})` : ''} — nothing reached the pod`);
|
|
1756
|
+
onError?.(Object.assign(
|
|
1757
|
+
new Error(`${event.type} server skipped the post: ${delivery.reason}`),
|
|
1758
|
+
{
|
|
1759
|
+
code: 'agent_delivery_skipped',
|
|
1760
|
+
reason: delivery.reason,
|
|
1761
|
+
eventId: event._id,
|
|
1762
|
+
postedMessages: delivery.messages,
|
|
1763
|
+
attemptedMessages: delivery.attemptedMessages,
|
|
1764
|
+
},
|
|
1765
|
+
));
|
|
1766
|
+
} else if (delivery.refused) {
|
|
1598
1767
|
// A run-cap refusal is a successful HTTP request but not a delivery.
|
|
1599
1768
|
// Ack it so the kernel does not replay the same text (the server
|
|
1600
1769
|
// guidance expressly says not to retry unchanged), while preserving
|
|
@@ -1640,6 +1809,8 @@ export const performRun = ({
|
|
|
1640
1809
|
if (deliveryRefusal) {
|
|
1641
1810
|
return {
|
|
1642
1811
|
outcome: 'no_action',
|
|
1812
|
+
// Kernel class, from REFUSAL_REASONS: a skip is the server declining to
|
|
1813
|
+
// deliver, same class as a refusal; the specific reason rides below.
|
|
1643
1814
|
refused: { reason: 'delivery-refused' },
|
|
1644
1815
|
reason: deliveryRefusal.reason,
|
|
1645
1816
|
details: {
|
|
@@ -2540,7 +2711,7 @@ Docs:
|
|
|
2540
2711
|
// ── attach (ADR-005, ADR-008) ─────────────────────────────────────────────
|
|
2541
2712
|
agent
|
|
2542
2713
|
.command('attach <adapter>')
|
|
2543
|
-
.description('Wrap a local CLI as a Commonly agent (stub|claude|codex|…)')
|
|
2714
|
+
.description('Wrap a local CLI as a Commonly agent (stub|claude|codex|opencode|…)')
|
|
2544
2715
|
.requiredOption('--pod <podId>', 'Pod ID to install into')
|
|
2545
2716
|
.requiredOption('--name <name>', 'Agent name (e.g. my-claude)')
|
|
2546
2717
|
.option('--display <name>', 'Display name shown in the pod')
|
|
@@ -2616,7 +2787,15 @@ Docs:
|
|
|
2616
2787
|
// The mention handle is the instanceId (what the UI dropdown inserts),
|
|
2617
2788
|
// not the registry agentName — say it here so users know how to ping it.
|
|
2618
2789
|
const handle = instanceId && instanceId !== 'default' ? instanceId : installation.agentName;
|
|
2619
|
-
|
|
2790
|
+
const binding = await checkAttachedAdapterBinding({
|
|
2791
|
+
adapterName: wrappedCli,
|
|
2792
|
+
agentName: opts.name,
|
|
2793
|
+
podId: opts.pod,
|
|
2794
|
+
instanceId,
|
|
2795
|
+
instanceUrl,
|
|
2796
|
+
runtimeToken,
|
|
2797
|
+
});
|
|
2798
|
+
printAttachRunGuidance({ agentName: opts.name, binding });
|
|
2620
2799
|
console.log(` Mention as: @${handle}`);
|
|
2621
2800
|
} catch (err) {
|
|
2622
2801
|
console.error(`Attach failed: ${err.message}`);
|
|
@@ -2714,7 +2893,7 @@ Docs:
|
|
|
2714
2893
|
.command('run <name>')
|
|
2715
2894
|
.description('Run the local-CLI wrapper loop for an attached agent')
|
|
2716
2895
|
.option('--interval <ms>', 'Poll interval in ms', '5000')
|
|
2717
|
-
.option('--adapter <name>', 'CLI to wrap on first-run bootstrap (claude|codex); an existing token file bound to a different adapter stops the run')
|
|
2896
|
+
.option('--adapter <name>', 'CLI to wrap on first-run bootstrap (claude|codex|pi|opencode); an existing token file bound to a different adapter stops the run')
|
|
2718
2897
|
.option('--cascade-cap <n>', `Consecutive agent-triggered turns allowed per pod (env ${CASCADE_ENV_VARS.cap}, default ${CASCADE_DEFAULTS.cap})`)
|
|
2719
2898
|
.option('--cascade-grace <n>', `Extra turns allowed when this seat was directly addressed; 0 disables the grace (env ${CASCADE_ENV_VARS.addressedGrace}, default ${CASCADE_DEFAULTS.addressedGrace})`)
|
|
2720
2899
|
.option('--cascade-reset <ms>', `Silence window that clears the streak (env ${CASCADE_ENV_VARS.resetMs}, default ${CASCADE_DEFAULTS.resetMs})`)
|
|
@@ -2760,6 +2939,30 @@ Docs:
|
|
|
2760
2939
|
process.exit(1);
|
|
2761
2940
|
}
|
|
2762
2941
|
|
|
2942
|
+
if (adapterRequiresServerBinding(adapter.name)) {
|
|
2943
|
+
let identity;
|
|
2944
|
+
try {
|
|
2945
|
+
identity = await createClient({
|
|
2946
|
+
instance: record.instanceUrl,
|
|
2947
|
+
token: record.runtimeToken,
|
|
2948
|
+
}).get('/api/agents/runtime/installations');
|
|
2949
|
+
} catch (err) {
|
|
2950
|
+
console.error(`${stamp()} [${name}] could not read the server-declared adapter: ${err.message}`);
|
|
2951
|
+
process.exit(1);
|
|
2952
|
+
}
|
|
2953
|
+
const bindingRefusal = serverAdapterBindingRefusal({
|
|
2954
|
+
adapterName: adapter.name,
|
|
2955
|
+
agentName: record.agentName,
|
|
2956
|
+
installations: identity?.installations,
|
|
2957
|
+
podId: record.podId,
|
|
2958
|
+
instanceId: record.instanceId,
|
|
2959
|
+
});
|
|
2960
|
+
if (bindingRefusal) {
|
|
2961
|
+
console.error(`${stamp()} [${name}] ${bindingRefusal}`);
|
|
2962
|
+
process.exit(1);
|
|
2963
|
+
}
|
|
2964
|
+
}
|
|
2965
|
+
|
|
2763
2966
|
// THE line to stamp, not just one of them. It is the first line of every
|
|
2764
2967
|
// seat log and the truncation boundary — the fleet is launched as
|
|
2765
2968
|
// `nohup … > log 2>&1`, so this banner is written at boot and everything
|
package/src/commands/daemon.js
CHANGED
|
@@ -136,8 +136,8 @@ export const unregisterDaemonMachine = async ({ client, record, remove = removeD
|
|
|
136
136
|
|
|
137
137
|
// The adapter names a binary on THIS machine — the one fact the server cannot
|
|
138
138
|
// know (same reasoning as `agent run`'s env bootstrap). A server-declared
|
|
139
|
-
// preference is honored when that CLI is installed; otherwise probe the
|
|
140
|
-
//
|
|
139
|
+
// preference is honored when that CLI is installed; otherwise probe only the
|
|
140
|
+
// established fallbacks. OpenCode must be named explicitly in runtime.adapter.
|
|
141
141
|
export const resolveAdapterForRuntime = async (runtime, registry = { getAdapter }) => {
|
|
142
142
|
const candidates = [runtime?.adapter, 'claude', 'codex'].filter(Boolean);
|
|
143
143
|
for (const name of candidates) {
|
|
@@ -631,10 +631,11 @@ const prepareArgv = async (innerArgv, ctx) => {
|
|
|
631
631
|
const wrapped = wrapArgvWithSeatbelt([claudeBin, ...innerArgv], {
|
|
632
632
|
workspacePath: ctx.cwd,
|
|
633
633
|
workspaceAccess: sandboxMode === 'read-only' ? 'read' : 'write',
|
|
634
|
-
|
|
634
|
+
executablePath: claudeBin,
|
|
635
635
|
statePath: ctx.publicClaudeState.statePath,
|
|
636
636
|
mcpConfigDir: ctx.mcpConfigDir,
|
|
637
637
|
executablePaths: [...mcpExecutables, ...commitHookExecutables, ...originalHookExecutables],
|
|
638
|
+
allowClaudeRuntimeAccess: true,
|
|
638
639
|
});
|
|
639
640
|
return {
|
|
640
641
|
cmd: wrapped[0],
|
|
@@ -11,12 +11,14 @@ import stub from './stub.js';
|
|
|
11
11
|
import claude from './claude.js';
|
|
12
12
|
import codex from './codex.js';
|
|
13
13
|
import pi from './pi.js';
|
|
14
|
+
import opencode from './opencode.js';
|
|
14
15
|
|
|
15
16
|
const ADAPTERS = {
|
|
16
17
|
[stub.name]: stub,
|
|
17
18
|
[claude.name]: claude,
|
|
18
19
|
[codex.name]: codex,
|
|
19
20
|
[pi.name]: pi,
|
|
21
|
+
[opencode.name]: opencode,
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
export const listAdapterNames = () => Object.keys(ADAPTERS);
|