@ouro.bot/cli 0.1.0-alpha.654 → 0.1.0-alpha.657

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,24 @@
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.657",
6
+ "changes": [
7
+ "Harden Workbench sense coverage and CI readiness checks."
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.656",
12
+ "changes": [
13
+ "Add Ouro Workbench as a first-class local sense with CLI repair, MCP registration status, prompt inventory, and source-of-truth tests."
14
+ ]
15
+ },
16
+ {
17
+ "version": "0.1.0-alpha.655",
18
+ "changes": [
19
+ "Add focused help for A2A CLI subcommands."
20
+ ]
21
+ },
4
22
  {
5
23
  "version": "0.1.0-alpha.654",
6
24
  "changes": [
@@ -200,6 +200,15 @@ const registryData = [
200
200
  topics: ["senses", "voice", "audio", "speech", "channels", "interface"],
201
201
  validate: validateObject({ enabled: validateBoolean }),
202
202
  },
203
+ {
204
+ path: "senses.workbench",
205
+ tier: "self",
206
+ description: "Workbench sense configuration. Controls whether the native terminal-agent control room is enabled for this agent.",
207
+ default: { enabled: false },
208
+ effects: "Enables the local Ouro Workbench sense. Workbench MCP registration remains in mcpServers.ouro_workbench.",
209
+ topics: ["senses", "workbench", "mcp", "terminal", "channels", "interface"],
210
+ validate: validateObject({ enabled: validateBoolean }),
211
+ },
203
212
  {
204
213
  path: "sync.enabled",
205
214
  tier: "self",
@@ -2496,25 +2496,64 @@ function enableAgentSense(agent, sense, deps) {
2496
2496
  mail: senses.mail ?? { enabled: false },
2497
2497
  voice: senses.voice ?? { enabled: false },
2498
2498
  a2a: senses.a2a ?? { enabled: false },
2499
+ workbench: senses.workbench ?? { enabled: false },
2499
2500
  [sense]: { ...existing, enabled: true },
2500
2501
  };
2501
2502
  fs.writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}\n`, "utf-8");
2502
2503
  }
2503
- const CONNECT_MENU_PROMPT = "Choose [1-8] or type a name: ";
2504
+ const CONNECT_MENU_PROMPT = "Choose [1-9] or type a name: ";
2504
2505
  function connectMenuIsTTY(deps) {
2505
2506
  return deps.isTTY ?? process.stdout.isTTY === true;
2506
2507
  }
2507
2508
  function readConnectBaySenseFlags(agent, deps) {
2508
2509
  const configPath = path.join(providerCliAgentRoot({ agent }, deps), "agent.json");
2509
2510
  const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
2511
+ const workbenchCommand = parsed.mcpServers?.ouro_workbench?.command;
2510
2512
  return {
2511
2513
  teamsEnabled: parsed.senses?.teams?.enabled === true,
2512
2514
  blueBubblesEnabled: parsed.senses?.bluebubbles?.enabled === true,
2513
2515
  mailEnabled: parsed.senses?.mail?.enabled === true,
2514
2516
  voiceEnabled: parsed.senses?.voice?.enabled === true,
2515
2517
  a2aEnabled: parsed.senses?.a2a?.enabled === true,
2518
+ workbenchEnabled: parsed.senses?.workbench?.enabled === true,
2519
+ workbenchMcpCommand: typeof workbenchCommand === "string" && workbenchCommand.trim().length > 0
2520
+ ? workbenchCommand
2521
+ : null,
2516
2522
  };
2517
2523
  }
2524
+ function defaultWorkbenchMcpCandidates(deps) {
2525
+ const homeDir = deps.homeDir ?? os.homedir();
2526
+ return [
2527
+ path.join(homeDir, "Applications", "Ouro Workbench.app", "Contents", "MacOS", "OuroWorkbenchMCP"),
2528
+ path.join("/Applications", "Ouro Workbench.app", "Contents", "MacOS", "OuroWorkbenchMCP"),
2529
+ ];
2530
+ }
2531
+ function cliPathExists(deps, filePath) {
2532
+ return !!filePath && (deps.existsSync ?? fs.existsSync)(filePath);
2533
+ }
2534
+ function findInstalledWorkbenchMcp(deps, preferred) {
2535
+ const candidates = [
2536
+ ...(preferred ? [preferred] : []),
2537
+ ...defaultWorkbenchMcpCandidates(deps),
2538
+ ];
2539
+ return candidates.find((candidate) => cliPathExists(deps, candidate)) ?? null;
2540
+ }
2541
+ function writeWorkbenchMcpRegistration(agent, executablePath, deps) {
2542
+ const { configPath } = (0, auth_flow_1.readAgentConfigForAgent)(agent, deps.bundlesRoot);
2543
+ enableAgentSense(agent, "workbench", deps);
2544
+ const nextRaw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
2545
+ const mcpServers = nextRaw.mcpServers && typeof nextRaw.mcpServers === "object" && !Array.isArray(nextRaw.mcpServers)
2546
+ ? nextRaw.mcpServers
2547
+ : {};
2548
+ nextRaw.mcpServers = {
2549
+ ...mcpServers,
2550
+ ouro_workbench: {
2551
+ command: executablePath,
2552
+ args: [],
2553
+ },
2554
+ };
2555
+ fs.writeFileSync(configPath, `${JSON.stringify(nextRaw, null, 2)}\n`, "utf-8");
2556
+ }
2518
2557
  async function buildConnectMenu(agent, deps, onProgress) {
2519
2558
  const bundlesRoot = path.dirname(providerCliAgentRoot({ agent }, deps));
2520
2559
  let providerHealth;
@@ -2542,7 +2581,7 @@ async function buildConnectMenu(agent, deps, onProgress) {
2542
2581
  const runtimeConfig = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(agent, { preserveCachedOnFailure: true });
2543
2582
  onProgress?.("loading this machine's settings");
2544
2583
  const machineRuntime = await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agent, currentMachineId(deps), { preserveCachedOnFailure: true });
2545
- const { teamsEnabled, blueBubblesEnabled, mailEnabled, voiceEnabled, a2aEnabled } = readConnectBaySenseFlags(agent, deps);
2584
+ const { teamsEnabled, blueBubblesEnabled, mailEnabled, voiceEnabled, a2aEnabled, workbenchEnabled, workbenchMcpCommand, } = readConnectBaySenseFlags(agent, deps);
2546
2585
  const perplexityApiKey = runtimeConfig.ok
2547
2586
  ? readRuntimeConfigString(runtimeConfig.config, "integrations.perplexityApiKey")
2548
2587
  : null;
@@ -2698,6 +2737,26 @@ async function buildConnectMenu(agent, deps, onProgress) {
2698
2737
  : "missing"
2699
2738
  : runtimeConfigReadStatus(runtimeConfig);
2700
2739
  const a2aStatus = a2aEnabled ? "ready" : "missing";
2740
+ const installedWorkbenchMcp = findInstalledWorkbenchMcp(deps, workbenchMcpCommand);
2741
+ const configuredWorkbenchMcpExists = cliPathExists(deps, workbenchMcpCommand);
2742
+ const workbenchStatus = workbenchEnabled && configuredWorkbenchMcpExists
2743
+ ? "ready"
2744
+ : workbenchEnabled || workbenchMcpCommand
2745
+ ? "needs attention"
2746
+ : installedWorkbenchMcp
2747
+ ? "not attached"
2748
+ : "missing";
2749
+ const workbenchDetailLines = [
2750
+ workbenchEnabled ? "senses.workbench.enabled = true" : "senses.workbench.enabled is not enabled",
2751
+ workbenchMcpCommand
2752
+ ? configuredWorkbenchMcpExists
2753
+ ? `MCP command registered: ${workbenchMcpCommand}`
2754
+ : `registered MCP command is missing: ${workbenchMcpCommand}`
2755
+ : "mcpServers.ouro_workbench is not registered",
2756
+ installedWorkbenchMcp
2757
+ ? `OuroWorkbenchMCP found: ${installedWorkbenchMcp}`
2758
+ : "OuroWorkbenchMCP not found in ~/Applications or /Applications",
2759
+ ];
2701
2760
  const entries = [
2702
2761
  {
2703
2762
  option: "1",
@@ -2826,6 +2885,20 @@ async function buildConnectMenu(agent, deps, onProgress) {
2826
2885
  status: a2aStatus,
2827
2886
  }) ? `ouro connect a2a --agent ${agent}` : undefined,
2828
2887
  },
2888
+ {
2889
+ option: "9",
2890
+ name: "Ouro Workbench",
2891
+ section: "This machine",
2892
+ status: workbenchStatus,
2893
+ description: "Native terminal-agent control room and local MCP sense.",
2894
+ detailLines: workbenchDetailLines,
2895
+ nextAction: (0, connect_bay_1.connectEntryNeedsAttention)({
2896
+ option: "9",
2897
+ name: "Ouro Workbench",
2898
+ section: "This machine",
2899
+ status: workbenchStatus,
2900
+ }) ? `ouro connect workbench --agent ${agent}` : undefined,
2901
+ },
2829
2902
  ];
2830
2903
  const isTTY = connectMenuIsTTY(deps);
2831
2904
  return (0, connect_bay_1.renderConnectBay)(entries, {
@@ -4364,6 +4437,8 @@ function connectMenuTarget(answer) {
4364
4437
  /* v8 ignore next -- direct `ouro connect a2a` covers behavior; interactive menu requires broader provider/vault readiness work @preserve */
4365
4438
  if (normalized === "8" || normalized === "a2a" || normalized === "agent2agent" || normalized === "agent-to-agent")
4366
4439
  return "a2a";
4440
+ if (normalized === "9" || normalized === "workbench" || normalized === "ouro-workbench" || normalized === "terminal-workbench")
4441
+ return "workbench";
4367
4442
  return "cancel";
4368
4443
  }
4369
4444
  async function executeConnectVoice(agent, deps) {
@@ -4421,6 +4496,30 @@ async function executeConnectA2A(agent, deps) {
4421
4496
  deps.writeStdout(message);
4422
4497
  return message;
4423
4498
  }
4499
+ async function executeConnectWorkbench(agent, deps) {
4500
+ const { workbenchMcpCommand } = readConnectBaySenseFlags(agent, deps);
4501
+ const executablePath = findInstalledWorkbenchMcp(deps, workbenchMcpCommand);
4502
+ if (!executablePath) {
4503
+ throw new Error([
4504
+ `Ouro Workbench is not installed for ${agent} on this machine.`,
4505
+ "Expected the MCP executable at one of:",
4506
+ ...defaultWorkbenchMcpCandidates(deps).map((candidate) => ` ${candidate}`),
4507
+ "Install or copy Ouro Workbench.app there, then run:",
4508
+ ` ouro connect workbench --agent ${agent}`,
4509
+ ].join("\n"));
4510
+ }
4511
+ writeWorkbenchMcpRegistration(agent, executablePath, deps);
4512
+ const syncSummary = pushAgentBundleAfterCliMutation(agent, deps);
4513
+ const message = [
4514
+ `Workbench connected for ${agent}`,
4515
+ "agent.json: senses.workbench.enabled = true",
4516
+ `agent.json: mcpServers.ouro_workbench.command = ${executablePath}`,
4517
+ "Workbench is a local machine sense; provider secrets stay in the agent vault.",
4518
+ ...(syncSummary ? [syncSummary] : []),
4519
+ ].join("\n");
4520
+ deps.writeStdout(message);
4521
+ return message;
4522
+ }
4424
4523
  async function executeConnect(command, deps) {
4425
4524
  if (command.target === "providers")
4426
4525
  return executeConnectProviders(command.agent, deps);
@@ -4438,6 +4537,8 @@ async function executeConnect(command, deps) {
4438
4537
  return executeConnectVoice(command.agent, deps);
4439
4538
  if (command.target === "a2a")
4440
4539
  return executeConnectA2A(command.agent, deps);
4540
+ if (command.target === "workbench")
4541
+ return executeConnectWorkbench(command.agent, deps);
4441
4542
  const progress = createHumanCommandProgress(deps, "connect");
4442
4543
  let menu;
4443
4544
  try {
@@ -4449,7 +4550,7 @@ async function executeConnect(command, deps) {
4449
4550
  const promptInput = deps.promptInput;
4450
4551
  if (!promptInput) {
4451
4552
  const message = [
4452
- menu.replace(/\nChoose \[1-8\] or type a name: $/, ""),
4553
+ menu.replace(/\nChoose \[1-9\] or type a name: $/, ""),
4453
4554
  "",
4454
4555
  `Run: ouro connect providers --agent ${command.agent}`,
4455
4556
  `Run: ouro connect perplexity --agent ${command.agent}`,
@@ -4459,6 +4560,7 @@ async function executeConnect(command, deps) {
4459
4560
  `Run: ouro connect mail --agent ${command.agent}`,
4460
4561
  `Run: ouro connect voice --agent ${command.agent}`,
4461
4562
  `Run: ouro connect a2a --agent ${command.agent}`,
4563
+ `Run: ouro connect workbench --agent ${command.agent}`,
4462
4564
  ].join("\n");
4463
4565
  deps.writeStdout(message);
4464
4566
  return message;
@@ -4481,6 +4583,8 @@ async function executeConnect(command, deps) {
4481
4583
  /* v8 ignore next -- direct `ouro connect a2a` covers behavior; interactive menu requires broader provider/vault readiness work @preserve */
4482
4584
  if (answer === "a2a")
4483
4585
  return executeConnectA2A(command.agent, deps);
4586
+ if (answer === "workbench")
4587
+ return executeConnectWorkbench(command.agent, deps);
4484
4588
  const message = "connect cancelled.";
4485
4589
  deps.writeStdout(message);
4486
4590
  return message;
@@ -187,9 +187,9 @@ exports.COMMAND_REGISTRY = {
187
187
  connect: {
188
188
  category: "Auth",
189
189
  description: "Set up providers, portable integrations, and local senses from one guided screen",
190
- usage: "ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail|voice|a2a] [--agent <name>]",
190
+ usage: "ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail|voice|a2a|workbench] [--agent <name>]",
191
191
  example: "ouro connect",
192
- subcommands: ["providers", "perplexity", "embeddings", "teams", "bluebubbles", "mail", "voice", "a2a"],
192
+ subcommands: ["providers", "perplexity", "embeddings", "teams", "bluebubbles", "mail", "voice", "a2a", "workbench"],
193
193
  },
194
194
  a2a: {
195
195
  category: "Friends",
@@ -342,6 +342,26 @@ const SUBCOMMAND_HELP = {
342
342
  usage: "ouro connect a2a [--agent <name>]",
343
343
  example: "ouro connect a2a --agent <agent>",
344
344
  },
345
+ "connect workbench": {
346
+ description: "Attach native Ouro Workbench on this machine and register its MCP sense",
347
+ usage: "ouro connect workbench [--agent <name>]",
348
+ example: "ouro connect workbench --agent <agent>",
349
+ },
350
+ "a2a card": {
351
+ description: "Print this agent's A2A Agent Card",
352
+ usage: "ouro a2a card [--agent <name>] [--base-url <url>] [--json]",
353
+ example: "ouro a2a card --agent <agent> --base-url https://agent.example --json",
354
+ },
355
+ "a2a onboard": {
356
+ description: "Onboard an A2A peer into the existing friend model",
357
+ usage: "ouro a2a onboard [--agent <name>] --card-url <url> [--trust <level>] [--name <name>]",
358
+ example: "ouro a2a onboard --agent <agent> --card-url https://peer.example/.well-known/agent-card.json --trust friend",
359
+ },
360
+ "a2a serve": {
361
+ description: "Run this agent's local A2A JSON-RPC sense server",
362
+ usage: "ouro a2a serve [--agent <name>] [--host <host>] [--port <port>] [--base-url <url>] [--path <path>]",
363
+ example: "ouro a2a serve --agent <agent> --base-url https://agent.example",
364
+ },
345
365
  "account ensure": {
346
366
  description: "Idempotently prepare an agent's vault-backed work substrate account and private Mailroom mailbox",
347
367
  usage: "ouro account ensure [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
@@ -84,7 +84,7 @@ function usage() {
84
84
  " ouro -v|--version",
85
85
  " ouro auth [--agent <name>] [--provider <provider>]",
86
86
  " ouro account ensure [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
87
- " ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail|voice|a2a] [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
87
+ " ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail|voice|a2a|workbench] [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]",
88
88
  " ouro mail import-mbox --file <path> [--owner-email <email>] [--source <label>] [--agent <name>] [--foreground]",
89
89
  " ouro mail backfill-indexes [--agent <name>] [--foreground]",
90
90
  " ouro auth verify [--agent <name>] [--provider <provider>]",
@@ -744,7 +744,9 @@ function normalizeConnectTarget(value) {
744
744
  return "voice";
745
745
  if (value === "a2a" || value === "agent2agent" || value === "agent-to-agent")
746
746
  return "a2a";
747
- throw new Error("Usage: ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail|voice|a2a] [--agent <name>]");
747
+ if (value === "workbench" || value === "ouro-workbench" || value === "terminal-workbench")
748
+ return "workbench";
749
+ throw new Error("Usage: ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail|voice|a2a|workbench] [--agent <name>]");
748
750
  }
749
751
  function extractMailSourceFlags(args, usageText) {
750
752
  const rest = [];
@@ -797,7 +799,7 @@ function extractMailSourceFlags(args, usageText) {
797
799
  };
798
800
  }
799
801
  function parseConnectCommand(args) {
800
- const usageText = "Usage: ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail|voice|a2a] [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]";
802
+ const usageText = "Usage: ouro connect [providers|perplexity|embeddings|teams|bluebubbles|mail|voice|a2a|workbench] [--agent <name>] [--owner-email <email> --source <label>|--no-delegated-source] [--rotate-missing-mail-keys]";
801
803
  const { agent, rest: afterAgent } = extractAgentFlag(args);
802
804
  const mailFlags = extractMailSourceFlags(afterAgent, usageText);
803
805
  if (mailFlags.rest.length > 1)
@@ -57,6 +57,7 @@ function defaultSenses() {
57
57
  mail: { ...identity_1.DEFAULT_AGENT_SENSES.mail },
58
58
  voice: { ...identity_1.DEFAULT_AGENT_SENSES.voice },
59
59
  a2a: { ...identity_1.DEFAULT_AGENT_SENSES.a2a },
60
+ workbench: { ...identity_1.DEFAULT_AGENT_SENSES.workbench },
60
61
  };
61
62
  }
62
63
  function readAgentSenses(agentJsonPath) {
@@ -82,7 +83,7 @@ function readAgentSenses(agentJsonPath) {
82
83
  if (!rawSenses || typeof rawSenses !== "object" || Array.isArray(rawSenses)) {
83
84
  return defaults;
84
85
  }
85
- for (const sense of ["cli", "teams", "bluebubbles", "mail", "voice", "a2a"]) {
86
+ for (const sense of ["cli", "teams", "bluebubbles", "mail", "voice", "a2a", "workbench"]) {
86
87
  const rawSense = rawSenses[sense];
87
88
  if (!rawSense || typeof rawSense !== "object" || Array.isArray(rawSense)) {
88
89
  continue;
@@ -147,6 +148,7 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntim
147
148
  mail: { configured: false, detail: "not enabled in agent.json" },
148
149
  voice: { configured: false, detail: "not enabled in agent.json" },
149
150
  a2a: { configured: false, detail: "not enabled in agent.json" },
151
+ workbench: { configured: false, detail: "not enabled in agent.json" },
150
152
  };
151
153
  const payload = runtimeConfig.ok ? runtimeConfig.config : {};
152
154
  const unavailableDetail = runtimeConfigUnavailableDetail(agent, runtimeConfig);
@@ -301,6 +303,12 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntim
301
303
  detail: publicUrl ? `${publicUrl}${endpointPath}` : `:${port} ${endpointPath}`,
302
304
  };
303
305
  }
306
+ if (senses.workbench.enabled) {
307
+ base.workbench = {
308
+ configured: true,
309
+ detail: "native Workbench local control room; MCP registration is stored in agent.json",
310
+ };
311
+ }
304
312
  return base;
305
313
  }
306
314
  function senseRepairHint(agent, sense) {
@@ -317,6 +325,10 @@ function senseRepairHint(agent, sense) {
317
325
  if (sense === "a2a") {
318
326
  return `Agent-runnable: run 'ouro connect a2a --agent ${agent}', then restart with 'ouro up'.`;
319
327
  }
328
+ /* v8 ignore next -- Workbench is deliberately not daemon-managed, so getSenseInventory never asks the daemon manager for a repair hint @preserve */
329
+ if (sense === "workbench") {
330
+ return `Agent-runnable: run 'ouro connect workbench --agent ${agent}' to enable senses.workbench.enabled and mcpServers.ouro_workbench in agent.json.`;
331
+ }
320
332
  return `Run 'ouro connect bluebubbles --agent ${agent}' to attach BlueBubbles on this machine; then run 'ouro up' again.`;
321
333
  }
322
334
  function currentMachineId() {
@@ -768,6 +780,9 @@ class DaemonSenseManager {
768
780
  configured: context.facts.a2a.configured,
769
781
  ...(runtime.get(agent)?.a2a ?? {}),
770
782
  },
783
+ workbench: {
784
+ configured: context.facts.workbench.configured,
785
+ },
771
786
  };
772
787
  const inventory = (0, sense_truth_1.getSenseInventory)({ senses: context.senses }, runtimeInfo);
773
788
  return inventory.map((entry) => ({
@@ -138,6 +138,7 @@ exports.DEFAULT_AGENT_SENSES = {
138
138
  mail: { enabled: false },
139
139
  voice: { enabled: false },
140
140
  a2a: { enabled: false },
141
+ workbench: { enabled: false },
141
142
  };
142
143
  function normalizeSenses(value, configFile) {
143
144
  const defaults = {
@@ -147,6 +148,7 @@ function normalizeSenses(value, configFile) {
147
148
  mail: { ...exports.DEFAULT_AGENT_SENSES.mail },
148
149
  voice: { ...exports.DEFAULT_AGENT_SENSES.voice },
149
150
  a2a: { ...exports.DEFAULT_AGENT_SENSES.a2a },
151
+ workbench: { ...exports.DEFAULT_AGENT_SENSES.workbench },
150
152
  };
151
153
  if (value === undefined) {
152
154
  return defaults;
@@ -162,7 +164,7 @@ function normalizeSenses(value, configFile) {
162
164
  throw new Error(`agent.json at ${configFile} must include senses as an object when present.`);
163
165
  }
164
166
  const raw = value;
165
- const senseNames = ["cli", "teams", "bluebubbles", "mail", "voice", "a2a"];
167
+ const senseNames = ["cli", "teams", "bluebubbles", "mail", "voice", "a2a", "workbench"];
166
168
  for (const senseName of senseNames) {
167
169
  const rawSense = raw[senseName];
168
170
  if (rawSense === undefined) {
@@ -207,6 +209,7 @@ function buildDefaultAgentTemplate(_agentName) {
207
209
  mail: { ...exports.DEFAULT_AGENT_SENSES.mail },
208
210
  voice: { ...exports.DEFAULT_AGENT_SENSES.voice },
209
211
  a2a: { ...exports.DEFAULT_AGENT_SENSES.a2a },
212
+ workbench: { ...exports.DEFAULT_AGENT_SENSES.workbench },
210
213
  },
211
214
  phrases: {
212
215
  thinking: [...exports.DEFAULT_AGENT_PHRASES.thinking],
@@ -10,6 +10,7 @@ const SENSES = [
10
10
  { sense: "mail", label: "Mail", daemonManaged: true },
11
11
  { sense: "voice", label: "Voice", daemonManaged: true },
12
12
  { sense: "a2a", label: "A2A", daemonManaged: true },
13
+ { sense: "workbench", label: "Workbench", daemonManaged: false },
13
14
  ];
14
15
  function configuredSenses(senses) {
15
16
  const configured = senses ?? {};
@@ -21,6 +22,7 @@ function configuredSenses(senses) {
21
22
  mail: configured.mail ?? { ...identity_1.DEFAULT_AGENT_SENSES.mail },
22
23
  voice: configured.voice ?? { ...identity_1.DEFAULT_AGENT_SENSES.voice },
23
24
  a2a: configured.a2a ?? { ...identity_1.DEFAULT_AGENT_SENSES.a2a },
25
+ workbench: configured.workbench ?? { ...identity_1.DEFAULT_AGENT_SENSES.workbench },
24
26
  };
25
27
  }
26
28
  function resolveStatus(enabled, daemonManaged, runtimeInfo) {
@@ -141,6 +141,7 @@ function readSenseStatusLines() {
141
141
  mail: configuredSenses.mail ?? { enabled: false },
142
142
  voice: configuredSenses.voice ?? { enabled: false },
143
143
  a2a: configuredSenses.a2a ?? { enabled: false },
144
+ workbench: configuredSenses.workbench ?? { enabled: false },
144
145
  };
145
146
  const payload = (0, config_1.loadConfig)();
146
147
  const agentName = (0, identity_1.getAgentName)();
@@ -185,6 +186,8 @@ function readSenseStatusLines() {
185
186
  ? openAIRealtimeVoiceReady
186
187
  : cascadeVoiceReady,
187
188
  a2a: true,
189
+ workbench: typeof config.mcpServers?.ouro_workbench?.command === "string"
190
+ && config.mcpServers.ouro_workbench.command.trim().length > 0,
188
191
  };
189
192
  const rows = [
190
193
  { label: "CLI", status: "interactive" },
@@ -208,6 +211,10 @@ function readSenseStatusLines() {
208
211
  label: "A2A",
209
212
  status: senses.a2a.enabled ? "ready" : "disabled",
210
213
  },
214
+ {
215
+ label: "Workbench",
216
+ status: !senses.workbench.enabled ? "disabled" : configured.workbench ? "ready" : "needs_config",
217
+ },
211
218
  ];
212
219
  return rows.map((row) => `- ${row.label}: ${row.status}`);
213
220
  }
@@ -440,6 +440,7 @@ function localSenseStatusLines() {
440
440
  mail: configuredSenses.mail ?? { enabled: false },
441
441
  voice: configuredSenses.voice ?? { enabled: false },
442
442
  a2a: configuredSenses.a2a ?? { enabled: false },
443
+ workbench: configuredSenses.workbench ?? { enabled: false },
443
444
  };
444
445
  const payload = (0, config_1.loadConfig)();
445
446
  const runtimeConfig = (0, runtime_credentials_1.readRuntimeCredentialConfig)((0, identity_1.getAgentName)());
@@ -463,6 +464,8 @@ function localSenseStatusLines() {
463
464
  && hasTextField(voice, "whisperCliPath")
464
465
  && hasTextField(voice, "whisperModelPath"),
465
466
  a2a: true,
467
+ workbench: typeof config.mcpServers?.ouro_workbench?.command === "string"
468
+ && config.mcpServers.ouro_workbench.command.trim().length > 0,
466
469
  };
467
470
  const rows = [
468
471
  { label: "CLI", status: "interactive" },
@@ -486,6 +489,10 @@ function localSenseStatusLines() {
486
489
  label: "A2A",
487
490
  status: senses.a2a.enabled ? "ready" : "disabled",
488
491
  },
492
+ {
493
+ label: "Workbench",
494
+ status: !senses.workbench.enabled ? "disabled" : configured.workbench ? "ready" : "needs_config",
495
+ },
489
496
  ];
490
497
  return rows.map((row) => `- ${row.label}: ${row.status}`);
491
498
  }
@@ -504,6 +511,7 @@ function senseRuntimeGuidance(channel, preReadStatusLines) {
504
511
  lines.push("teams setup truth: run `ouro connect teams --agent <agent>` from the connect bay; it stores Teams runtime/config fields and enables `senses.teams.enabled`.");
505
512
  lines.push("bluebubbles setup truth: run `ouro connect bluebubbles --agent <agent>` from the connect bay; it stores this machine's BlueBubbles URL/password/listener config in the agent vault machine runtime item.");
506
513
  lines.push("a2a setup truth: run `ouro connect a2a --agent <agent>` to enable the A2A sense, `ouro a2a card --agent <agent> --base-url <public-url>` to publish an agent card, and `ouro a2a onboard --agent <agent> --card-url <peer-card-url>` to add a peer as an agent friend. A2A uses the existing friend trust model, not a separate trust registry.");
514
+ lines.push("workbench setup truth: Ouro Workbench is the local machine sense for terminal/TUI agents. Enabling it means `agent.json` has `senses.workbench.enabled=true` and an `mcpServers.ouro_workbench` entry pointing at the installed `OuroWorkbenchMCP` executable. The boss observes and queues auditable Workbench actions through `workbench_status`, `workbench_sense`, `workbench_transcript_tail`, `workbench_search_transcripts`, `workbench_recovery_drill`, and `workbench_request_action`; raw provider secrets remain in the agent vault, and Apple notarization is unrelated to local use.");
507
515
  lines.push("mail setup AX: if a human asks me to set up email, I do not hand them a terminal checklist. I guide the flow end-to-end: name the current phase, run agent-runnable commands myself with shell/tools when available, ask the human only for human-required facts or browser actions, wait for their reply, verify the result, then continue.");
508
516
  lines.push("mail setup hard rule: never tell the human to run `ouro account ensure`, `ouro connect mail`, `ouro mail import-mbox`, `ouro status`, or `ouro doctor` for setup. Say what I am about to run, run it myself, and report the result. If my current surface cannot run shell/tools, I ask for a tool-capable Ouro setup session or companion to continue; I do not offload CLI operation to the human.");
509
517
  lines.push("mail setup truth: Agent Mail uses Mailroom, not HEY OAuth/IMAP. For the full work substrate account, the agent-runnable command is `ouro account ensure --agent <agent> --owner-email <email> --source hey`; use `ouro connect mail --agent <agent> --owner-email <email> --source hey` for mail-only repair/provisioning, or `--no-delegated-source` for native-only mail. The detailed runbook is `docs/agent-mail-setup.md`.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.654",
3
+ "version": "0.1.0-alpha.657",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",