@ouro.bot/cli 0.1.0-alpha.86 → 0.1.0-alpha.87
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 +7 -0
- package/dist/heart/safe-workspace.js +93 -0
- package/dist/senses/cli.js +3 -16
- package/package.json +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
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.87",
|
|
6
|
+
"changes": [
|
|
7
|
+
"CLI chat now keeps model reasoning private again. The visible surface stays on spinners, tool updates, and actual replies instead of dim internal-thinking text leaking into the operator conversation.",
|
|
8
|
+
"Safe workspace selection now persists across daemon restarts and `ouro up`, so repo-local reads, edits, and shell commands keep targeting the same acquired scratch clone or worktree after a runtime update."
|
|
9
|
+
]
|
|
10
|
+
},
|
|
4
11
|
{
|
|
5
12
|
"version": "0.1.0-alpha.86",
|
|
6
13
|
"changes": [
|
|
@@ -45,6 +45,75 @@ const identity_1 = require("./identity");
|
|
|
45
45
|
const runtime_1 = require("../nerves/runtime");
|
|
46
46
|
let activeSelection = null;
|
|
47
47
|
let cleanupHookRegistered = false;
|
|
48
|
+
function workspaceSelectionStateFile(workspaceBase) {
|
|
49
|
+
return path.join(workspaceBase, ".active-safe-workspace.json");
|
|
50
|
+
}
|
|
51
|
+
function getOptionalFsFn(name) {
|
|
52
|
+
try {
|
|
53
|
+
return fs[name];
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
function shouldPersistSelection(options) {
|
|
60
|
+
return options.persistSelection ?? options.workspaceRoot === undefined;
|
|
61
|
+
}
|
|
62
|
+
function isPersistedSelectionShape(value) {
|
|
63
|
+
if (!value || typeof value !== "object")
|
|
64
|
+
return false;
|
|
65
|
+
const candidate = value;
|
|
66
|
+
return (typeof candidate.runtimeKind === "string"
|
|
67
|
+
&& typeof candidate.repoRoot === "string"
|
|
68
|
+
&& typeof candidate.workspaceRoot === "string"
|
|
69
|
+
&& typeof candidate.workspaceBranch === "string"
|
|
70
|
+
&& (candidate.sourceBranch === null || typeof candidate.sourceBranch === "string")
|
|
71
|
+
&& typeof candidate.sourceCloneUrl === "string"
|
|
72
|
+
&& typeof candidate.cleanupAfterMerge === "boolean"
|
|
73
|
+
&& typeof candidate.created === "boolean"
|
|
74
|
+
&& typeof candidate.note === "string");
|
|
75
|
+
}
|
|
76
|
+
function loadPersistedSelection(workspaceBase, options) {
|
|
77
|
+
const existsSync = options.existsSync ?? fs.existsSync;
|
|
78
|
+
const readFileSync = options.readFileSync ?? getOptionalFsFn("readFileSync");
|
|
79
|
+
const unlinkSync = options.unlinkSync ?? getOptionalFsFn("unlinkSync");
|
|
80
|
+
const stateFile = workspaceSelectionStateFile(workspaceBase);
|
|
81
|
+
if (!existsSync(stateFile))
|
|
82
|
+
return null;
|
|
83
|
+
if (!readFileSync)
|
|
84
|
+
return null;
|
|
85
|
+
try {
|
|
86
|
+
const raw = readFileSync(stateFile, "utf-8");
|
|
87
|
+
const parsed = JSON.parse(raw);
|
|
88
|
+
if (!isPersistedSelectionShape(parsed) || !existsSync(parsed.workspaceRoot)) {
|
|
89
|
+
try {
|
|
90
|
+
unlinkSync?.(stateFile);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// best effort
|
|
94
|
+
}
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return parsed;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
try {
|
|
101
|
+
unlinkSync?.(stateFile);
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
// best effort
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function persistSelectionState(workspaceBase, selection, options) {
|
|
110
|
+
const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
|
|
111
|
+
const writeFileSync = options.writeFileSync ?? getOptionalFsFn("writeFileSync");
|
|
112
|
+
if (!writeFileSync)
|
|
113
|
+
return;
|
|
114
|
+
mkdirSync(workspaceBase, { recursive: true });
|
|
115
|
+
writeFileSync(workspaceSelectionStateFile(workspaceBase), JSON.stringify(selection, null, 2), "utf-8");
|
|
116
|
+
}
|
|
48
117
|
function defaultNow() {
|
|
49
118
|
return Date.now();
|
|
50
119
|
}
|
|
@@ -146,6 +215,7 @@ function ensureSafeRepoWorkspace(options = {}) {
|
|
|
146
215
|
const agentName = resolveAgentName(options.agentName);
|
|
147
216
|
const canonicalRepoUrl = options.canonicalRepoUrl ?? identity_1.HARNESS_CANONICAL_REPO_URL;
|
|
148
217
|
const workspaceBase = options.workspaceRoot ?? (0, identity_1.getAgentRepoWorkspacesRoot)(agentName);
|
|
218
|
+
const persistSelection = shouldPersistSelection(options);
|
|
149
219
|
const spawnSync = options.spawnSync ?? child_process_1.spawnSync;
|
|
150
220
|
const existsSync = options.existsSync ?? fs.existsSync;
|
|
151
221
|
const mkdirSync = options.mkdirSync ?? fs.mkdirSync;
|
|
@@ -153,6 +223,26 @@ function ensureSafeRepoWorkspace(options = {}) {
|
|
|
153
223
|
const now = options.now ?? defaultNow;
|
|
154
224
|
const stamp = String(now());
|
|
155
225
|
registerCleanupHook({ rmSync });
|
|
226
|
+
if (persistSelection) {
|
|
227
|
+
const restored = loadPersistedSelection(workspaceBase, options);
|
|
228
|
+
if (restored) {
|
|
229
|
+
activeSelection = restored;
|
|
230
|
+
(0, runtime_1.emitNervesEvent)({
|
|
231
|
+
component: "workspace",
|
|
232
|
+
event: "workspace.safe_repo_restored",
|
|
233
|
+
message: "restored safe repo workspace after runtime restart",
|
|
234
|
+
meta: {
|
|
235
|
+
runtimeKind: restored.runtimeKind,
|
|
236
|
+
repoRoot: restored.repoRoot,
|
|
237
|
+
workspaceRoot: restored.workspaceRoot,
|
|
238
|
+
workspaceBranch: restored.workspaceBranch,
|
|
239
|
+
sourceBranch: restored.sourceBranch,
|
|
240
|
+
cleanupAfterMerge: restored.cleanupAfterMerge,
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
return restored;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
156
246
|
let selection;
|
|
157
247
|
if (isGitClone(repoRoot, spawnSync)) {
|
|
158
248
|
const branch = readCurrentBranch(repoRoot, spawnSync);
|
|
@@ -205,6 +295,9 @@ function ensureSafeRepoWorkspace(options = {}) {
|
|
|
205
295
|
};
|
|
206
296
|
}
|
|
207
297
|
activeSelection = selection;
|
|
298
|
+
if (persistSelection) {
|
|
299
|
+
persistSelectionState(workspaceBase, selection, options);
|
|
300
|
+
}
|
|
208
301
|
(0, runtime_1.emitNervesEvent)({
|
|
209
302
|
component: "workspace",
|
|
210
303
|
event: "workspace.safe_repo_acquired",
|
package/dist/senses/cli.js
CHANGED
|
@@ -346,7 +346,6 @@ function createCliCallbacks() {
|
|
|
346
346
|
});
|
|
347
347
|
let currentSpinner = null;
|
|
348
348
|
function setSpinner(s) { currentSpinner = s; setActiveSpinner(s); }
|
|
349
|
-
let hadReasoning = false;
|
|
350
349
|
let hadToolRun = false;
|
|
351
350
|
let textDirty = false; // true when text/reasoning was written without a trailing newline
|
|
352
351
|
const streamer = new MarkdownStreamer();
|
|
@@ -355,7 +354,6 @@ function createCliCallbacks() {
|
|
|
355
354
|
onModelStart: () => {
|
|
356
355
|
currentSpinner?.stop();
|
|
357
356
|
setSpinner(null);
|
|
358
|
-
hadReasoning = false;
|
|
359
357
|
textDirty = false;
|
|
360
358
|
streamer.reset();
|
|
361
359
|
wrapper.reset();
|
|
@@ -382,12 +380,6 @@ function createCliCallbacks() {
|
|
|
382
380
|
currentSpinner.stop();
|
|
383
381
|
setSpinner(null);
|
|
384
382
|
}
|
|
385
|
-
if (hadReasoning) {
|
|
386
|
-
// Single newline to separate reasoning from reply — reasoning
|
|
387
|
-
// output often ends with its own trailing newline(s)
|
|
388
|
-
process.stdout.write("\n");
|
|
389
|
-
hadReasoning = false;
|
|
390
|
-
}
|
|
391
383
|
const rendered = streamer.push(text);
|
|
392
384
|
/* v8 ignore start -- wrapper integration: tested via cli.test.ts onTextChunk tests @preserve */
|
|
393
385
|
if (rendered) {
|
|
@@ -398,14 +390,9 @@ function createCliCallbacks() {
|
|
|
398
390
|
/* v8 ignore stop */
|
|
399
391
|
textDirty = text.length > 0 && !text.endsWith("\n");
|
|
400
392
|
},
|
|
401
|
-
onReasoningChunk: (
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
setSpinner(null);
|
|
405
|
-
}
|
|
406
|
-
hadReasoning = true;
|
|
407
|
-
process.stdout.write(`\x1b[2m${text}\x1b[0m`);
|
|
408
|
-
textDirty = text.length > 0 && !text.endsWith("\n");
|
|
393
|
+
onReasoningChunk: (_text) => {
|
|
394
|
+
// Keep reasoning private in the CLI surface. The spinner continues to
|
|
395
|
+
// represent active thinking until actual tool or answer output arrives.
|
|
409
396
|
},
|
|
410
397
|
onToolStart: (_name, _args) => {
|
|
411
398
|
// Stop the model-start spinner: when the model returns only tool calls
|