@ouro.bot/cli 0.1.0-alpha.891 → 0.1.0-alpha.893

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/changelog.json CHANGED
@@ -1,6 +1,18 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.893",
6
+ "changes": [
7
+ "New cmux sense: an agent with senses.cmux.enabled watches the coding agents running in cmux on this Mac. It follows cmux's event stream, keeps what each terminal's agent last did, and brings each permission request, question or plan approval that cmux is holding for the human to the agent once, the way the mail sense does: a pending message in the agent's private runtime plus a private-turn wake that the daemon's policy now allows for the cmux sense (never including the request's tool input; every Feed field is flattened to one line, capped and marked untrusted, and a burst of requests costs one wake). This works for an agent with no tool-profiles.json. Three family-only tools: cmux_overview (who needs the human and what each session did), cmux_read (a terminal's text, redacted and bounded) and cmux_signal (a one-line workspace status or a desktop notification). The socket credential is a cmux capability token stored per machine with 'ouro vault config set --scope machine --key cmux.socketCapability'. After a gap in cmux's event stream or a cmux restart it starts its picture of the terminals afresh; a request that cannot be queued is retried on the next check while the others carry on; a failed state write no longer stops the Feed checks; it records the cmux version it is attached to (a prerelease such as 0.65.0-beta counts as older than 0.65.0); and the cmux tools are denied whenever the caller's trust level is unknown. See docs/cmux-sense.md."
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.892",
12
+ "changes": [
13
+ "Local audio joins play the agent's voice through ffmpeg instead of sox, because sox's CoreAudio output can deadlock and stay silent when the process runs at lowered priority. An outbound call is treated as the job's friend only when the dialed number is on that friend's record. Phone greetings now name the agent: they had introduced themselves as ChatGPT in half of the live test calls."
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.891",
6
18
  "changes": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.891",
2
+ "runtimeVersion": "0.1.0-alpha.893",
3
3
  "bundleSchemaVersion": 3,
4
4
  "lastUpdated": "2026-09-03T00:00:00.000Z"
5
5
  }
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0"?>
2
2
  <Container version="2">
3
3
  <Name>ouro-butler</Name>
4
- <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.891</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.893</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -0,0 +1,102 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.cmuxRepairHint = cmuxRepairHint;
37
+ exports.cmuxConfigFacts = cmuxConfigFacts;
38
+ exports.cmuxSenseStatus = cmuxSenseStatus;
39
+ exports.defaultCmuxSocketPath = defaultCmuxSocketPath;
40
+ exports.resolveCmuxConnection = resolveCmuxConnection;
41
+ const fs = __importStar(require("node:fs"));
42
+ const os = __importStar(require("node:os"));
43
+ const path = __importStar(require("node:path"));
44
+ const runtime_1 = require("../nerves/runtime");
45
+ function cmuxRepairHint(agent) {
46
+ return `human-required: in a cmux terminal run 'ouro vault config set --agent ${agent} --scope machine --key cmux.socketCapability --value "$CMUX_SOCKET_CAPABILITY"' (or set cmux.socketPassword when cmux is in password mode), then 'ouro up'.`;
47
+ }
48
+ function text(record, key) {
49
+ const value = record?.[key];
50
+ return typeof value === "string" ? value.trim() : "";
51
+ }
52
+ function cmuxRecord(machinePayload) {
53
+ const value = machinePayload.cmux;
54
+ return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
55
+ }
56
+ function authFrom(cmux) {
57
+ const token = text(cmux, "socketCapability");
58
+ if (token)
59
+ return { kind: "capability", token };
60
+ const password = text(cmux, "socketPassword");
61
+ if (password)
62
+ return { kind: "password", password };
63
+ if (text(cmux, "socketMode") === "automation")
64
+ return { kind: "none" };
65
+ return null;
66
+ }
67
+ function cmuxConfigFacts(machinePayload) {
68
+ const cmux = cmuxRecord(machinePayload);
69
+ const auth = authFrom(cmux);
70
+ if (!auth)
71
+ return cmux ? { configured: false, detail: "missing cmux.socketCapability" } : { configured: false, optional: true, detail: "not attached on this machine" };
72
+ return { configured: true, detail: `socket auth: ${auth.kind === "none" ? "automation mode" : auth.kind}` };
73
+ }
74
+ /** The one-word status the prompt and turn context show for the cmux sense, matching the daemon's sense rows. */
75
+ function cmuxSenseStatus(enabled, machinePayload) {
76
+ if (!enabled)
77
+ return "disabled";
78
+ const facts = cmuxConfigFacts(machinePayload);
79
+ if (facts.configured)
80
+ return "ready";
81
+ return facts.optional ? "not_attached" : "needs_config";
82
+ }
83
+ /** cmux's own default: `~/.local/state/cmux/cmux-<uid>.sock`, then the older unscoped `cmux.sock`. */
84
+ function defaultCmuxSocketPath(homeDir = os.homedir(), uid = process.getuid?.() ?? 0, exists = fs.existsSync) {
85
+ const dir = path.join(homeDir, ".local", "state", "cmux");
86
+ const scoped = path.join(dir, `cmux-${uid}.sock`);
87
+ const unscoped = path.join(dir, "cmux.sock");
88
+ return !exists(scoped) && exists(unscoped) ? unscoped : scoped;
89
+ }
90
+ function resolveCmuxConnection(agent, machinePayload, defaultSocketPath = defaultCmuxSocketPath) {
91
+ const cmux = cmuxRecord(machinePayload);
92
+ const auth = authFrom(cmux);
93
+ (0, runtime_1.emitNervesEvent)({
94
+ component: "senses",
95
+ event: "senses.cmux_connection_resolved",
96
+ message: "resolved cmux socket connection settings",
97
+ meta: { agent, auth: auth?.kind ?? "missing" },
98
+ });
99
+ if (!auth)
100
+ return { ok: false, error: `cmux socket auth is not configured on this machine; ${cmuxRepairHint(agent)}` };
101
+ return { ok: true, connection: { socketPath: text(cmux, "socketPath") || defaultSocketPath(), auth } };
102
+ }
@@ -58,7 +58,7 @@ function defaultLoggingForProcess(processName) {
58
58
  sinks: ["terminal", "ndjson"],
59
59
  };
60
60
  }
