@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 +14 -0
- package/deploy/unraid/README.txt +38 -18
- 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/shared-turn.js +369 -257
- 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.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": [
|
package/deploy/unraid/README.txt
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
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
|
|
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
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
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,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.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>
|
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);
|