@ouro.bot/cli 0.1.0-alpha.133 → 0.1.0-alpha.134
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 +15 -0
- package/dist/heart/core.js +93 -68
- 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/mind/prompt.js +10 -10
- package/dist/repertoire/tools-base.js +8 -8
- package/dist/repertoire/tools.js +179 -4
- 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
|
@@ -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/mind/prompt.js
CHANGED
|
@@ -271,11 +271,11 @@ function runtimeInfoSection(channel) {
|
|
|
271
271
|
lines.push("i introduce myself on boot with a fun random greeting.");
|
|
272
272
|
}
|
|
273
273
|
else if (channel === "inner") {
|
|
274
|
-
|
|
274
|
+
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
275
|
}
|
|
276
276
|
else if (channel === "bluebubbles") {
|
|
277
277
|
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
|
|
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 settle.");
|
|
279
279
|
}
|
|
280
280
|
else {
|
|
281
281
|
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 +356,7 @@ function dateSection() {
|
|
|
356
356
|
}
|
|
357
357
|
function toolsSection(channel, options, context) {
|
|
358
358
|
const channelTools = (0, tools_1.getToolsForChannel)((0, channel_1.getChannelCapabilities)(channel), undefined, context, options?.providerCapabilities);
|
|
359
|
-
const activeTools = (options?.toolChoiceRequired ?? true) ? [...channelTools, tools_1.
|
|
359
|
+
const activeTools = (options?.toolChoiceRequired ?? true) ? [...channelTools, tools_1.settleTool] : channelTools;
|
|
360
360
|
const list = activeTools
|
|
361
361
|
.map((t) => `- ${t.function.name}: ${t.function.description}`)
|
|
362
362
|
.join("\n");
|
|
@@ -619,10 +619,10 @@ function toolBehaviorSection(options) {
|
|
|
619
619
|
return `## tool behavior
|
|
620
620
|
tool_choice is set to "required" -- i must call a tool on every turn.
|
|
621
621
|
- 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 \`
|
|
622
|
+
- ready to respond to the user? i call \`settle\`.
|
|
623
|
+
\`settle\` is a tool call -- it satisfies the tool_choice requirement.
|
|
624
|
+
\`settle\` must be the ONLY tool call in that turn. do not combine it with other tool calls.
|
|
625
|
+
do NOT call no-op tools just before \`settle\`. if i am done, i call \`settle\` directly.`;
|
|
626
626
|
}
|
|
627
627
|
function workspaceDisciplineSection() {
|
|
628
628
|
return `## repo workspace discipline
|
|
@@ -668,7 +668,7 @@ function contextSection(context, options) {
|
|
|
668
668
|
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
669
|
lines.push("the conversation is my source of truth. my notes are a journal for future me -- they may be stale or incomplete.");
|
|
670
670
|
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
|
|
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 settle on the next turn.");
|
|
672
672
|
// Onboarding instructions (only below token threshold -- drop once exceeded)
|
|
673
673
|
const impressions = (0, first_impressions_1.getFirstImpressions)(friend, options);
|
|
674
674
|
if (impressions) {
|
|
@@ -727,13 +727,13 @@ function groupChatParticipationSection(context) {
|
|
|
727
727
|
group chats are conversations between people. i'm one participant, not the host.
|
|
728
728
|
|
|
729
729
|
i don't need to respond to everything. most reactions, tapbacks, and side
|
|
730
|
-
conversations between others aren't for me. i use
|
|
730
|
+
conversations between others aren't for me. i use observe to stay quiet
|
|
731
731
|
when the moment doesn't call for my voice — same as any person would.
|
|
732
732
|
|
|
733
733
|
when a reaction or emoji says it better than words, i can react instead of
|
|
734
734
|
typing a full reply. a thumbs-up is often the perfect response.
|
|
735
735
|
|
|
736
|
-
|
|
736
|
+
observe must be the sole tool call in the turn (same rule as settle).
|
|
737
737
|
when unsure whether to chime in, i lean toward silence rather than noise.`;
|
|
738
738
|
}
|
|
739
739
|
function mixedTrustGroupSection(context) {
|
|
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.
|
|
36
|
+
exports.settleTool = exports.observeTool = exports.goInwardTool = exports.tools = exports.baseToolDefinitions = exports.editFileReadTracker = void 0;
|
|
37
37
|
exports.renderInnerProgressStatus = renderInnerProgressStatus;
|
|
38
38
|
const fs = __importStar(require("fs"));
|
|
39
39
|
const fg = __importStar(require("fast-glob"));
|
|
@@ -1354,9 +1354,9 @@ exports.goInwardTool = {
|
|
|
1354
1354
|
parameters: {
|
|
1355
1355
|
type: "object",
|
|
1356
1356
|
properties: {
|
|
1357
|
-
|
|
1357
|
+
topic: {
|
|
1358
1358
|
type: "string",
|
|
1359
|
-
description: "
|
|
1359
|
+
description: "the question or topic that needs private thought — brief framing, not your analysis. your inner dialog will do the actual thinking.",
|
|
1360
1360
|
},
|
|
1361
1361
|
answer: {
|
|
1362
1362
|
type: "string",
|
|
@@ -1368,14 +1368,14 @@ exports.goInwardTool = {
|
|
|
1368
1368
|
description: "reflect: something to sit with. plan: something to work through. relay: something to carry across.",
|
|
1369
1369
|
},
|
|
1370
1370
|
},
|
|
1371
|
-
required: ["
|
|
1371
|
+
required: ["topic"],
|
|
1372
1372
|
},
|
|
1373
1373
|
},
|
|
1374
1374
|
};
|
|
1375
|
-
exports.
|
|
1375
|
+
exports.observeTool = {
|
|
1376
1376
|
type: "function",
|
|
1377
1377
|
function: {
|
|
1378
|
-
name: "
|
|
1378
|
+
name: "observe",
|
|
1379
1379
|
description: "stay silent in this group chat — the moment doesn't call for a response. must be the only tool call in the turn.",
|
|
1380
1380
|
parameters: {
|
|
1381
1381
|
type: "object",
|
|
@@ -1385,10 +1385,10 @@ exports.noResponseTool = {
|
|
|
1385
1385
|
},
|
|
1386
1386
|
},
|
|
1387
1387
|
};
|
|
1388
|
-
exports.
|
|
1388
|
+
exports.settleTool = {
|
|
1389
1389
|
type: "function",
|
|
1390
1390
|
function: {
|
|
1391
|
-
name: "
|
|
1391
|
+
name: "settle",
|
|
1392
1392
|
description: "respond to the user with your message. call this tool when you are ready to deliver your response.",
|
|
1393
1393
|
parameters: {
|
|
1394
1394
|
type: "object",
|
package/dist/repertoire/tools.js
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.goInwardTool = exports.
|
|
36
|
+
exports.goInwardTool = exports.observeTool = exports.settleTool = exports.tools = void 0;
|
|
4
37
|
exports.getToolsForChannel = getToolsForChannel;
|
|
5
38
|
exports.isConfirmationRequired = isConfirmationRequired;
|
|
6
39
|
exports.execTool = execTool;
|
|
@@ -14,6 +47,10 @@ const runtime_1 = require("../nerves/runtime");
|
|
|
14
47
|
const guardrails_1 = require("./guardrails");
|
|
15
48
|
const identity_1 = require("../heart/identity");
|
|
16
49
|
const safe_workspace_1 = require("../heart/safe-workspace");
|
|
50
|
+
const surface_tool_1 = require("../senses/surface-tool");
|
|
51
|
+
const obligations_1 = require("../mind/obligations");
|
|
52
|
+
const session_activity_1 = require("../heart/session-activity");
|
|
53
|
+
const path = __importStar(require("path"));
|
|
17
54
|
function safeGetAgentRoot() {
|
|
18
55
|
try {
|
|
19
56
|
return (0, identity_1.getAgentRoot)();
|
|
@@ -25,11 +62,149 @@ function safeGetAgentRoot() {
|
|
|
25
62
|
// Re-export types and constants used by the rest of the codebase
|
|
26
63
|
var tools_base_2 = require("./tools-base");
|
|
27
64
|
Object.defineProperty(exports, "tools", { enumerable: true, get: function () { return tools_base_2.tools; } });
|
|
28
|
-
Object.defineProperty(exports, "
|
|
29
|
-
Object.defineProperty(exports, "
|
|
65
|
+
Object.defineProperty(exports, "settleTool", { enumerable: true, get: function () { return tools_base_2.settleTool; } });
|
|
66
|
+
Object.defineProperty(exports, "observeTool", { enumerable: true, get: function () { return tools_base_2.observeTool; } });
|
|
30
67
|
Object.defineProperty(exports, "goInwardTool", { enumerable: true, get: function () { return tools_base_2.goInwardTool; } });
|
|
68
|
+
// Surface tool handler: routes content to friend's freshest session
|
|
69
|
+
/* v8 ignore start -- surface handler wiring: core logic tested via surface-tool.test.ts; this wires identity/routing deps @preserve */
|
|
70
|
+
const surfaceToolDefinition = {
|
|
71
|
+
tool: surface_tool_1.surfaceToolDef,
|
|
72
|
+
handler: async (args, ctx) => {
|
|
73
|
+
const queue = ctx?.delegatedOrigins ?? [];
|
|
74
|
+
const agentName = (() => { try {
|
|
75
|
+
return (0, identity_1.getAgentName)();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return "unknown";
|
|
79
|
+
} })();
|
|
80
|
+
const routeToFriend = async (friendId, content, queueItem) => {
|
|
81
|
+
/* v8 ignore start -- routing: integration path tested via inner-dialog routing tests @preserve */
|
|
82
|
+
try {
|
|
83
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
84
|
+
const sessionsDir = path.join(agentRoot, "state", "sessions");
|
|
85
|
+
const friendsDir = path.join(agentRoot, "friends");
|
|
86
|
+
// Priority 1: Bridge-preferred session (if queue item has a bridgeId)
|
|
87
|
+
if (queueItem?.bridgeId) {
|
|
88
|
+
const { createBridgeManager } = await Promise.resolve().then(() => __importStar(require("../heart/bridges/manager")));
|
|
89
|
+
const bridge = createBridgeManager().getBridge(queueItem.bridgeId);
|
|
90
|
+
if (bridge && bridge.lifecycle !== "completed" && bridge.lifecycle !== "cancelled") {
|
|
91
|
+
const allSessions = (0, session_activity_1.listSessionActivity)({ sessionsDir, friendsDir, agentName });
|
|
92
|
+
const bridgeTarget = allSessions.find((activity) => activity.friendId === friendId
|
|
93
|
+
&& activity.channel !== "inner"
|
|
94
|
+
&& bridge.attachedSessions.some((s) => s.friendId === activity.friendId && s.channel === activity.channel && s.key === activity.key));
|
|
95
|
+
if (bridgeTarget) {
|
|
96
|
+
// Attempt proactive BB delivery for bridge target
|
|
97
|
+
if (bridgeTarget.channel === "bluebubbles") {
|
|
98
|
+
const { sendProactiveBlueBubblesMessageToSession } = await Promise.resolve().then(() => __importStar(require("../senses/bluebubbles")));
|
|
99
|
+
const proactiveResult = await sendProactiveBlueBubblesMessageToSession({
|
|
100
|
+
friendId: bridgeTarget.friendId,
|
|
101
|
+
sessionKey: bridgeTarget.key,
|
|
102
|
+
text: content,
|
|
103
|
+
});
|
|
104
|
+
if (proactiveResult.delivered) {
|
|
105
|
+
return { status: "delivered", detail: "via iMessage" };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// Fall back to pending queue for bridge target
|
|
109
|
+
const { queuePendingMessage, getPendingDir } = await Promise.resolve().then(() => __importStar(require("../mind/pending")));
|
|
110
|
+
const pendingDir = getPendingDir(agentName, bridgeTarget.friendId, bridgeTarget.channel, bridgeTarget.key);
|
|
111
|
+
queuePendingMessage(pendingDir, {
|
|
112
|
+
from: agentName,
|
|
113
|
+
friendId: bridgeTarget.friendId,
|
|
114
|
+
channel: bridgeTarget.channel,
|
|
115
|
+
key: bridgeTarget.key,
|
|
116
|
+
content,
|
|
117
|
+
timestamp: Date.now(),
|
|
118
|
+
});
|
|
119
|
+
return { status: "queued", detail: `for next interaction via ${bridgeTarget.channel}` };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Priority 2: Freshest active friend session
|
|
124
|
+
const freshest = (0, session_activity_1.findFreshestFriendSession)({
|
|
125
|
+
sessionsDir,
|
|
126
|
+
friendsDir,
|
|
127
|
+
agentName,
|
|
128
|
+
friendId,
|
|
129
|
+
activeOnly: true,
|
|
130
|
+
});
|
|
131
|
+
if (freshest && freshest.channel !== "inner") {
|
|
132
|
+
// Attempt proactive BB delivery
|
|
133
|
+
if (freshest.channel === "bluebubbles") {
|
|
134
|
+
const { sendProactiveBlueBubblesMessageToSession } = await Promise.resolve().then(() => __importStar(require("../senses/bluebubbles")));
|
|
135
|
+
const proactiveResult = await sendProactiveBlueBubblesMessageToSession({
|
|
136
|
+
friendId: freshest.friendId,
|
|
137
|
+
sessionKey: freshest.key,
|
|
138
|
+
text: content,
|
|
139
|
+
});
|
|
140
|
+
if (proactiveResult.delivered) {
|
|
141
|
+
return { status: "delivered", detail: "via iMessage" };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
// Queue as pending for next interaction
|
|
145
|
+
const { queuePendingMessage, getPendingDir } = await Promise.resolve().then(() => __importStar(require("../mind/pending")));
|
|
146
|
+
const pendingDir = getPendingDir(agentName, freshest.friendId, freshest.channel, freshest.key);
|
|
147
|
+
queuePendingMessage(pendingDir, {
|
|
148
|
+
from: agentName,
|
|
149
|
+
friendId: freshest.friendId,
|
|
150
|
+
channel: freshest.channel,
|
|
151
|
+
key: freshest.key,
|
|
152
|
+
content,
|
|
153
|
+
timestamp: Date.now(),
|
|
154
|
+
});
|
|
155
|
+
return { status: "queued", detail: `for next interaction via ${freshest.channel}` };
|
|
156
|
+
}
|
|
157
|
+
// Priority 3: Deferred — no active session found
|
|
158
|
+
const { getDeferredReturnDir } = await Promise.resolve().then(() => __importStar(require("../mind/pending")));
|
|
159
|
+
const { queuePendingMessage: queueDeferred } = await Promise.resolve().then(() => __importStar(require("../mind/pending")));
|
|
160
|
+
const deferredDir = getDeferredReturnDir(agentName, friendId);
|
|
161
|
+
queueDeferred(deferredDir, {
|
|
162
|
+
from: agentName,
|
|
163
|
+
friendId,
|
|
164
|
+
channel: "deferred",
|
|
165
|
+
key: "return",
|
|
166
|
+
content,
|
|
167
|
+
timestamp: Date.now(),
|
|
168
|
+
});
|
|
169
|
+
return { status: "deferred", detail: "they'll see it next time" };
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return { status: "failed" };
|
|
173
|
+
}
|
|
174
|
+
/* v8 ignore stop */
|
|
175
|
+
};
|
|
176
|
+
return (0, surface_tool_1.handleSurface)({
|
|
177
|
+
content: args.content ?? "",
|
|
178
|
+
delegationId: args.delegationId,
|
|
179
|
+
friendId: args.friendId,
|
|
180
|
+
queue,
|
|
181
|
+
routeToFriend,
|
|
182
|
+
advanceObligation: (obligationId, update) => {
|
|
183
|
+
/* v8 ignore start -- obligation advance: tested via attention-queue tests @preserve */
|
|
184
|
+
try {
|
|
185
|
+
const name = (() => { try {
|
|
186
|
+
return (0, identity_1.getAgentName)();
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return "unknown";
|
|
190
|
+
} })();
|
|
191
|
+
(0, obligations_1.advanceObligation)(name, obligationId, {
|
|
192
|
+
status: update.status,
|
|
193
|
+
...(update.returnedAt !== undefined ? { returnedAt: update.returnedAt } : {}),
|
|
194
|
+
...(update.returnTarget !== undefined ? { returnTarget: update.returnTarget } : {}),
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
// swallowed — obligation advance must never break surface delivery
|
|
199
|
+
}
|
|
200
|
+
/* v8 ignore stop */
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
/* v8 ignore stop */
|
|
31
206
|
// All tool definitions in a single registry
|
|
32
|
-
const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_bluebubbles_1.bluebubblesToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions];
|
|
207
|
+
const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_bluebubbles_1.bluebubblesToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions, surfaceToolDefinition];
|
|
33
208
|
function baseToolsForCapabilities() {
|
|
34
209
|
// Use baseToolDefinitions at call time so dynamically-added tools are included
|
|
35
210
|
return tools_base_1.baseToolDefinitions.map((d) => d.tool);
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildAttentionQueue = buildAttentionQueue;
|
|
4
|
+
exports.dequeueAttentionItem = dequeueAttentionItem;
|
|
5
|
+
exports.attentionQueueEmpty = attentionQueueEmpty;
|
|
6
|
+
exports.buildAttentionQueueSummary = buildAttentionQueueSummary;
|
|
7
|
+
const runtime_1 = require("../nerves/runtime");
|
|
8
|
+
// ── Queue construction ───────────────────────────────────────────
|
|
9
|
+
function generateItemId() {
|
|
10
|
+
return Math.random().toString(36).slice(2, 10);
|
|
11
|
+
}
|
|
12
|
+
function originKey(friendId, channel, key) {
|
|
13
|
+
return `${friendId}/${channel}/${key}`;
|
|
14
|
+
}
|
|
15
|
+
function buildAttentionQueue(input) {
|
|
16
|
+
const { drainedPending, outstandingObligations, friendNameResolver } = input;
|
|
17
|
+
const seen = new Set();
|
|
18
|
+
const items = [];
|
|
19
|
+
// Source 1: drained pending messages with delegatedFrom (current-turn delegations)
|
|
20
|
+
for (const msg of drainedPending) {
|
|
21
|
+
if (!msg.delegatedFrom)
|
|
22
|
+
continue;
|
|
23
|
+
const { friendId, channel, key, bridgeId } = msg.delegatedFrom;
|
|
24
|
+
const oKey = originKey(friendId, channel, key);
|
|
25
|
+
seen.add(oKey);
|
|
26
|
+
const resolvedName = friendNameResolver(friendId);
|
|
27
|
+
items.push({
|
|
28
|
+
id: msg.obligationId ?? generateItemId(),
|
|
29
|
+
friendId,
|
|
30
|
+
friendName: resolvedName ?? friendId,
|
|
31
|
+
channel,
|
|
32
|
+
key,
|
|
33
|
+
...(bridgeId ? { bridgeId } : {}),
|
|
34
|
+
delegatedContent: msg.content,
|
|
35
|
+
...(msg.obligationId ? { obligationId: msg.obligationId } : {}),
|
|
36
|
+
source: "drained",
|
|
37
|
+
timestamp: msg.timestamp,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// Source 2: outstanding obligations (crash recovery)
|
|
41
|
+
for (const obligation of outstandingObligations) {
|
|
42
|
+
const { friendId, channel, key, bridgeId } = obligation.origin;
|
|
43
|
+
const oKey = originKey(friendId, channel, key);
|
|
44
|
+
if (seen.has(oKey))
|
|
45
|
+
continue; // deduplicate: prefer drained version
|
|
46
|
+
seen.add(oKey);
|
|
47
|
+
const resolvedName = friendNameResolver(friendId);
|
|
48
|
+
items.push({
|
|
49
|
+
id: obligation.id,
|
|
50
|
+
friendId,
|
|
51
|
+
friendName: resolvedName ?? friendId,
|
|
52
|
+
channel,
|
|
53
|
+
key,
|
|
54
|
+
...(bridgeId ? { bridgeId } : {}),
|
|
55
|
+
delegatedContent: obligation.delegatedContent,
|
|
56
|
+
obligationId: obligation.id,
|
|
57
|
+
source: "obligation-recovery",
|
|
58
|
+
timestamp: obligation.createdAt,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
// Sort FIFO (oldest first)
|
|
62
|
+
items.sort((a, b) => a.timestamp - b.timestamp);
|
|
63
|
+
(0, runtime_1.emitNervesEvent)({
|
|
64
|
+
event: "senses.attention_queue_built",
|
|
65
|
+
component: "senses",
|
|
66
|
+
message: `attention queue built with ${items.length} item(s)`,
|
|
67
|
+
meta: {
|
|
68
|
+
drainedCount: items.filter((i) => i.source === "drained").length,
|
|
69
|
+
recoveredCount: items.filter((i) => i.source === "obligation-recovery").length,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
return items;
|
|
73
|
+
}
|
|
74
|
+
// ── Queue operations ─────────────────────────────────────────────
|
|
75
|
+
function dequeueAttentionItem(queue, id) {
|
|
76
|
+
const index = queue.findIndex((item) => item.id === id);
|
|
77
|
+
if (index === -1)
|
|
78
|
+
return null;
|
|
79
|
+
return queue.splice(index, 1)[0];
|
|
80
|
+
}
|
|
81
|
+
function attentionQueueEmpty(queue) {
|
|
82
|
+
return queue.length === 0;
|
|
83
|
+
}
|
|
84
|
+
// ── Queue visibility ─────────────────────────────────────────────
|
|
85
|
+
const CONTENT_PREVIEW_MAX = 80;
|
|
86
|
+
function buildAttentionQueueSummary(queue) {
|
|
87
|
+
if (queue.length === 0)
|
|
88
|
+
return "";
|
|
89
|
+
const lines = ["you're holding:"];
|
|
90
|
+
for (const item of queue) {
|
|
91
|
+
const preview = item.delegatedContent.length > CONTENT_PREVIEW_MAX
|
|
92
|
+
? `${item.delegatedContent.slice(0, CONTENT_PREVIEW_MAX - 3)}...`
|
|
93
|
+
: item.delegatedContent;
|
|
94
|
+
lines.push(`- [${item.id}] ${item.friendName} asked: "${preview}"`);
|
|
95
|
+
}
|
|
96
|
+
return lines.join("\n");
|
|
97
|
+
}
|
package/dist/senses/cli.js
CHANGED
|
@@ -382,14 +382,14 @@ function createCliCallbacks() {
|
|
|
382
382
|
onModelStreamStart: () => {
|
|
383
383
|
// No-op: content callbacks (onTextChunk, onReasoningChunk) handle
|
|
384
384
|
// stopping the spinner. onModelStreamStart fires too early and
|
|
385
|
-
// doesn't fire at all for
|
|
385
|
+
// doesn't fire at all for settle tool streaming.
|
|
386
386
|
},
|
|
387
387
|
onClearText: () => {
|
|
388
388
|
streamer.reset();
|
|
389
389
|
wrapper.reset();
|
|
390
390
|
},
|
|
391
391
|
onTextChunk: (text) => {
|
|
392
|
-
// Stop spinner if still running —
|
|
392
|
+
// Stop spinner if still running — settle streaming and Anthropic
|
|
393
393
|
// tool-only responses bypass onModelStreamStart, so the spinner would
|
|
394
394
|
// otherwise keep running (and its \r writes overwrite response text).
|
|
395
395
|
if (currentSpinner) {
|
|
@@ -558,7 +558,7 @@ async function runCliSession(options) {
|
|
|
558
558
|
exitToolResult = result;
|
|
559
559
|
exitToolFired = true;
|
|
560
560
|
// Abort immediately so the model doesn't generate more output
|
|
561
|
-
// (e.g. reasoning about calling
|
|
561
|
+
// (e.g. reasoning about calling settle after complete_adoption)
|
|
562
562
|
currentAbort?.abort();
|
|
563
563
|
}
|
|
564
564
|
return result;
|