@ouro.bot/cli 0.1.0-alpha.803 → 0.1.0-alpha.805

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,20 @@
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.805",
6
+ "changes": [
7
+ "Add the ACP frontend and daemon socket used by Ouro Workbench to start and supervise a selected boss agent.",
8
+ "Keep Workbench-owned session identity across prompts, approvals, cancellation, journaling, and session subscriptions.",
9
+ "Serialize identity-sensitive turns and per-turn runtime MCP cleanup so frontend state does not carry into the next turn."
10
+ ]
11
+ },
12
+ {
13
+ "version": "0.1.0-alpha.804",
14
+ "changes": [
15
+ "Complete safe Sanctuary rollback work before reporting container cleanup failure."
16
+ ]
17
+ },
4
18
  {
5
19
  "version": "0.1.0-alpha.803",
6
20
  "changes": [
@@ -2285,6 +2285,8 @@ ouro-butler-rollback
2285
2285
  :
2286
2286
  else
2287
2287
  PRODUCTION_PREPARATION_STATUS=$?
2288
+ PRODUCTION_PREPARATION_CLEANUP_STATUS=0
2289
+ PRODUCTION_PREPARATION_RECOVERY_SAFE=true
2288
2290
  /usr/local/bin/node "$STAGED_DOCKERMAN_TRANSACTION" verify-jellyfin >/dev/null
2289
2291
  if docker container inspect ouro-butler >/dev/null 2>&1; then
2290
2292
  if docker container inspect ouro-butler-rollback >/dev/null 2>&1; then
@@ -2293,13 +2295,20 @@ ouro-butler-rollback
2293
2295
  test "$CURRENT_ROLLBACK_IMAGE_ID" = "$ROLLBACK_IMAGE_ID"
2294
2296
  docker rm --force ouro-butler-rollback >/dev/null 2>&1 || true
2295
2297
  fi
2296
- ! docker container inspect ouro-butler-rollback >/dev/null 2>&1 || exit 1
2298
+ if docker container inspect ouro-butler-rollback >/dev/null 2>&1; then
2299
+ PRODUCTION_PREPARATION_CLEANUP_STATUS=1
2300
+ if test "$(docker inspect --format '{{.State.Running}}' ouro-butler-rollback)" != false; then
2301
+ PRODUCTION_PREPARATION_RECOVERY_SAFE=false
2302
+ fi
2303
+ fi
2297
2304
  rollback_sanctuary_bundle_if_pending "$IMAGE_ID"
2298
- assert_update_source "$ROLLBACK_IMAGE_ID" "$AUDIT_RUNNER_IMAGE_ID"
2299
- start_only_butler_for_recovery
2300
- wait_butler_ready ouro-butler
2301
- enable_butler_autostart
2302
- finalize_sanctuary_bundle_rollback_if_retained "$IMAGE_ID"
2305
+ if test "$PRODUCTION_PREPARATION_RECOVERY_SAFE" = true; then
2306
+ assert_update_source "$ROLLBACK_IMAGE_ID" "$AUDIT_RUNNER_IMAGE_ID"
2307
+ start_only_butler_for_recovery
2308
+ wait_butler_ready ouro-butler
2309
+ enable_butler_autostart
2310
+ finalize_sanctuary_bundle_rollback_if_retained "$IMAGE_ID"
2311
+ fi
2303
2312
  elif docker container inspect ouro-butler-rollback >/dev/null 2>&1; then
2304
2313
  docker stop ouro-butler-rollback >/dev/null 2>&1 || true
2305
2314
  test "$(docker inspect --format '{{.Image}}' ouro-butler-rollback)" = "$ROLLBACK_IMAGE_ID"
@@ -2312,14 +2321,17 @@ ouro-butler-rollback
2312
2321
  finalize_sanctuary_bundle_rollback_if_retained "$IMAGE_ID"
2313
2322
  fi
2314
2323
  /usr/local/bin/node "$STAGED_DOCKERMAN_TRANSACTION" rollback >/dev/null
2324
+ if test "$PRODUCTION_PREPARATION_CLEANUP_STATUS" -ne 0; then
2325
+ exit "$PRODUCTION_PREPARATION_CLEANUP_STATUS"
2326
+ fi
2315
2327
  (exit "$PRODUCTION_PREPARATION_STATUS")
2316
2328
  fi
2317
- Preparation failure therefore either restores the still-named exact production after removing any stale rollback, or renames the exact stopped rollback back. Both paths revalidate and start the old container, wait within the fixed bound, restore production-only autostart atomically, and return the original failure.
2329
+ Preparation failure therefore either restores the still-named exact production after attempting to remove any stale rollback, or renames the exact stopped rollback back. Both safe paths revalidate and start the old container, wait within the fixed bound, restore production-only autostart atomically, and return the original failure. If stale rollback removal fails, restore production only when the leftover is proven stopped; otherwise leave both containers untouched, roll back the journals, and return the cleanup failure for repair.
2318
2330
  If neither exact container can be found, the failure returns with Butler autostart disabled.
2319
2331
  Do not start a target-image daemon between the production rename and final production activation. The exact-image static audit, download-queue readiness, and vault-backed Telegram identity check have already passed before autostart or live-container changes.
2320
2332
  Provider and complete daemon readiness are exercised only by the transactional production activation below. Its failure arm restores and revalidates the exact prior production, so a disposable daemon cannot reconcile or claim live external-event state before cutover.
2321
2333
  Create and activate production from the same exact image ID and exact authority in one explicit conditional so `set -eu` cannot exit before rollback. Only a successful create, effective audit, start, stopped-rollback assertion, and bounded readiness wait may enable production autostart.
2322
- On failure, capture the activation status, remove only a partially created new production container, restore and audit the stopped rollback against its exact old image ID, prove it ready, restore production-only autostart atomically, and return the original failure:
2334
+ On failure, capture the activation status, remove only a partially created new production container, restore and audit the stopped rollback against its exact old image ID, prove it ready, restore production-only autostart atomically, and return the original failure. If the partial target cannot be removed, preserve its matching target bundle and retained migration receipt, roll back the DockerMan journal without attempting the colliding rename, then return the cleanup failure:
2323
2335
  if test "$(inspect_registry_manifest_digest "$VERSION_IMAGE")" = "$MANIFEST_DIGEST" \
2324
2336
  && test "$(docker image inspect --format '{{.Id}}' "$VERSION_IMAGE")" = "$IMAGE_ID" \
2325
2337
  && docker create --pull=never --name ouro-butler --network host --restart unless-stopped --user 10001:10001 \
@@ -2341,6 +2353,7 @@ ouro-butler-rollback
2341
2353
  :
2342
2354
  else
2343
2355
  PRODUCTION_ACTIVATION_STATUS=$?
2356
+ PRODUCTION_ACTIVATION_CLEANUP_STATUS=0
2344
2357
  /usr/local/bin/node "$STAGED_DOCKERMAN_TRANSACTION" verify-jellyfin >/dev/null
2345
2358
  if docker container inspect ouro-butler >/dev/null 2>&1; then
2346
2359
  docker stop ouro-butler >/dev/null 2>&1 || true
@@ -2348,17 +2361,24 @@ ouro-butler-rollback
2348
2361
  test "$PARTIAL_PRODUCTION_IMAGE_ID" = "$IMAGE_ID"
2349
2362
  docker rm --force ouro-butler >/dev/null 2>&1 || true
2350
2363
  fi
2351
- ! docker container inspect ouro-butler >/dev/null 2>&1 || exit 1
2352
- CURRENT_ROLLBACK_IMAGE_ID=$(docker inspect --format '{{.Image}}' ouro-butler-rollback)
2353
- test "$CURRENT_ROLLBACK_IMAGE_ID" = "$ROLLBACK_IMAGE_ID"
2354
- migrate_sanctuary_package_managed_bundle "$IMAGE_ID" rollback
2355
- docker rename ouro-butler-rollback ouro-butler
2356
- assert_update_source "$ROLLBACK_IMAGE_ID" "$AUDIT_RUNNER_IMAGE_ID"
2357
- start_only_butler_for_recovery
2358
- wait_butler_ready ouro-butler
2359
- enable_butler_autostart
2360
- migrate_sanctuary_package_managed_bundle "$IMAGE_ID" finalize-rollback
2364
+ if docker container inspect ouro-butler >/dev/null 2>&1; then
2365
+ PRODUCTION_ACTIVATION_CLEANUP_STATUS=1
2366
+ fi
2367
+ if test "$PRODUCTION_ACTIVATION_CLEANUP_STATUS" -eq 0; then
2368
+ CURRENT_ROLLBACK_IMAGE_ID=$(docker inspect --format '{{.Image}}' ouro-butler-rollback)
2369
+ test "$CURRENT_ROLLBACK_IMAGE_ID" = "$ROLLBACK_IMAGE_ID"
2370
+ migrate_sanctuary_package_managed_bundle "$IMAGE_ID" rollback
2371
+ docker rename ouro-butler-rollback ouro-butler
2372
+ assert_update_source "$ROLLBACK_IMAGE_ID" "$AUDIT_RUNNER_IMAGE_ID"
2373
+ start_only_butler_for_recovery
2374
+ wait_butler_ready ouro-butler
2375
+ enable_butler_autostart
2376
+ migrate_sanctuary_package_managed_bundle "$IMAGE_ID" finalize-rollback
2377
+ fi
2361
2378
  /usr/local/bin/node "$STAGED_DOCKERMAN_TRANSACTION" rollback >/dev/null
2379
+ if test "$PRODUCTION_ACTIVATION_CLEANUP_STATUS" -ne 0; then
2380
+ exit "$PRODUCTION_ACTIVATION_CLEANUP_STATUS"
2381
+ fi
2362
2382
  (exit "$PRODUCTION_ACTIVATION_STATUS")
2363
2383
  fi
2364
2384
  migrate_sanctuary_package_managed_bundle "$IMAGE_ID" commit
@@ -1,5 +1,5 @@
1
1
  {
2
- "runtimeVersion": "0.1.0-alpha.803",
2
+ "runtimeVersion": "0.1.0-alpha.805",
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.803</Repository>
4
+ <Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.805</Repository>
5
5
  <Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
6
6
  <Network>host</Network>
7
7
  <Shell>sh</Shell>
@@ -967,7 +967,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
967
967
  const facing = (0, friends_1.channelToFacing)(channel);
968
968
  let providerRuntime = options?.providerRuntimeOverride ?? await getProviderRuntime(facing);
969
969
  const provider = providerRuntime.id;
970
- const toolChoiceRequired = options?.toolChoiceRequired ?? true;
970
+ const toolChoiceRequired = options?.hardDisableTools ? false : options?.toolChoiceRequired ?? true;
971
971
  const traceId = options?.traceId;
972
972
  (0, runtime_1.emitNervesEvent)({
973
973
  event: "engine.turn_start",
@@ -1253,15 +1253,17 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1253
1253
  const ordinaryActiveTools = relationshipToolNames
1254
1254
  ? unscopedOrdinaryActiveTools.filter((tool) => relationshipToolNames.includes(tool.function.name))
1255
1255
  : unscopedOrdinaryActiveTools;
1256
- const candidateActiveTools = options?.toolProfile === "sanctuary-health-private"
1257
- ? (() => {
1258
- const sendTools = baseTools.filter((tool) => tool.function.name === "send_message");
1259
- if (channel !== "inner" || sendTools.length !== 1 || baseTools.length !== 1) {
1260
- throw new Error("sanctuary-health-private requires inner channel with exactly one canonical send_message definition");
1261
- }
1262
- return [sendTools[0], tools_1.restTool];
1263
- })()
1264
- : ordinaryActiveTools;
1256
+ const candidateActiveTools = options?.hardDisableTools
1257
+ ? []
1258
+ : options?.toolProfile === "sanctuary-health-private"
1259
+ ? (() => {
1260
+ const sendTools = baseTools.filter((tool) => tool.function.name === "send_message");
1261
+ if (channel !== "inner" || sendTools.length !== 1 || baseTools.length !== 1) {
1262
+ throw new Error("sanctuary-health-private requires inner channel with exactly one canonical send_message definition");
1263
+ }
1264
+ return [sendTools[0], tools_1.restTool];
1265
+ })()
1266
+ : ordinaryActiveTools;
1265
1267
  const candidateToolNames = new Set(candidateActiveTools.map((tool) => tool.function.name));
1266
1268
  const unadvertisedRequiredTool = requiredToolCallNames.find((name) => !candidateToolNames.has(name));
1267
1269
  if (unadvertisedRequiredTool) {
@@ -1328,7 +1330,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
1328
1330
  callbacks: turnCallbackBufferRef.current?.callbacks ?? callbacks,
1329
1331
  signal,
1330
1332
  traceId,
1331
- toolChoiceRequired: forcingHistoricalEffect || toolChoiceRequired,
1333
+ toolChoiceRequired: options?.hardDisableTools ? false : forcingHistoricalEffect || toolChoiceRequired,
1332
1334
  reasoningEffort: currentReasoningEffort,
1333
1335
  eagerSettleStreaming: true,
1334
1336
  systemPrompt: structuredSystemPrompt,
@@ -2028,6 +2030,7 @@ async function runAgent(messages, callbacks, channel, signal, options) {
2028
2030
  policyDigest,
2029
2031
  policyId: protectedCall.policy.policyId,
2030
2032
  actionClass: protectedCall.policy.actionClass,
2033
+ liveToolContext: augmentedToolContext,
2031
2034
  });
2032
2035
  suspension = {
2033
2036
  approvalId: committed.approvalId,
@@ -40,6 +40,7 @@ var __importStar = (this && this.__importStar) || (function () {
40
40
  })();
41
41
  Object.defineProperty(exports, "__esModule", { value: true });
42
42
  exports.defaultStartDaemonProcess = defaultStartDaemonProcess;
43
+ exports.defaultStartDaemonForFrontend = defaultStartDaemonForFrontend;
43
44
  exports.readFirstBundleMetaVersion = readFirstBundleMetaVersion;
44
45
  exports.isDaemonLaunchAgentLoaded = isDaemonLaunchAgentLoaded;
45
46
  exports.waitForBootstrappedDaemonSocket = waitForBootstrappedDaemonSocket;
@@ -80,10 +81,7 @@ const provider_credentials_1 = require("../provider-credentials");
80
81
  const startup_tui_1 = require("./startup-tui");
81
82
  const daemon_tombstone_1 = require("./daemon-tombstone");
82
83
  // ── Default implementations ──
83
- async function defaultStartDaemonProcess(socketPath) {
84
- const launchdStarted = await startDaemonProcessViaLaunchd(socketPath);
85
- if (launchdStarted)
86
- return launchdStarted;
84
+ function spawnDetachedDaemonProcess(socketPath, environment = process.env) {
87
85
  const entry = path.join((0, identity_1.getRepoRoot)(), "dist", "heart", "daemon", "daemon-entry.js");
88
86
  // Redirect stdio to /dev/null via file descriptors — using 'ignore' causes EPIPE
89
87
  // when the daemon's logging system writes to stderr after the parent exits.
@@ -92,11 +90,30 @@ async function defaultStartDaemonProcess(socketPath) {
92
90
  const child = (0, child_process_1.spawn)(process.execPath, [entry, "--socket", socketPath], {
93
91
  detached: true,
94
92
  stdio: ["ignore", outFd, errFd],
93
+ env: environment,
95
94
  });
96
95
  child.unref();
97
96
  // Don't close fds — the child process needs them. They'll be cleaned up when the parent exits.
98
97
  return { pid: child.pid ?? null };
99
98
  }
99
+ async function defaultStartDaemonProcess(socketPath) {
100
+ const launchdStarted = await startDaemonProcessViaLaunchd(socketPath);
101
+ if (launchdStarted)
102
+ return launchdStarted;
103
+ return spawnDetachedDaemonProcess(socketPath);
104
+ }
105
+ async function defaultStartDaemonForFrontend(socketPath, agent) {
106
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(agent)) {
107
+ throw new Error("frontend daemon startup requires a safe agent name");
108
+ }
109
+ if (process.platform === "darwin") {
110
+ bootoutDaemonLaunchAgentLabel(currentUserUid());
111
+ }
112
+ return spawnDetachedDaemonProcess(socketPath, {
113
+ ...process.env,
114
+ OURO_DAEMON_REQUIRED_AGENT: agent,
115
+ });
116
+ }
100
117
  function defaultWriteStdout(text) {
101
118
  process.stdout.write(text.endsWith("\n") ? text : `${text}\n`);
102
119
  }
@@ -711,6 +728,7 @@ function createDefaultOuroCliDeps(socketPath = socket_client_1.DEFAULT_DAEMON_SO
711
728
  : socket_client_1.DEFAULT_DAEMON_COMMAND_TIMEOUT_MS,
712
729
  }),
713
730
  startDaemonProcess: defaultStartDaemonProcess,
731
+ startDaemonForFrontend: defaultStartDaemonForFrontend,
714
732
  writeStdout: defaultWriteStdout,
715
733
  setExitCode: (code) => {
716
734
  const current = typeof process.exitCode === "number" ? process.exitCode : 0;
@@ -48,6 +48,7 @@ exports.ensureDaemonRunning = ensureDaemonRunning;
48
48
  exports.listGithubCopilotModels = listGithubCopilotModels;
49
49
  exports.checkManualCloneBundles = checkManualCloneBundles;
50
50
  exports.resolveWorkbenchRuntimeMcp = resolveWorkbenchRuntimeMcp;
51
+ exports.resolveFrontendFriendId = resolveFrontendFriendId;
51
52
  exports.setupBlueBubblesHostForConnect = setupBlueBubblesHostForConnect;
52
53
  exports.resolveMailImportFilePath = resolveMailImportFilePath;
53
54
  exports.runOuroCli = runOuroCli;
@@ -2921,17 +2922,49 @@ function findInstalledWorkbenchMcp(deps, preferred) {
2921
2922
  * - a string path → use it if it exists, otherwise fall back to discovery
2922
2923
  * (treating the string as the preferred candidate).
2923
2924
  *
2924
- * Returns `{ ouro_workbench: { command, args: [] } }` so the daemon merges it
2925
- * into the boss agent's toolset for the turn without writing to agent.json.
2925
+ * Returns the command plus the exact Workbench runtime coordinates needed by
2926
+ * the daemon-spawned MCP process, without writing them to agent.json.
2926
2927
  */
2927
- function resolveWorkbenchRuntimeMcp(flag, deps) {
2928
+ function resolveWorkbenchRuntimeMcp(flag, deps, environment = process.env) {
2928
2929
  if (flag === undefined)
2929
2930
  return null;
2930
2931
  const preferred = typeof flag === "string" ? flag : null;
2931
2932
  const command = findInstalledWorkbenchMcp(deps, preferred);
2932
2933
  if (!command)
2933
2934
  return null;
2934
- return { ouro_workbench: { command, args: [] } };
2935
+ const keys = [
2936
+ "BUN_BIN",
2937
+ "CMUX_BUNDLED_CLI_PATH",
2938
+ "CMUX_SOCKET_PATH",
2939
+ "CMUX_SOCKET_CAPABILITY",
2940
+ ];
2941
+ const env = Object.fromEntries(keys.map((key) => [key, environment[key]?.trim()]));
2942
+ if (Object.values(env).some((value) => !value))
2943
+ return null;
2944
+ return { ouro_workbench: { command, args: [], env: env } };
2945
+ }
2946
+ async function resolveFrontendFriendId(input) {
2947
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(input.agent))
2948
+ throw new Error("frontend requires a safe agent name");
2949
+ const agentRoot = path.join(input.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)(), `${input.agent}.ouro`);
2950
+ if (!input.store)
2951
+ (0, auth_flow_1.readAgentConfigForAgent)(input.agent, input.bundlesRoot);
2952
+ const store = input.store ?? new friends_1.FileFriendStore(path.join(agentRoot, "friends"));
2953
+ if (input.explicitFriendId) {
2954
+ const existing = await store.get(input.explicitFriendId);
2955
+ if (!existing)
2956
+ throw new Error(`friend not found: ${input.explicitFriendId}`);
2957
+ return existing.id;
2958
+ }
2959
+ const ownerUsername = input.ownerUsername === undefined ? (0, friends_1.machineOwnerUsername)() : input.ownerUsername;
2960
+ if (!ownerUsername)
2961
+ throw new Error("local machine owner identity is unavailable");
2962
+ return (await new friends_1.FriendResolver(store, {
2963
+ provider: "local",
2964
+ externalId: ownerUsername,
2965
+ displayName: ownerUsername,
2966
+ channel: "mcp",
2967
+ }).resolve()).friend.id;
2935
2968
  }
2936
2969
  function removeWorkbenchMcpRegistration(agent, deps) {
2937
2970
  const { configPath } = (0, auth_flow_1.readAgentConfigForAgent)(agent, deps.bundlesRoot);
@@ -5128,7 +5161,7 @@ async function executeConnectWorkbench(agent, deps) {
5128
5161
  workbenchHasStaleBundleEntry
5129
5162
  ? "agent.json: removed stale senses.workbench / mcpServers.ouro_workbench entries"
5130
5163
  : "agent.json: no Workbench entries needed or written",
5131
- `boss launch path: ouro mcp-serve --agent ${agent} --workbench-mcp ${executablePath}`,
5164
+ `boss launch path: ouro acp-serve --agent ${agent} --workbench-mcp ${executablePath}`,
5132
5165
  "Workbench tools are injected per served turn; provider secrets stay in the agent vault.",
5133
5166
  ...(syncSummary ? [syncSummary] : []),
5134
5167
  ].join("\n");
@@ -7485,7 +7518,10 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
7485
7518
  // bridge on every senseTurn and merged per-turn in the daemon — nothing is
7486
7519
  // written to agent.json.
7487
7520
  const workbenchMcpFlag = command.workbenchMcp;
7488
- const runtimeMcp = resolveWorkbenchRuntimeMcp(workbenchMcpFlag, deps);
7521
+ const runtimeMcp = resolveWorkbenchRuntimeMcp(workbenchMcpFlag, deps, deps.workbenchMcpEnvironment ?? process.env);
7522
+ if (workbenchMcpFlag !== undefined && !runtimeMcp) {
7523
+ throw new Error("Workbench MCP runtime coordinates are unavailable");
7524
+ }
7489
7525
  const server = createMcpServer({
7490
7526
  agent: command.agent,
7491
7527
  friendId,
@@ -7503,6 +7539,59 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
7503
7539
  await runMcpServeCliLifecycle(server, mcpInput);
7504
7540
  return "";
7505
7541
  }
7542
+ if (command.kind === "acp-serve") {
7543
+ const createAcpServer = deps.createAcpServer ?? (await Promise.resolve().then(() => __importStar(require("../../senses/acp-server")))).createAcpServer;
7544
+ const resolveFriendId = deps.resolveFrontendFriendId ?? resolveFrontendFriendId;
7545
+ const friendId = await resolveFriendId({
7546
+ agent: command.agent,
7547
+ ...(command.friendId ? { explicitFriendId: command.friendId } : {}),
7548
+ ...(deps.bundlesRoot ? { bundlesRoot: deps.bundlesRoot } : {}),
7549
+ });
7550
+ const commandSocketPath = command.socketOverride ?? deps.socketPath;
7551
+ const { frontendSocketPathForDaemon } = await Promise.resolve().then(() => __importStar(require("../frontend-socket")));
7552
+ const input = deps.acpServeInput ?? process.stdin;
7553
+ const output = deps.acpServeOutput ?? process.stdout;
7554
+ const runtimeMcpServers = command.observeOnly
7555
+ ? undefined
7556
+ : resolveWorkbenchRuntimeMcp(command.workbenchMcp, deps, deps.workbenchMcpEnvironment ?? process.env);
7557
+ if (!command.observeOnly && command.workbenchMcp !== undefined && !runtimeMcpServers) {
7558
+ throw new Error("Workbench MCP runtime coordinates are unavailable");
7559
+ }
7560
+ const daemonResult = deps.ensureDaemonForFrontend
7561
+ ? await deps.ensureDaemonForFrontend(command.agent)
7562
+ : await ensureDaemonRunning({
7563
+ ...deps,
7564
+ startDaemonProcess: (socketPath) => deps.startDaemonForFrontend
7565
+ ? deps.startDaemonForFrontend(socketPath, command.agent)
7566
+ : deps.startDaemonProcess(socketPath),
7567
+ reportDaemonStartupPhase: () => { },
7568
+ });
7569
+ if (!daemonResult.ok) {
7570
+ throw new Error(daemonResult.message);
7571
+ }
7572
+ const server = createAcpServer({
7573
+ agent: command.agent,
7574
+ friendId,
7575
+ frontendSocketPath: frontendSocketPathForDaemon(commandSocketPath),
7576
+ stdin: input,
7577
+ stdout: output,
7578
+ ...(command.observeOnly ? { disableTools: true } : {}),
7579
+ ...(runtimeMcpServers ? { runtimeMcpServers } : {}),
7580
+ });
7581
+ (0, runtime_1.emitNervesEvent)({
7582
+ component: "daemon",
7583
+ event: "daemon.acp_serve_started",
7584
+ message: "ACP frontend server started via CLI",
7585
+ meta: {
7586
+ agent: command.agent,
7587
+ friendId,
7588
+ observeOnly: command.observeOnly === true,
7589
+ workbenchRuntimeMcp: !!runtimeMcpServers,
7590
+ },
7591
+ });
7592
+ await runMcpServeCliLifecycle(server, input);
7593
+ return "";
7594
+ }
7506
7595
  if (command.kind === "private.decisions") {
7507
7596
  let response;
7508
7597
  try {
@@ -345,6 +345,12 @@ exports.COMMAND_REGISTRY = {
345
345
  usage: "ouro mcp-serve --agent <name> [--friend <id>]",
346
346
  example: "ouro mcp-serve --agent ouroboros",
347
347
  },
348
+ "acp-serve": {
349
+ category: "System",
350
+ description: "Start the ACP frontend adapter for an Ouro agent",
351
+ usage: "ouro acp-serve --agent <name> [--friend-id <id>] [--workbench-mcp [path]] [--observe-only]",
352
+ example: "ouro acp-serve --agent ouroboros --workbench-mcp",
353
+ },
348
354
  setup: {
349
355
  category: "System",
350
356
  description: "Register MCP server and hooks for a dev tool",
@@ -13,6 +13,7 @@ exports.isAgentProvider = isAgentProvider;
13
13
  exports.usage = usage;
14
14
  exports.inferAgentNameFromRemote = inferAgentNameFromRemote;
15
15
  exports.parseMcpServeCommand = parseMcpServeCommand;
16
+ exports.parseAcpServeCommand = parseAcpServeCommand;
16
17
  exports.parseOuroCommand = parseOuroCommand;
17
18
  const friends_1 = require("@ouro.bot/friends");
18
19
  const cli_help_1 = require("./cli-help");
@@ -1686,6 +1687,55 @@ function parseMcpServeCommand(args) {
1686
1687
  ...(workbenchMcp !== undefined ? { workbenchMcp } : {}),
1687
1688
  };
1688
1689
  }
1690
+ function parseAcpServeCommand(args) {
1691
+ const usage = "Usage: ouro acp-serve --agent <name> [--friend-id <id>] [--socket <path>] [--workbench-mcp [path]] [--observe-only]";
1692
+ let agent;
1693
+ let friendId;
1694
+ let socketOverride;
1695
+ let workbenchMcp;
1696
+ let observeOnly = false;
1697
+ for (let i = 0; i < args.length; i++) {
1698
+ const token = args[i];
1699
+ if (token === "--workbench-mcp") {
1700
+ const next = args[i + 1];
1701
+ if (next && !next.startsWith("--"))
1702
+ workbenchMcp = args[++i];
1703
+ else
1704
+ workbenchMcp = true;
1705
+ continue;
1706
+ }
1707
+ if (token === "--observe-only") {
1708
+ observeOnly = true;
1709
+ continue;
1710
+ }
1711
+ const next = args[i + 1];
1712
+ if (token === "--agent" || token === "--friend-id" || token === "--socket") {
1713
+ if (!next || next.startsWith("--"))
1714
+ throw new Error(`${token} requires a value\n${usage}`);
1715
+ if (token === "--agent")
1716
+ agent = next;
1717
+ else if (token === "--friend-id")
1718
+ friendId = next;
1719
+ else
1720
+ socketOverride = next;
1721
+ i += 1;
1722
+ continue;
1723
+ }
1724
+ throw new Error(usage);
1725
+ }
1726
+ if (!agent)
1727
+ throw new Error(`acp-serve requires --agent <name>\n${usage}`);
1728
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(agent))
1729
+ throw new Error("acp-serve requires a safe agent name");
1730
+ return {
1731
+ kind: "acp-serve",
1732
+ agent,
1733
+ ...(friendId ? { friendId } : {}),
1734
+ ...(socketOverride ? { socketOverride } : {}),
1735
+ ...(workbenchMcp !== undefined ? { workbenchMcp } : {}),
1736
+ ...(observeOnly ? { observeOnly: true } : {}),
1737
+ };
1738
+ }
1689
1739
  function parseSetupCommand(args) {
1690
1740
  let tool;
1691
1741
  let agent;
@@ -2675,6 +2725,8 @@ function parseOuroCommand(args) {
2675
2725
  return parseNervesReviewCommand(args.slice(1));
2676
2726
  if (head === "link")
2677
2727
  return parseLinkCommand(args.slice(1));
2728
+ if (head === "acp-serve")
2729
+ return parseAcpServeCommand(args.slice(1));
2678
2730
  if (head === "mcp-serve")
2679
2731
  return parseMcpServeCommand(args.slice(1));
2680
2732
  if (head === "setup")
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PUBLIC_DAEMON_STARTUP_FAILURE_REASON = void 0;
4
+ exports.providerReadinessAgents = providerReadinessAgents;
4
5
  exports.createProviderReadinessPreparationFailure = createProviderReadinessPreparationFailure;
5
6
  exports.createSanctuaryBundlePreparationFailure = createSanctuaryBundlePreparationFailure;
6
7
  exports.failFastContainerCredentialBootstrapStartup = failFastContainerCredentialBootstrapStartup;
@@ -20,6 +21,13 @@ exports.PUBLIC_DAEMON_STARTUP_FAILURE_REASON = "startupFailurePublic";
20
21
  class DaemonPreparationFailure extends Error {
21
22
  name = "DaemonPreparationFailure";
22
23
  }
24
+ function providerReadinessAgents(managedAgents, requiredAgent) {
25
+ const candidate = requiredAgent?.trim();
26
+ if (!candidate || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(candidate)) {
27
+ return [...managedAgents];
28
+ }
29
+ return managedAgents.includes(candidate) ? [candidate] : [...managedAgents];
30
+ }
23
31
  function createProviderReadinessPreparationFailure(issues) {
24
32
  const lines = ["Provider checks need attention"];
25
33
  for (const issue of issues) {
@@ -94,6 +94,7 @@ const socketPath = parseSocketPath(process.argv);
94
94
  const entryPath = path.resolve(__dirname, "daemon-entry.js");
95
95
  const mode = (0, runtime_mode_1.detectRuntimeMode)((0, identity_1.getRepoRoot)());
96
96
  const managedAgents = (0, agent_discovery_1.listEnabledBundleAgents)();
97
+ const readinessAgents = (0, daemon_bootstrap_startup_1.providerReadinessAgents)(managedAgents, process.env.OURO_DAEMON_REQUIRED_AGENT);
97
98
  const sanctuaryPackageManagement = (0, sanctuary_package_management_1.resolveSanctuaryPackageManagementActivation)({ mode, argv: process.argv, managedAgents, repoRoot: (0, identity_1.getRepoRoot)(), bundlesRoot: (0, identity_1.getAgentBundlesRoot)(), runtimePackageVersion: (0, bundle_manifest_1.getPackageVersion)() });
98
99
  try {
99
100
  (0, sanctuary_package_management_1.requireSanctuaryPackageManagementDecision)(sanctuaryPackageManagement);
@@ -598,7 +599,7 @@ function writeStopCommandHealthState() {
598
599
  }
599
600
  async function prepareProviderRuntime() {
600
601
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
601
- const readiness = await Promise.all(managedAgents.map(async (agent) => {
602
+ const readiness = await Promise.all(readinessAgents.map(async (agent) => {
602
603
  try {
603
604
  await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(agent, { preserveCachedOnFailure: true });
604
605
  const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot);