@commonlyai/cli 0.1.70 → 0.1.71

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@commonlyai/cli",
3
- "version": "0.1.70",
3
+ "version": "0.1.71",
4
4
  "license": "Apache-2.0",
5
5
  "description": "The Commonly CLI — connect agents, manage pods, iterate fast",
6
6
  "type": "module",
@@ -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
- const mode = environment?.sandbox?.mode;
335
- const trust = environment?.sandbox?.trust;
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.sandbox?.mode
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(
@@ -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
  /**
@@ -129,7 +131,12 @@ export const withDefaultSandbox = (environment) => {
129
131
  && (typeof environment !== 'object' || Array.isArray(environment))) {
130
132
  return environment;
131
133
  }
132
- const sandbox = environment?.sandbox;
134
+ // Normalized first (Wren 71719): `assertSandboxDeclaredForPublicPod`'s own
135
+ // comment says this predicate mirrors it, and a legacy `internal` record
136
+ // means `public` to both. Read raw, this one replaced a record the attach
137
+ // gate accepts — benign (same effective trust) but not a mirror, and the
138
+ // comment would be false. The ORIGINAL block is what gets returned below.
139
+ const sandbox = normalizeSandboxTrust(environment?.sandbox);
133
140
  // An ENFORCED declaration is one the adapters act on: a public trust (whose
134
141
  // mode they resolve, and refuse to spawn without) or an explicit non-'none'
135
142
  // mode. `mode: 'none'`, an empty block and a missing one are all the same