@ouro.bot/cli 0.1.0-alpha.804 → 0.1.0-alpha.806
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 +14 -0
- package/deploy/unraid/sanctuary.ouro/bundle-meta.json +1 -1
- package/deploy/unraid/sanctuary.xml +1 -1
- package/dist/heart/core.js +14 -11
- package/dist/heart/daemon/cli-defaults.js +22 -4
- package/dist/heart/daemon/cli-exec.js +95 -6
- package/dist/heart/daemon/cli-help.js +6 -0
- package/dist/heart/daemon/cli-parse.js +52 -0
- package/dist/heart/daemon/daemon-bootstrap-startup.js +8 -0
- package/dist/heart/daemon/daemon-entry.js +2 -1
- package/dist/heart/daemon/daemon.js +94 -47
- package/dist/heart/daemon/sense-manager.js +1 -1
- package/dist/heart/frontend-approval-runtime.js +432 -0
- package/dist/heart/frontend-journal.js +215 -0
- package/dist/heart/frontend-session-service.js +237 -0
- package/dist/heart/frontend-socket-client.js +154 -0
- package/dist/heart/frontend-socket.js +400 -0
- package/dist/heart/mail-import-discovery.js +3 -0
- package/dist/heart/turn-context.js +1 -0
- package/dist/heart/turn-execution-lease.js +30 -0
- package/dist/mind/prompt.js +4 -1
- package/dist/repertoire/mcp-manager.js +82 -17
- package/dist/senses/acp-server.js +386 -0
- package/dist/senses/pipeline.js +5 -0
- package/dist/senses/sanctuary-install-state-contract.js +126 -0
- package/dist/senses/shared-turn.js +369 -257
- package/dist/senses/telegram.js +5 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
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.806",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Require fresh install-state evidence for explicit Sanctuary owner requests."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"version": "0.1.0-alpha.805",
|
|
12
|
+
"changes": [
|
|
13
|
+
"Add the ACP frontend and daemon socket used by Ouro Workbench to start and supervise a selected boss agent.",
|
|
14
|
+
"Keep Workbench-owned session identity across prompts, approvals, cancellation, journaling, and session subscriptions.",
|
|
15
|
+
"Serialize identity-sensitive turns and per-turn runtime MCP cleanup so frontend state does not carry into the next turn."
|
|
16
|
+
]
|
|
17
|
+
},
|
|
4
18
|
{
|
|
5
19
|
"version": "0.1.0-alpha.804",
|
|
6
20
|
"changes": [
|
|
@@ -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.
|
|
4
|
+
<Repository>ghcr.io/ourostack/ouroboros-butler:0.1.0-alpha.806</Repository>
|
|
5
5
|
<Registry>https://github.com/ourostack/ouroboros/pkgs/container/ouroboros-butler</Registry>
|
|
6
6
|
<Network>host</Network>
|
|
7
7
|
<Shell>sh</Shell>
|
package/dist/heart/core.js
CHANGED
|
@@ -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?.
|
|
1257
|
-
?
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
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
|
-
|
|
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
|
|
2925
|
-
*
|
|
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
|
-
|
|
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
|
|
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(
|
|
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);
|
|
@@ -82,6 +82,11 @@ const router_1 = require("../external-events/router");
|
|
|
82
82
|
const habit_policy_1 = require("../../rsvp/habit-policy");
|
|
83
83
|
const container_runtime_1 = require("./container-runtime");
|
|
84
84
|
const habit_session_1 = require("../habits/habit-session");
|
|
85
|
+
const turn_execution_lease_1 = require("../turn-execution-lease");
|
|
86
|
+
const frontend_session_service_1 = require("../frontend-session-service");
|
|
87
|
+
const frontend_socket_1 = require("../frontend-socket");
|
|
88
|
+
const frontend_journal_1 = require("../frontend-journal");
|
|
89
|
+
const frontend_approval_runtime_1 = require("../frontend-approval-runtime");
|
|
85
90
|
const PIDFILE_PATH = path.join(os.homedir(), ".ouro-cli", "daemon.pids");
|
|
86
91
|
/**
|
|
87
92
|
* Defense-in-depth: detect if we're running under vitest. The pidfile lives
|
|
@@ -556,33 +561,35 @@ function taskIdFromTaskPokePrivateWakeCommand(command) {
|
|
|
556
561
|
* restarts the daemon process (fresh module cache).
|
|
557
562
|
*/
|
|
558
563
|
async function handleAgentSenseTurn(command, runtime) {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
564
|
+
return (0, turn_execution_lease_1.withTurnExecutionLease)(async () => {
|
|
565
|
+
try {
|
|
566
|
+
const { setAgentName } = await Promise.resolve().then(() => __importStar(require("../identity")));
|
|
567
|
+
setAgentName(command.agent);
|
|
568
|
+
const { runSenseTurn } = await Promise.resolve().then(() => __importStar(require("../../senses/shared-turn")));
|
|
569
|
+
const result = await runSenseTurn({
|
|
570
|
+
agentName: command.agent,
|
|
571
|
+
channel: command.channel,
|
|
572
|
+
sessionKey: command.sessionKey,
|
|
573
|
+
friendId: command.friendId,
|
|
574
|
+
userMessage: command.message,
|
|
575
|
+
...(runtime?.socketPath ? { toolContext: { daemonSocketPath: runtime.socketPath } } : {}),
|
|
576
|
+
// Per-turn, per-agent runtime MCP injection (e.g. Workbench's ouro_workbench).
|
|
577
|
+
// Scoped to THIS turn only — never stored as module state, so a concurrent
|
|
578
|
+
// turn for a different agent cannot inherit these servers.
|
|
579
|
+
...(command.runtimeMcp ? { runtimeMcpServers: command.runtimeMcp } : {}),
|
|
580
|
+
});
|
|
581
|
+
return {
|
|
582
|
+
ok: true,
|
|
583
|
+
message: result.response,
|
|
584
|
+
data: { ponderDeferred: result.ponderDeferred },
|
|
585
|
+
};
|
|
586
|
+
}
|
|
587
|
+
catch (error) {
|
|
588
|
+
/* v8 ignore next -- branch: String(error) fallback only for non-Error throws @preserve */
|
|
589
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
590
|
+
return { ok: false, error: `sense turn failed: ${errorMessage}` };
|
|
591
|
+
}
|
|
592
|
+
});
|
|
586
593
|
}
|
|
587
594
|
async function handleAgentAskTurn(command, runtime) {
|
|
588
595
|
/* v8 ignore start -- ask command parameter defaults are legacy MCP compatibility; send_message shares the primary path @preserve */
|
|
@@ -614,12 +621,16 @@ class OuroDaemon {
|
|
|
614
621
|
mode;
|
|
615
622
|
server = null;
|
|
616
623
|
mailboxServer = null;
|
|
624
|
+
frontendSocketServer = null;
|
|
617
625
|
socketIdentity = null;
|
|
618
626
|
senseAutostartTimer = null;
|
|
619
627
|
externalEventReconcileTimer = null;
|
|
620
628
|
externalEventReconcileRunning = false;
|
|
621
629
|
constructedAtMs = Date.now();
|
|
622
630
|
mailboxServerFactory;
|
|
631
|
+
frontendSocketPath;
|
|
632
|
+
frontendSessionService;
|
|
633
|
+
frontendSocketServerFactory;
|
|
623
634
|
privateRuntimePolicyDeps;
|
|
624
635
|
onStopCommandComplete;
|
|
625
636
|
externalEventRoot;
|
|
@@ -644,6 +655,17 @@ class OuroDaemon {
|
|
|
644
655
|
this.bundlesRoot = options.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
645
656
|
this.mode = options.mode ?? "production";
|
|
646
657
|
this.mailboxServerFactory = options.mailboxServerFactory ?? this.createDefaultMailboxServer.bind(this);
|
|
658
|
+
this.frontendSocketPath = options.frontendSocketPath ?? (0, frontend_socket_1.frontendSocketPathForDaemon)(this.socketPath);
|
|
659
|
+
/* v8 ignore next -- path-only default adapter; frontend service tests inject and verify the same root contract @preserve */
|
|
660
|
+
const frontendAgentRoot = (agent) => path.join(this.bundlesRoot, `${agent}.ouro`);
|
|
661
|
+
this.frontendSessionService = options.frontendSessionService ?? new frontend_session_service_1.FrontendSessionService({
|
|
662
|
+
journal: new frontend_journal_1.FrontendJournalStore({ agentRoot: frontendAgentRoot }),
|
|
663
|
+
authority: (0, frontend_approval_runtime_1.createFrontendApprovalRuntime)({
|
|
664
|
+
agentRoot: frontendAgentRoot,
|
|
665
|
+
settleApproval: frontend_approval_runtime_1.settleFrontendApproval,
|
|
666
|
+
}),
|
|
667
|
+
});
|
|
668
|
+
this.frontendSocketServerFactory = options.frontendSocketServerFactory ?? frontend_socket_1.startFrontendSocketServer;
|
|
647
669
|
this.privateRuntimePolicyDeps = options.privateRuntimePolicyDeps ?? {};
|
|
648
670
|
this.onStopCommandComplete = options.onStopCommandComplete ?? null;
|
|
649
671
|
this.externalEventRoot = options.externalEventRoot ?? null;
|
|
@@ -848,6 +870,21 @@ class OuroDaemon {
|
|
|
848
870
|
meta: { port: mailbox_types_1.MAILBOX_DEFAULT_PORT },
|
|
849
871
|
});
|
|
850
872
|
}
|
|
873
|
+
try {
|
|
874
|
+
this.frontendSocketServer = await this.frontendSocketServerFactory({
|
|
875
|
+
socketPath: this.frontendSocketPath,
|
|
876
|
+
service: this.frontendSessionService,
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
catch (error) {
|
|
880
|
+
(0, runtime_1.emitNervesEvent)({
|
|
881
|
+
level: "warn",
|
|
882
|
+
component: "daemon",
|
|
883
|
+
event: "daemon.frontend_socket_start_failed",
|
|
884
|
+
message: `Frontend socket failed to start: ${String(error)}`,
|
|
885
|
+
meta: { socketPath: this.frontendSocketPath },
|
|
886
|
+
});
|
|
887
|
+
}
|
|
851
888
|
}
|
|
852
889
|
triggerAutoStartAgents() {
|
|
853
890
|
if (this.processManager.triggerAutoStartAgents) {
|
|
@@ -1117,6 +1154,12 @@ class OuroDaemon {
|
|
|
1117
1154
|
message: "stopping daemon server",
|
|
1118
1155
|
meta: { socketPath: this.socketPath },
|
|
1119
1156
|
});
|
|
1157
|
+
this.frontendSessionService.cancelAllTurns();
|
|
1158
|
+
if (this.frontendSocketServer) {
|
|
1159
|
+
await this.frontendSocketServer.stop();
|
|
1160
|
+
this.frontendSocketServer = null;
|
|
1161
|
+
}
|
|
1162
|
+
this.frontendSessionService.close?.();
|
|
1120
1163
|
(0, update_checker_1.stopUpdateChecker)();
|
|
1121
1164
|
(0, mcp_manager_1.shutdownSharedMcpManager)();
|
|
1122
1165
|
this.scheduler.stop?.();
|
|
@@ -2402,28 +2445,32 @@ class OuroDaemon {
|
|
|
2402
2445
|
return this.handlePrivateRuntimeWake(this.buildAwaitPrivateWakeCommand(command));
|
|
2403
2446
|
}
|
|
2404
2447
|
case "mcp.list": {
|
|
2405
|
-
(0,
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2448
|
+
return (0, turn_execution_lease_1.withTurnExecutionLease)(async () => {
|
|
2449
|
+
(0, identity_1.setAgentName)(command.agent ?? "default");
|
|
2450
|
+
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)();
|
|
2451
|
+
if (!mcpManager) {
|
|
2452
|
+
return { ok: true, data: [], message: "no MCP servers configured" };
|
|
2453
|
+
}
|
|
2454
|
+
return { ok: true, data: mcpManager.listAllTools() };
|
|
2455
|
+
});
|
|
2411
2456
|
}
|
|
2412
2457
|
case "mcp.call": {
|
|
2413
|
-
(0,
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2458
|
+
return (0, turn_execution_lease_1.withTurnExecutionLease)(async () => {
|
|
2459
|
+
(0, identity_1.setAgentName)(command.agent ?? "default");
|
|
2460
|
+
const mcpCallManager = await (0, mcp_manager_1.getSharedMcpManager)();
|
|
2461
|
+
if (!mcpCallManager) {
|
|
2462
|
+
return { ok: false, error: "no MCP servers configured" };
|
|
2463
|
+
}
|
|
2464
|
+
try {
|
|
2465
|
+
const parsedArgs = command.args ? JSON.parse(command.args) : {};
|
|
2466
|
+
const result = await mcpCallManager.callTool(command.server, command.tool, parsedArgs);
|
|
2467
|
+
return { ok: true, data: result };
|
|
2468
|
+
}
|
|
2469
|
+
catch (error) {
|
|
2470
|
+
/* v8 ignore next -- defensive: callTool errors are always Error instances @preserve */
|
|
2471
|
+
return { ok: false, error: error instanceof Error ? error.message : String(error) };
|
|
2472
|
+
}
|
|
2473
|
+
});
|
|
2427
2474
|
}
|
|
2428
2475
|
case "hatch.start":
|
|
2429
2476
|
return {
|
|
@@ -359,7 +359,7 @@ function senseRepairHint(agent, sense) {
|
|
|
359
359
|
}
|
|
360
360
|
/* v8 ignore next -- Workbench is deliberately not daemon-managed, so getSenseInventory never asks the daemon manager for a repair hint @preserve */
|
|
361
361
|
if (sense === "workbench") {
|
|
362
|
-
return `Agent-runnable: install Ouro Workbench.app, then launch the boss through Workbench or run 'ouro
|
|
362
|
+
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.`;
|
|
363
363
|
}
|
|
364
364
|
return `Run 'ouro connect bluebubbles --agent ${agent}' to attach BlueBubbles on this machine; then run 'ouro up' again.`;
|
|
365
365
|
}
|