@ouro.bot/cli 0.1.0-alpha.133 → 0.1.0-alpha.135
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 +22 -0
- package/dist/heart/core.js +149 -100
- package/dist/heart/daemon/daemon-cli.js +81 -0
- package/dist/heart/daemon/specialist-prompt.js +3 -3
- package/dist/heart/daemon/specialist-tools.js +1 -1
- package/dist/heart/daemon/thoughts.js +6 -6
- package/dist/heart/delegation.js +1 -1
- package/dist/heart/kicks.js +1 -1
- package/dist/heart/providers/anthropic.js +7 -7
- package/dist/heart/providers/azure.js +1 -1
- package/dist/heart/providers/github-copilot.js +2 -2
- package/dist/heart/providers/minimax.js +1 -1
- package/dist/heart/providers/openai-codex.js +1 -1
- package/dist/heart/streaming.js +25 -25
- package/dist/heart/tool-loop.js +5 -1
- package/dist/mind/context.js +62 -0
- package/dist/mind/prompt.js +19 -14
- package/dist/repertoire/ado-semantic.js +6 -0
- package/dist/repertoire/coding/tools.js +5 -0
- package/dist/repertoire/tools-base.js +31 -38
- package/dist/repertoire/tools-bluebubbles.js +1 -0
- package/dist/repertoire/tools-github.js +1 -6
- package/dist/repertoire/tools-teams.js +9 -36
- package/dist/repertoire/tools.js +193 -72
- package/dist/senses/attention-queue.js +97 -0
- package/dist/senses/cli.js +3 -3
- package/dist/senses/inner-dialog.js +39 -22
- package/dist/senses/pipeline.js +4 -3
- package/dist/senses/surface-tool.js +82 -0
- package/package.json +1 -1
- package/dist/heart/safe-workspace.js +0 -381
|
@@ -93,7 +93,7 @@ function extractToolNames(messages) {
|
|
|
93
93
|
if (msg.role === "assistant" && Array.isArray(msg.tool_calls)) {
|
|
94
94
|
for (const tc of msg.tool_calls) {
|
|
95
95
|
const toolFunction = extractToolFunction(tc);
|
|
96
|
-
if (toolFunction?.name && toolFunction.name !== "
|
|
96
|
+
if (toolFunction?.name && toolFunction.name !== "settle")
|
|
97
97
|
names.push(toolFunction.name);
|
|
98
98
|
}
|
|
99
99
|
}
|
|
@@ -321,15 +321,15 @@ function formatInnerDialogStatus(status) {
|
|
|
321
321
|
}
|
|
322
322
|
return lines.join("\n");
|
|
323
323
|
}
|
|
324
|
-
/** Extract text from a
|
|
325
|
-
function
|
|
324
|
+
/** Extract text from a settle tool call's arguments. */
|
|
325
|
+
function extractSettleAnswer(messages) {
|
|
326
326
|
for (let k = messages.length - 1; k >= 0; k--) {
|
|
327
327
|
const msg = messages[k];
|
|
328
328
|
if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls))
|
|
329
329
|
continue;
|
|
330
330
|
for (const tc of msg.tool_calls) {
|
|
331
331
|
const toolFunction = extractToolFunction(tc);
|
|
332
|
-
if (toolFunction?.name !== "
|
|
332
|
+
if (toolFunction?.name !== "settle")
|
|
333
333
|
continue;
|
|
334
334
|
try {
|
|
335
335
|
const parsed = JSON.parse(toolFunction.arguments ?? "{}");
|
|
@@ -348,7 +348,7 @@ function extractThoughtResponseFromMessages(messages) {
|
|
|
348
348
|
const lastAssistant = assistantMsgs.reverse().find((message) => contentToText(message.content).trim().length > 0);
|
|
349
349
|
return lastAssistant
|
|
350
350
|
? contentToText(lastAssistant.content).trim()
|
|
351
|
-
:
|
|
351
|
+
: extractSettleAnswer(messages);
|
|
352
352
|
}
|
|
353
353
|
function parseInnerDialogSession(sessionPath) {
|
|
354
354
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -397,7 +397,7 @@ function parseInnerDialogSession(sessionPath) {
|
|
|
397
397
|
j++;
|
|
398
398
|
}
|
|
399
399
|
// Find the last assistant text response in this turn.
|
|
400
|
-
// With tool_choice="required", the response may be inside a
|
|
400
|
+
// With tool_choice="required", the response may be inside a settle tool call.
|
|
401
401
|
const response = extractThoughtResponseFromMessages(turnMessages);
|
|
402
402
|
const tools = extractToolNames(turnMessages);
|
|
403
403
|
turns.push({
|
package/dist/heart/delegation.js
CHANGED
|
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.decideDelegation = decideDelegation;
|
|
4
4
|
const runtime_1 = require("../nerves/runtime");
|
|
5
5
|
const CROSS_SESSION_TOOLS = new Set(["query_session", "send_message", "bridge_manage"]);
|
|
6
|
-
const FAST_PATH_TOOLS = new Set(["
|
|
6
|
+
const FAST_PATH_TOOLS = new Set(["settle"]);
|
|
7
7
|
const REFLECTION_PATTERN = /\b(think|reflect|ponder|surface|surfaces|surfaced|sit with|metaboli[sz]e)\b/i;
|
|
8
8
|
const CROSS_SESSION_PATTERN = /\b(other chat|other session|across chats?|across sessions?|keep .* aligned|relay|carry .* across)\b/i;
|
|
9
9
|
function hasExplicitReflection(ingressTexts) {
|
package/dist/heart/kicks.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.detectKick = detectKick;
|
|
|
5
5
|
const runtime_1 = require("../nerves/runtime");
|
|
6
6
|
const KICK_MESSAGES = {
|
|
7
7
|
empty: "I sent an empty message by accident — let me try again.",
|
|
8
|
-
narration: "I narrated instead of acting. Using the tool now -- if done, calling
|
|
8
|
+
narration: "I narrated instead of acting. Using the tool now -- if done, calling settle.",
|
|
9
9
|
tool_required: "tool-required is on — I need to call a tool. use /tool-required to turn it off.",
|
|
10
10
|
};
|
|
11
11
|
const TOOL_INTENT_PATTERNS = [
|
|
@@ -301,7 +301,7 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
301
301
|
const toolCalls = new Map();
|
|
302
302
|
const thinkingBlocks = new Map();
|
|
303
303
|
const redactedBlocks = new Map();
|
|
304
|
-
const answerStreamer = new streaming_1.
|
|
304
|
+
const answerStreamer = new streaming_1.SettleStreamer(request.callbacks, request.eagerSettleStreaming);
|
|
305
305
|
try {
|
|
306
306
|
for await (const event of response) {
|
|
307
307
|
if (request.signal?.aborted)
|
|
@@ -327,9 +327,9 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
327
327
|
name,
|
|
328
328
|
arguments: input,
|
|
329
329
|
});
|
|
330
|
-
// Activate eager streaming for sole
|
|
331
|
-
/* v8 ignore next --
|
|
332
|
-
if (name === "
|
|
330
|
+
// Activate eager streaming for sole settle tool call
|
|
331
|
+
/* v8 ignore next -- settle streaming activation, tested via SettleStreamer unit tests @preserve */
|
|
332
|
+
if (name === "settle" && toolCalls.size === 1) {
|
|
333
333
|
answerStreamer.activate();
|
|
334
334
|
}
|
|
335
335
|
}
|
|
@@ -374,8 +374,8 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
374
374
|
if (existing) {
|
|
375
375
|
const partialJson = String(delta?.partial_json ?? "");
|
|
376
376
|
existing.arguments = mergeAnthropicToolArguments(existing.arguments, partialJson);
|
|
377
|
-
/* v8 ignore next --
|
|
378
|
-
if (existing.name === "
|
|
377
|
+
/* v8 ignore next -- settle delta streaming, tested via SettleStreamer unit tests @preserve */
|
|
378
|
+
if (existing.name === "settle" && toolCalls.size === 1) {
|
|
379
379
|
answerStreamer.processDelta(partialJson);
|
|
380
380
|
}
|
|
381
381
|
}
|
|
@@ -414,7 +414,7 @@ async function streamAnthropicMessages(client, model, request) {
|
|
|
414
414
|
toolCalls: [...toolCalls.values()],
|
|
415
415
|
outputItems,
|
|
416
416
|
usage,
|
|
417
|
-
|
|
417
|
+
settleStreamed: answerStreamer.streamed,
|
|
418
418
|
};
|
|
419
419
|
}
|
|
420
420
|
function createAnthropicProviderRuntime(config) {
|
|
@@ -159,7 +159,7 @@ function createAzureProviderRuntime(config) {
|
|
|
159
159
|
params.metadata = { trace_id: request.traceId };
|
|
160
160
|
if (request.toolChoiceRequired)
|
|
161
161
|
params.tool_choice = "required";
|
|
162
|
-
const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.
|
|
162
|
+
const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerSettleStreaming);
|
|
163
163
|
for (const item of result.outputItems)
|
|
164
164
|
nativeInput.push(item);
|
|
165
165
|
return result;
|
|
@@ -88,7 +88,7 @@ function createGithubCopilotProviderRuntime(injectedConfig) {
|
|
|
88
88
|
if (request.toolChoiceRequired)
|
|
89
89
|
params.tool_choice = "required";
|
|
90
90
|
try {
|
|
91
|
-
return await (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.
|
|
91
|
+
return await (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.eagerSettleStreaming);
|
|
92
92
|
}
|
|
93
93
|
catch (error) {
|
|
94
94
|
throw error instanceof Error ? error : new Error(String(error));
|
|
@@ -139,7 +139,7 @@ function createGithubCopilotProviderRuntime(injectedConfig) {
|
|
|
139
139
|
if (request.toolChoiceRequired)
|
|
140
140
|
params.tool_choice = "required";
|
|
141
141
|
try {
|
|
142
|
-
const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.
|
|
142
|
+
const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerSettleStreaming);
|
|
143
143
|
for (const item of result.outputItems)
|
|
144
144
|
nativeInput.push(item);
|
|
145
145
|
return result;
|
|
@@ -74,7 +74,7 @@ function createMinimaxProviderRuntime(config) {
|
|
|
74
74
|
params.metadata = { trace_id: request.traceId };
|
|
75
75
|
if (request.toolChoiceRequired)
|
|
76
76
|
params.tool_choice = "required";
|
|
77
|
-
return (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.
|
|
77
|
+
return (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.eagerSettleStreaming);
|
|
78
78
|
},
|
|
79
79
|
classifyError(error) {
|
|
80
80
|
return classifyMinimaxError(error);
|
|
@@ -180,7 +180,7 @@ function createOpenAICodexProviderRuntime(config) {
|
|
|
180
180
|
if (request.toolChoiceRequired)
|
|
181
181
|
params.tool_choice = "required";
|
|
182
182
|
try {
|
|
183
|
-
const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.
|
|
183
|
+
const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerSettleStreaming);
|
|
184
184
|
for (const item of result.outputItems)
|
|
185
185
|
nativeInput.push(item);
|
|
186
186
|
return result;
|
package/dist/heart/streaming.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.SettleStreamer = exports.SettleParser = void 0;
|
|
4
4
|
exports.toResponsesInput = toResponsesInput;
|
|
5
5
|
exports.toResponsesTools = toResponsesTools;
|
|
6
6
|
exports.streamChatCompletion = streamChatCompletion;
|
|
7
7
|
exports.streamResponsesApi = streamResponsesApi;
|
|
8
8
|
const runtime_1 = require("../nerves/runtime");
|
|
9
9
|
// Character-level state machine that extracts the answer value from
|
|
10
|
-
// `
|
|
10
|
+
// `settle` tool call JSON arguments as they stream in.
|
|
11
11
|
// Scans for prefix `"answer":"` or `"answer": "` in the character stream,
|
|
12
12
|
// then emits text handling JSON escapes, stopping at unescaped closing `"`.
|
|
13
|
-
class
|
|
13
|
+
class SettleParser {
|
|
14
14
|
// Possible prefixes to match (with and without space after colon)
|
|
15
15
|
static PREFIXES = ['"answer":"', '"answer": "'];
|
|
16
16
|
// Buffer of characters seen so far (pre-activation only)
|
|
@@ -29,7 +29,7 @@ class FinalAnswerParser {
|
|
|
29
29
|
if (!this._active) {
|
|
30
30
|
this.buf += ch;
|
|
31
31
|
// Check if any prefix has been fully matched in the buffer
|
|
32
|
-
for (const prefix of
|
|
32
|
+
for (const prefix of SettleParser.PREFIXES) {
|
|
33
33
|
if (this.buf.endsWith(prefix)) {
|
|
34
34
|
this._active = true;
|
|
35
35
|
break;
|
|
@@ -76,12 +76,12 @@ class FinalAnswerParser {
|
|
|
76
76
|
return out;
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
|
-
exports.
|
|
80
|
-
// Shared helper: wraps
|
|
79
|
+
exports.SettleParser = SettleParser;
|
|
80
|
+
// Shared helper: wraps SettleParser with onClearText + onTextChunk wiring.
|
|
81
81
|
// Used by all streaming providers (Chat Completions, Responses API, Anthropic)
|
|
82
|
-
// so the eager-match streaming pattern lives in one place.
|
|
83
|
-
class
|
|
84
|
-
parser = new
|
|
82
|
+
// so the eager-match settle streaming pattern lives in one place.
|
|
83
|
+
class SettleStreamer {
|
|
84
|
+
parser = new SettleParser();
|
|
85
85
|
_detected = false;
|
|
86
86
|
callbacks;
|
|
87
87
|
enabled;
|
|
@@ -91,7 +91,7 @@ class FinalAnswerStreamer {
|
|
|
91
91
|
}
|
|
92
92
|
get detected() { return this._detected; }
|
|
93
93
|
get streamed() { return this.parser.active; }
|
|
94
|
-
/** Mark
|
|
94
|
+
/** Mark settle as detected. Calls onClearText on the callbacks. */
|
|
95
95
|
activate() {
|
|
96
96
|
if (!this.enabled)
|
|
97
97
|
return;
|
|
@@ -111,7 +111,7 @@ class FinalAnswerStreamer {
|
|
|
111
111
|
this.callbacks.onTextChunk(text);
|
|
112
112
|
}
|
|
113
113
|
}
|
|
114
|
-
exports.
|
|
114
|
+
exports.SettleStreamer = SettleStreamer;
|
|
115
115
|
function toResponsesUserContent(content) {
|
|
116
116
|
if (typeof content === "string") {
|
|
117
117
|
return content;
|
|
@@ -233,7 +233,7 @@ function toResponsesTools(ccTools) {
|
|
|
233
233
|
strict: false,
|
|
234
234
|
}));
|
|
235
235
|
}
|
|
236
|
-
async function streamChatCompletion(client, createParams, callbacks, signal,
|
|
236
|
+
async function streamChatCompletion(client, createParams, callbacks, signal, eagerSettleStreaming = true) {
|
|
237
237
|
(0, runtime_1.emitNervesEvent)({
|
|
238
238
|
component: "engine",
|
|
239
239
|
event: "engine.stream_start",
|
|
@@ -247,7 +247,7 @@ async function streamChatCompletion(client, createParams, callbacks, signal, eag
|
|
|
247
247
|
let toolCalls = {};
|
|
248
248
|
let streamStarted = false;
|
|
249
249
|
let usage;
|
|
250
|
-
const answerStreamer = new
|
|
250
|
+
const answerStreamer = new SettleStreamer(callbacks, eagerSettleStreaming);
|
|
251
251
|
// State machine for parsing inline <think> tags (MiniMax pattern)
|
|
252
252
|
let contentBuf = "";
|
|
253
253
|
let inThinkTag = false;
|
|
@@ -362,19 +362,19 @@ async function streamChatCompletion(client, createParams, callbacks, signal, eag
|
|
|
362
362
|
toolCalls[tc.index].id = tc.id;
|
|
363
363
|
if (tc.function?.name) {
|
|
364
364
|
toolCalls[tc.index].name = tc.function.name;
|
|
365
|
-
// Detect
|
|
365
|
+
// Detect settle tool call on first name delta.
|
|
366
366
|
// Only activate streaming if this is the sole tool call (index 0
|
|
367
367
|
// and no other indices seen). Mixed calls are rejected by core.ts.
|
|
368
|
-
if (tc.function.name === "
|
|
368
|
+
if (tc.function.name === "settle" && !answerStreamer.detected
|
|
369
369
|
&& tc.index === 0 && Object.keys(toolCalls).length === 1) {
|
|
370
370
|
answerStreamer.activate();
|
|
371
371
|
}
|
|
372
372
|
}
|
|
373
373
|
if (tc.function?.arguments) {
|
|
374
374
|
toolCalls[tc.index].arguments += tc.function.arguments;
|
|
375
|
-
// Feed
|
|
375
|
+
// Feed settle argument deltas to the parser for progressive
|
|
376
376
|
// streaming, but only when it appears to be the sole tool call.
|
|
377
|
-
if (answerStreamer.detected && toolCalls[tc.index].name === "
|
|
377
|
+
if (answerStreamer.detected && toolCalls[tc.index].name === "settle"
|
|
378
378
|
&& Object.keys(toolCalls).length === 1) {
|
|
379
379
|
answerStreamer.processDelta(tc.function.arguments);
|
|
380
380
|
}
|
|
@@ -390,10 +390,10 @@ async function streamChatCompletion(client, createParams, callbacks, signal, eag
|
|
|
390
390
|
toolCalls: Object.values(toolCalls),
|
|
391
391
|
outputItems: [],
|
|
392
392
|
usage,
|
|
393
|
-
|
|
393
|
+
settleStreamed: answerStreamer.streamed,
|
|
394
394
|
};
|
|
395
395
|
}
|
|
396
|
-
async function streamResponsesApi(client, createParams, callbacks, signal,
|
|
396
|
+
async function streamResponsesApi(client, createParams, callbacks, signal, eagerSettleStreaming = true) {
|
|
397
397
|
(0, runtime_1.emitNervesEvent)({
|
|
398
398
|
component: "engine",
|
|
399
399
|
event: "engine.stream_start",
|
|
@@ -408,7 +408,7 @@ async function streamResponsesApi(client, createParams, callbacks, signal, eager
|
|
|
408
408
|
const outputItems = [];
|
|
409
409
|
let currentToolCall = null;
|
|
410
410
|
let usage;
|
|
411
|
-
const answerStreamer = new
|
|
411
|
+
const answerStreamer = new SettleStreamer(callbacks, eagerSettleStreaming);
|
|
412
412
|
let functionCallCount = 0;
|
|
413
413
|
for await (const event of response) {
|
|
414
414
|
if (signal?.aborted)
|
|
@@ -438,10 +438,10 @@ async function streamResponsesApi(client, createParams, callbacks, signal, eager
|
|
|
438
438
|
name: String(event.item.name),
|
|
439
439
|
arguments: "",
|
|
440
440
|
};
|
|
441
|
-
// Detect
|
|
441
|
+
// Detect settle function call -- clear any streamed noise.
|
|
442
442
|
// Only activate when this is the first (and so far only) function call.
|
|
443
443
|
// Mixed calls are rejected by core.ts; no need to stream their args.
|
|
444
|
-
if (String(event.item.name) === "
|
|
444
|
+
if (String(event.item.name) === "settle" && functionCallCount === 1) {
|
|
445
445
|
answerStreamer.activate();
|
|
446
446
|
}
|
|
447
447
|
}
|
|
@@ -450,9 +450,9 @@ async function streamResponsesApi(client, createParams, callbacks, signal, eager
|
|
|
450
450
|
case "response.function_call_arguments.delta": {
|
|
451
451
|
if (currentToolCall) {
|
|
452
452
|
currentToolCall.arguments += event.delta;
|
|
453
|
-
// Feed
|
|
453
|
+
// Feed settle argument deltas to the parser for progressive
|
|
454
454
|
// streaming, but only when it appears to be the sole function call.
|
|
455
|
-
if (answerStreamer.detected && currentToolCall.name === "
|
|
455
|
+
if (answerStreamer.detected && currentToolCall.name === "settle"
|
|
456
456
|
&& functionCallCount === 1) {
|
|
457
457
|
answerStreamer.processDelta(String(event.delta));
|
|
458
458
|
}
|
|
@@ -494,6 +494,6 @@ async function streamResponsesApi(client, createParams, callbacks, signal, eager
|
|
|
494
494
|
toolCalls,
|
|
495
495
|
outputItems,
|
|
496
496
|
usage,
|
|
497
|
-
|
|
497
|
+
settleStreamed: answerStreamer.streamed,
|
|
498
498
|
};
|
|
499
499
|
}
|
package/dist/heart/tool-loop.js
CHANGED
|
@@ -178,8 +178,12 @@ function recordToolOutcome(state, toolName, args, result, success) {
|
|
|
178
178
|
state.history.splice(0, state.history.length - exports.TOOL_LOOP_HISTORY_LIMIT);
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
+
// Tools that must never be blocked by the circuit breaker.
|
|
182
|
+
// settle = end the turn, surface = deliver results outward.
|
|
183
|
+
// Blocking these traps the agent: it can think all it wants but can never speak.
|
|
184
|
+
const CIRCUIT_BREAKER_EXEMPT = new Set(["settle", "surface"]);
|
|
181
185
|
function detectToolLoop(state, toolName, args) {
|
|
182
|
-
if (state.history.length >= exports.GLOBAL_CIRCUIT_BREAKER_LIMIT) {
|
|
186
|
+
if (state.history.length >= exports.GLOBAL_CIRCUIT_BREAKER_LIMIT && !CIRCUIT_BREAKER_EXEMPT.has(toolName)) {
|
|
183
187
|
return emitDetection("global_circuit_breaker", toolName, state.history.length, `this turn has already made ${state.history.length} tool calls. stop thrashing, use the current evidence, and either change approach or answer truthfully with the best grounded status.`);
|
|
184
188
|
}
|
|
185
189
|
const callHash = digest(normalizeArgs(toolName, args));
|
package/dist/mind/context.js
CHANGED
|
@@ -36,7 +36,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.trimMessages = trimMessages;
|
|
37
37
|
exports.validateSessionMessages = validateSessionMessages;
|
|
38
38
|
exports.repairSessionMessages = repairSessionMessages;
|
|
39
|
+
exports.migrateToolNames = migrateToolNames;
|
|
39
40
|
exports.saveSession = saveSession;
|
|
41
|
+
exports.appendSyntheticAssistantMessage = appendSyntheticAssistantMessage;
|
|
40
42
|
exports.loadSession = loadSession;
|
|
41
43
|
exports.postTurn = postTurn;
|
|
42
44
|
exports.deleteSession = deleteSession;
|
|
@@ -255,6 +257,43 @@ function stripOrphanedToolResults(messages) {
|
|
|
255
257
|
}
|
|
256
258
|
return repaired;
|
|
257
259
|
}
|
|
260
|
+
// Tool renames that have shipped. Old names in session history confuse the
|
|
261
|
+
// model into calling tools that no longer exist. Applied on session load so
|
|
262
|
+
// the transcript uses the current vocabulary.
|
|
263
|
+
const TOOL_NAME_MIGRATIONS = {
|
|
264
|
+
final_answer: "settle",
|
|
265
|
+
no_response: "observe",
|
|
266
|
+
go_inward: "descend",
|
|
267
|
+
};
|
|
268
|
+
function migrateToolNames(messages) {
|
|
269
|
+
let migrated = 0;
|
|
270
|
+
const result = messages.map((msg) => {
|
|
271
|
+
if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls) || msg.tool_calls.length === 0)
|
|
272
|
+
return msg;
|
|
273
|
+
let changed = false;
|
|
274
|
+
const updatedCalls = msg.tool_calls.map((tc) => {
|
|
275
|
+
if (tc.type !== "function")
|
|
276
|
+
return tc;
|
|
277
|
+
const newName = TOOL_NAME_MIGRATIONS[tc.function.name];
|
|
278
|
+
if (!newName)
|
|
279
|
+
return tc;
|
|
280
|
+
changed = true;
|
|
281
|
+
migrated++;
|
|
282
|
+
return { ...tc, function: { ...tc.function, name: newName } };
|
|
283
|
+
});
|
|
284
|
+
return changed ? { ...msg, tool_calls: updatedCalls } : msg;
|
|
285
|
+
});
|
|
286
|
+
if (migrated > 0) {
|
|
287
|
+
(0, runtime_1.emitNervesEvent)({
|
|
288
|
+
level: "info",
|
|
289
|
+
event: "mind.session_tool_name_migration",
|
|
290
|
+
component: "mind",
|
|
291
|
+
message: "migrated deprecated tool names in session history",
|
|
292
|
+
meta: { migrated },
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
258
297
|
function saveSession(filePath, messages, lastUsage, state) {
|
|
259
298
|
const violations = validateSessionMessages(messages);
|
|
260
299
|
if (violations.length > 0) {
|
|
@@ -280,6 +319,28 @@ function saveSession(filePath, messages, lastUsage, state) {
|
|
|
280
319
|
}
|
|
281
320
|
fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
|
|
282
321
|
}
|
|
322
|
+
function appendSyntheticAssistantMessage(filePath, content) {
|
|
323
|
+
try {
|
|
324
|
+
if (!fs.existsSync(filePath))
|
|
325
|
+
return false;
|
|
326
|
+
const raw = fs.readFileSync(filePath, "utf-8");
|
|
327
|
+
const data = JSON.parse(raw);
|
|
328
|
+
if (data.version !== 1 || !Array.isArray(data.messages))
|
|
329
|
+
return false;
|
|
330
|
+
data.messages.push({ role: "assistant", content });
|
|
331
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
332
|
+
(0, runtime_1.emitNervesEvent)({
|
|
333
|
+
component: "mind",
|
|
334
|
+
event: "mind.session_synthetic_message_appended",
|
|
335
|
+
message: "appended synthetic assistant message to session",
|
|
336
|
+
meta: { path: filePath, contentLength: content.length },
|
|
337
|
+
});
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
283
344
|
function loadSession(filePath) {
|
|
284
345
|
try {
|
|
285
346
|
const raw = fs.readFileSync(filePath, "utf-8");
|
|
@@ -299,6 +360,7 @@ function loadSession(filePath) {
|
|
|
299
360
|
messages = repairSessionMessages(messages);
|
|
300
361
|
}
|
|
301
362
|
messages = stripOrphanedToolResults(messages);
|
|
363
|
+
messages = migrateToolNames(messages);
|
|
302
364
|
const rawState = data?.state && typeof data.state === "object" && data.state !== null
|
|
303
365
|
? data.state
|
|
304
366
|
: undefined;
|
package/dist/mind/prompt.js
CHANGED
|
@@ -56,6 +56,7 @@ const ouro_version_manager_1 = require("../heart/daemon/ouro-version-manager");
|
|
|
56
56
|
const tools_1 = require("../repertoire/tools");
|
|
57
57
|
const skills_1 = require("../repertoire/skills");
|
|
58
58
|
const identity_1 = require("../heart/identity");
|
|
59
|
+
const runtime_mode_1 = require("../heart/daemon/runtime-mode");
|
|
59
60
|
const types_1 = require("./friends/types");
|
|
60
61
|
const trust_explanation_1 = require("./friends/trust-explanation");
|
|
61
62
|
const channel_1 = require("./friends/channel");
|
|
@@ -262,6 +263,9 @@ function runtimeInfoSection(channel) {
|
|
|
262
263
|
}
|
|
263
264
|
}
|
|
264
265
|
lines.push(`changelog available at: ${(0, bundle_manifest_1.getChangelogPath)()}`);
|
|
266
|
+
const sourceRoot = (0, identity_1.getRepoRoot)();
|
|
267
|
+
lines.push(`source root: ${sourceRoot}`);
|
|
268
|
+
lines.push(`runtime mode: ${(0, runtime_mode_1.detectRuntimeMode)(sourceRoot)}`);
|
|
265
269
|
lines.push(`cwd: ${process.cwd()}`);
|
|
266
270
|
lines.push(`channel: ${channel}`);
|
|
267
271
|
lines.push(`current sense: ${channel}`);
|
|
@@ -271,11 +275,11 @@ function runtimeInfoSection(channel) {
|
|
|
271
275
|
lines.push("i introduce myself on boot with a fun random greeting.");
|
|
272
276
|
}
|
|
273
277
|
else if (channel === "inner") {
|
|
274
|
-
|
|
278
|
+
lines.push("this is my private thinking space. when a thought is ready to share, i surface it to whoever needs to hear it. i settle when i'm done thinking.");
|
|
275
279
|
}
|
|
276
280
|
else if (channel === "bluebubbles") {
|
|
277
281
|
lines.push("i am responding in iMessage through BlueBubbles. i keep replies short and phone-native. i do not use markdown. i do not introduce myself on boot.");
|
|
278
|
-
lines.push("when a bluebubbles turn arrives from a thread, the harness tells me the current lane and any recent active thread ids. if widening back to top-level or routing into a different active thread is the better move, i use bluebubbles_set_reply_target before
|
|
282
|
+
lines.push("when a bluebubbles turn arrives from a thread, the harness tells me the current lane and any recent active thread ids. if widening back to top-level or routing into a different active thread is the better move, i use bluebubbles_set_reply_target before settle.");
|
|
279
283
|
}
|
|
280
284
|
else {
|
|
281
285
|
lines.push("i am responding in Microsoft Teams. i keep responses concise. i use markdown formatting. i do not introduce myself on boot.");
|
|
@@ -356,7 +360,7 @@ function dateSection() {
|
|
|
356
360
|
}
|
|
357
361
|
function toolsSection(channel, options, context) {
|
|
358
362
|
const channelTools = (0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)(channel), undefined, context, options?.providerCapabilities);
|
|
359
|
-
const activeTools = (options?.toolChoiceRequired ?? true) ? [...channelTools, tools_1.
|
|
363
|
+
const activeTools = (options?.toolChoiceRequired ?? true) ? [...channelTools, tools_1.settleTool] : channelTools;
|
|
360
364
|
const list = activeTools
|
|
361
365
|
.map((t) => `- ${t.function.name}: ${t.function.description}`)
|
|
362
366
|
.join("\n");
|
|
@@ -557,7 +561,7 @@ i should orient around that live lane first, then decide what still needs to com
|
|
|
557
561
|
return `## where my attention is
|
|
558
562
|
i have unfinished work that needs attention before i move on.
|
|
559
563
|
|
|
560
|
-
i can take it inward with
|
|
564
|
+
i can take it inward with descend to think privately, or address it directly here.`;
|
|
561
565
|
}
|
|
562
566
|
if (cog === "shared-work") {
|
|
563
567
|
/* v8 ignore stop */
|
|
@@ -619,19 +623,20 @@ function toolBehaviorSection(options) {
|
|
|
619
623
|
return `## tool behavior
|
|
620
624
|
tool_choice is set to "required" -- i must call a tool on every turn.
|
|
621
625
|
- need more information? i call a tool.
|
|
622
|
-
- ready to respond to the user? i call \`
|
|
623
|
-
\`
|
|
624
|
-
\`
|
|
625
|
-
do NOT call no-op tools just before \`
|
|
626
|
+
- ready to respond to the user? i call \`settle\`.
|
|
627
|
+
\`settle\` is a tool call -- it satisfies the tool_choice requirement.
|
|
628
|
+
\`settle\` must be the ONLY tool call in that turn. do not combine it with other tool calls.
|
|
629
|
+
do NOT call no-op tools just before \`settle\`. if i am done, i call \`settle\` directly.`;
|
|
626
630
|
}
|
|
627
631
|
function workspaceDisciplineSection() {
|
|
628
632
|
return `## repo workspace discipline
|
|
629
|
-
|
|
630
|
-
|
|
633
|
+
my source code lives at the path shown in \`source root\` above. that always matches my running version.
|
|
634
|
+
when i need to read my own code to understand my tools or debug behavior, i read from source root.
|
|
635
|
+
when i need to EDIT harness code (self-fix, feature work), i create a git worktree first so i don't dirty the working tree.
|
|
631
636
|
|
|
632
637
|
before the first repo edit, i tell the user in 1-2 short lines:
|
|
633
638
|
- the friction i'm fixing
|
|
634
|
-
- the
|
|
639
|
+
- the worktree path/branch i'm using
|
|
635
640
|
- the first concrete action i'm taking`;
|
|
636
641
|
}
|
|
637
642
|
function contextSection(context, options) {
|
|
@@ -668,7 +673,7 @@ function contextSection(context, options) {
|
|
|
668
673
|
lines.push("my conversation memory is ephemeral -- it resets between sessions. anything i learn about my friend, i save with save_friend_note so future me remembers.");
|
|
669
674
|
lines.push("the conversation is my source of truth. my notes are a journal for future me -- they may be stale or incomplete.");
|
|
670
675
|
lines.push("when i learn something that might invalidate an existing note, i check related notes and update or override any that are stale.");
|
|
671
|
-
lines.push("i save ANYTHING i learn about my friend immediately with save_friend_note -- names, preferences, what they do, what they care about. when in doubt, save it. saving comes BEFORE responding: i call save_friend_note first, then
|
|
676
|
+
lines.push("i save ANYTHING i learn about my friend immediately with save_friend_note -- names, preferences, what they do, what they care about. when in doubt, save it. saving comes BEFORE responding: i call save_friend_note first, then settle on the next turn.");
|
|
672
677
|
// Onboarding instructions (only below token threshold -- drop once exceeded)
|
|
673
678
|
const impressions = (0, first_impressions_1.getFirstImpressions)(friend, options);
|
|
674
679
|
if (impressions) {
|
|
@@ -727,13 +732,13 @@ function groupChatParticipationSection(context) {
|
|
|
727
732
|
group chats are conversations between people. i'm one participant, not the host.
|
|
728
733
|
|
|
729
734
|
i don't need to respond to everything. most reactions, tapbacks, and side
|
|
730
|
-
conversations between others aren't for me. i use
|
|
735
|
+
conversations between others aren't for me. i use observe to stay quiet
|
|
731
736
|
when the moment doesn't call for my voice — same as any person would.
|
|
732
737
|
|
|
733
738
|
when a reaction or emoji says it better than words, i can react instead of
|
|
734
739
|
typing a full reply. a thumbs-up is often the perfect response.
|
|
735
740
|
|
|
736
|
-
|
|
741
|
+
observe must be the sole tool call in the turn (same rule as settle).
|
|
737
742
|
when unsure whether to chime in, i lean toward silence rather than noise.`;
|
|
738
743
|
}
|
|
739
744
|
function mixedTrustGroupSection(context) {
|
|
@@ -279,6 +279,7 @@ exports.adoSemanticToolDefinitions = [
|
|
|
279
279
|
return formatForChannel(items, ctx, organization, project);
|
|
280
280
|
},
|
|
281
281
|
integration: "ado",
|
|
282
|
+
summaryKeys: ["organization", "project"],
|
|
282
283
|
},
|
|
283
284
|
// -- ado_create_epic --
|
|
284
285
|
{
|
|
@@ -318,6 +319,7 @@ exports.adoSemanticToolDefinitions = [
|
|
|
318
319
|
},
|
|
319
320
|
integration: "ado",
|
|
320
321
|
confirmationRequired: true,
|
|
322
|
+
summaryKeys: ["organization", "project", "title"],
|
|
321
323
|
},
|
|
322
324
|
// -- ado_create_issue --
|
|
323
325
|
{
|
|
@@ -359,6 +361,7 @@ exports.adoSemanticToolDefinitions = [
|
|
|
359
361
|
},
|
|
360
362
|
integration: "ado",
|
|
361
363
|
confirmationRequired: true,
|
|
364
|
+
summaryKeys: ["organization", "project", "title"],
|
|
362
365
|
},
|
|
363
366
|
// -- ado_move_items --
|
|
364
367
|
{
|
|
@@ -413,6 +416,7 @@ exports.adoSemanticToolDefinitions = [
|
|
|
413
416
|
},
|
|
414
417
|
integration: "ado",
|
|
415
418
|
confirmationRequired: true,
|
|
419
|
+
summaryKeys: ["organization", "project", "workItemIds"],
|
|
416
420
|
},
|
|
417
421
|
// -- ado_restructure_backlog --
|
|
418
422
|
{
|
|
@@ -471,6 +475,7 @@ exports.adoSemanticToolDefinitions = [
|
|
|
471
475
|
},
|
|
472
476
|
integration: "ado",
|
|
473
477
|
confirmationRequired: true,
|
|
478
|
+
summaryKeys: ["organization", "project"],
|
|
474
479
|
},
|
|
475
480
|
// -- ado_validate_structure --
|
|
476
481
|
{
|
|
@@ -672,6 +677,7 @@ exports.adoSemanticToolDefinitions = [
|
|
|
672
677
|
},
|
|
673
678
|
integration: "ado",
|
|
674
679
|
confirmationRequired: true,
|
|
680
|
+
summaryKeys: ["organization", "project"],
|
|
675
681
|
},
|
|
676
682
|
// -- ado_detect_orphans --
|
|
677
683
|
{
|
|
@@ -279,6 +279,7 @@ exports.codingToolDefinitions = [
|
|
|
279
279
|
}
|
|
280
280
|
return JSON.stringify(session);
|
|
281
281
|
},
|
|
282
|
+
summaryKeys: ["runner", "workdir", "taskRef"],
|
|
282
283
|
},
|
|
283
284
|
{
|
|
284
285
|
tool: codingStatusTool,
|
|
@@ -294,6 +295,7 @@ exports.codingToolDefinitions = [
|
|
|
294
295
|
return `session not found: ${sessionId}`;
|
|
295
296
|
return JSON.stringify(session);
|
|
296
297
|
},
|
|
298
|
+
summaryKeys: ["sessionId"],
|
|
297
299
|
},
|
|
298
300
|
{
|
|
299
301
|
tool: codingTailTool,
|
|
@@ -307,6 +309,7 @@ exports.codingToolDefinitions = [
|
|
|
307
309
|
return `session not found: ${sessionId}`;
|
|
308
310
|
return (0, index_1.formatCodingTail)(session);
|
|
309
311
|
},
|
|
312
|
+
summaryKeys: ["sessionId"],
|
|
310
313
|
},
|
|
311
314
|
{
|
|
312
315
|
tool: codingSendInputTool,
|
|
@@ -320,6 +323,7 @@ exports.codingToolDefinitions = [
|
|
|
320
323
|
return "input is required";
|
|
321
324
|
return JSON.stringify((0, index_1.getCodingSessionManager)().sendInput(sessionId, input));
|
|
322
325
|
},
|
|
326
|
+
summaryKeys: ["sessionId", "input"],
|
|
323
327
|
},
|
|
324
328
|
{
|
|
325
329
|
tool: codingKillTool,
|
|
@@ -330,5 +334,6 @@ exports.codingToolDefinitions = [
|
|
|
330
334
|
return "sessionId is required";
|
|
331
335
|
return JSON.stringify((0, index_1.getCodingSessionManager)().killSession(sessionId));
|
|
332
336
|
},
|
|
337
|
+
summaryKeys: ["sessionId"],
|
|
333
338
|
},
|
|
334
339
|
];
|