@ouro.bot/cli 0.1.0-alpha.77 → 0.1.0-alpha.79
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/dist/heart/daemon/daemon-cli.js +104 -1
- package/dist/heart/daemon/ouro-path-installer.js +90 -8
- package/dist/heart/daemon/ouro-version-manager.js +164 -0
- package/dist/mind/prompt.js +2 -0
- package/dist/repertoire/guardrails.js +2 -0
- package/dist/senses/bluebubbles.js +4 -3
- package/dist/senses/debug-activity.js +7 -1
- 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.79",
|
|
6
|
+
"changes": [
|
|
7
|
+
"New: Versioned CLI directory layout (~/.ouro-cli/) replaces npx-based ouro wrapper. Explicit version management, rollback support, and deterministic updates.",
|
|
8
|
+
"New: `ouro up` now checks the registry for newer CLI versions, installs them into ~/.ouro-cli/versions/, activates via symlink flip, and re-execs — no more silent npx downloads.",
|
|
9
|
+
"New: `ouro rollback [<version>]` swaps CurrentVersion/previous symlinks, stops the daemon. With a version arg, installs if needed then activates.",
|
|
10
|
+
"New: `ouro versions` lists cached CLI versions with * current and (previous) markers.",
|
|
11
|
+
"Migration: On first run, old ~/.local/bin/ouro wrapper is removed, old PATH entry cleaned from shell profile, new ~/.ouro-cli/bin added to PATH.",
|
|
12
|
+
"Trust manifest: rollback requires family trust, versions requires acquaintance.",
|
|
13
|
+
"Fix: BlueBubbles group chats no longer start typing just because the model turn began. Group chats stay visually quiet until the agent has committed to replying.",
|
|
14
|
+
"Tool/progress updates now count as reply commitment in BlueBubbles group chats, so read/typing can begin before final user-facing text without regressing back to model-start typing.",
|
|
15
|
+
"Silent group-chat turns remain fully quiet: when the agent chooses no_response, the inbound message still reaches the model but the chat is not marked read, never types, and never sends a message."
|
|
16
|
+
]
|
|
17
|
+
},
|
|
4
18
|
{
|
|
5
19
|
"version": "0.1.0-alpha.77",
|
|
6
20
|
"changes": [
|
|
@@ -298,6 +298,8 @@ function usage() {
|
|
|
298
298
|
" ouro session list [--agent <name>]",
|
|
299
299
|
" ouro mcp list",
|
|
300
300
|
" ouro mcp call <server> <tool> [--args '{...}']",
|
|
301
|
+
" ouro rollback [<version>]",
|
|
302
|
+
" ouro versions",
|
|
301
303
|
].join("\n");
|
|
302
304
|
}
|
|
303
305
|
function formatVersionOutput() {
|
|
@@ -908,6 +910,10 @@ function parseOuroCommand(args) {
|
|
|
908
910
|
}
|
|
909
911
|
if (head === "up")
|
|
910
912
|
return { kind: "daemon.up" };
|
|
913
|
+
if (head === "rollback")
|
|
914
|
+
return { kind: "rollback", ...(second ? { version: second } : {}) };
|
|
915
|
+
if (head === "versions")
|
|
916
|
+
return { kind: "versions" };
|
|
911
917
|
if (head === "stop" || head === "down")
|
|
912
918
|
return { kind: "daemon.stop" };
|
|
913
919
|
if (head === "status")
|
|
@@ -1439,7 +1445,11 @@ async function performSystemSetup(deps) {
|
|
|
1439
1445
|
// Install ouro command to PATH (non-blocking)
|
|
1440
1446
|
if (deps.installOuroCommand) {
|
|
1441
1447
|
try {
|
|
1442
|
-
deps.installOuroCommand();
|
|
1448
|
+
const installResult = deps.installOuroCommand();
|
|
1449
|
+
/* v8 ignore next -- migration hint: only fires once during old→new layout migration @preserve */
|
|
1450
|
+
if (installResult.migratedFromOldPath) {
|
|
1451
|
+
deps.writeStdout("migrated ouro to ~/.ouro-cli/ — open a new terminal or run: source ~/.zshrc");
|
|
1452
|
+
}
|
|
1443
1453
|
}
|
|
1444
1454
|
catch (error) {
|
|
1445
1455
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -1752,6 +1762,35 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
1752
1762
|
meta: { kind: command.kind },
|
|
1753
1763
|
});
|
|
1754
1764
|
if (command.kind === "daemon.up") {
|
|
1765
|
+
// ── versioned CLI update check ──
|
|
1766
|
+
if (deps.checkForCliUpdate) {
|
|
1767
|
+
let pendingReExec = false;
|
|
1768
|
+
try {
|
|
1769
|
+
const updateResult = await deps.checkForCliUpdate();
|
|
1770
|
+
if (updateResult.available && updateResult.latestVersion) {
|
|
1771
|
+
/* v8 ignore next -- fallback: getCurrentCliVersion always injected in tests @preserve */
|
|
1772
|
+
const currentVersion = deps.getCurrentCliVersion?.() ?? "unknown";
|
|
1773
|
+
await deps.installCliVersion(updateResult.latestVersion);
|
|
1774
|
+
deps.activateCliVersion(updateResult.latestVersion);
|
|
1775
|
+
deps.writeStdout(`ouro updated to ${updateResult.latestVersion} (was ${currentVersion})`);
|
|
1776
|
+
pendingReExec = true;
|
|
1777
|
+
}
|
|
1778
|
+
/* v8 ignore start -- update check error: tested via daemon-cli-update-flow.test.ts @preserve */
|
|
1779
|
+
}
|
|
1780
|
+
catch (error) {
|
|
1781
|
+
(0, runtime_1.emitNervesEvent)({
|
|
1782
|
+
level: "warn",
|
|
1783
|
+
component: "daemon",
|
|
1784
|
+
event: "daemon.cli_update_check_error",
|
|
1785
|
+
message: "CLI update check failed",
|
|
1786
|
+
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
1787
|
+
});
|
|
1788
|
+
}
|
|
1789
|
+
/* v8 ignore stop */
|
|
1790
|
+
if (pendingReExec) {
|
|
1791
|
+
deps.reExecFromNewVersion(args);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1755
1794
|
await performSystemSetup(deps);
|
|
1756
1795
|
if (deps.ensureDaemonBootPersistence) {
|
|
1757
1796
|
try {
|
|
@@ -1793,6 +1832,70 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
|
|
|
1793
1832
|
deps.writeStdout(daemonResult.message);
|
|
1794
1833
|
return daemonResult.message;
|
|
1795
1834
|
}
|
|
1835
|
+
// ── rollback command (local, no daemon socket needed for symlinks) ──
|
|
1836
|
+
/* v8 ignore start -- rollback/versions: tested via daemon-cli-rollback/versions tests @preserve */
|
|
1837
|
+
if (command.kind === "rollback") {
|
|
1838
|
+
const currentVersion = deps.getCurrentCliVersion?.() ?? "unknown";
|
|
1839
|
+
if (command.version) {
|
|
1840
|
+
// Rollback to a specific version
|
|
1841
|
+
const installed = deps.listCliVersions?.() ?? [];
|
|
1842
|
+
if (!installed.includes(command.version)) {
|
|
1843
|
+
try {
|
|
1844
|
+
await deps.installCliVersion(command.version);
|
|
1845
|
+
}
|
|
1846
|
+
catch (error) {
|
|
1847
|
+
const message = `failed to install version ${command.version}: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
|
|
1848
|
+
deps.writeStdout(message);
|
|
1849
|
+
return message;
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
deps.activateCliVersion(command.version);
|
|
1853
|
+
}
|
|
1854
|
+
else {
|
|
1855
|
+
// Rollback to previous version
|
|
1856
|
+
const previousVersion = deps.getPreviousCliVersion?.();
|
|
1857
|
+
if (!previousVersion) {
|
|
1858
|
+
const message = "no previous version to roll back to";
|
|
1859
|
+
deps.writeStdout(message);
|
|
1860
|
+
return message;
|
|
1861
|
+
}
|
|
1862
|
+
deps.activateCliVersion(previousVersion);
|
|
1863
|
+
command = { ...command, version: previousVersion };
|
|
1864
|
+
}
|
|
1865
|
+
// Stop daemon (non-fatal if not running)
|
|
1866
|
+
try {
|
|
1867
|
+
await deps.sendCommand(deps.socketPath, { kind: "daemon.stop" });
|
|
1868
|
+
}
|
|
1869
|
+
catch {
|
|
1870
|
+
// Daemon may not be running — that's fine
|
|
1871
|
+
}
|
|
1872
|
+
const message = `rolled back to ${command.version} (was ${currentVersion})`;
|
|
1873
|
+
deps.writeStdout(message);
|
|
1874
|
+
return message;
|
|
1875
|
+
}
|
|
1876
|
+
// ── versions command (local, no daemon socket needed) ──
|
|
1877
|
+
if (command.kind === "versions") {
|
|
1878
|
+
const versions = deps.listCliVersions?.() ?? [];
|
|
1879
|
+
if (versions.length === 0) {
|
|
1880
|
+
const message = "no versions installed";
|
|
1881
|
+
deps.writeStdout(message);
|
|
1882
|
+
return message;
|
|
1883
|
+
}
|
|
1884
|
+
const current = deps.getCurrentCliVersion?.();
|
|
1885
|
+
const previous = deps.getPreviousCliVersion?.();
|
|
1886
|
+
const lines = versions.map((v) => {
|
|
1887
|
+
let line = v;
|
|
1888
|
+
if (v === current)
|
|
1889
|
+
line += " * current";
|
|
1890
|
+
if (v === previous)
|
|
1891
|
+
line += " (previous)";
|
|
1892
|
+
return line;
|
|
1893
|
+
});
|
|
1894
|
+
const message = lines.join("\n");
|
|
1895
|
+
deps.writeStdout(message);
|
|
1896
|
+
return message;
|
|
1897
|
+
}
|
|
1898
|
+
/* v8 ignore stop */
|
|
1796
1899
|
if (command.kind === "daemon.logs" && deps.tailLogs) {
|
|
1797
1900
|
deps.tailLogs();
|
|
1798
1901
|
return "";
|
|
@@ -38,9 +38,13 @@ const fs = __importStar(require("fs"));
|
|
|
38
38
|
const os = __importStar(require("os"));
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const runtime_1 = require("../../nerves/runtime");
|
|
41
|
-
const CLI_PACKAGE_SPECIFIER = "@ouro.bot/cli@alpha";
|
|
42
41
|
const WRAPPER_SCRIPT = `#!/bin/sh
|
|
43
|
-
|
|
42
|
+
ENTRY="$HOME/.ouro-cli/CurrentVersion/node_modules/@ouro.bot/cli/dist/heart/daemon/ouro-entry.js"
|
|
43
|
+
if [ ! -e "$ENTRY" ]; then
|
|
44
|
+
echo "ouro not installed. Run: npx ouro.bot" >&2
|
|
45
|
+
exit 1
|
|
46
|
+
fi
|
|
47
|
+
exec node "$ENTRY" "$@"
|
|
44
48
|
`;
|
|
45
49
|
function detectShellProfile(homeDir, shell) {
|
|
46
50
|
if (!shell)
|
|
@@ -67,6 +71,30 @@ function buildPathExportLine(binDir, shell) {
|
|
|
67
71
|
}
|
|
68
72
|
return `\n# Added by ouro\nexport PATH="${binDir}:$PATH"\n`;
|
|
69
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* Remove lines matching the old ouro PATH block from shell profile content.
|
|
76
|
+
* Returns the cleaned content.
|
|
77
|
+
*/
|
|
78
|
+
function removeOldPathBlock(content, oldBinDir) {
|
|
79
|
+
const lines = content.split("\n");
|
|
80
|
+
const result = [];
|
|
81
|
+
let i = 0;
|
|
82
|
+
while (i < lines.length) {
|
|
83
|
+
// Detect "# Added by ouro" followed by a PATH export containing the old binDir
|
|
84
|
+
if (lines[i].trim() === "# Added by ouro" && i + 1 < lines.length && lines[i + 1].includes(oldBinDir)) {
|
|
85
|
+
// Skip both lines (comment + export)
|
|
86
|
+
i += 2;
|
|
87
|
+
// Also skip trailing blank line if present
|
|
88
|
+
/* v8 ignore next -- edge: trailing blank line presence varies @preserve */
|
|
89
|
+
if (i < lines.length && lines[i].trim() === "")
|
|
90
|
+
i++;
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
result.push(lines[i]);
|
|
94
|
+
i++;
|
|
95
|
+
}
|
|
96
|
+
return result.join("\n");
|
|
97
|
+
}
|
|
70
98
|
function installOuroCommand(deps = {}) {
|
|
71
99
|
/* v8 ignore start -- dep defaults: only used in real runtime, tests always inject @preserve */
|
|
72
100
|
const platform = deps.platform ?? process.platform;
|
|
@@ -77,6 +105,9 @@ function installOuroCommand(deps = {}) {
|
|
|
77
105
|
const readFileSync = deps.readFileSync ?? ((p, enc) => fs.readFileSync(p, enc));
|
|
78
106
|
const appendFileSync = deps.appendFileSync ?? fs.appendFileSync;
|
|
79
107
|
const chmodSync = deps.chmodSync ?? fs.chmodSync;
|
|
108
|
+
const unlinkSync = deps.unlinkSync ?? fs.unlinkSync;
|
|
109
|
+
const rmdirSync = deps.rmdirSync ?? fs.rmdirSync;
|
|
110
|
+
const readdirSync = deps.readdirSync ?? ((p) => fs.readdirSync(p).map(String));
|
|
80
111
|
const envPath = deps.envPath ?? process.env.PATH ?? "";
|
|
81
112
|
const shell = deps.shell ?? process.env.SHELL;
|
|
82
113
|
/* v8 ignore stop */
|
|
@@ -87,10 +118,61 @@ function installOuroCommand(deps = {}) {
|
|
|
87
118
|
message: "skipped ouro PATH install on Windows",
|
|
88
119
|
meta: { platform },
|
|
89
120
|
});
|
|
90
|
-
return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: "windows" };
|
|
121
|
+
return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: "windows", migratedFromOldPath: false };
|
|
91
122
|
}
|
|
92
|
-
|
|
123
|
+
// Ensure ~/.ouro-cli/ directory layout exists
|
|
124
|
+
if (deps.ensureCliLayout) {
|
|
125
|
+
deps.ensureCliLayout();
|
|
126
|
+
}
|
|
127
|
+
const binDir = path.join(homeDir, ".ouro-cli", "bin");
|
|
93
128
|
const scriptPath = path.join(binDir, "ouro");
|
|
129
|
+
// ── Migration from old ~/.local/bin/ouro ──
|
|
130
|
+
const oldBinDir = path.join(homeDir, ".local", "bin");
|
|
131
|
+
const oldScriptPath = path.join(oldBinDir, "ouro");
|
|
132
|
+
let migratedFromOldPath = false;
|
|
133
|
+
if (existsSync(oldScriptPath)) {
|
|
134
|
+
(0, runtime_1.emitNervesEvent)({
|
|
135
|
+
component: "daemon",
|
|
136
|
+
event: "daemon.ouro_path_migrate_start",
|
|
137
|
+
message: "migrating ouro from old PATH location",
|
|
138
|
+
meta: { oldScriptPath },
|
|
139
|
+
});
|
|
140
|
+
try {
|
|
141
|
+
unlinkSync(oldScriptPath);
|
|
142
|
+
migratedFromOldPath = true;
|
|
143
|
+
// Remove empty ~/.local/bin/ directory
|
|
144
|
+
if (existsSync(oldBinDir)) {
|
|
145
|
+
try {
|
|
146
|
+
const remaining = readdirSync(oldBinDir);
|
|
147
|
+
if (remaining.length === 0) {
|
|
148
|
+
rmdirSync(oldBinDir);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Best effort cleanup
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
// Best effort migration — continue with new install
|
|
158
|
+
}
|
|
159
|
+
// Remove old PATH entry from shell profile
|
|
160
|
+
const profilePath = detectShellProfile(homeDir, shell);
|
|
161
|
+
/* v8 ignore start -- profile cleanup: only fires during migration from old layout @preserve */
|
|
162
|
+
if (profilePath) {
|
|
163
|
+
try {
|
|
164
|
+
const profileContent = readFileSync(profilePath, "utf-8");
|
|
165
|
+
if (profileContent.includes(oldBinDir)) {
|
|
166
|
+
const cleaned = removeOldPathBlock(profileContent, oldBinDir);
|
|
167
|
+
writeFileSync(profilePath, cleaned);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// Best effort profile cleanup
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/* v8 ignore stop */
|
|
175
|
+
}
|
|
94
176
|
(0, runtime_1.emitNervesEvent)({
|
|
95
177
|
component: "daemon",
|
|
96
178
|
event: "daemon.ouro_path_install_start",
|
|
@@ -113,7 +195,7 @@ function installOuroCommand(deps = {}) {
|
|
|
113
195
|
message: "ouro command already installed",
|
|
114
196
|
meta: { scriptPath },
|
|
115
197
|
});
|
|
116
|
-
return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed" };
|
|
198
|
+
return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed", migratedFromOldPath };
|
|
117
199
|
}
|
|
118
200
|
// Content is stale — repair by overwriting
|
|
119
201
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -136,9 +218,9 @@ function installOuroCommand(deps = {}) {
|
|
|
136
218
|
message: "failed to install ouro command",
|
|
137
219
|
meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
|
|
138
220
|
});
|
|
139
|
-
return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: error instanceof Error ? error.message : /* v8 ignore next -- defensive @preserve */ String(error) };
|
|
221
|
+
return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: error instanceof Error ? error.message : /* v8 ignore next -- defensive @preserve */ String(error), migratedFromOldPath };
|
|
140
222
|
}
|
|
141
|
-
// Check if ~/.
|
|
223
|
+
// Check if ~/.ouro-cli/bin is already in PATH
|
|
142
224
|
let shellProfileUpdated = null;
|
|
143
225
|
const pathReady = isBinDirInPath(binDir, envPath);
|
|
144
226
|
if (!pathReady) {
|
|
@@ -174,5 +256,5 @@ function installOuroCommand(deps = {}) {
|
|
|
174
256
|
message: "ouro command installed",
|
|
175
257
|
meta: { scriptPath, pathReady, shellProfileUpdated },
|
|
176
258
|
});
|
|
177
|
-
return { installed: true, scriptPath, pathReady, shellProfileUpdated };
|
|
259
|
+
return { installed: true, scriptPath, pathReady, shellProfileUpdated, migratedFromOldPath };
|
|
178
260
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getOuroCliHome = getOuroCliHome;
|
|
37
|
+
exports.getCurrentVersion = getCurrentVersion;
|
|
38
|
+
exports.getPreviousVersion = getPreviousVersion;
|
|
39
|
+
exports.listInstalledVersions = listInstalledVersions;
|
|
40
|
+
exports.installVersion = installVersion;
|
|
41
|
+
exports.activateVersion = activateVersion;
|
|
42
|
+
exports.ensureLayout = ensureLayout;
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const os = __importStar(require("os"));
|
|
45
|
+
const path = __importStar(require("path"));
|
|
46
|
+
const runtime_1 = require("../../nerves/runtime");
|
|
47
|
+
function getOuroCliHome(homeDir) {
|
|
48
|
+
/* v8 ignore next -- dep default: tests always inject @preserve */
|
|
49
|
+
const home = homeDir ?? os.homedir();
|
|
50
|
+
return path.join(home, ".ouro-cli");
|
|
51
|
+
}
|
|
52
|
+
function getCurrentVersion(deps) {
|
|
53
|
+
const cliHome = getOuroCliHome(deps.homeDir);
|
|
54
|
+
/* v8 ignore next -- dep default: tests always inject @preserve */
|
|
55
|
+
const readlinkSync = deps.readlinkSync ?? fs.readlinkSync;
|
|
56
|
+
try {
|
|
57
|
+
const target = readlinkSync(path.join(cliHome, "CurrentVersion"));
|
|
58
|
+
return path.basename(target);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function getPreviousVersion(deps) {
|
|
65
|
+
const cliHome = getOuroCliHome(deps.homeDir);
|
|
66
|
+
/* v8 ignore next -- dep default: tests always inject @preserve */
|
|
67
|
+
const readlinkSync = deps.readlinkSync ?? fs.readlinkSync;
|
|
68
|
+
try {
|
|
69
|
+
const target = readlinkSync(path.join(cliHome, "previous"));
|
|
70
|
+
return path.basename(target);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function listInstalledVersions(deps) {
|
|
77
|
+
const cliHome = getOuroCliHome(deps.homeDir);
|
|
78
|
+
/* v8 ignore next -- dep default: tests always inject @preserve */
|
|
79
|
+
const readdirSync = deps.readdirSync ?? ((p, opts) => fs.readdirSync(p, opts));
|
|
80
|
+
try {
|
|
81
|
+
const entries = readdirSync(path.join(cliHome, "versions"), { withFileTypes: true });
|
|
82
|
+
return entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return [];
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function installVersion(version, deps) {
|
|
89
|
+
const cliHome = getOuroCliHome(deps.homeDir);
|
|
90
|
+
/* v8 ignore start -- dep defaults: tests always inject @preserve */
|
|
91
|
+
const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
|
|
92
|
+
const execSync = deps.execSync ?? ((cmd, opts) => require("child_process").execSync(cmd, opts));
|
|
93
|
+
/* v8 ignore stop */
|
|
94
|
+
const versionDir = path.join(cliHome, "versions", version);
|
|
95
|
+
(0, runtime_1.emitNervesEvent)({
|
|
96
|
+
component: "daemon",
|
|
97
|
+
event: "daemon.cli_version_install_start",
|
|
98
|
+
message: "installing CLI version",
|
|
99
|
+
meta: { version, versionDir },
|
|
100
|
+
});
|
|
101
|
+
mkdirSync(versionDir, { recursive: true });
|
|
102
|
+
execSync(`npm install --prefix ${versionDir} @ouro.bot/cli@${version}`, { stdio: "pipe" });
|
|
103
|
+
(0, runtime_1.emitNervesEvent)({
|
|
104
|
+
component: "daemon",
|
|
105
|
+
event: "daemon.cli_version_install_end",
|
|
106
|
+
message: "CLI version installed",
|
|
107
|
+
meta: { version, versionDir },
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function activateVersion(version, deps) {
|
|
111
|
+
const cliHome = getOuroCliHome(deps.homeDir);
|
|
112
|
+
/* v8 ignore start -- dep defaults: tests always inject @preserve */
|
|
113
|
+
const readlinkSync = deps.readlinkSync ?? fs.readlinkSync;
|
|
114
|
+
const unlinkSync = deps.unlinkSync ?? fs.unlinkSync;
|
|
115
|
+
const symlinkSync = deps.symlinkSync ?? fs.symlinkSync;
|
|
116
|
+
const existsSync = deps.existsSync ?? fs.existsSync;
|
|
117
|
+
/* v8 ignore stop */
|
|
118
|
+
const currentVersionPath = path.join(cliHome, "CurrentVersion");
|
|
119
|
+
const previousPath = path.join(cliHome, "previous");
|
|
120
|
+
const newTarget = path.join(cliHome, "versions", version);
|
|
121
|
+
(0, runtime_1.emitNervesEvent)({
|
|
122
|
+
component: "daemon",
|
|
123
|
+
event: "daemon.cli_version_activate",
|
|
124
|
+
message: "activating CLI version",
|
|
125
|
+
meta: { version },
|
|
126
|
+
});
|
|
127
|
+
// Read old CurrentVersion target (may not exist)
|
|
128
|
+
let oldTarget = null;
|
|
129
|
+
try {
|
|
130
|
+
oldTarget = readlinkSync(currentVersionPath);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// No current version — first install
|
|
134
|
+
}
|
|
135
|
+
// Update previous symlink to point to old current
|
|
136
|
+
if (oldTarget) {
|
|
137
|
+
try {
|
|
138
|
+
unlinkSync(previousPath);
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// previous symlink may not exist yet
|
|
142
|
+
}
|
|
143
|
+
symlinkSync(oldTarget, previousPath);
|
|
144
|
+
}
|
|
145
|
+
// Update CurrentVersion symlink
|
|
146
|
+
if (existsSync(currentVersionPath)) {
|
|
147
|
+
unlinkSync(currentVersionPath);
|
|
148
|
+
}
|
|
149
|
+
symlinkSync(newTarget, currentVersionPath);
|
|
150
|
+
}
|
|
151
|
+
function ensureLayout(deps) {
|
|
152
|
+
const cliHome = getOuroCliHome(deps.homeDir);
|
|
153
|
+
/* v8 ignore next -- dep default: tests always inject @preserve */
|
|
154
|
+
const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
|
|
155
|
+
mkdirSync(cliHome, { recursive: true });
|
|
156
|
+
mkdirSync(path.join(cliHome, "bin"), { recursive: true });
|
|
157
|
+
mkdirSync(path.join(cliHome, "versions"), { recursive: true });
|
|
158
|
+
(0, runtime_1.emitNervesEvent)({
|
|
159
|
+
component: "daemon",
|
|
160
|
+
event: "daemon.cli_layout_ensured",
|
|
161
|
+
message: "CLI directory layout ensured",
|
|
162
|
+
meta: { cliHome },
|
|
163
|
+
});
|
|
164
|
+
}
|
package/dist/mind/prompt.js
CHANGED
|
@@ -192,6 +192,8 @@ my bones give me the \`ouro\` cli. always pass \`--agent ${agentName}\`:
|
|
|
192
192
|
ouro auth switch --agent ${agentName} --provider <provider>
|
|
193
193
|
ouro mcp list --agent ${agentName}
|
|
194
194
|
ouro mcp call --agent ${agentName} <server> <tool> --args '{...}'
|
|
195
|
+
ouro versions --agent ${agentName}
|
|
196
|
+
ouro rollback --agent ${agentName} [<version>]
|
|
195
197
|
ouro --help
|
|
196
198
|
|
|
197
199
|
provider/model changes via \`ouro config model\` or \`ouro auth switch\` take effect on the next turn automatically — no restart needed.`;
|
|
@@ -343,13 +343,14 @@ function emitBlueBubblesMarkReadWarning(chat, error) {
|
|
|
343
343
|
},
|
|
344
344
|
});
|
|
345
345
|
}
|
|
346
|
-
function createBlueBubblesCallbacks(client, chat, replyTarget) {
|
|
346
|
+
function createBlueBubblesCallbacks(client, chat, replyTarget, isGroupChat) {
|
|
347
347
|
let textBuffer = "";
|
|
348
348
|
const phrases = (0, phrases_1.getPhrases)();
|
|
349
349
|
const activity = (0, debug_activity_1.createDebugActivityController)({
|
|
350
350
|
thinkingPhrases: phrases.thinking,
|
|
351
351
|
followupPhrases: phrases.followup,
|
|
352
|
-
startTypingOnModelStart:
|
|
352
|
+
startTypingOnModelStart: !isGroupChat,
|
|
353
|
+
startTypingOnFirstTextChunk: isGroupChat,
|
|
353
354
|
suppressInitialModelStatus: true,
|
|
354
355
|
suppressFollowupPhraseStatus: true,
|
|
355
356
|
transport: {
|
|
@@ -607,7 +608,7 @@ async function handleBlueBubblesNormalizedEvent(event, resolvedDeps, source) {
|
|
|
607
608
|
role: "user",
|
|
608
609
|
content: buildInboundContent(event, existing?.messages ?? sessionMessages),
|
|
609
610
|
};
|
|
610
|
-
const callbacks = createBlueBubblesCallbacks(client, event.chat, replyTarget);
|
|
611
|
+
const callbacks = createBlueBubblesCallbacks(client, event.chat, replyTarget, event.chat.isGroup);
|
|
611
612
|
const controller = new AbortController();
|
|
612
613
|
// BB-specific tool context wrappers
|
|
613
614
|
const summarize = (0, core_1.createSummarize)();
|
|
@@ -109,7 +109,13 @@ function createDebugActivityController(options) {
|
|
|
109
109
|
})));
|
|
110
110
|
},
|
|
111
111
|
onTextChunk(text) {
|
|
112
|
-
if (!text
|
|
112
|
+
if (!text) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (!hadToolRun && options.startTypingOnFirstTextChunk) {
|
|
116
|
+
startTypingNow();
|
|
117
|
+
}
|
|
118
|
+
if (!hadToolRun || followupShown) {
|
|
113
119
|
return;
|
|
114
120
|
}
|
|
115
121
|
followupShown = true;
|