61
- if (processName === "bluebubbles" || processName === "mail" || processName === "voice" || processName === "a2a") {
61
+ if (processName === "bluebubbles" || processName === "mail" || processName === "voice" || processName === "a2a" || processName === "cmux") {
62
62
  return {
63
63
  level: "warn",
64
64
  sinks: ["terminal", "ndjson"],
@@ -43,6 +43,7 @@ const runtime_credentials_1 = require("../runtime-credentials");
43
43
  const provider_credentials_1 = require("../provider-credentials");
44
44
  const sense_truth_1 = require("../sense-truth");
45
45
  const machine_identity_1 = require("../machine-identity");
46
+ const cmux_config_1 = require("../cmux-config");
46
47
  const process_manager_1 = require("./process-manager");
47
48
  const config_1 = require("../../a2a/config");
48
49
  const DEFAULT_TEAMS_PORT = 3978;
@@ -62,6 +63,7 @@ function defaultSenses() {
62
63
  a2a: { ...identity_1.DEFAULT_AGENT_SENSES.a2a },
63
64
  telegram: { ...identity_1.DEFAULT_AGENT_SENSES.telegram },
64
65
  workbench: { ...identity_1.DEFAULT_AGENT_SENSES.workbench },
66
+ cmux: { ...identity_1.DEFAULT_AGENT_SENSES.cmux },
65
67
  };
66
68
  }
67
69
  function readAgentSenses(agentJsonPath) {
@@ -101,7 +103,7 @@ function readAgentSenses(agentJsonPath) {
101
103
  && !Array.isArray(rawMcpServers)
102
104
  && Object.prototype.hasOwnProperty.call(rawMcpServers, "ouro_workbench");
103
105
  const workbenchHasStaleBundleEntry = workbenchSensePresent || workbenchMcpPresent;
104
- for (const sense of ["cli", "teams", "bluebubbles", "mail", "voice", "a2a", "telegram", "workbench"]) {
106
+ for (const sense of ["cli", "teams", "bluebubbles", "mail", "voice", "a2a", "telegram", "workbench", "cmux"]) {
105
107
  const rawSense = rawSenses[sense];
106
108
  if (!rawSense || typeof rawSense !== "object" || Array.isArray(rawSense)) {
107
109
  continue;
@@ -162,7 +164,7 @@ function runtimeConfigUnavailableDetail(agent, runtimeConfig) {
162
164
  return `vault ${itemName} unavailable (${compactRuntimeConfigError(agent, runtimeConfig.error)})`;
163
165
  }
164
166
  function needsMachineRuntimeConfig(agent, sense) {
165
- return sense === "bluebubbles" || sense === "voice" || sense === "a2a" || (agent === "sanctuary" && sense === "telegram");
167
+ return sense === "bluebubbles" || sense === "voice" || sense === "a2a" || sense === "cmux" || (agent === "sanctuary" && sense === "telegram");
166
168
  }
167
169
  function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntimeConfig = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent), workbenchHasStaleBundleEntry = false) {
168
170
  const base = {
@@ -174,6 +176,7 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntim
174
176
  a2a: { configured: false, detail: "not enabled in agent.json" },
175
177
  telegram: { configured: false, detail: "not enabled in agent.json" },
176
178
  workbench: { configured: false, detail: "not enabled in agent.json" },
179
+ cmux: { configured: false, detail: "not enabled in agent.json" },
177
180
  };
178
181
  const payload = runtimeConfig.ok ? runtimeConfig.config : {};
179
182
  const unavailableDetail = runtimeConfigUnavailableDetail(agent, runtimeConfig);
@@ -342,6 +345,11 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntim
342
345
  detail: publicUrl ? `${publicUrl}${endpointPath}` : `:${port} ${endpointPath}`,
343
346
  };
344
347
  }
348
+ if (senses.cmux.enabled) {
349
+ base.cmux = !machineRuntimeConfig.ok && machineRuntimeConfig.reason !== "missing"
350
+ ? { configured: false, detail: runtimeConfigUnavailableDetail(agent, machineRuntimeConfig) }
351
+ : (0, cmux_config_1.cmuxConfigFacts)(machinePayload);
352
+ }
345
353
  if (workbenchHasStaleBundleEntry || senses.workbench.enabled) {
346
354
  base.workbench = {
347
355
  configured: false,
@@ -369,6 +377,8 @@ function senseRepairHint(agent, sense) {
369
377
  ? "Agent-runnable: complete or recover the root authority migration; do not restore a resident Telegram token."
370
378
  : `Agent-runnable: store Telegram bot/user/chat coordinates with 'ouro connect telegram --agent ${agent}', then restart with 'ouro up'.`;
371
379
  }
380
+ if (sense === "cmux")
381
+ return (0, cmux_config_1.cmuxRepairHint)(agent);
372
382
  /* v8 ignore next -- Workbench is deliberately not daemon-managed, so getSenseInventory never asks the daemon manager for a repair hint @preserve */
373
383
  if (sense === "workbench") {
374
384
  return `Agent-runnable: install Ouro Workbench.app, then launch the boss through Workbench or run 'ouro acp-serve --agent ${agent} --workbench-mcp'; 'ouro connect workbench --agent ${agent}' only cleans stale bundle entries.`;
@@ -383,7 +393,7 @@ function parseSenseSnapshotName(name) {
383
393
  if (parts.length !== 2)
384
394
  return null;
385
395
  const [agent, sense] = parts;
386
- if (sense !== "teams" && sense !== "bluebubbles" && sense !== "mail" && sense !== "voice" && sense !== "a2a" && sense !== "telegram")
396
+ if (sense !== "teams" && sense !== "bluebubbles" && sense !== "mail" && sense !== "voice" && sense !== "a2a" && sense !== "telegram" && sense !== "cmux")
387
397
  return null;
388
398
  return { agent, sense };
389
399
  }
@@ -403,11 +413,13 @@ function managedSenseEntry(sense) {
403
413
  return "senses/a2a-entry.js";
404
414
  if (sense === "telegram")
405
415
  return "senses/telegram-entry.js";
416
+ if (sense === "cmux")
417
+ return "senses/cmux-entry.js";
406
418
  return "senses/mail-entry.js";
407
419
  }
408
420
  function runtimeCredentialBootstrapFor(agent, sense) {
409
421
  const runtime = (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent);
410
- const usesMachineConfig = sense === "bluebubbles" || sense === "voice" || sense === "a2a" || sense === "telegram";
422
+ const usesMachineConfig = sense === "bluebubbles" || sense === "voice" || sense === "a2a" || sense === "telegram" || sense === "cmux";
411
423
  const machineId = usesMachineConfig ? currentMachineId() : undefined;
412
424
  const machine = usesMachineConfig ? (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent) : null;
413
425
  const providerPool = (0, provider_credentials_1.readProviderCredentialPool)(agent);
@@ -578,7 +590,7 @@ class DaemonSenseManager {
578
590
  return [agent, { senses, workbenchHasStaleBundleEntry, facts }];
579
591
  }));
580
592
  const managedSenseAgents = [...this.contexts.entries()].flatMap(([agent, context]) => {
581
- return ["teams", "bluebubbles", "mail", "voice", "a2a", "telegram"]
593
+ return ["teams", "bluebubbles", "mail", "voice", "a2a", "telegram", "cmux"]
582
594
  .filter((sense) => context.senses[sense].enabled)
583
595
  .map((sense) => ({
584
596
  name: `${agent}:${sense}`,
@@ -730,7 +742,7 @@ class DaemonSenseManager {
730
742
  }
731
743
  async refreshEnabledSenseConfigs() {
732
744
  const refreshes = [...this.contexts.entries()].map(async ([agent, context]) => {
733
- const enabledManagedSenses = ["teams", "bluebubbles", "mail", "voice", "a2a", "telegram"]
745
+ const enabledManagedSenses = ["teams", "bluebubbles", "mail", "voice", "a2a", "telegram", "cmux"]
734
746
  .filter((sense) => context.senses[sense].enabled);
735
747
  /* v8 ignore next -- periodic refresh work only exists when a managed background sense is enabled @preserve */
736
748
  if (enabledManagedSenses.length === 0)
@@ -904,6 +916,11 @@ class DaemonSenseManager {
904
916
  workbench: {
905
917
  configured: context.facts.workbench.configured,
906
918
  },
919
+ cmux: {
920
+ configured: context.facts.cmux.configured,
921
+ optional: context.facts.cmux.optional,
922
+ ...(runtime.get(agent)?.cmux ?? {}),
923
+ },
907
924
  };
908
925
  const inventory = (0, sense_truth_1.getSenseInventory)({ senses: context.senses }, runtimeInfo);
909
926
  return inventory.map((entry) => ({
@@ -145,6 +145,7 @@ exports.DEFAULT_AGENT_SENSES = {
145
145
  a2a: { enabled: false },
146
146
  telegram: { enabled: false },
147
147
  workbench: { enabled: false },
148
+ cmux: { enabled: false },
148
149
  };
149
150
  function normalizeSenses(value, configFile) {
150
151
  const defaults = {
@@ -156,6 +157,7 @@ function normalizeSenses(value, configFile) {
156
157
  a2a: { ...exports.DEFAULT_AGENT_SENSES.a2a },
157
158
  telegram: { ...exports.DEFAULT_AGENT_SENSES.telegram },
158
159
  workbench: { ...exports.DEFAULT_AGENT_SENSES.workbench },
160
+ cmux: { ...exports.DEFAULT_AGENT_SENSES.cmux },
159
161
  };
160
162
  if (value === undefined) {
161
163
  return defaults;
@@ -171,7 +173,7 @@ function normalizeSenses(value, configFile) {
171
173
  throw new Error(`agent.json at ${configFile} must include senses as an object when present.`);
172
174
  }
173
175
  const raw = value;
174
- const senseNames = ["cli", "teams", "bluebubbles", "mail", "voice", "a2a", "telegram", "workbench"];
176
+ const senseNames = ["cli", "teams", "bluebubbles", "mail", "voice", "a2a", "telegram", "workbench", "cmux"];
175
177
  for (const senseName of senseNames) {
176
178
  const rawSense = raw[senseName];
177
179
  if (rawSense === undefined) {
@@ -219,6 +221,7 @@ function buildDefaultAgentTemplate(_agentName) {
219
221
  a2a: { ...exports.DEFAULT_AGENT_SENSES.a2a },
220
222
  telegram: { ...exports.DEFAULT_AGENT_SENSES.telegram },
221
223
  workbench: { ...exports.DEFAULT_AGENT_SENSES.workbench },
224
+ cmux: { ...exports.DEFAULT_AGENT_SENSES.cmux },
222
225
  },
223
226
  phrases: {
224
227
  thinking: [...exports.DEFAULT_AGENT_PHRASES.thinking],
@@ -150,6 +150,14 @@ async function evaluatePolicy(request, context, deps) {
150
150
  reason: "verified daemon external event",
151
151
  };
152
152
  }
153
+ if (request.triggerSource === "cmux-feed"
154
+ && hasNonEmptyOriginRef(originRefs, "cmux-feed")
155
+ && hasOriginRef(originRefs, "sense", "cmux")) {
156
+ return {
157
+ result: "allow",
158
+ reason: "cmux sense escalation",
159
+ };
160
+ }
153
161
  if (request.triggerSource === "operator-cli"
154
162
  && hasOriginRef(originRefs, "cli-command", "ouro msg")
155
163
  && hasNonEmptyOriginRef(originRefs, "daemon-receipt")) {
@@ -12,6 +12,7 @@ const SENSES = [
12
12
  { sense: "a2a", label: "A2A", daemonManaged: true },
13
13
  { sense: "telegram", label: "Telegram", daemonManaged: true },
14
14
  { sense: "workbench", label: "Workbench", daemonManaged: false },
15
+ { sense: "cmux", label: "cmux", daemonManaged: true },
15
16
  ];
16
17
  function configuredSenses(senses) {
17
18
  const configured = senses ?? {};
@@ -25,6 +26,7 @@ function configuredSenses(senses) {
25
26
  a2a: configured.a2a ?? { ...identity_1.DEFAULT_AGENT_SENSES.a2a },
26
27
  telegram: configured.telegram ?? { ...identity_1.DEFAULT_AGENT_SENSES.telegram },
27
28
  workbench: configured.workbench ?? { ...identity_1.DEFAULT_AGENT_SENSES.workbench },
29
+ cmux: configured.cmux ?? { ...identity_1.DEFAULT_AGENT_SENSES.cmux },
28
30
  };
29
31
  }
30
32
  function resolveStatus(enabled, daemonManaged, runtimeInfo) {
@@ -63,6 +63,7 @@ const provider_visibility_1 = require("./provider-visibility");
63
63
  const mail_import_discovery_1 = require("./mail-import-discovery");
64
64
  const flight_recorder_1 = require("../arc/flight-recorder");
65
65
  const context_loss_sentinel_1 = require("./context-loss-sentinel");
66
+ const cmux_config_1 = require("./cmux-config");
66
67
  // ── Helpers ─────────────────────────────────────────────────────────
67
68
  const DAEMON_SOCKET_PATH = "/tmp/ouroboros-daemon.sock";
68
69
  function isLiveCodingSessionStatus(status) {
@@ -167,6 +168,7 @@ function readSenseStatusLines() {
167
168
  a2a: configuredSenses.a2a ?? { enabled: false },
168
169
  telegram: configuredSenses.telegram ?? { enabled: false },
169
170
  workbench: configuredSenses.workbench ?? { enabled: false },
171
+ cmux: configuredSenses.cmux ?? { enabled: false },
170
172
  };
171
173
  const payload = (0, config_1.loadConfig)();
172
174
  const agentName = (0, identity_1.getAgentName)();
@@ -221,6 +223,7 @@ function readSenseStatusLines() {
221
223
  && hasTextField(runtimePayload, "telegramAuthorizedUserId")
222
224
  && hasTextField(runtimePayload, "telegramAuthorizedChatId"),
223
225
  workbench: false,
226
+ cmux: (0, cmux_config_1.cmuxSenseStatus)(true, machinePayload) === "ready",
224
227
  };
225
228
  const rows = [
226
229
  { label: "CLI", status: "interactive" },
@@ -248,6 +251,10 @@ function readSenseStatusLines() {
248
251
  label: "Telegram",
249
252
  status: !senses.telegram.enabled ? "disabled" : configured.telegram ? "ready" : "needs_config",
250
253
  },
254
+ {
255
+ label: "cmux",
256
+ status: (0, cmux_config_1.cmuxSenseStatus)(senses.cmux.enabled, machinePayload),
257
+ },
251
258
  {
252
259
  label: "Workbench",
253
260
  // Workbench can be injected at runtime (the Workbench app spawns
@@ -86,6 +86,7 @@ const daemon_health_1 = require("../heart/daemon/daemon-health");
86
86
  const scrutiny_1 = require("./scrutiny");
87
87
  const pulse_1 = require("../heart/daemon/pulse");
88
88
  const provider_visibility_1 = require("../heart/provider-visibility");
89
+ const cmux_config_1 = require("../heart/cmux-config");
89
90
  const store_1 = require("../trips/store");
90
91
  const orientation_frame_1 = require("../heart/orientation-frame");
91
92
  const flight_recorder_1 = require("../arc/flight-recorder");
@@ -478,6 +479,7 @@ function localSenseStatusLines() {
478
479
  a2a: configuredSenses.a2a ?? { enabled: false },
479
480
  telegram: configuredSenses.telegram ?? { enabled: false },
480
481
  workbench: configuredSenses.workbench ?? { enabled: false },
482
+ cmux: configuredSenses.cmux ?? { enabled: false },
481
483
  };
482
484
  const payload = (0, config_1.loadConfig)();
483
485
  const runtimeConfig = (0, runtime_credentials_1.readRuntimeCredentialConfig)((0, identity_1.getAgentName)());
@@ -506,6 +508,7 @@ function localSenseStatusLines() {
506
508
  && hasTextField(runtimePayload, "telegramAuthorizedUserId")
507
509
  && hasTextField(runtimePayload, "telegramAuthorizedChatId"),
508
510
  workbench: false,
511
+ cmux: (0, cmux_config_1.cmuxSenseStatus)(true, machinePayload) === "ready",
509
512
  };
510
513
  const rows = [
511
514
  { label: "CLI", status: "interactive" },
@@ -533,6 +536,10 @@ function localSenseStatusLines() {
533
536
  label: "Telegram",
534
537
  status: !senses.telegram.enabled ? "disabled" : configured.telegram ? "ready" : "needs_config",
535
538
  },
539
+ {
540
+ label: "cmux",
541
+ status: (0, cmux_config_1.cmuxSenseStatus)(senses.cmux.enabled, machinePayload),
542
+ },
536
543
  {
537
544
  label: "Workbench",
538
545
  // Workbench can be injected at runtime (the Workbench app spawns
@@ -441,6 +441,9 @@ const TRAVEL_TRUSTED_TOOLS = new Set(["weather_lookup", "travel_advisory", "geoc
441
441
  const A2A_TRUSTED_TOOLS = new Set(["a2a_list_peers", "a2a_send_message", "a2a_get_task"]);
442
442
  const COMMERCE_FAMILY_TOOLS = new Set(["commerce_checkout_preview", "commerce_checkout_commit", "commerce_receipt_get", "commerce_access_log"]);
443
443
  const COMMERCE_AUTHORITY_TOOLS = new Set(["stripe_create_card", "flight_hold", "flight_book"]);
444
+ // The cmux tools read the owner's terminals and speak to them through their desktop, so only family may use them.
445
+ // A call with no known trust level fails closed.
446
+ const CMUX_FAMILY_TOOLS = new Set(["cmux_overview", "cmux_read", "cmux_signal"]);
444
447
  const MAIL_FAMILY_TOOLS = new Set(["mail_screener", "mail_decide", "mail_access_log", "mail_send", "mail_index_refresh"]);
445
448
  const MAIL_DELEGATED_READ_TOOLS = new Set(["mail_recent", "mail_search"]);
446
449
  function opaqueAnsiShellAtoms(command) {
@@ -1181,6 +1184,9 @@ function checkTrustLevelGuardrails(toolName, args, context) {
1181
1184
  const mailResult = mailTrustGuardrail(toolName, args, context);
1182
1185
  if (!mailResult.allowed)
1183
1186
  return mailResult;
1187
+ if (CMUX_FAMILY_TOOLS.has(toolName) && context.trustLevel !== "family") {
1188
+ return deny("the owner's cmux terminals require family trust.");
1189
+ }
1184
1190
  // Credential tools have their own trust rules that apply at all levels
1185
1191
  const credentialResult = checkCredentialTrustGuardrails(toolName, context);
1186
1192
  if (!credentialResult.allowed)