@commonlyai/cli 0.1.196 → 0.1.198
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/node_modules/@commonlyai/seats/README.md +2 -3
- package/package.json +1 -1
- package/src/commands/agent.js +12 -14
- package/src/lib/adapters/claude.js +11 -11
- package/src/lib/adapters/codex.js +2 -2
- package/src/lib/adapters/dsh.js +2 -2
- package/src/lib/adapters/pi.js +6 -7
- package/src/lib/daemon-supervisor.js +1 -1
- package/src/lib/enforcement.js +3 -3
- package/src/lib/environment.js +6 -6
- package/src/lib/memory-bridge.js +1 -1
- package/src/lib/public-tool-error.js +1 -1
- package/src/lib/sandbox/mode.js +3 -4
- package/src/lib/sandbox/seatbelt.js +1 -1
- package/src/lib/spawn-retry.js +1 -1
- package/src/lib/webhook-server.js +2 -2
|
@@ -15,12 +15,11 @@ It is private and is not published on its own. `@commonlyai/cli` depends on it a
|
|
|
15
15
|
- Operator-pinned fleet seats select their CLI path in each seat's local `run.sh` launcher,
|
|
16
16
|
outside this repo. Update that pin and restart the seat to load a release.
|
|
17
17
|
Global CLI installs must be upgraded too; publishing does not update a running seat.
|
|
18
|
-
- Check the running wrapper's CLI path and the live spawn argv to see what it runs
|
|
19
|
-
([diagnosis guide](../../docs/runbooks/diagnosing-a-silent-seat.md#2-read-the-live-spawn--the-prompt-is-in-argv)).
|
|
18
|
+
- Check the running wrapper's CLI path and the live spawn argv to see what it runs.
|
|
20
19
|
The registry describes configuration; it does not prove what was loaded.
|
|
21
20
|
|
|
22
21
|
**Rule:** nothing here imports Commonly code. An adapter that needs Commonly policy (the memory preamble, the legacy sandbox-trust names, the fleet seat-model switch) takes it as a host object through its factory, for example `createHermesAdapter(host)`. The CLI binds that host in `cli/src/lib/adapters/<name>.js`. `cli/__tests__/seats-package.test.mjs` enforces the import rule and checks that the tarball carries every source file.
|
|
23
22
|
|
|
24
|
-
The extraction plan is
|
|
23
|
+
The extraction plan is extract-in-place.
|
|
25
24
|
|
|
26
25
|
**Run record.** The trace store writes each run as a versioned, hash-chained JSONL record. It covers who granted which agent what access for which task, who approved each call, what was attempted, and the result. A tool outside this package can read, verify and export it to OTLP. The format is specified in [`docs/record-v1.md`](docs/record-v1.md), and a generated sample is in [`docs/examples/`](docs/examples/).
|
package/package.json
CHANGED
package/src/commands/agent.js
CHANGED
|
@@ -178,8 +178,7 @@ export const NO_AGENT_CLI_CODE = 'no_agent_cli';
|
|
|
178
178
|
* workspace, and an environment that is absent or enforces nothing (a record
|
|
179
179
|
* from before bootstrap applied the default, or one attached without an
|
|
180
180
|
* environment file). Such a record is told how to pick the default up; it is
|
|
181
|
-
* not rewritten here
|
|
182
|
-
* tests).
|
|
181
|
+
* not rewritten here.
|
|
183
182
|
*/
|
|
184
183
|
export const savedSeatLacksDefaultSandbox = (record) => Boolean(record)
|
|
185
184
|
&& ADAPTERS_WITH_DEFAULT_SANDBOX.has(record.adapter)
|
|
@@ -717,7 +716,7 @@ export const assertSandboxDeclaredForPublicPod = async ({
|
|
|
717
716
|
);
|
|
718
717
|
}
|
|
719
718
|
|
|
720
|
-
// Normalized FIRST
|
|
719
|
+
// Normalized FIRST: the predicate below reads a
|
|
721
720
|
// legacy `internal` record as the `public` it means, the same mapping the
|
|
722
721
|
// attach gate and both adapters use. Reading it raw refused a record that
|
|
723
722
|
// means public one call before the gate #1840 fixes — TASK-113 claims
|
|
@@ -904,7 +903,7 @@ export const updateAgentConfiguration = async ({
|
|
|
904
903
|
* The sandbox an attach will actually run under — derived, then gated.
|
|
905
904
|
*
|
|
906
905
|
* The trust is normalized ONCE, before it is used for anything. A stored
|
|
907
|
-
* `internal` means `public` (environment.js
|
|
906
|
+
* `internal` means `public` (environment.js), and resolving the mode
|
|
908
907
|
* off the raw value made this gate disagree with the adapters in BOTH
|
|
909
908
|
* directions: `{ trust: 'internal' }` with no declared mode left the attach-time
|
|
910
909
|
* mode at `'none'` and refused nothing here, and `internal` beside
|
|
@@ -917,8 +916,7 @@ export const updateAgentConfiguration = async ({
|
|
|
917
916
|
* either way; what was wrong is that this gate's verdict contradicted theirs, and
|
|
918
917
|
* its refusal for the `mode: 'none'` shape landed later, at adapter spawn, after
|
|
919
918
|
* attach had already published and minted. Normalizing here makes the two agree
|
|
920
|
-
* and puts the refusal where the user can see it (
|
|
921
|
-
* source after Vera 71664).
|
|
919
|
+
* and puts the refusal where the user can see it (TASK-113).
|
|
922
920
|
*
|
|
923
921
|
* Whether the resolved mode is AVAILABLE here — bwrap installed, macOS for
|
|
924
922
|
* Seatbelt, the codex permission-profile version — is deliberately not this
|
|
@@ -932,7 +930,7 @@ export const updateAgentConfiguration = async ({
|
|
|
932
930
|
* codex.js:546) and passed the normalized object on, which is why their suites
|
|
933
931
|
* confined a legacy record all along; the grant-broker guard normalized for its
|
|
934
932
|
* own trust check and then handed the resolver the raw object, so its symptom was
|
|
935
|
-
* a false `sandbox_mode_unenforceable` refusal
|
|
933
|
+
* a false `sandbox_mode_unenforceable` refusal. Both are settled on
|
|
936
934
|
* main now — the resolver reads the effective trust (mode.js:38) and the guard
|
|
937
935
|
* imports the same reader from environment.js (grant-broker-guard.js:186) — so
|
|
938
936
|
* this helper needs no coupling to them: it normalizes once, here, before its own
|
|
@@ -944,7 +942,7 @@ export const resolveAttachSandbox = ({
|
|
|
944
942
|
// An adapter that enforces one confined shape of its own (dsh: Seatbelt with a
|
|
945
943
|
// restricted network and NO trust) says so here, and its check is the one that
|
|
946
944
|
// decides: the trust-keyed gates below cannot express that shape, so before
|
|
947
|
-
// this they refused the only environment dsh accepts
|
|
945
|
+
// this they refused the only environment dsh accepts. The adapter's
|
|
948
946
|
// check throws on every other shape, so this admits nothing it would refuse.
|
|
949
947
|
if (typeof adapter?.confinesOwnSandbox === 'function' && adapter.confinesOwnSandbox(environment)) {
|
|
950
948
|
return { mode: environment.sandbox.mode, trust: environment.sandbox.trust };
|
|
@@ -967,7 +965,7 @@ export const resolveAttachSandbox = ({
|
|
|
967
965
|
`sandbox.trust=${trust} requires an enforced sandbox mode; refusing to attach unsandboxed`,
|
|
968
966
|
);
|
|
969
967
|
}
|
|
970
|
-
// Moved in from the call site
|
|
968
|
+
// Moved in from the call site: this is the check the raw compare
|
|
971
969
|
// used to fail closed on, refusing `internal` beside `mode: 'workspace'`. It is
|
|
972
970
|
// pure — a mode and an adapter name — so it belongs with the derivation, and
|
|
973
971
|
// its witnesses live beside it.
|
|
@@ -1490,7 +1488,7 @@ export const performRun = ({
|
|
|
1490
1488
|
// (`daemon-supervisor.js`); this is the same predicate at the only other site
|
|
1491
1489
|
// that can see the record — same code, so one refusal with two emitters.
|
|
1492
1490
|
//
|
|
1493
|
-
// IN MEMORY, RECORD UNTOUCHED
|
|
1491
|
+
// IN MEMORY, RECORD UNTOUCHED: the withheld environment is what
|
|
1494
1492
|
// the adapter spawns with, and the stored record keeps its broker, so the
|
|
1495
1493
|
// daemon stays the layer that judges a host it knows. Applied once for the
|
|
1496
1494
|
// run rather than per turn: `agent run` reads its record once at boot.
|
|
@@ -1903,7 +1901,7 @@ export const performRun = ({
|
|
|
1903
1901
|
const memoryWithheld = Boolean(placement.reason);
|
|
1904
1902
|
const memoryLongTerm = memoryWithheld ? undefined : await readLongTerm(client, { onError });
|
|
1905
1903
|
|
|
1906
|
-
//
|
|
1904
|
+
// TASK-129: focus is a turn-start read, not an enqueue-time
|
|
1907
1905
|
// snapshot. Read through the authorized runtime context route immediately
|
|
1908
1906
|
// before spawn so queued events observe the current revision. The helper
|
|
1909
1907
|
// disables pod-skill synthesis and throws on failure; the surrounding
|
|
@@ -1997,8 +1995,8 @@ export const performRun = ({
|
|
|
1997
1995
|
try {
|
|
1998
1996
|
opened = await lease.open();
|
|
1999
1997
|
} catch (err) {
|
|
2000
|
-
// A VERDICT IS NOT A CRASH, AND IT IS NOT A NO-OP
|
|
2001
|
-
//
|
|
1998
|
+
// A VERDICT IS NOT A CRASH, AND IT IS NOT A NO-OP.
|
|
1999
|
+
// Letting it escape as a bare throw left `turnResult` undefined,
|
|
2002
2000
|
// so `claimReleaseFor` returned the legacy holder-only DELETE — right for
|
|
2003
2001
|
// a transient failure, wrong for a refusal that recurs on every
|
|
2004
2002
|
// redelivery: the wake loops forever with one local log line as its only
|
|
@@ -2409,7 +2407,7 @@ export const performRun = ({
|
|
|
2409
2407
|
// its batch and chose silence; one that printed nothing may have crashed,
|
|
2410
2408
|
// timed out or lost its route without saying so. The bare `no_action`
|
|
2411
2409
|
// stays the sentinel's alone, because a digest counts as read only on
|
|
2412
|
-
// that (
|
|
2410
|
+
// that (#2753). Release, hand-off and spawn counting treat
|
|
2413
2411
|
// `empty-output` exactly as they treat a bare decline.
|
|
2414
2412
|
if (!delivered && !replyText) return { outcome: 'no_action', reason: EMPTY_OUTPUT };
|
|
2415
2413
|
return { outcome: delivered ? 'posted' : 'no_action' };
|
|
@@ -105,8 +105,8 @@ const DEFAULT_TIMEOUT_MS = (() => {
|
|
|
105
105
|
|
|
106
106
|
const buildPrompt = buildMemoryPreamble;
|
|
107
107
|
|
|
108
|
-
// The working habits a private claude seat is taught on a fresh session
|
|
109
|
-
// TASK-545). Parallel reads live here because only this CLI has a subagent
|
|
108
|
+
// The working habits a private claude seat is taught on a fresh session
|
|
109
|
+
// (TASK-545). Parallel reads live here because only this CLI has a subagent
|
|
110
110
|
// tool; codex is passed row slicing alone. A public seat is passed neither: its
|
|
111
111
|
// argv allows Read and the MCP under dontAsk, so it has no subagent to spawn,
|
|
112
112
|
// and it answers strangers rather than working board rows.
|
|
@@ -841,7 +841,7 @@ const prepareArgv = async (innerArgv, ctx) => {
|
|
|
841
841
|
// A public trust is ONE contract on both platforms: the jail bounds the
|
|
842
842
|
// filesystem and the policy bounds the tools. The bwrap branch below used to
|
|
843
843
|
// get the jail only — a derived Linux seat kept Bash/Write/WebFetch and ran on
|
|
844
|
-
// the operator's own Claude settings inside the namespace
|
|
844
|
+
// the operator's own Claude settings inside the namespace.
|
|
845
845
|
const publicBwrapSandbox = sandboxTrust === 'public' && sandboxMode === 'bwrap';
|
|
846
846
|
// A workspace seat on Linux: the same jail, with the workspace tier's policy
|
|
847
847
|
// and a HOME of its own outside the project.
|
|
@@ -867,7 +867,7 @@ const prepareArgv = async (innerArgv, ctx) => {
|
|
|
867
867
|
// above; anything else that lands here (the literal 'none', a typo, a
|
|
868
868
|
// non-string) used to fall through to the bare `claude` spawn below — an
|
|
869
869
|
// unconfined seat with a public record, which is how a record can claim
|
|
870
|
-
// confinement it never applies
|
|
870
|
+
// confinement it never applies.
|
|
871
871
|
if (isConfinedTrust(sandboxTrust) && !publicNativeSandbox && sandboxMode !== 'bwrap') {
|
|
872
872
|
throw new Error(
|
|
873
873
|
`${sandboxTrust} Claude agents require an enforced sandbox mode `
|
|
@@ -929,7 +929,7 @@ const prepareArgv = async (innerArgv, ctx) => {
|
|
|
929
929
|
if (publicBwrapSandbox || workspaceBwrapSandbox) {
|
|
930
930
|
// The mode is chosen here, so the mechanism is verified here: a missing
|
|
931
931
|
// bwrap must fail the seat's derivation with an actionable message, not
|
|
932
|
-
// every one of its spawns with a bare ENOENT
|
|
932
|
+
// every one of its spawns with a bare ENOENT. On a non-Linux
|
|
933
933
|
// host this reports the macOS-only message, same as the wrapper would.
|
|
934
934
|
const bwrap = (ctx._detectBwrap || detectBwrap)();
|
|
935
935
|
if (!bwrap.available) {
|
|
@@ -1043,7 +1043,7 @@ export default {
|
|
|
1043
1043
|
const seatModel = requireSeatModel(rawCtx.environment, 'claude');
|
|
1044
1044
|
// `sandbox.trust: 'internal'` is refused for new declarations and read as
|
|
1045
1045
|
// `public` for a record that already carries it; such a seat is confined
|
|
1046
|
-
// where it can be and refuses to derive where it cannot
|
|
1046
|
+
// where it can be and refuses to derive where it cannot.
|
|
1047
1047
|
// Resolved ONCE here and threaded to BOTH consumers — the public-state
|
|
1048
1048
|
// preparation below and the argv builder — because two independent reads of
|
|
1049
1049
|
// one declaration is exactly how this seat crashed with "public Claude
|
|
@@ -1091,9 +1091,9 @@ export default {
|
|
|
1091
1091
|
// present in one but not the other means a retry silently runs a different
|
|
1092
1092
|
// model than the turn it is replacing — the same drifting-copy shape that
|
|
1093
1093
|
// has bitten this codebase repeatedly.
|
|
1094
|
-
// `effort` rides in the same array for the same reason:
|
|
1095
|
-
//
|
|
1096
|
-
// effort"
|
|
1094
|
+
// `effort` rides in the same array for the same reason: an order
|
|
1095
|
+
// like "flip all fable agents into fable 5.1 with high or above
|
|
1096
|
+
// effort" needs both facts to reach every spawn, including the
|
|
1097
1097
|
// session-recovery retry, or a retry runs at a different effort than the
|
|
1098
1098
|
// turn it replaces.
|
|
1099
1099
|
const modelArgs = [
|
|
@@ -1133,7 +1133,7 @@ export default {
|
|
|
1133
1133
|
// Trust is normalized at entry (legacy `internal` reads as public). The
|
|
1134
1134
|
// MCP policy follows trust alone, whatever confines the seat: a public
|
|
1135
1135
|
// bwrap seat needs a host-prepared build exactly as a Seatbelt one does
|
|
1136
|
-
// (
|
|
1136
|
+
// (#2247).
|
|
1137
1137
|
if (publicNativeSandbox) {
|
|
1138
1138
|
publicClaudeState = await preparePublicClaudeState(ctx);
|
|
1139
1139
|
} else if (workspaceBwrapSandbox) {
|
|
@@ -1210,7 +1210,7 @@ export default {
|
|
|
1210
1210
|
} catch (err) {
|
|
1211
1211
|
// A public seat's refusal is not a session-store failure, whatever its
|
|
1212
1212
|
// message happens to contain: it must reach the run loop, never the
|
|
1213
|
-
// fresh-session retry below (
|
|
1213
|
+
// fresh-session retry below (#2247).
|
|
1214
1214
|
if (err?.code === PUBLIC_MCP_UNAVAILABLE_CODE) throw err;
|
|
1215
1215
|
// Self-heal against a corrupted session store. If the persisted id is
|
|
1216
1216
|
// already in use (or claude has forgotten about it), discard it and
|
|
@@ -115,7 +115,7 @@ const buildPrompt = buildMemoryPreamble;
|
|
|
115
115
|
// when they also carry a `command`. `auditDeclaredMcp` classifies by
|
|
116
116
|
// `transport` and never judges the command of an entry that declared an
|
|
117
117
|
// http one, so emitting it here executes a command the guard did not
|
|
118
|
-
// approve —
|
|
118
|
+
// approve — a case measured on 2026-09-18.
|
|
119
119
|
// - Token-bearing values ride through `env_vars`, never a `-c ...env=...`
|
|
120
120
|
// argv override. Command lines are visible to other same-user processes
|
|
121
121
|
// unless the OS sandbox blocks process inspection; keeping bearer tokens
|
|
@@ -661,7 +661,7 @@ export default {
|
|
|
661
661
|
// undefined and '' as absence itself; it does not need a guard, it needs
|
|
662
662
|
// the value.
|
|
663
663
|
//
|
|
664
|
-
// Codex has no subagent tool, so of
|
|
664
|
+
// Codex has no subagent tool, so of the two habits (TASK-545) it is
|
|
665
665
|
// taught row slicing alone; claude.js adds parallel reads. A public seat
|
|
666
666
|
// answers strangers rather than working board rows, so it is taught neither.
|
|
667
667
|
const fullPrompt = buildPrompt(prompt, ctx.memoryLongTerm, {
|
package/src/lib/adapters/dsh.js
CHANGED
|
@@ -59,7 +59,7 @@ export const assertPrivateDsh = (environment = {}) => { isConfinedDsh(environmen
|
|
|
59
59
|
* The macOS developer tools a confined writer needs for git, granted read-only.
|
|
60
60
|
* /usr/bin/git is an xcrun shim: inside the sandbox it cannot read the tools it
|
|
61
61
|
* forwards to, so `git` fails with "No developer tools were found" and a writer
|
|
62
|
-
* cannot commit
|
|
62
|
+
* cannot commit. Returns null when no tools are installed; the seat then
|
|
63
63
|
* runs without git rather than failing.
|
|
64
64
|
*
|
|
65
65
|
* DEVELOPER_DIR and xcode-select are both settable by whoever starts the seat,
|
|
@@ -333,7 +333,7 @@ export default {
|
|
|
333
333
|
requiresModel: true,
|
|
334
334
|
name: 'dsh', runtimeType: 'dsh', supportsProvider: true,
|
|
335
335
|
|
|
336
|
-
// The attach gate defers to this for dsh's one confined shape
|
|
336
|
+
// The attach gate defers to this for dsh's one confined shape.
|
|
337
337
|
confinesOwnSandbox: (environment) => isConfinedDsh(environment),
|
|
338
338
|
|
|
339
339
|
async validateEnvironment(environment, workspacePath) {
|
package/src/lib/adapters/pi.js
CHANGED
|
@@ -100,7 +100,7 @@ const substitutePlaceholders = (value, ctx) => {
|
|
|
100
100
|
* deliberately not friendlier than the guard's — a normalized `'HTTP'` or a
|
|
101
101
|
* padded `' http '` is a shape the guard refuses as an unknown transport, and an
|
|
102
102
|
* adapter that accepted one would be running something the guard never judged
|
|
103
|
-
*
|
|
103
|
+
* The schema admits `http`/`stdio`/`sse`
|
|
104
104
|
* (environment.js:236) and admits an ABSENT transport, which the guard reads as
|
|
105
105
|
* stdio; that is what this reads too.
|
|
106
106
|
*/
|
|
@@ -129,8 +129,7 @@ const originOf = (value) => {
|
|
|
129
129
|
* environment is not usable for this: the kernel keeps the copy the process was
|
|
130
130
|
* started with, so a same-user child could read the token back with
|
|
131
131
|
* `ps eww $PPID` / `/proc/$PPID/environ` even after the bridge deleted its own
|
|
132
|
-
* copy — which is what the previous channel did, and the row this closes
|
|
133
|
-
* (Vera, Connectors).
|
|
132
|
+
* copy — which is what the previous channel did, and the row this closes.
|
|
134
133
|
*
|
|
135
134
|
* WHICH shape an entry becomes is decided by `transport` — the same field, read
|
|
136
135
|
* with the same default and the same exact comparison the daemon's
|
|
@@ -141,7 +140,7 @@ const originOf = (value) => {
|
|
|
141
140
|
* `${COMMONLY_AGENT_TOKEN}` in the command is one of the known placeholders), and
|
|
142
141
|
* a presence-classifier then ran that command as stdio with the real token
|
|
143
142
|
* substituted. The guard's judgement and the adapter's disagreed, and the adapter
|
|
144
|
-
* is what executes
|
|
143
|
+
* is what executes.
|
|
145
144
|
*
|
|
146
145
|
* The http half also enforces the guard's own origin rule, so the HTTP half does
|
|
147
146
|
* not depend on a guard that may not be on the machine: `auditDeclaredMcp`
|
|
@@ -153,7 +152,7 @@ const originOf = (value) => {
|
|
|
153
152
|
*
|
|
154
153
|
* The grant broker is refused outright, by PATH rather than by entry name: it is
|
|
155
154
|
* the one http entry that carries authority rather than data, and pi confines on
|
|
156
|
-
* no host.
|
|
155
|
+
* no host. The ruling for TASK-063 (`isGrantBrokerUrl` in pi-mcp-client.mjs),
|
|
157
156
|
* the daemon-side half of the same refusal the server makes at the projection —
|
|
158
157
|
* this is the half that holds on deploy skew, when the row names no adapter for
|
|
159
158
|
* the server to key on, and in any backend older than the refusal.
|
|
@@ -162,7 +161,7 @@ const originOf = (value) => {
|
|
|
162
161
|
* a declared stdio command is executed with no allowlist check, here and in both
|
|
163
162
|
* sibling adapters, so it depends entirely on the guard — `auditDeclaredMcp`
|
|
164
163
|
* admits only the shipped commonly MCP server or a command already present in the
|
|
165
|
-
* local record, and that rule exists nowhere else (
|
|
164
|
+
* local record, and that rule exists nowhere else (TASK-069).
|
|
166
165
|
*/
|
|
167
166
|
export const resolveMcpServers = (mcpServers, ctx = {}) => {
|
|
168
167
|
const carried = [];
|
|
@@ -295,7 +294,7 @@ export const sessionExists = async (sessionDir, sessionId) => {
|
|
|
295
294
|
* one does not start.
|
|
296
295
|
*
|
|
297
296
|
* The trust is read THROUGH the legacy table (`effectiveSandboxTrust`), not
|
|
298
|
-
* raw. A stored `internal` means `public`
|
|
297
|
+
* raw. A stored `internal` means `public` and every
|
|
299
298
|
* other gate in this package resolves it that way: the attach path
|
|
300
299
|
* (`agent.js`), the mode resolver (`sandbox/mode.js`) and the grant-broker
|
|
301
300
|
* guard. Reading it raw here made this gate the one site that failed OPEN — a
|
|
@@ -238,7 +238,7 @@ export const createDaemonSupervisor = ({
|
|
|
238
238
|
// seat RUNS under, and `withholdGrantBroker` asks the same question the
|
|
239
239
|
// server asks (`can this seat confine a granter's authority?`) for the cases
|
|
240
240
|
// the server cannot see — a row naming no adapter, a backend older than the
|
|
241
|
-
// refusal, a record written by hand. Entry-level
|
|
241
|
+
// refusal, a record written by hand. Entry-level: the broker
|
|
242
242
|
// entry is withheld and the seat still starts, because it was never promised
|
|
243
243
|
// confinement. `record.instanceUrl` is what makes the injected url
|
|
244
244
|
// identifiable at all — the record holds the UNRESOLVED
|
package/src/lib/enforcement.js
CHANGED
|
@@ -635,7 +635,7 @@ export const splitForChat = (text, { limit = 400 } = {}) => {
|
|
|
635
635
|
* text and post one message — the reply's
|
|
636
636
|
* own opening plus the file card.
|
|
637
637
|
*
|
|
638
|
-
* The thread rung is new
|
|
638
|
+
* The thread rung is new and it replaces attachment as the answer
|
|
639
639
|
* for PROSE overflow. Before threads existed, a long analysis had nowhere to
|
|
640
640
|
* go but a file, and that was the right workaround. It is now the wrong one:
|
|
641
641
|
* an attachment is un-quotable, un-followable, and an all-or-nothing read,
|
|
@@ -678,7 +678,7 @@ export const deliverChatReply = async ({
|
|
|
678
678
|
//
|
|
679
679
|
// TASK-535 — the send-time freshness hold. The backend answers 409
|
|
680
680
|
// `unseen_messages` when other members posted after this seat's last
|
|
681
|
-
// recorded tail read. A turn that ran ~43 minutes
|
|
681
|
+
// recorded tail read. A turn that ran ~43 minutes
|
|
682
682
|
// hit it on the thread headline, the attach card and the split fallback in
|
|
683
683
|
// turn, so the reply was lost and its 10 events re-ran. A retry without an
|
|
684
684
|
// override cannot succeed — every rung posts to the same guarded route.
|
|
@@ -771,7 +771,7 @@ export const deliverChatReply = async ({
|
|
|
771
771
|
// its rendering. The tone contract's own rule covers it: over ~800 chars of
|
|
772
772
|
// ONE indivisible thing is a document, not a message — attach it. Without
|
|
773
773
|
// this check a 659-char fence rode the single/split branches straight past
|
|
774
|
-
// the gate (found by the fleet's implementation audit
|
|
774
|
+
// the gate (found by the fleet's implementation audit).
|
|
775
775
|
const hasIndivisibleOversize = chunks.some((c) => c.length > attachThreshold);
|
|
776
776
|
if (chunks.length <= 1 && !hasIndivisibleOversize) {
|
|
777
777
|
const response = await postMessage({ content: chunks[0] ?? text });
|
package/src/lib/environment.js
CHANGED
|
@@ -75,9 +75,9 @@ const findInlineProviderCredentialField = (value, path) => {
|
|
|
75
75
|
// daemon path it was silently inert — claude fell through to a bare unconfined
|
|
76
76
|
// spawn and codex took `--dangerously-bypass-approvals-and-sandbox` — so a
|
|
77
77
|
// declaration that reads as "confine me, not as a public agent" meant the
|
|
78
|
-
// opposite of what it said
|
|
78
|
+
// opposite of what it said. It is refused for new declarations
|
|
79
79
|
// above, and a record that already carries it is resolved TOWARD confinement,
|
|
80
|
-
// never toward the bare spawn
|
|
80
|
+
// never toward the bare spawn: `internal` is read as `public`, so
|
|
81
81
|
// such a seat is confined where it can be and refuses to derive where it
|
|
82
82
|
// cannot. A real middle trust arrives as its own adapter that reads it — and
|
|
83
83
|
// `workspace` (above) is that trust, which is why it is NOT a legacy alias.
|
|
@@ -100,7 +100,7 @@ export const normalizeSandboxTrust = (sandbox) => (
|
|
|
100
100
|
* compares the trust and must not write anything back. The server spells the
|
|
101
101
|
* same predicate `effectiveSandboxTrust` (`grantBrokerConfinement.ts`) — the two
|
|
102
102
|
* sides have to read `internal` the same way or one of them confines a seat the
|
|
103
|
-
* other withholds from
|
|
103
|
+
* other withholds from.
|
|
104
104
|
*/
|
|
105
105
|
export const effectiveSandboxTrust = (trust) => (
|
|
106
106
|
typeof trust === 'string' && Object.prototype.hasOwnProperty.call(LEGACY_SANDBOX_TRUST, trust)
|
|
@@ -112,7 +112,7 @@ export const effectiveSandboxTrust = (trust) => (
|
|
|
112
112
|
* Throw when an adapter that does not implement the `workspace` tier is handed
|
|
113
113
|
* one (TASK-294). Only claude reads it. Every other adapter branches on
|
|
114
114
|
* `trust === 'public'`, so an unread `workspace` would fall into its
|
|
115
|
-
* non-public branch — the same silent widening `internal` was
|
|
115
|
+
* non-public branch — the same silent widening `internal` was.
|
|
116
116
|
* Called at each adapter's spawn entry, before any other sandbox read.
|
|
117
117
|
*/
|
|
118
118
|
export const refuseWorkspaceTrust = (sandbox, adapterLabel) => {
|
|
@@ -391,8 +391,8 @@ export const validateEnvironmentSpec = (spec) => {
|
|
|
391
391
|
}
|
|
392
392
|
}
|
|
393
393
|
|
|
394
|
-
// A TRANSPORT DECIDES THE ENTRY, so the fields must agree with it (TASK-071
|
|
395
|
-
//
|
|
394
|
+
// A TRANSPORT DECIDES THE ENTRY, so the fields must agree with it (TASK-071).
|
|
395
|
+
// This block is MIRRORED, not shared: the other
|
|
396
396
|
// writer-side check is the backend's, in
|
|
397
397
|
// backend/utils/environmentSpecValidation.ts, called from the
|
|
398
398
|
// `PATCH /api/registry/pods/:podId/agents/:name` handler that stores
|
package/src/lib/memory-bridge.js
CHANGED
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
export const SOURCE_RUNTIME = 'local-cli';
|
|
24
24
|
|
|
25
25
|
/**
|
|
26
|
-
* Row slicing, the runtime-agnostic half of
|
|
26
|
+
* Row slicing, the runtime-agnostic half of TASK-545. A row too large
|
|
27
27
|
* for one turn drifts when one seat carries it whole, so the seat splits it on
|
|
28
28
|
* the board before any code. It names the exact call and field for the reason
|
|
29
29
|
* the memory cue below names `long_term`: a seat adopts a habit through a
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Which failed Commonly tool calls a public seat's model may answer in plain
|
|
3
|
-
* words. Everything else withholds the turn (#2374
|
|
3
|
+
* words. Everything else withholds the turn (#2374).
|
|
4
4
|
*
|
|
5
5
|
* The rule is inverted on purpose. Matching known connection failures let an
|
|
6
6
|
* API 5xx through: the shipped MCP turns a non-2xx into a tool error carrying
|
package/src/lib/sandbox/mode.js
CHANGED
|
@@ -5,15 +5,14 @@
|
|
|
5
5
|
* derives `sandbox: { trust: 'public' }` with NO mode (lib/default-environment.js)
|
|
6
6
|
* because `mode: 'workspace'` is Seatbelt on macOS and is refused on Linux,
|
|
7
7
|
* while `mode: 'bwrap'` is meaningless on macOS — writing either into the row
|
|
8
|
-
* moves a host fact into the database and breaks the day the seat is re-homed
|
|
9
|
-
* (Wren 69545).
|
|
8
|
+
* moves a host fact into the database and breaks the day the seat is re-homed.
|
|
10
9
|
*
|
|
11
10
|
* So the mode is resolved here, at the point that knows the host:
|
|
12
11
|
* darwin → 'workspace' (the claude adapter maps it to Seatbelt)
|
|
13
12
|
* else → 'bwrap' (bubblewrap; attach checks it is installed)
|
|
14
13
|
*
|
|
15
14
|
* Resolution is PUBLIC-only on purpose, and PUBLIC IS THE EFFECTIVE TRUST: a
|
|
16
|
-
* legacy `internal` declaration reads as `public` (environment.js
|
|
15
|
+
* legacy `internal` declaration reads as `public` (environment.js),
|
|
17
16
|
* so the mapping is applied before this comparison. Comparing the raw value
|
|
18
17
|
* made `{ trust: 'internal' }` resolve to no mode at all — the mapping was
|
|
19
18
|
* applied in `confinementReason` and not here, which is half a rule, and the
|
|
@@ -22,7 +21,7 @@
|
|
|
22
21
|
* whatever mode it wrote, and an adapter that cannot enforce it is expected to
|
|
23
22
|
* refuse rather than quietly spawn unconfined. An explicit mode on a public
|
|
24
23
|
* declaration still wins — including a wrong one, which the caller then refuses
|
|
25
|
-
* (
|
|
24
|
+
* (an unresolvable public mode must not fall through to a bare,
|
|
26
25
|
* unconfined spawn).
|
|
27
26
|
*
|
|
28
27
|
* NOT used by the codex adapter. Codex's `mode` is read-vs-write access, not a
|
|
@@ -301,7 +301,7 @@ const OPEN_NETWORK = String.raw`
|
|
|
301
301
|
// Resolver is constructed. Denied, that lookup does not always fail: at
|
|
302
302
|
// times it blocks the main thread forever. dsh builds a Resolver as it loads,
|
|
303
303
|
// so confined turns ran to their 900 s timeout without sending a request
|
|
304
|
-
//
|
|
304
|
+
// Admitted, resolution fails fast, because mDNSResponder and port 53
|
|
305
305
|
// stay unreachable in a loopback profile. The service returns configuration
|
|
306
306
|
// and opens no channel out.
|
|
307
307
|
const RESOLVER_CONFIG = String.raw`
|
package/src/lib/spawn-retry.js
CHANGED
|
@@ -124,7 +124,7 @@ export const classifySpawnFailure = (error) => {
|
|
|
124
124
|
// and "limit", and its `(?!rate[ -]?limit)` lookahead only guards the word
|
|
125
125
|
// IMMEDIATELY after "your" — so "you've hit your API rate limit" walked past
|
|
126
126
|
// it into the 15-minute circuit, where main correctly gives the 60-second
|
|
127
|
-
// rate-limit probe (
|
|
127
|
+
// rate-limit probe (gate on #2491; it was a regression, not a gap).
|
|
128
128
|
// A throttle wording anywhere in the text therefore wins over the shape.
|
|
129
129
|
// QUOTA_RE is still tested first, so an explicit quota word keeps QUOTA even
|
|
130
130
|
// when the same message also mentions rate limits.
|
|
@@ -73,7 +73,7 @@ export const startWebhookServer = ({
|
|
|
73
73
|
* The handler `agent connect` serves on its own port. It only logs what
|
|
74
74
|
* arrives, and no agent has read the event, so it states no outcome a read
|
|
75
75
|
* would count as: the wake still settles, and the pod digest stays unread
|
|
76
|
-
* (
|
|
76
|
+
* (#2753).
|
|
77
77
|
*/
|
|
78
78
|
export const logOnlyHandler = async (event) => {
|
|
79
79
|
console.log(`[local] ${event.type} from pod ${event.podId}`);
|
|
@@ -99,7 +99,7 @@ export const forwardToLocalWebhook = async (event, webhookUrl, secret = null) =>
|
|
|
99
99
|
if (!res.ok) throw new Error(`Local webhook returned HTTP ${res.status}`);
|
|
100
100
|
// Only a stated outcome counts. An empty or non-JSON body, or one without
|
|
101
101
|
// an outcome, is a handler that said nothing, the same as a CLI turn that
|
|
102
|
-
// printed nothing, so it must not read the pod digest (
|
|
102
|
+
// printed nothing, so it must not read the pod digest (#2753).
|
|
103
103
|
let body = null;
|
|
104
104
|
try { body = await res.json(); } catch { body = null; }
|
|
105
105
|
if (!body || typeof body !== 'object' || typeof body.outcome !== 'string') {
|