@commonlyai/cli 0.1.70 → 0.1.72
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 +74 -19
- package/src/lib/adapters/pi.js +16 -2
- package/src/lib/default-environment.js +11 -3
package/package.json
CHANGED
package/src/commands/agent.js
CHANGED
|
@@ -32,7 +32,7 @@ import { readLongTerm, syncBack } from '../lib/memory-bridge.js';
|
|
|
32
32
|
import { pollRetryPolicy } from '../lib/poll-retry.js';
|
|
33
33
|
import { detectMemorySources, composeImport, importMemory } from '../lib/memory-import.js';
|
|
34
34
|
import { detectSkills, importSkills } from '../lib/skills-import.js';
|
|
35
|
-
import { parseEnvironmentFile, resolveWorkspace, validateEnvironmentSpec } from '../lib/environment.js';
|
|
35
|
+
import { normalizeSandboxTrust, parseEnvironmentFile, resolveWorkspace, validateEnvironmentSpec } from '../lib/environment.js';
|
|
36
36
|
import { ADAPTERS_WITH_DEFAULT_MCP, defaultMcpServers } from '../lib/default-environment.js';
|
|
37
37
|
import { withholdGrantBroker } from '../lib/grant-broker-guard.js';
|
|
38
38
|
import {
|
|
@@ -331,8 +331,14 @@ export const assertSandboxDeclaredForPublicPod = async ({
|
|
|
331
331
|
}) => {
|
|
332
332
|
if (!podId || !client) return;
|
|
333
333
|
|
|
334
|
-
|
|
335
|
-
|
|
334
|
+
// Normalized FIRST (Vera 71715, Wren 71718): the predicate below reads a
|
|
335
|
+
// legacy `internal` record as the `public` it means, the same mapping the
|
|
336
|
+
// attach gate and both adapters use. Reading it raw refused a record that
|
|
337
|
+
// means public one call before the gate #1840 fixes — TASK-113 claims
|
|
338
|
+
// attach's inconsistent derivation is closed, and this path is attach's.
|
|
339
|
+
const sandbox = normalizeSandboxTrust(environment?.sandbox);
|
|
340
|
+
const mode = sandbox?.mode;
|
|
341
|
+
const trust = sandbox?.trust;
|
|
336
342
|
// An ENFORCED declaration is one the adapters act on: a public trust (whose
|
|
337
343
|
// mode they resolve per host at spawn) or an explicit non-'none' mode. This
|
|
338
344
|
// mirrors the daemon's predicate in lib/default-environment.js, so the shape
|
|
@@ -477,6 +483,70 @@ export const updateAgentConfiguration = async ({
|
|
|
477
483
|
|
|
478
484
|
// ── attach: register a local-CLI-wrapped agent (ADR-005) ────────────────────
|
|
479
485
|
|
|
486
|
+
/**
|
|
487
|
+
* The sandbox an attach will actually run under — derived, then gated.
|
|
488
|
+
*
|
|
489
|
+
* The trust is normalized ONCE, before it is used for anything. A stored
|
|
490
|
+
* `internal` means `public` (environment.js, Wren 69585), and resolving the mode
|
|
491
|
+
* off the raw value made this gate disagree with the adapters in BOTH
|
|
492
|
+
* directions: `{ trust: 'internal' }` with no declared mode left the attach-time
|
|
493
|
+
* mode at `'none'` and refused nothing here, and `internal` beside
|
|
494
|
+
* `mode: 'workspace'` was refused outright as "implemented only for public codex
|
|
495
|
+
* or Claude adapters" — turning away a record both adapters confine happily.
|
|
496
|
+
*
|
|
497
|
+
* This does NOT take the confinement decision from the adapters. They normalize
|
|
498
|
+
* and re-derive from the environment themselves (claude.js:615, codex.js:546) and
|
|
499
|
+
* the attach-time value never reaches them, so a legacy record was confined
|
|
500
|
+
* either way; what was wrong is that this gate's verdict contradicted theirs, and
|
|
501
|
+
* its refusal for the `mode: 'none'` shape landed later, at adapter spawn, after
|
|
502
|
+
* attach had already published and minted. Normalizing here makes the two agree
|
|
503
|
+
* and puts the refusal where the user can see it (Kai, TASK-113; re-measured at
|
|
504
|
+
* source after Vera 71664).
|
|
505
|
+
*
|
|
506
|
+
* Whether the resolved mode is AVAILABLE here — bwrap installed, macOS for
|
|
507
|
+
* Seatbelt, the codex permission-profile version — is deliberately not this
|
|
508
|
+
* function's question: that needs the host and the detected version and stays at
|
|
509
|
+
* the call site. What an adapter can honour at all is just a mode and a name, so
|
|
510
|
+
* that check is in here, with witnesses of both signs.
|
|
511
|
+
*
|
|
512
|
+
* Why this gate was the one that disagreed: the resolver compared the RAW trust,
|
|
513
|
+
* so a caller that did not normalize first read a legacy `internal` record as
|
|
514
|
+
* non-public. The adapters normalize at their own entry (claude.js:615,
|
|
515
|
+
* codex.js:546) and passed the normalized object on, which is why their suites
|
|
516
|
+
* confined a legacy record all along; the grant-broker guard normalized for its
|
|
517
|
+
* own trust check and then handed the resolver the raw object, so its symptom was
|
|
518
|
+
* a false `sandbox_mode_unenforceable` refusal (Vera 71676). Both are settled on
|
|
519
|
+
* main now — the resolver reads the effective trust (mode.js:38) and the guard
|
|
520
|
+
* imports the same reader from environment.js (grant-broker-guard.js:186) — so
|
|
521
|
+
* this helper needs no coupling to them: it normalizes once, here, before its own
|
|
522
|
+
* two gates, because those gates compare `trust` directly.
|
|
523
|
+
*/
|
|
524
|
+
export const resolveAttachSandbox = ({
|
|
525
|
+
environment, adapterName, platform = process.platform,
|
|
526
|
+
} = {}) => {
|
|
527
|
+
const sandbox = normalizeSandboxTrust(environment?.sandbox);
|
|
528
|
+
const trust = sandbox?.trust;
|
|
529
|
+
const mode = sandbox?.mode
|
|
530
|
+
|| (trust === 'public' ? resolvePublicSandboxMode(sandbox, platform) : 'none');
|
|
531
|
+
if (trust === 'public' && mode === 'none') {
|
|
532
|
+
throw new Error(
|
|
533
|
+
'sandbox.trust=public requires an enforced sandbox mode; refusing to attach unsandboxed',
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
// Moved in from the call site (Vera 71675): this is the check the raw compare
|
|
537
|
+
// used to fail closed on, refusing `internal` beside `mode: 'workspace'`. It is
|
|
538
|
+
// pure — a mode and an adapter name — so it belongs with the derivation, and
|
|
539
|
+
// its witnesses live beside it.
|
|
540
|
+
if ((mode === 'workspace' || mode === 'read-only')
|
|
541
|
+
&& (trust !== 'public' || !['codex', 'claude'].includes(adapterName))) {
|
|
542
|
+
throw new Error(
|
|
543
|
+
`sandbox.mode=${mode} is currently implemented only for public `
|
|
544
|
+
+ 'codex or Claude adapters',
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
return { mode, trust };
|
|
548
|
+
};
|
|
549
|
+
|
|
480
550
|
/**
|
|
481
551
|
* Publish, install, and mint a runtime token for a local-CLI-wrapped agent.
|
|
482
552
|
* Pure core — the commander action wraps this with config loading + logging.
|
|
@@ -513,16 +583,7 @@ export const performAttach = async ({
|
|
|
513
583
|
workspace = await resolveWorkspace(environment, agentName, dirname(envPath));
|
|
514
584
|
log(`workspace: ${workspace.path}${workspace.created ? ' (created)' : ''}`);
|
|
515
585
|
|
|
516
|
-
const sandboxMode = environment
|
|
517
|
-
|| (environment.sandbox?.trust === 'public'
|
|
518
|
-
? resolvePublicSandboxMode(environment.sandbox)
|
|
519
|
-
: 'none');
|
|
520
|
-
const sandboxTrust = environment.sandbox?.trust;
|
|
521
|
-
if (sandboxTrust === 'public' && sandboxMode === 'none') {
|
|
522
|
-
throw new Error(
|
|
523
|
-
'sandbox.trust=public requires an enforced sandbox mode; refusing to attach unsandboxed',
|
|
524
|
-
);
|
|
525
|
-
}
|
|
586
|
+
const { mode: sandboxMode } = resolveAttachSandbox({ environment, adapterName });
|
|
526
587
|
if (sandboxMode === 'bwrap') {
|
|
527
588
|
const bwrap = detectBwrap();
|
|
528
589
|
if (!bwrap.available) {
|
|
@@ -535,12 +596,6 @@ export const performAttach = async ({
|
|
|
535
596
|
);
|
|
536
597
|
}
|
|
537
598
|
} else if (sandboxMode === 'workspace' || sandboxMode === 'read-only') {
|
|
538
|
-
if (sandboxTrust !== 'public' || !['codex', 'claude'].includes(adapterName)) {
|
|
539
|
-
throw new Error(
|
|
540
|
-
`sandbox.mode=${sandboxMode} is currently implemented only for public `
|
|
541
|
-
+ `codex or Claude adapters`,
|
|
542
|
-
);
|
|
543
|
-
}
|
|
544
599
|
if (adapterName === 'codex') {
|
|
545
600
|
if (!versionAtLeast(detected.version, CODEX_PERMISSION_PROFILE_MIN_VERSION)) {
|
|
546
601
|
throw new Error(
|
package/src/lib/adapters/pi.js
CHANGED
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
spawnCredentials,
|
|
44
44
|
} from '../upstream-refusal.js';
|
|
45
45
|
import { buildMemoryPreamble } from '../memory-bridge.js';
|
|
46
|
+
import { effectiveSandboxTrust } from '../environment.js';
|
|
46
47
|
import { GRANT_BROKER_REFUSAL, isGrantBrokerUrl } from './pi-mcp-client.mjs';
|
|
47
48
|
import { deliverSeatCredential, withholdRuntimeCredential } from '../mcp-credential-delivery.js';
|
|
48
49
|
import { removeCredentialFile, writeCredentialFile } from '../credential-file.js';
|
|
@@ -283,10 +284,23 @@ export const sessionExists = async (sessionDir, sessionId) => {
|
|
|
283
284
|
}
|
|
284
285
|
};
|
|
285
286
|
|
|
286
|
-
/**
|
|
287
|
+
/**
|
|
288
|
+
* Fail closed: pi cannot enforce `environment.sandbox`, so a seat that declares
|
|
289
|
+
* one does not start.
|
|
290
|
+
*
|
|
291
|
+
* The trust is read THROUGH the legacy table (`effectiveSandboxTrust`), not
|
|
292
|
+
* raw. A stored `internal` means `public` (Vera 69592, Wren 69585) and every
|
|
293
|
+
* other gate in this package resolves it that way: the attach path
|
|
294
|
+
* (`agent.js`), the mode resolver (`sandbox/mode.js`) and the grant-broker
|
|
295
|
+
* guard. Reading it raw here made this gate the one site that failed OPEN — a
|
|
296
|
+
* legacy record passed both checks and the seat started unconfined, which is
|
|
297
|
+
* the exact outcome that was refused for the record it was migrated from.
|
|
298
|
+
* The mode clause needs no mapping: `internal` and the derived mode are
|
|
299
|
+
* independent, and a public-meaning record is refused before the mode is read.
|
|
300
|
+
*/
|
|
287
301
|
export const assertNoSandboxDeclared = (environment = {}) => {
|
|
288
302
|
const sandbox = environment?.sandbox || {};
|
|
289
|
-
if (sandbox.trust === 'public') {
|
|
303
|
+
if (effectiveSandboxTrust(sandbox.trust) === 'public') {
|
|
290
304
|
throw new Error('pi adapter: public-trust seats are not supported — pi has no enforced sandbox; do not attach a pi seat to a stranger-readable pod');
|
|
291
305
|
}
|
|
292
306
|
if (sandbox.mode && sandbox.mode !== 'none') {
|
|
@@ -22,6 +22,8 @@
|
|
|
22
22
|
* declaration itself carries no secret and is safe to persist to a token file.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
+
import { normalizeSandboxTrust } from './environment.js';
|
|
26
|
+
|
|
25
27
|
export const ADAPTERS_WITH_DEFAULT_MCP = new Set(['claude', 'codex', 'pi']);
|
|
26
28
|
|
|
27
29
|
/**
|
|
@@ -30,8 +32,9 @@ export const ADAPTERS_WITH_DEFAULT_MCP = new Set(['claude', 'codex', 'pi']);
|
|
|
30
32
|
*
|
|
31
33
|
* `pi` is absent deliberately. It has no sandbox path until #1740's transport
|
|
32
34
|
* work gives it one, and since #1727 it REFUSES TO START on a spec that
|
|
33
|
-
* declares one: `assertNoSandboxDeclared` in adapters/pi.js throws on
|
|
34
|
-
* `trust: 'public'`
|
|
35
|
+
* declares one: `assertNoSandboxDeclared` in adapters/pi.js throws on an
|
|
36
|
+
* effective `trust: 'public'` (a legacy `internal` resolves to it) and on any
|
|
37
|
+
* `mode` other than 'none'. So handing pi this
|
|
35
38
|
* block is not a harmless no-op — it is an unspawnable seat. A pi seat gets the
|
|
36
39
|
* `mcp[]` half only, and the residual is that such a seat runs unconfined: that
|
|
37
40
|
* belongs to the row that owns pi confinement, not to a declaration written
|
|
@@ -129,7 +132,12 @@ export const withDefaultSandbox = (environment) => {
|
|
|
129
132
|
&& (typeof environment !== 'object' || Array.isArray(environment))) {
|
|
130
133
|
return environment;
|
|
131
134
|
}
|
|
132
|
-
|
|
135
|
+
// Normalized first (Wren 71719): `assertSandboxDeclaredForPublicPod`'s own
|
|
136
|
+
// comment says this predicate mirrors it, and a legacy `internal` record
|
|
137
|
+
// means `public` to both. Read raw, this one replaced a record the attach
|
|
138
|
+
// gate accepts — benign (same effective trust) but not a mirror, and the
|
|
139
|
+
// comment would be false. The ORIGINAL block is what gets returned below.
|
|
140
|
+
const sandbox = normalizeSandboxTrust(environment?.sandbox);
|
|
133
141
|
// An ENFORCED declaration is one the adapters act on: a public trust (whose
|
|
134
142
|
// mode they resolve, and refuse to spawn without) or an explicit non-'none'
|
|
135
143
|
// mode. `mode: 'none'`, an empty block and a missing one are all the same
|