@ouro.bot/cli 0.1.0-alpha.693 → 0.1.0-alpha.694
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 +6 -0
- package/dist/heart/config-registry.js +2 -2
- package/dist/heart/daemon/cli-exec.js +68 -51
- package/dist/heart/daemon/cli-help.js +1 -1
- package/dist/heart/daemon/sense-manager.js +40 -19
- package/dist/heart/sense-truth.js +3 -0
- package/dist/heart/turn-context.js +28 -6
- package/dist/mind/prompt.js +29 -7
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
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.694",
|
|
6
|
+
"changes": [
|
|
7
|
+
"Align Workbench setup with runtime MCP injection and stale bundle cleanup, including prompt/turn stale-state coverage."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.693",
|
|
6
12
|
"changes": [
|
|
@@ -203,9 +203,9 @@ const registryData = [
|
|
|
203
203
|
{
|
|
204
204
|
path: "senses.workbench",
|
|
205
205
|
tier: "self",
|
|
206
|
-
description: "Workbench sense
|
|
206
|
+
description: "Legacy Workbench sense marker. Runtime-injected Workbench tools do not require this bundle entry.",
|
|
207
207
|
default: { enabled: false },
|
|
208
|
-
effects: "
|
|
208
|
+
effects: "Kept only for old bundles; modern Workbench launches the boss with --workbench-mcp and injects tools per turn.",
|
|
209
209
|
topics: ["senses", "workbench", "mcp", "terminal", "channels", "interface"],
|
|
210
210
|
validate: validateObject({ enabled: validateBoolean }),
|
|
211
211
|
},
|
|
@@ -2649,7 +2649,6 @@ function enableAgentSense(agent, sense, deps) {
|
|
|
2649
2649
|
mail: senses.mail ?? { enabled: false },
|
|
2650
2650
|
voice: senses.voice ?? { enabled: false },
|
|
2651
2651
|
a2a: senses.a2a ?? { enabled: false },
|
|
2652
|
-
workbench: senses.workbench ?? { enabled: false },
|
|
2653
2652
|
[sense]: { ...existing, enabled: true },
|
|
2654
2653
|
};
|
|
2655
2654
|
fs.writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}\n`, "utf-8");
|
|
@@ -2661,17 +2660,32 @@ function connectMenuIsTTY(deps) {
|
|
|
2661
2660
|
function readConnectBaySenseFlags(agent, deps) {
|
|
2662
2661
|
const configPath = path.join(providerCliAgentRoot({ agent }, deps), "agent.json");
|
|
2663
2662
|
const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
2664
|
-
const
|
|
2663
|
+
const senses = parsed.senses && typeof parsed.senses === "object" && !Array.isArray(parsed.senses)
|
|
2664
|
+
? parsed.senses
|
|
2665
|
+
: undefined;
|
|
2666
|
+
const mcpServers = parsed.mcpServers && typeof parsed.mcpServers === "object" && !Array.isArray(parsed.mcpServers)
|
|
2667
|
+
? parsed.mcpServers
|
|
2668
|
+
: undefined;
|
|
2669
|
+
const workbenchSensePresent = senses
|
|
2670
|
+
? Object.prototype.hasOwnProperty.call(senses, "workbench")
|
|
2671
|
+
: false;
|
|
2672
|
+
const workbenchMcpPresent = mcpServers
|
|
2673
|
+
? Object.prototype.hasOwnProperty.call(mcpServers, "ouro_workbench")
|
|
2674
|
+
: false;
|
|
2675
|
+
const workbenchCommand = mcpServers?.ouro_workbench?.command;
|
|
2676
|
+
const workbenchEnabled = senses?.workbench?.enabled === true;
|
|
2677
|
+
const workbenchMcpCommand = typeof workbenchCommand === "string" && workbenchCommand.trim().length > 0
|
|
2678
|
+
? workbenchCommand
|
|
2679
|
+
: null;
|
|
2665
2680
|
return {
|
|
2666
|
-
teamsEnabled:
|
|
2667
|
-
blueBubblesEnabled:
|
|
2668
|
-
mailEnabled:
|
|
2669
|
-
voiceEnabled:
|
|
2670
|
-
a2aEnabled:
|
|
2671
|
-
workbenchEnabled
|
|
2672
|
-
workbenchMcpCommand
|
|
2673
|
-
|
|
2674
|
-
: null,
|
|
2681
|
+
teamsEnabled: senses?.teams?.enabled === true,
|
|
2682
|
+
blueBubblesEnabled: senses?.bluebubbles?.enabled === true,
|
|
2683
|
+
mailEnabled: senses?.mail?.enabled === true,
|
|
2684
|
+
voiceEnabled: senses?.voice?.enabled === true,
|
|
2685
|
+
a2aEnabled: senses?.a2a?.enabled === true,
|
|
2686
|
+
workbenchEnabled,
|
|
2687
|
+
workbenchMcpCommand,
|
|
2688
|
+
workbenchHasStaleBundleEntry: workbenchSensePresent || workbenchMcpPresent,
|
|
2675
2689
|
};
|
|
2676
2690
|
}
|
|
2677
2691
|
function defaultWorkbenchMcpCandidates(deps) {
|
|
@@ -2713,21 +2727,30 @@ function resolveWorkbenchRuntimeMcp(flag, deps) {
|
|
|
2713
2727
|
return null;
|
|
2714
2728
|
return { ouro_workbench: { command, args: [] } };
|
|
2715
2729
|
}
|
|
2716
|
-
function
|
|
2730
|
+
function removeWorkbenchMcpRegistration(agent, deps) {
|
|
2717
2731
|
const { configPath } = (0, auth_flow_1.readAgentConfigForAgent)(agent, deps.bundlesRoot);
|
|
2718
|
-
enableAgentSense(agent, "workbench", deps);
|
|
2719
2732
|
const nextRaw = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
2733
|
+
let changed = false;
|
|
2720
2734
|
const mcpServers = nextRaw.mcpServers && typeof nextRaw.mcpServers === "object" && !Array.isArray(nextRaw.mcpServers)
|
|
2721
2735
|
? nextRaw.mcpServers
|
|
2722
|
-
:
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
|
|
2726
|
-
|
|
2727
|
-
|
|
2728
|
-
|
|
2729
|
-
|
|
2730
|
-
|
|
2736
|
+
: null;
|
|
2737
|
+
if (mcpServers && "ouro_workbench" in mcpServers) {
|
|
2738
|
+
delete mcpServers.ouro_workbench;
|
|
2739
|
+
nextRaw.mcpServers = mcpServers;
|
|
2740
|
+
changed = true;
|
|
2741
|
+
}
|
|
2742
|
+
const senses = nextRaw.senses && typeof nextRaw.senses === "object" && !Array.isArray(nextRaw.senses)
|
|
2743
|
+
? nextRaw.senses
|
|
2744
|
+
: null;
|
|
2745
|
+
if (senses && "workbench" in senses) {
|
|
2746
|
+
delete senses.workbench;
|
|
2747
|
+
nextRaw.senses = senses;
|
|
2748
|
+
changed = true;
|
|
2749
|
+
}
|
|
2750
|
+
if (changed) {
|
|
2751
|
+
fs.writeFileSync(configPath, `${JSON.stringify(nextRaw, null, 2)}\n`, "utf-8");
|
|
2752
|
+
}
|
|
2753
|
+
return changed;
|
|
2731
2754
|
}
|
|
2732
2755
|
async function buildConnectMenu(agent, deps, onProgress) {
|
|
2733
2756
|
const bundlesRoot = path.dirname(providerCliAgentRoot({ agent }, deps));
|
|
@@ -2756,7 +2779,7 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
2756
2779
|
const runtimeConfig = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(agent, { preserveCachedOnFailure: true });
|
|
2757
2780
|
onProgress?.("loading this machine's settings");
|
|
2758
2781
|
const machineRuntime = await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agent, currentMachineId(deps), { preserveCachedOnFailure: true });
|
|
2759
|
-
const { teamsEnabled, blueBubblesEnabled, mailEnabled, voiceEnabled, a2aEnabled,
|
|
2782
|
+
const { teamsEnabled, blueBubblesEnabled, mailEnabled, voiceEnabled, a2aEnabled, workbenchMcpCommand, workbenchHasStaleBundleEntry, } = readConnectBaySenseFlags(agent, deps);
|
|
2760
2783
|
const perplexityApiKey = runtimeConfig.ok
|
|
2761
2784
|
? readRuntimeConfigString(runtimeConfig.config, "integrations.perplexityApiKey")
|
|
2762
2785
|
: null;
|
|
@@ -2913,31 +2936,18 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
2913
2936
|
: runtimeConfigReadStatus(runtimeConfig);
|
|
2914
2937
|
const a2aStatus = a2aEnabled ? "ready" : "missing";
|
|
2915
2938
|
const installedWorkbenchMcp = findInstalledWorkbenchMcp(deps, workbenchMcpCommand);
|
|
2916
|
-
const
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
: workbenchEnabled || workbenchMcpCommand
|
|
2920
|
-
? "needs attention"
|
|
2921
|
-
: installedWorkbenchMcp
|
|
2922
|
-
? "not attached"
|
|
2923
|
-
: "missing";
|
|
2939
|
+
const workbenchStatus = installedWorkbenchMcp
|
|
2940
|
+
? workbenchHasStaleBundleEntry ? "needs attention" : "ready"
|
|
2941
|
+
: "missing";
|
|
2924
2942
|
const workbenchDetailLines = [
|
|
2925
|
-
workbenchEnabled ? "senses.workbench.enabled = true" : "senses.workbench.enabled is not enabled",
|
|
2926
|
-
workbenchMcpCommand
|
|
2927
|
-
? configuredWorkbenchMcpExists
|
|
2928
|
-
? `MCP command registered: ${workbenchMcpCommand}`
|
|
2929
|
-
: `registered MCP command is missing: ${workbenchMcpCommand}`
|
|
2930
|
-
: "mcpServers.ouro_workbench is not registered",
|
|
2931
2943
|
installedWorkbenchMcp
|
|
2932
2944
|
? `OuroWorkbenchMCP found: ${installedWorkbenchMcp}`
|
|
2933
2945
|
: "OuroWorkbenchMCP not found in ~/Applications or /Applications",
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
// registered” bundle state above is expected for a runtime-only boss; this
|
|
2938
|
-
// `connect workbench` command only writes a persistent entry as an opt-in.
|
|
2946
|
+
workbenchHasStaleBundleEntry
|
|
2947
|
+
? "stale bundle entry detected: remove senses.workbench and mcpServers.ouro_workbench; Workbench tools are injected at runtime instead"
|
|
2948
|
+
: "bundle state: clean; no senses.workbench or mcpServers.ouro_workbench entry required",
|
|
2939
2949
|
installedWorkbenchMcp
|
|
2940
|
-
? "boss runtime injection: when the Workbench app launches this agent it injects ouro_workbench per-turn; an agent.json entry is not required
|
|
2950
|
+
? "boss runtime injection: when the Workbench app launches this agent it injects ouro_workbench per-turn; an agent.json entry is not required."
|
|
2941
2951
|
: "boss runtime injection: install Ouro Workbench.app so the app can inject ouro_workbench per-turn when it launches this agent as boss (no agent.json entry required).",
|
|
2942
2952
|
];
|
|
2943
2953
|
const entries = [
|
|
@@ -3073,7 +3083,7 @@ async function buildConnectMenu(agent, deps, onProgress) {
|
|
|
3073
3083
|
name: "Ouro Workbench",
|
|
3074
3084
|
section: "This machine",
|
|
3075
3085
|
status: workbenchStatus,
|
|
3076
|
-
description: "Native terminal-agent control room
|
|
3086
|
+
description: "Native terminal-agent control room with runtime-injected MCP tools.",
|
|
3077
3087
|
detailLines: workbenchDetailLines,
|
|
3078
3088
|
nextAction: (0, connect_bay_1.connectEntryNeedsAttention)({
|
|
3079
3089
|
option: "9",
|
|
@@ -4695,24 +4705,31 @@ async function executeConnectA2A(agent, deps) {
|
|
|
4695
4705
|
return message;
|
|
4696
4706
|
}
|
|
4697
4707
|
async function executeConnectWorkbench(agent, deps) {
|
|
4698
|
-
const { workbenchMcpCommand } = readConnectBaySenseFlags(agent, deps);
|
|
4708
|
+
const { workbenchMcpCommand, workbenchHasStaleBundleEntry } = readConnectBaySenseFlags(agent, deps);
|
|
4709
|
+
const changed = removeWorkbenchMcpRegistration(agent, deps);
|
|
4710
|
+
const syncSummary = changed ? pushAgentBundleAfterCliMutation(agent, deps) : "";
|
|
4699
4711
|
const executablePath = findInstalledWorkbenchMcp(deps, workbenchMcpCommand);
|
|
4700
4712
|
if (!executablePath) {
|
|
4701
4713
|
throw new Error([
|
|
4702
4714
|
`Ouro Workbench is not installed for ${agent} on this machine.`,
|
|
4715
|
+
changed
|
|
4716
|
+
? "agent.json: removed stale senses.workbench / mcpServers.ouro_workbench entries"
|
|
4717
|
+
: "agent.json: no Workbench entries needed or written",
|
|
4718
|
+
...(syncSummary ? [syncSummary] : []),
|
|
4703
4719
|
"Expected the MCP executable at one of:",
|
|
4704
4720
|
...defaultWorkbenchMcpCandidates(deps).map((candidate) => ` ${candidate}`),
|
|
4705
4721
|
"Install or copy Ouro Workbench.app there, then run:",
|
|
4706
4722
|
` ouro connect workbench --agent ${agent}`,
|
|
4707
4723
|
].join("\n"));
|
|
4708
4724
|
}
|
|
4709
|
-
writeWorkbenchMcpRegistration(agent, executablePath, deps);
|
|
4710
|
-
const syncSummary = pushAgentBundleAfterCliMutation(agent, deps);
|
|
4711
4725
|
const message = [
|
|
4712
|
-
`Workbench
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4726
|
+
`Workbench runtime injection ready for ${agent}`,
|
|
4727
|
+
`OuroWorkbenchMCP found: ${executablePath}`,
|
|
4728
|
+
workbenchHasStaleBundleEntry
|
|
4729
|
+
? "agent.json: removed stale senses.workbench / mcpServers.ouro_workbench entries"
|
|
4730
|
+
: "agent.json: no Workbench entries needed or written",
|
|
4731
|
+
`boss launch path: ouro mcp-serve --agent ${agent} --workbench-mcp ${executablePath}`,
|
|
4732
|
+
"Workbench tools are injected per served turn; provider secrets stay in the agent vault.",
|
|
4716
4733
|
...(syncSummary ? [syncSummary] : []),
|
|
4717
4734
|
].join("\n");
|
|
4718
4735
|
deps.writeStdout(message);
|
|
@@ -402,7 +402,7 @@ const SUBCOMMAND_HELP = {
|
|
|
402
402
|
example: "ouro connect a2a --agent <agent>",
|
|
403
403
|
},
|
|
404
404
|
"connect workbench": {
|
|
405
|
-
description: "
|
|
405
|
+
description: "Verify native Ouro Workbench runtime injection and clean stale bundle entries",
|
|
406
406
|
usage: "ouro connect workbench [--agent <name>]",
|
|
407
407
|
example: "ouro connect workbench --agent <agent>",
|
|
408
408
|
},
|
|
@@ -50,6 +50,8 @@ const DEFAULT_BLUEBUBBLES_PORT = 18790;
|
|
|
50
50
|
const DEFAULT_BLUEBUBBLES_WEBHOOK_PATH = "/bluebubbles-webhook";
|
|
51
51
|
const BLUEBUBBLES_RUNTIME_FRESHNESS_WINDOW_MS = 90_000;
|
|
52
52
|
const SENSE_CONFIG_RETRY_DELAYS_MS = [1_000, 2_000, 5_000, 10_000, 30_000, 60_000];
|
|
53
|
+
const WORKBENCH_RUNTIME_INJECTION_DETAIL = "runtime-injected by Workbench app; no agent.json entry required";
|
|
54
|
+
const WORKBENCH_STALE_BUNDLE_DETAIL = "stale agent.json Workbench entry; run 'ouro connect workbench' to clean it and use runtime injection";
|
|
53
55
|
function defaultSenses() {
|
|
54
56
|
return {
|
|
55
57
|
cli: { ...identity_1.DEFAULT_AGENT_SENSES.cli },
|
|
@@ -78,12 +80,26 @@ function readAgentSenses(agentJsonPath) {
|
|
|
78
80
|
reason: error instanceof Error ? error.message : String(error),
|
|
79
81
|
},
|
|
80
82
|
});
|
|
81
|
-
return defaults;
|
|
83
|
+
return { senses: defaults, workbenchHasStaleBundleEntry: false };
|
|
82
84
|
}
|
|
83
85
|
const rawSenses = parsed.senses;
|
|
84
86
|
if (!rawSenses || typeof rawSenses !== "object" || Array.isArray(rawSenses)) {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
+
const rawMcpServers = parsed.mcpServers;
|
|
88
|
+
const workbenchHasStaleBundleEntry = !!rawMcpServers
|
|
89
|
+
&& typeof rawMcpServers === "object"
|
|
90
|
+
&& !Array.isArray(rawMcpServers)
|
|
91
|
+
&& Object.prototype.hasOwnProperty.call(rawMcpServers, "ouro_workbench");
|
|
92
|
+
if (workbenchHasStaleBundleEntry)
|
|
93
|
+
defaults.workbench = { enabled: true };
|
|
94
|
+
return { senses: defaults, workbenchHasStaleBundleEntry };
|
|
95
|
+
}
|
|
96
|
+
const workbenchSensePresent = Object.prototype.hasOwnProperty.call(rawSenses, "workbench");
|
|
97
|
+
const rawMcpServers = parsed.mcpServers;
|
|
98
|
+
const workbenchMcpPresent = !!rawMcpServers
|
|
99
|
+
&& typeof rawMcpServers === "object"
|
|
100
|
+
&& !Array.isArray(rawMcpServers)
|
|
101
|
+
&& Object.prototype.hasOwnProperty.call(rawMcpServers, "ouro_workbench");
|
|
102
|
+
const workbenchHasStaleBundleEntry = workbenchSensePresent || workbenchMcpPresent;
|
|
87
103
|
for (const sense of ["cli", "teams", "bluebubbles", "mail", "voice", "a2a", "workbench"]) {
|
|
88
104
|
const rawSense = rawSenses[sense];
|
|
89
105
|
if (!rawSense || typeof rawSense !== "object" || Array.isArray(rawSense)) {
|
|
@@ -94,7 +110,10 @@ function readAgentSenses(agentJsonPath) {
|
|
|
94
110
|
defaults[sense] = { enabled };
|
|
95
111
|
}
|
|
96
112
|
}
|
|
97
|
-
|
|
113
|
+
if (workbenchHasStaleBundleEntry) {
|
|
114
|
+
defaults.workbench = { enabled: true };
|
|
115
|
+
}
|
|
116
|
+
return { senses: defaults, workbenchHasStaleBundleEntry };
|
|
98
117
|
}
|
|
99
118
|
function textField(record, key) {
|
|
100
119
|
const value = record?.[key];
|
|
@@ -141,7 +160,7 @@ function runtimeConfigUnavailableDetail(agent, runtimeConfig) {
|
|
|
141
160
|
return `missing vault ${itemName} (${agent})`;
|
|
142
161
|
return `vault ${itemName} unavailable (${compactRuntimeConfigError(agent, runtimeConfig.error)})`;
|
|
143
162
|
}
|
|
144
|
-
function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntimeConfig = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent)) {
|
|
163
|
+
function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntimeConfig = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent), workbenchHasStaleBundleEntry = false) {
|
|
145
164
|
const base = {
|
|
146
165
|
cli: { configured: true, detail: "local interactive terminal" },
|
|
147
166
|
teams: { configured: false, detail: "not enabled in agent.json" },
|
|
@@ -304,10 +323,10 @@ function senseFactsFromRuntimeConfig(agent, senses, runtimeConfig, machineRuntim
|
|
|
304
323
|
detail: publicUrl ? `${publicUrl}${endpointPath}` : `:${port} ${endpointPath}`,
|
|
305
324
|
};
|
|
306
325
|
}
|
|
307
|
-
if (senses.workbench.enabled) {
|
|
326
|
+
if (workbenchHasStaleBundleEntry || senses.workbench.enabled) {
|
|
308
327
|
base.workbench = {
|
|
309
|
-
configured:
|
|
310
|
-
detail:
|
|
328
|
+
configured: false,
|
|
329
|
+
detail: WORKBENCH_STALE_BUNDLE_DETAIL,
|
|
311
330
|
};
|
|
312
331
|
}
|
|
313
332
|
return base;
|
|
@@ -328,7 +347,7 @@ function senseRepairHint(agent, sense) {
|
|
|
328
347
|
}
|
|
329
348
|
/* v8 ignore next -- Workbench is deliberately not daemon-managed, so getSenseInventory never asks the daemon manager for a repair hint @preserve */
|
|
330
349
|
if (sense === "workbench") {
|
|
331
|
-
return `Agent-runnable: run 'ouro connect workbench --agent ${agent}'
|
|
350
|
+
return `Agent-runnable: install Ouro Workbench.app, then launch the boss through Workbench or run 'ouro mcp-serve --agent ${agent} --workbench-mcp'; 'ouro connect workbench --agent ${agent}' only cleans stale bundle entries.`;
|
|
332
351
|
}
|
|
333
352
|
return `Run 'ouro connect bluebubbles --agent ${agent}' to attach BlueBubbles on this machine; then run 'ouro up' again.`;
|
|
334
353
|
}
|
|
@@ -527,9 +546,9 @@ class DaemonSenseManager {
|
|
|
527
546
|
const bundlesRoot = options.bundlesRoot ?? path.join(os.homedir(), "AgentBundles");
|
|
528
547
|
this.bundlesRoot = bundlesRoot;
|
|
529
548
|
this.contexts = new Map(options.agents.map((agent) => {
|
|
530
|
-
const senses = readAgentSenses(path.join(bundlesRoot, `${agent}.ouro`, "agent.json"));
|
|
531
|
-
const facts = senseFactsFromRuntimeConfig(agent, senses, (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent), (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent));
|
|
532
|
-
return [agent, { senses, facts }];
|
|
549
|
+
const { senses, workbenchHasStaleBundleEntry } = readAgentSenses(path.join(bundlesRoot, `${agent}.ouro`, "agent.json"));
|
|
550
|
+
const facts = senseFactsFromRuntimeConfig(agent, senses, (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent), (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent), workbenchHasStaleBundleEntry);
|
|
551
|
+
return [agent, { senses, workbenchHasStaleBundleEntry, facts }];
|
|
533
552
|
}));
|
|
534
553
|
const managedSenseAgents = [...this.contexts.entries()].flatMap(([agent, context]) => {
|
|
535
554
|
return ["teams", "bluebubbles", "mail", "voice", "a2a"]
|
|
@@ -553,7 +572,7 @@ class DaemonSenseManager {
|
|
|
553
572
|
const context = this.contexts.get(parsed.agent);
|
|
554
573
|
if (!context)
|
|
555
574
|
return { ok: true };
|
|
556
|
-
context.facts = senseFactsFromRuntimeConfig(parsed.agent, context.senses, (0, runtime_credentials_1.readRuntimeCredentialConfig)(parsed.agent), (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(parsed.agent));
|
|
575
|
+
context.facts = senseFactsFromRuntimeConfig(parsed.agent, context.senses, (0, runtime_credentials_1.readRuntimeCredentialConfig)(parsed.agent), (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(parsed.agent), context.workbenchHasStaleBundleEntry);
|
|
557
576
|
const fact = context.facts[parsed.sense];
|
|
558
577
|
if (fact.configured)
|
|
559
578
|
return { ok: true };
|
|
@@ -641,7 +660,7 @@ class DaemonSenseManager {
|
|
|
641
660
|
/* v8 ignore next -- defensive: config refreshes are only scheduled for known agent contexts @preserve */
|
|
642
661
|
if (!context)
|
|
643
662
|
return;
|
|
644
|
-
context.facts = senseFactsFromRuntimeConfig(parsed.agent, context.senses, refreshed, machineRefreshed);
|
|
663
|
+
context.facts = senseFactsFromRuntimeConfig(parsed.agent, context.senses, refreshed, machineRefreshed, context.workbenchHasStaleBundleEntry);
|
|
645
664
|
if (!context.facts[parsed.sense].configured) {
|
|
646
665
|
if (this.shouldRetryConfigRefresh(parsed.sense, refreshed, machineRefreshed)) {
|
|
647
666
|
retryAfterMs = this.nextConfigRetryDelayMs(name);
|
|
@@ -696,7 +715,7 @@ class DaemonSenseManager {
|
|
|
696
715
|
? await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agent, currentMachineId(), { preserveCachedOnFailure: true })
|
|
697
716
|
: (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent);
|
|
698
717
|
/* v8 ignore stop */
|
|
699
|
-
context.facts = senseFactsFromRuntimeConfig(agent, context.senses, runtimeConfig, machineRuntimeConfig);
|
|
718
|
+
context.facts = senseFactsFromRuntimeConfig(agent, context.senses, runtimeConfig, machineRuntimeConfig, context.workbenchHasStaleBundleEntry);
|
|
700
719
|
});
|
|
701
720
|
await Promise.all(refreshes);
|
|
702
721
|
}
|
|
@@ -751,7 +770,7 @@ class DaemonSenseManager {
|
|
|
751
770
|
for (const [agent, context] of this.contexts.entries()) {
|
|
752
771
|
const runtimeConfig = (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent);
|
|
753
772
|
const machineRuntimeConfig = (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent);
|
|
754
|
-
context.facts = senseFactsFromRuntimeConfig(agent, context.senses, runtimeConfig, machineRuntimeConfig);
|
|
773
|
+
context.facts = senseFactsFromRuntimeConfig(agent, context.senses, runtimeConfig, machineRuntimeConfig, context.workbenchHasStaleBundleEntry);
|
|
755
774
|
if (!context.senses.bluebubbles.enabled || !context.facts.bluebubbles.configured || !machineRuntimeConfig.ok) {
|
|
756
775
|
continue;
|
|
757
776
|
}
|
|
@@ -805,7 +824,7 @@ class DaemonSenseManager {
|
|
|
805
824
|
const machineRuntimeConfig = needsMachineConfig
|
|
806
825
|
? await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(parsed.agent, currentMachineId(), { preserveCachedOnFailure: true })
|
|
807
826
|
: (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(parsed.agent);
|
|
808
|
-
context.facts = senseFactsFromRuntimeConfig(parsed.agent, context.senses, runtimeConfig, machineRuntimeConfig);
|
|
827
|
+
context.facts = senseFactsFromRuntimeConfig(parsed.agent, context.senses, runtimeConfig, machineRuntimeConfig, context.workbenchHasStaleBundleEntry);
|
|
809
828
|
this.processManager.resetAgentFailureState?.(managedName);
|
|
810
829
|
await this.processManager.startAgent?.(managedName);
|
|
811
830
|
return this.listSenseRows().find((row) => row.agent === parsed.agent && row.sense === parsed.sense) ?? null;
|
|
@@ -821,7 +840,7 @@ class DaemonSenseManager {
|
|
|
821
840
|
runtime.set(parsed.agent, current);
|
|
822
841
|
}
|
|
823
842
|
const rows = [...this.contexts.entries()].flatMap(([agent, context]) => {
|
|
824
|
-
context.facts = senseFactsFromRuntimeConfig(agent, context.senses, (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent), (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent));
|
|
843
|
+
context.facts = senseFactsFromRuntimeConfig(agent, context.senses, (0, runtime_credentials_1.readRuntimeCredentialConfig)(agent), (0, runtime_credentials_1.readMachineRuntimeCredentialConfig)(agent), context.workbenchHasStaleBundleEntry);
|
|
825
844
|
const blueBubblesRuntimeFacts = readBlueBubblesRuntimeFacts(agent, this.bundlesRoot, runtime.get(agent)?.bluebubbles);
|
|
826
845
|
const blueBubblesConfigured = context.facts.bluebubbles.configured;
|
|
827
846
|
const runtimeInfo = {
|
|
@@ -871,7 +890,9 @@ class DaemonSenseManager {
|
|
|
871
890
|
?? context.facts[entry.sense].detail
|
|
872
891
|
: context.facts[entry.sense].detail
|
|
873
892
|
: context.facts[entry.sense].detail
|
|
874
|
-
:
|
|
893
|
+
: entry.sense === "workbench"
|
|
894
|
+
? WORKBENCH_RUNTIME_INJECTION_DETAIL
|
|
895
|
+
: "not enabled in agent.json",
|
|
875
896
|
...(entry.sense === "bluebubbles" && blueBubblesConfigured ? {
|
|
876
897
|
proofMethod: blueBubblesRuntimeFacts.proofMethod,
|
|
877
898
|
lastProofAt: blueBubblesRuntimeFacts.lastProofAt,
|
|
@@ -30,6 +30,9 @@ function resolveStatus(enabled, daemonManaged, runtimeInfo) {
|
|
|
30
30
|
return "disabled";
|
|
31
31
|
}
|
|
32
32
|
if (!daemonManaged) {
|
|
33
|
+
if (runtimeInfo?.configured === false) {
|
|
34
|
+
return "needs_config";
|
|
35
|
+
}
|
|
33
36
|
return "interactive";
|
|
34
37
|
}
|
|
35
38
|
if (runtimeInfo?.runtime === "error") {
|
|
@@ -131,6 +131,28 @@ function hasOpenAIRealtimeVoiceConfig(options) {
|
|
|
131
131
|
function recordOrUndefined(value) {
|
|
132
132
|
return !!value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
133
133
|
}
|
|
134
|
+
function rawWorkbenchBundleEntryPresent() {
|
|
135
|
+
try {
|
|
136
|
+
const raw = JSON.parse(fs.readFileSync(path.join((0, identity_1.getAgentRoot)(), "agent.json"), "utf-8"));
|
|
137
|
+
const senses = recordOrUndefined(raw.senses);
|
|
138
|
+
const mcpServers = recordOrUndefined(raw.mcpServers);
|
|
139
|
+
const hasWorkbenchSense = senses
|
|
140
|
+
? Object.prototype.hasOwnProperty.call(senses, "workbench")
|
|
141
|
+
: false;
|
|
142
|
+
const hasWorkbenchMcp = mcpServers
|
|
143
|
+
? Object.prototype.hasOwnProperty.call(mcpServers, "ouro_workbench")
|
|
144
|
+
: false;
|
|
145
|
+
return hasWorkbenchSense || hasWorkbenchMcp;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function hasStaleWorkbenchBundleEntry(config) {
|
|
152
|
+
return rawWorkbenchBundleEntryPresent()
|
|
153
|
+
?? (config.senses?.workbench?.enabled === true
|
|
154
|
+
|| Object.prototype.hasOwnProperty.call(config.mcpServers ?? {}, "ouro_workbench"));
|
|
155
|
+
}
|
|
134
156
|
function readSenseStatusLines() {
|
|
135
157
|
const config = (0, identity_1.loadAgentConfig)();
|
|
136
158
|
const configuredSenses = config.senses ?? {};
|
|
@@ -175,6 +197,7 @@ function readSenseStatusLines() {
|
|
|
175
197
|
&& hasTextField(voice, "whisperCliPath")
|
|
176
198
|
&& hasTextField(voice, "whisperModelPath");
|
|
177
199
|
const privateKeys = mailroom?.privateKeys;
|
|
200
|
+
const workbenchHasStaleBundleEntry = hasStaleWorkbenchBundleEntry(config);
|
|
178
201
|
const configured = {
|
|
179
202
|
cli: true,
|
|
180
203
|
teams: hasTextField(teams, "clientId") && hasTextField(teams, "clientSecret") && hasTextField(teams, "tenantId"),
|
|
@@ -187,8 +210,7 @@ function readSenseStatusLines() {
|
|
|
187
210
|
? openAIRealtimeVoiceReady
|
|
188
211
|
: cascadeVoiceReady,
|
|
189
212
|
a2a: true,
|
|
190
|
-
workbench:
|
|
191
|
-
&& config.mcpServers.ouro_workbench.command.trim().length > 0,
|
|
213
|
+
workbench: false,
|
|
192
214
|
};
|
|
193
215
|
const rows = [
|
|
194
216
|
{ label: "CLI", status: "interactive" },
|
|
@@ -216,13 +238,13 @@ function readSenseStatusLines() {
|
|
|
216
238
|
label: "Workbench",
|
|
217
239
|
// Workbench can be injected at runtime (the Workbench app spawns
|
|
218
240
|
// `ouro mcp-serve --workbench-mcp` for its boss) with NO agent.json entry.
|
|
219
|
-
// A disabled/
|
|
241
|
+
// A disabled/stale row here does NOT mean the tools are absent —
|
|
220
242
|
// the authoritative signal is whether `workbench_*` tools are in the
|
|
221
243
|
// toolset this turn. The annotation prevents the agent from misreading the
|
|
222
244
|
// row as "blocked".
|
|
223
|
-
status:
|
|
224
|
-
? "
|
|
225
|
-
:
|
|
245
|
+
status: workbenchHasStaleBundleEntry
|
|
246
|
+
? "stale_bundle_entry (runtime-injected when launched by Workbench app; run ouro connect workbench to clean agent.json)"
|
|
247
|
+
: "disabled (runtime-injected when launched by Workbench app)",
|
|
226
248
|
},
|
|
227
249
|
];
|
|
228
250
|
return rows.map((row) => `- ${row.label}: ${row.status}`);
|
package/dist/mind/prompt.js
CHANGED
|
@@ -435,6 +435,28 @@ function hasTextField(record, key) {
|
|
|
435
435
|
function recordOrUndefined(value) {
|
|
436
436
|
return !!value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
437
437
|
}
|
|
438
|
+
function rawWorkbenchBundleEntryPresent() {
|
|
439
|
+
try {
|
|
440
|
+
const raw = JSON.parse(fs.readFileSync(path.join((0, identity_1.getAgentRoot)(), "agent.json"), "utf-8"));
|
|
441
|
+
const senses = recordOrUndefined(raw.senses);
|
|
442
|
+
const mcpServers = recordOrUndefined(raw.mcpServers);
|
|
443
|
+
const hasWorkbenchSense = senses
|
|
444
|
+
? Object.prototype.hasOwnProperty.call(senses, "workbench")
|
|
445
|
+
: false;
|
|
446
|
+
const hasWorkbenchMcp = mcpServers
|
|
447
|
+
? Object.prototype.hasOwnProperty.call(mcpServers, "ouro_workbench")
|
|
448
|
+
: false;
|
|
449
|
+
return hasWorkbenchSense || hasWorkbenchMcp;
|
|
450
|
+
}
|
|
451
|
+
catch {
|
|
452
|
+
return null;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
function hasStaleWorkbenchBundleEntry(config) {
|
|
456
|
+
return rawWorkbenchBundleEntryPresent()
|
|
457
|
+
?? (config.senses?.workbench?.enabled === true
|
|
458
|
+
|| Object.prototype.hasOwnProperty.call(config.mcpServers ?? {}, "ouro_workbench"));
|
|
459
|
+
}
|
|
438
460
|
function localSenseStatusLines() {
|
|
439
461
|
const config = (0, identity_1.loadAgentConfig)();
|
|
440
462
|
const configuredSenses = config.senses ?? {};
|
|
@@ -460,6 +482,7 @@ function localSenseStatusLines() {
|
|
|
460
482
|
const portableVoice = recordOrUndefined(runtimePayload.voice) ?? recordOrUndefined(payload.voice);
|
|
461
483
|
const integrations = recordOrUndefined(runtimePayload.integrations) ?? recordOrUndefined(payload.integrations);
|
|
462
484
|
const privateKeys = mailroom?.privateKeys;
|
|
485
|
+
const workbenchHasStaleBundleEntry = hasStaleWorkbenchBundleEntry(config);
|
|
463
486
|
const configured = {
|
|
464
487
|
cli: true,
|
|
465
488
|
teams: hasTextField(teams, "clientId") && hasTextField(teams, "clientSecret") && hasTextField(teams, "tenantId"),
|
|
@@ -470,8 +493,7 @@ function localSenseStatusLines() {
|
|
|
470
493
|
&& hasTextField(voice, "whisperCliPath")
|
|
471
494
|
&& hasTextField(voice, "whisperModelPath"),
|
|
472
495
|
a2a: true,
|
|
473
|
-
workbench:
|
|
474
|
-
&& config.mcpServers.ouro_workbench.command.trim().length > 0,
|
|
496
|
+
workbench: false,
|
|
475
497
|
};
|
|
476
498
|
const rows = [
|
|
477
499
|
{ label: "CLI", status: "interactive" },
|
|
@@ -499,13 +521,13 @@ function localSenseStatusLines() {
|
|
|
499
521
|
label: "Workbench",
|
|
500
522
|
// Workbench can be injected at runtime (the Workbench app spawns
|
|
501
523
|
// `ouro mcp-serve --workbench-mcp` for its boss) with NO agent.json entry.
|
|
502
|
-
// A disabled/
|
|
524
|
+
// A disabled/stale row here does NOT mean the tools are absent —
|
|
503
525
|
// the authoritative signal is whether `workbench_*` tools are in the
|
|
504
526
|
// toolset this turn. The annotation prevents the agent from misreading the
|
|
505
527
|
// row as "blocked".
|
|
506
|
-
status:
|
|
507
|
-
? "
|
|
508
|
-
:
|
|
528
|
+
status: workbenchHasStaleBundleEntry
|
|
529
|
+
? "stale_bundle_entry (runtime-injected when launched by Workbench app; run ouro connect workbench to clean agent.json)"
|
|
530
|
+
: "disabled (runtime-injected when launched by Workbench app)",
|
|
509
531
|
},
|
|
510
532
|
];
|
|
511
533
|
return rows.map((row) => `- ${row.label}: ${row.status}`);
|
|
@@ -525,7 +547,7 @@ function senseRuntimeGuidance(channel, preReadStatusLines) {
|
|
|
525
547
|
lines.push("teams setup truth: run `ouro connect teams --agent <agent>` from the connect bay; it stores Teams runtime/config fields and enables `senses.teams.enabled`.");
|
|
526
548
|
lines.push("bluebubbles setup truth: run `ouro connect bluebubbles --agent <agent>` from the connect bay; it stores this machine's BlueBubbles URL/password/listener config in the agent vault machine runtime item.");
|
|
527
549
|
lines.push("a2a setup truth: run `ouro connect a2a --agent <agent>` to enable the A2A sense, `ouro a2a card --agent <agent> --base-url <public-url>` to publish an agent card, and `ouro a2a onboard --agent <agent> --card-url <peer-card-url>` to add a peer as an agent friend. A2A uses the existing friend trust model, not a separate trust registry.");
|
|
528
|
-
lines.push("workbench setup truth: Ouro Workbench is the local machine sense for terminal/TUI agents. When the Workbench app launches me as its boss it injects the `ouro_workbench` MCP into my turn at runtime (it spawns `ouro mcp-serve --agent <me> --workbench-mcp`), so I receive the tools for the served session without any `agent.json` `mcpServers.ouro_workbench` entry — nothing is written to the bundle, so this stays path-free and cross-machine clean. The authoritative signal that Workbench is active is simply that the `workbench_*` tools are present in my toolset this turn; the sense table
|
|
550
|
+
lines.push("workbench setup truth: Ouro Workbench is the local machine sense for terminal/TUI agents. When the Workbench app launches me as its boss it injects the `ouro_workbench` MCP into my turn at runtime (it spawns `ouro mcp-serve --agent <me> --workbench-mcp`), so I receive the tools for the served session without any `agent.json` `mcpServers.ouro_workbench` entry — nothing is written to the bundle, so this stays path-free and cross-machine clean. The authoritative signal that Workbench is active is simply that the `workbench_*` tools are present in my toolset this turn; the sense table may read as disabled when no bundle entry exists, or as `stale_bundle_entry` when legacy bundle config needs cleanup, and neither state proves the runtime-injected tools are absent — I do NOT treat that as blocked or as a trust-level problem. I observe and queue auditable Workbench actions through `workbench_status`, `workbench_sense`, `workbench_transcript_tail`, `workbench_search_transcripts`, `workbench_recovery_drill`, and `workbench_request_action`; raw provider secrets remain in the agent vault, and Apple notarization is unrelated to local use. The explicit `ouro connect workbench --agent <me>` command verifies the installed MCP binary and removes stale Workbench bundle entries; it does not enable `senses.workbench` or write `mcpServers.ouro_workbench`.");
|
|
529
551
|
lines.push("mail setup AX: if a human asks me to set up email, I do not hand them a terminal checklist. I guide the flow end-to-end: name the current phase, run agent-runnable commands myself with shell/tools when available, ask the human only for human-required facts or browser actions, wait for their reply, verify the result, then continue.");
|
|
530
552
|
lines.push("mail setup hard rule: never tell the human to run `ouro account ensure`, `ouro connect mail`, `ouro mail import-mbox`, `ouro status`, or `ouro doctor` for setup. Say what I am about to run, run it myself, and report the result. If my current surface cannot run shell/tools, I ask for a tool-capable Ouro setup session or companion to continue; I do not offload CLI operation to the human.");
|
|
531
553
|
lines.push("mail setup truth: Agent Mail uses Mailroom, not HEY OAuth/IMAP. For the full work substrate account, the agent-runnable command is `ouro account ensure --agent <agent> --owner-email <email> --source hey`; use `ouro connect mail --agent <agent> --owner-email <email> --source hey` for mail-only repair/provisioning, or `--no-delegated-source` for native-only mail. The detailed runbook is `docs/agent-mail-setup.md`.");
|