@ouro.bot/cli 0.1.0-alpha.38 → 0.1.0-alpha.39
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 +10 -0
- package/dist/heart/daemon/daemon-cli.js +129 -44
- package/dist/heart/daemon/daemon.js +90 -0
- package/dist/mind/friends/channel.js +27 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/pending.js +8 -0
- package/dist/mind/prompt.js +26 -22
- package/dist/repertoire/tools-base.js +1 -1
- package/dist/repertoire/tools.js +11 -16
- package/dist/senses/bluebubbles-model.js +10 -0
- package/dist/senses/bluebubbles.js +125 -29
- package/dist/senses/cli.js +59 -54
- package/dist/senses/inner-dialog.js +88 -45
- package/dist/senses/pipeline.js +124 -0
- package/dist/senses/teams.js +87 -65
- package/dist/senses/trust-gate.js +113 -2
- package/package.json +1 -1
package/dist/repertoire/tools.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.teamsTools = exports.finalAnswerTool = exports.tools = void 0;
|
|
3
|
+
exports.REMOTE_BLOCKED_LOCAL_TOOLS = exports.teamsTools = exports.finalAnswerTool = exports.tools = void 0;
|
|
4
4
|
exports.getToolsForChannel = getToolsForChannel;
|
|
5
5
|
exports.isConfirmationRequired = isConfirmationRequired;
|
|
6
6
|
exports.execTool = execTool;
|
|
@@ -10,6 +10,8 @@ const tools_teams_1 = require("./tools-teams");
|
|
|
10
10
|
const tools_bluebubbles_1 = require("./tools-bluebubbles");
|
|
11
11
|
const ado_semantic_1 = require("./ado-semantic");
|
|
12
12
|
const tools_github_1 = require("./tools-github");
|
|
13
|
+
const types_1 = require("../mind/friends/types");
|
|
14
|
+
const channel_1 = require("../mind/friends/channel");
|
|
13
15
|
const runtime_1 = require("../nerves/runtime");
|
|
14
16
|
// Re-export types and constants used by the rest of the codebase
|
|
15
17
|
var tools_base_2 = require("./tools-base");
|
|
@@ -19,32 +21,25 @@ var tools_teams_2 = require("./tools-teams");
|
|
|
19
21
|
Object.defineProperty(exports, "teamsTools", { enumerable: true, get: function () { return tools_teams_2.teamsTools; } });
|
|
20
22
|
// All tool definitions in a single registry
|
|
21
23
|
const allDefinitions = [...tools_base_1.baseToolDefinitions, ...tools_bluebubbles_1.bluebubblesToolDefinitions, ...tools_teams_1.teamsToolDefinitions, ...ado_semantic_1.adoSemanticToolDefinitions, ...tools_github_1.githubToolDefinitions];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
return capabilities?.channel === "teams" || capabilities?.channel === "bluebubbles";
|
|
25
|
-
}
|
|
26
|
-
function isSharedRemoteContext(friend) {
|
|
27
|
-
const externalIds = friend.externalIds ?? [];
|
|
28
|
-
return externalIds.some((externalId) => externalId.externalId.startsWith("group:") || externalId.provider === "teams-conversation");
|
|
29
|
-
}
|
|
24
|
+
/** Tool names blocked for untrusted remote contexts. Shared with prompt.ts for restriction messaging. */
|
|
25
|
+
exports.REMOTE_BLOCKED_LOCAL_TOOLS = new Set(["shell", "read_file", "write_file", "edit_file", "glob", "grep"]);
|
|
30
26
|
function isTrustedRemoteContext(context) {
|
|
31
|
-
if (!context?.friend || !isRemoteChannel(context.channel))
|
|
27
|
+
if (!context?.friend || !(0, channel_1.isRemoteChannel)(context.channel))
|
|
32
28
|
return false;
|
|
33
|
-
|
|
34
|
-
return trustLevel !== "stranger" && !isSharedRemoteContext(context.friend);
|
|
29
|
+
return (0, types_1.isTrustedLevel)(context.friend.trustLevel);
|
|
35
30
|
}
|
|
36
31
|
function shouldBlockLocalTools(capabilities, context) {
|
|
37
|
-
if (!isRemoteChannel(capabilities))
|
|
32
|
+
if (!(0, channel_1.isRemoteChannel)(capabilities))
|
|
38
33
|
return false;
|
|
39
34
|
return !isTrustedRemoteContext(context);
|
|
40
35
|
}
|
|
41
36
|
function blockedLocalToolMessage() {
|
|
42
|
-
return "I can't do that
|
|
37
|
+
return "I can't do that because my trust level with you isn't high enough for local shell/file operations. Ask me for a remote-safe alternative (Graph/ADO/web), or run that operation from CLI.";
|
|
43
38
|
}
|
|
44
39
|
function baseToolsForCapabilities(capabilities, context) {
|
|
45
40
|
if (!shouldBlockLocalTools(capabilities, context))
|
|
46
41
|
return tools_base_1.tools;
|
|
47
|
-
return tools_base_1.tools.filter((tool) => !REMOTE_BLOCKED_LOCAL_TOOLS.has(tool.function.name));
|
|
42
|
+
return tools_base_1.tools.filter((tool) => !exports.REMOTE_BLOCKED_LOCAL_TOOLS.has(tool.function.name));
|
|
48
43
|
}
|
|
49
44
|
// Apply a single tool preference to a tool schema, returning a new object.
|
|
50
45
|
function applyPreference(tool, pref) {
|
|
@@ -113,7 +108,7 @@ async function execTool(name, args, ctx) {
|
|
|
113
108
|
});
|
|
114
109
|
return `unknown: ${name}`;
|
|
115
110
|
}
|
|
116
|
-
if (shouldBlockLocalTools(ctx?.context?.channel, ctx?.context) && REMOTE_BLOCKED_LOCAL_TOOLS.has(name)) {
|
|
111
|
+
if (shouldBlockLocalTools(ctx?.context?.channel, ctx?.context) && exports.REMOTE_BLOCKED_LOCAL_TOOLS.has(name)) {
|
|
117
112
|
const message = blockedLocalToolMessage();
|
|
118
113
|
(0, runtime_1.emitNervesEvent)({
|
|
119
114
|
level: "warn",
|
|
@@ -52,6 +52,15 @@ function buildChatRef(data, threadOriginatorGuid) {
|
|
|
52
52
|
const sessionKey = chatGuid?.trim()
|
|
53
53
|
? `chat:${chatGuid.trim()}`
|
|
54
54
|
: `chat_identifier:${(chatIdentifier ?? "unknown").trim()}`;
|
|
55
|
+
// Extract participant handles from chat.participants (when available from BB API)
|
|
56
|
+
const rawParticipants = Array.isArray(chat?.participants) ? chat.participants : [];
|
|
57
|
+
const participantHandles = rawParticipants
|
|
58
|
+
.map((p) => {
|
|
59
|
+
const rec = asRecord(p);
|
|
60
|
+
const addr = readString(rec, "address") ?? readString(rec, "id");
|
|
61
|
+
return addr ? normalizeHandle(addr) : "";
|
|
62
|
+
})
|
|
63
|
+
.filter(Boolean);
|
|
55
64
|
return {
|
|
56
65
|
chatGuid: chatGuid?.trim() || undefined,
|
|
57
66
|
chatIdentifier: chatIdentifier?.trim() || undefined,
|
|
@@ -61,6 +70,7 @@ function buildChatRef(data, threadOriginatorGuid) {
|
|
|
61
70
|
sendTarget: chatGuid?.trim()
|
|
62
71
|
? { kind: "chat_guid", value: chatGuid.trim() }
|
|
63
72
|
: { kind: "chat_identifier", value: (chatIdentifier ?? "unknown").trim() },
|
|
73
|
+
participantHandles,
|
|
64
74
|
};
|
|
65
75
|
}
|
|
66
76
|
function extractSender(data, chat) {
|
|
@@ -47,6 +47,9 @@ const context_1 = require("../mind/context");
|
|
|
47
47
|
const tokens_1 = require("../mind/friends/tokens");
|
|
48
48
|
const resolver_1 = require("../mind/friends/resolver");
|
|
49
49
|
const store_file_1 = require("../mind/friends/store-file");
|
|
50
|
+
const types_1 = require("../mind/friends/types");
|
|
51
|
+
const channel_1 = require("../mind/friends/channel");
|
|
52
|
+
const pending_1 = require("../mind/pending");
|
|
50
53
|
const prompt_1 = require("../mind/prompt");
|
|
51
54
|
const phrases_1 = require("../mind/phrases");
|
|
52
55
|
const runtime_1 = require("../nerves/runtime");
|
|
@@ -55,6 +58,8 @@ const bluebubbles_client_1 = require("./bluebubbles-client");
|
|
|
55
58
|
const bluebubbles_mutation_log_1 = require("./bluebubbles-mutation-log");
|
|
56
59
|
const bluebubbles_session_cleanup_1 = require("./bluebubbles-session-cleanup");
|
|
57
60
|
const debug_activity_1 = require("./debug-activity");
|
|
61
|
+
const trust_gate_1 = require("./trust-gate");
|
|
62
|
+
const pipeline_1 = require("./pipeline");
|
|
58
63
|
const defaultDeps = {
|
|
59
64
|
getAgentName: identity_1.getAgentName,
|
|
60
65
|
buildSystem: prompt_1.buildSystem,
|
|
@@ -86,6 +91,47 @@ function resolveFriendParams(event) {
|
|
|
86
91
|
channel: "bluebubbles",
|
|
87
92
|
};
|
|
88
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Check if any participant in a group chat is a known family member.
|
|
96
|
+
* Looks up each participant handle in the friend store.
|
|
97
|
+
*/
|
|
98
|
+
async function checkGroupHasFamilyMember(store, event) {
|
|
99
|
+
if (!event.chat.isGroup)
|
|
100
|
+
return false;
|
|
101
|
+
for (const handle of event.chat.participantHandles ?? []) {
|
|
102
|
+
const friend = await store.findByExternalId("imessage-handle", handle);
|
|
103
|
+
if (friend?.trustLevel === "family")
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if an acquaintance shares any group chat with a family member.
|
|
110
|
+
* Compares group-prefixed externalIds between the acquaintance and all family members.
|
|
111
|
+
*/
|
|
112
|
+
async function checkHasExistingGroupWithFamily(store, senderFriend) {
|
|
113
|
+
const trustLevel = senderFriend.trustLevel ?? "friend";
|
|
114
|
+
if (trustLevel !== "acquaintance")
|
|
115
|
+
return false;
|
|
116
|
+
const acquaintanceGroups = new Set((senderFriend.externalIds ?? [])
|
|
117
|
+
.filter((eid) => eid.externalId.startsWith("group:"))
|
|
118
|
+
.map((eid) => eid.externalId));
|
|
119
|
+
if (acquaintanceGroups.size === 0)
|
|
120
|
+
return false;
|
|
121
|
+
const allFriends = await (store.listAll?.() ?? Promise.resolve([]));
|
|
122
|
+
for (const friend of allFriends) {
|
|
123
|
+
if (friend.trustLevel !== "family")
|
|
124
|
+
continue;
|
|
125
|
+
const friendGroups = (friend.externalIds ?? [])
|
|
126
|
+
.filter((eid) => eid.externalId.startsWith("group:"))
|
|
127
|
+
.map((eid) => eid.externalId);
|
|
128
|
+
for (const group of friendGroups) {
|
|
129
|
+
if (acquaintanceGroups.has(group))
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
89
135
|
function extractMessageText(content) {
|
|
90
136
|
if (typeof content === "string")
|
|
91
137
|
return content;
|
|
@@ -414,28 +460,12 @@ async function handleBlueBubblesEvent(payload, deps = {}) {
|
|
|
414
460
|
});
|
|
415
461
|
return { handled: true, notifiedAgent: false, kind: event.kind, reason: "mutation_state_only" };
|
|
416
462
|
}
|
|
463
|
+
// ── Adapter setup: friend, session, content, callbacks ──────────
|
|
417
464
|
const store = resolvedDeps.createFriendStore();
|
|
418
465
|
const resolver = resolvedDeps.createFriendResolver(store, resolveFriendParams(event));
|
|
419
|
-
const
|
|
466
|
+
const baseContext = await resolver.resolve();
|
|
467
|
+
const context = { ...baseContext, isGroupChat: event.chat.isGroup };
|
|
420
468
|
const replyTarget = createReplyTargetController(event);
|
|
421
|
-
const toolContext = {
|
|
422
|
-
signin: async () => undefined,
|
|
423
|
-
friendStore: store,
|
|
424
|
-
summarize: (0, core_1.createSummarize)(),
|
|
425
|
-
context,
|
|
426
|
-
bluebubblesReplyTarget: {
|
|
427
|
-
setSelection: (selection) => replyTarget.setSelection(selection),
|
|
428
|
-
},
|
|
429
|
-
codingFeedback: {
|
|
430
|
-
send: async (message) => {
|
|
431
|
-
await client.sendText({
|
|
432
|
-
chat: event.chat,
|
|
433
|
-
text: message,
|
|
434
|
-
replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
|
|
435
|
-
});
|
|
436
|
-
},
|
|
437
|
-
},
|
|
438
|
-
};
|
|
439
469
|
const friendId = context.friend.id;
|
|
440
470
|
const sessPath = resolvedDeps.sessionPath(friendId, "bluebubbles", event.chat.sessionKey);
|
|
441
471
|
try {
|
|
@@ -453,21 +483,87 @@ async function handleBlueBubblesEvent(payload, deps = {}) {
|
|
|
453
483
|
},
|
|
454
484
|
});
|
|
455
485
|
}
|
|
486
|
+
// Pre-load session (adapter needs existing messages for lane history in content building)
|
|
456
487
|
const existing = resolvedDeps.loadSession(sessPath);
|
|
457
|
-
const
|
|
488
|
+
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
458
489
|
? existing.messages
|
|
459
490
|
: [{ role: "system", content: await resolvedDeps.buildSystem("bluebubbles", undefined, context) }];
|
|
460
|
-
|
|
491
|
+
// Build inbound user message (adapter concern: BB-specific content formatting)
|
|
492
|
+
const userMessage = {
|
|
493
|
+
role: "user",
|
|
494
|
+
content: buildInboundContent(event, existing?.messages ?? sessionMessages),
|
|
495
|
+
};
|
|
461
496
|
const callbacks = createBlueBubblesCallbacks(client, event.chat, replyTarget);
|
|
462
497
|
const controller = new AbortController();
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
498
|
+
// BB-specific tool context wrappers
|
|
499
|
+
const summarize = (0, core_1.createSummarize)();
|
|
500
|
+
const bbCapabilities = (0, channel_1.getChannelCapabilities)("bluebubbles");
|
|
501
|
+
const pendingDir = (0, pending_1.getPendingDir)(resolvedDeps.getAgentName(), friendId, "bluebubbles", event.chat.sessionKey);
|
|
502
|
+
// ── Compute trust gate context for group/acquaintance rules ─────
|
|
503
|
+
const groupHasFamilyMember = await checkGroupHasFamilyMember(store, event);
|
|
504
|
+
const hasExistingGroupWithFamily = event.chat.isGroup
|
|
505
|
+
? false
|
|
506
|
+
: await checkHasExistingGroupWithFamily(store, context.friend);
|
|
507
|
+
// ── Call shared pipeline ──────────────────────────────────────────
|
|
466
508
|
try {
|
|
467
|
-
const result = await
|
|
509
|
+
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
510
|
+
channel: "bluebubbles",
|
|
511
|
+
capabilities: bbCapabilities,
|
|
512
|
+
messages: [userMessage],
|
|
513
|
+
callbacks,
|
|
514
|
+
friendResolver: { resolve: () => Promise.resolve(context) },
|
|
515
|
+
sessionLoader: { loadOrCreate: () => Promise.resolve({ messages: sessionMessages, sessionPath: sessPath }) },
|
|
516
|
+
pendingDir,
|
|
517
|
+
friendStore: store,
|
|
518
|
+
provider: "imessage-handle",
|
|
519
|
+
externalId: event.sender.externalId || event.sender.rawId,
|
|
520
|
+
isGroupChat: event.chat.isGroup,
|
|
521
|
+
groupHasFamilyMember,
|
|
522
|
+
hasExistingGroupWithFamily,
|
|
523
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
524
|
+
drainPending: pending_1.drainPending,
|
|
525
|
+
runAgent: (msgs, cb, channel, sig, opts) => resolvedDeps.runAgent(msgs, cb, channel, sig, {
|
|
526
|
+
...opts,
|
|
527
|
+
toolContext: {
|
|
528
|
+
/* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
|
|
529
|
+
signin: async () => undefined,
|
|
530
|
+
...opts?.toolContext,
|
|
531
|
+
summarize,
|
|
532
|
+
bluebubblesReplyTarget: {
|
|
533
|
+
setSelection: (selection) => replyTarget.setSelection(selection),
|
|
534
|
+
},
|
|
535
|
+
codingFeedback: {
|
|
536
|
+
send: async (message) => {
|
|
537
|
+
await client.sendText({
|
|
538
|
+
chat: event.chat,
|
|
539
|
+
text: message,
|
|
540
|
+
replyToMessageGuid: replyTarget.getReplyToMessageGuid(),
|
|
541
|
+
});
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
},
|
|
545
|
+
}),
|
|
546
|
+
postTurn: resolvedDeps.postTurn,
|
|
547
|
+
accumulateFriendTokens: resolvedDeps.accumulateFriendTokens,
|
|
548
|
+
signal: controller.signal,
|
|
549
|
+
});
|
|
550
|
+
// ── Handle gate result ────────────────────────────────────────
|
|
551
|
+
if (!result.gateResult.allowed) {
|
|
552
|
+
// Send auto-reply via BB API if the gate provides one
|
|
553
|
+
if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
|
|
554
|
+
await client.sendText({
|
|
555
|
+
chat: event.chat,
|
|
556
|
+
text: result.gateResult.autoReply,
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
return {
|
|
560
|
+
handled: true,
|
|
561
|
+
notifiedAgent: false,
|
|
562
|
+
kind: event.kind,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
// Gate allowed — flush the agent's reply
|
|
468
566
|
await callbacks.flush();
|
|
469
|
-
resolvedDeps.postTurn(messages, sessPath, result.usage);
|
|
470
|
-
await resolvedDeps.accumulateFriendTokens(store, friendId, result.usage);
|
|
471
567
|
(0, runtime_1.emitNervesEvent)({
|
|
472
568
|
component: "senses",
|
|
473
569
|
event: "senses.bluebubbles_turn_end",
|
|
@@ -543,7 +639,6 @@ function createBlueBubblesWebhookHandler(deps = {}) {
|
|
|
543
639
|
}
|
|
544
640
|
};
|
|
545
641
|
}
|
|
546
|
-
const PROACTIVE_SEND_ALLOWED_TRUST = new Set(["family", "friend"]);
|
|
547
642
|
function findImessageHandle(friend) {
|
|
548
643
|
for (const ext of friend.externalIds) {
|
|
549
644
|
if (ext.provider === "imessage-handle" && !ext.externalId.startsWith("group:")) {
|
|
@@ -644,7 +739,7 @@ async function drainAndSendPendingBlueBubbles(deps = {}, pendingRoot) {
|
|
|
644
739
|
});
|
|
645
740
|
continue;
|
|
646
741
|
}
|
|
647
|
-
if (!
|
|
742
|
+
if (!types_1.TRUSTED_LEVELS.has(friend.trustLevel ?? "stranger")) {
|
|
648
743
|
result.skipped++;
|
|
649
744
|
try {
|
|
650
745
|
fs.unlinkSync(filePath);
|
|
@@ -679,6 +774,7 @@ async function drainAndSendPendingBlueBubbles(deps = {}, pendingRoot) {
|
|
|
679
774
|
isGroup: false,
|
|
680
775
|
sessionKey: friendId,
|
|
681
776
|
sendTarget: { kind: "chat_identifier", value: handle },
|
|
777
|
+
participantHandles: [],
|
|
682
778
|
};
|
|
683
779
|
try {
|
|
684
780
|
await client.sendText({ chat, text: messageText });
|
package/dist/senses/cli.js
CHANGED
|
@@ -52,7 +52,6 @@ const format_1 = require("../mind/format");
|
|
|
52
52
|
const config_1 = require("../heart/config");
|
|
53
53
|
const context_1 = require("../mind/context");
|
|
54
54
|
const pending_1 = require("../mind/pending");
|
|
55
|
-
const prompt_refresh_1 = require("../mind/prompt-refresh");
|
|
56
55
|
const commands_1 = require("./commands");
|
|
57
56
|
const identity_1 = require("../heart/identity");
|
|
58
57
|
const nerves_1 = require("../nerves");
|
|
@@ -62,6 +61,8 @@ const tokens_1 = require("../mind/friends/tokens");
|
|
|
62
61
|
const cli_logging_1 = require("../nerves/cli-logging");
|
|
63
62
|
const runtime_1 = require("../nerves/runtime");
|
|
64
63
|
const trust_gate_1 = require("./trust-gate");
|
|
64
|
+
const pipeline_1 = require("./pipeline");
|
|
65
|
+
const channel_1 = require("../mind/friends/channel");
|
|
65
66
|
const session_lock_1 = require("./session-lock");
|
|
66
67
|
const update_hooks_1 = require("../heart/daemon/update-hooks");
|
|
67
68
|
const bundle_meta_1 = require("../heart/daemon/hooks/bundle-meta");
|
|
@@ -620,21 +621,29 @@ async function runCliSession(options) {
|
|
|
620
621
|
echoRows += Math.ceil((2 + line.length) / cols);
|
|
621
622
|
}
|
|
622
623
|
process.stdout.write(`\x1b[${echoRows}A\x1b[K` + `\x1b[1m> ${inputLines[0]}${inputLines.length > 1 ? ` (+${inputLines.length - 1} lines)` : ""}\x1b[0m\n\n`);
|
|
623
|
-
const prefix = options.getContentPrefix?.();
|
|
624
|
-
messages.push({ role: "user", content: prefix ? `${prefix}\n\n${input}` : input });
|
|
625
624
|
addHistory(history, input);
|
|
626
625
|
currentAbort = new AbortController();
|
|
627
|
-
const traceId = (0, nerves_1.createTraceId)();
|
|
628
626
|
ctrl.suppress(() => currentAbort.abort());
|
|
629
627
|
let result;
|
|
630
628
|
try {
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
629
|
+
if (options.runTurn) {
|
|
630
|
+
// Pipeline-based turn: the runTurn callback handles user message assembly,
|
|
631
|
+
// pending drain, trust gate, runAgent, postTurn, and token accumulation.
|
|
632
|
+
result = await options.runTurn(messages, input, cliCallbacks, currentAbort.signal);
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
// Legacy path: inline runAgent (used by adoption specialist and tests)
|
|
636
|
+
const prefix = options.getContentPrefix?.();
|
|
637
|
+
messages.push({ role: "user", content: prefix ? `${prefix}\n\n${input}` : input });
|
|
638
|
+
const traceId = (0, nerves_1.createTraceId)();
|
|
639
|
+
result = await (0, core_1.runAgent)(messages, cliCallbacks, options.skipSystemPromptRefresh ? undefined : "cli", currentAbort.signal, {
|
|
640
|
+
toolChoiceRequired: getEffectiveToolChoiceRequired(),
|
|
641
|
+
traceId,
|
|
642
|
+
tools: options.tools,
|
|
643
|
+
execTool: wrappedExecTool,
|
|
644
|
+
toolContext: options.toolContext,
|
|
645
|
+
});
|
|
646
|
+
}
|
|
638
647
|
}
|
|
639
648
|
catch (err) {
|
|
640
649
|
// AbortError (Ctrl-C) -- silently return to prompt
|
|
@@ -698,13 +707,6 @@ async function main(agentName, options) {
|
|
|
698
707
|
channel: "cli",
|
|
699
708
|
});
|
|
700
709
|
const resolvedContext = await resolver.resolve();
|
|
701
|
-
const cliToolContext = {
|
|
702
|
-
/* v8 ignore next -- CLI has no OAuth sign-in; this no-op satisfies the interface @preserve */
|
|
703
|
-
signin: async () => undefined,
|
|
704
|
-
context: resolvedContext,
|
|
705
|
-
friendStore,
|
|
706
|
-
summarize: (0, core_1.createSummarize)(),
|
|
707
|
-
};
|
|
708
710
|
const friendId = resolvedContext.friend.id;
|
|
709
711
|
const agentConfig = (0, identity_1.loadAgentConfig)();
|
|
710
712
|
(0, cli_logging_1.configureCliRuntimeLogger)(friendId, {
|
|
@@ -730,52 +732,55 @@ async function main(agentName, options) {
|
|
|
730
732
|
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
731
733
|
? existing.messages
|
|
732
734
|
: [{ role: "system", content: await (0, prompt_1.buildSystem)("cli", undefined, resolvedContext) }];
|
|
733
|
-
//
|
|
735
|
+
// Per-turn pipeline input: CLI capabilities and pending dir
|
|
736
|
+
const cliCapabilities = (0, channel_1.getChannelCapabilities)("cli");
|
|
734
737
|
const pendingDir = (0, pending_1.getPendingDir)((0, identity_1.getAgentName)(), friendId, "cli", "session");
|
|
735
|
-
|
|
736
|
-
const drainToPrefix = () => {
|
|
737
|
-
const pending = (0, pending_1.drainPending)(pendingDir);
|
|
738
|
-
if (pending.length === 0)
|
|
739
|
-
return 0;
|
|
740
|
-
pendingPrefix = formatPendingPrefix(pending, (0, identity_1.getAgentName)());
|
|
741
|
-
return pending.length;
|
|
742
|
-
};
|
|
743
|
-
// Startup drain: collect offline messages as prefix for next user message
|
|
744
|
-
const startupCount = drainToPrefix();
|
|
745
|
-
if (startupCount > 0) {
|
|
746
|
-
(0, context_1.saveSession)(sessPath, sessionMessages);
|
|
747
|
-
}
|
|
738
|
+
const summarize = (0, core_1.createSummarize)();
|
|
748
739
|
try {
|
|
749
740
|
await runCliSession({
|
|
750
741
|
agentName: (0, identity_1.getAgentName)(),
|
|
751
742
|
pasteDebounceMs,
|
|
752
743
|
messages: sessionMessages,
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
744
|
+
runTurn: async (messages, userInput, callbacks, signal) => {
|
|
745
|
+
// Run the full per-turn pipeline: resolve -> gate -> session -> drain -> runAgent -> postTurn -> tokens
|
|
746
|
+
// User message passed via input.messages so the pipeline can prepend pending messages to it.
|
|
747
|
+
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
748
|
+
channel: "cli",
|
|
749
|
+
capabilities: cliCapabilities,
|
|
750
|
+
messages: [{ role: "user", content: userInput }],
|
|
751
|
+
callbacks,
|
|
752
|
+
friendResolver: { resolve: () => Promise.resolve(resolvedContext) },
|
|
753
|
+
sessionLoader: { loadOrCreate: () => Promise.resolve({ messages, sessionPath: sessPath }) },
|
|
754
|
+
pendingDir,
|
|
755
|
+
friendStore,
|
|
757
756
|
provider: "local",
|
|
758
757
|
externalId: localExternalId,
|
|
759
|
-
|
|
758
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
759
|
+
drainPending: pending_1.drainPending,
|
|
760
|
+
runAgent: (msgs, cb, channel, sig, opts) => (0, core_1.runAgent)(msgs, cb, channel, sig, {
|
|
761
|
+
...opts,
|
|
762
|
+
toolContext: {
|
|
763
|
+
/* v8 ignore next -- default no-op signin; pipeline provides the real one @preserve */
|
|
764
|
+
signin: async () => undefined,
|
|
765
|
+
...opts?.toolContext,
|
|
766
|
+
summarize,
|
|
767
|
+
},
|
|
768
|
+
}),
|
|
769
|
+
postTurn: context_1.postTurn,
|
|
770
|
+
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
771
|
+
signal,
|
|
772
|
+
runAgentOptions: {
|
|
773
|
+
toolChoiceRequired: (0, commands_1.getToolChoiceRequired)(),
|
|
774
|
+
traceId: (0, nerves_1.createTraceId)(),
|
|
775
|
+
},
|
|
760
776
|
});
|
|
761
|
-
if
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
}
|
|
777
|
+
// Handle gate rejection: display auto-reply if present
|
|
778
|
+
if (!result.gateResult.allowed) {
|
|
779
|
+
if ("autoReply" in result.gateResult && result.gateResult.autoReply) {
|
|
780
|
+
process.stdout.write(`${result.gateResult.autoReply}\n`);
|
|
781
|
+
}
|
|
766
782
|
}
|
|
767
|
-
return {
|
|
768
|
-
},
|
|
769
|
-
getContentPrefix: () => {
|
|
770
|
-
const prefix = pendingPrefix;
|
|
771
|
-
pendingPrefix = undefined;
|
|
772
|
-
return prefix;
|
|
773
|
-
},
|
|
774
|
-
onTurnEnd: async (msgs, result) => {
|
|
775
|
-
(0, context_1.postTurn)(msgs, sessPath, result.usage);
|
|
776
|
-
await (0, tokens_1.accumulateFriendTokens)(friendStore, resolvedContext.friend.id, result.usage);
|
|
777
|
-
drainToPrefix();
|
|
778
|
-
await (0, prompt_refresh_1.refreshSystemPrompt)(msgs, "cli", undefined, resolvedContext);
|
|
783
|
+
return { usage: result.usage };
|
|
779
784
|
},
|
|
780
785
|
onNewSession: () => {
|
|
781
786
|
(0, context_1.deleteSession)(sessPath);
|
|
@@ -49,6 +49,10 @@ const context_1 = require("../mind/context");
|
|
|
49
49
|
const prompt_1 = require("../mind/prompt");
|
|
50
50
|
const bundle_manifest_1 = require("../mind/bundle-manifest");
|
|
51
51
|
const pending_1 = require("../mind/pending");
|
|
52
|
+
const channel_1 = require("../mind/friends/channel");
|
|
53
|
+
const trust_gate_1 = require("./trust-gate");
|
|
54
|
+
const tokens_1 = require("../mind/friends/tokens");
|
|
55
|
+
const pipeline_1 = require("./pipeline");
|
|
52
56
|
const nerves_1 = require("../nerves");
|
|
53
57
|
const runtime_1 = require("../nerves/runtime");
|
|
54
58
|
const DEFAULT_INNER_DIALOG_INSTINCTS = [
|
|
@@ -148,77 +152,116 @@ function createInnerDialogCallbacks() {
|
|
|
148
152
|
};
|
|
149
153
|
}
|
|
150
154
|
function innerDialogSessionPath() {
|
|
151
|
-
return (0, config_1.sessionPath)(
|
|
155
|
+
return (0, config_1.sessionPath)(pending_1.INNER_DIALOG_PENDING.friendId, pending_1.INNER_DIALOG_PENDING.channel, pending_1.INNER_DIALOG_PENDING.key);
|
|
156
|
+
}
|
|
157
|
+
// Self-referencing friend record for inner dialog (agent talking to itself).
|
|
158
|
+
// No real friend to resolve -- this satisfies the pipeline's friend resolver contract.
|
|
159
|
+
function createSelfFriend(agentName) {
|
|
160
|
+
return {
|
|
161
|
+
id: "self",
|
|
162
|
+
name: agentName,
|
|
163
|
+
trustLevel: "family",
|
|
164
|
+
externalIds: [],
|
|
165
|
+
tenantMemberships: [],
|
|
166
|
+
toolPreferences: {},
|
|
167
|
+
notes: {},
|
|
168
|
+
totalTokens: 0,
|
|
169
|
+
createdAt: new Date().toISOString(),
|
|
170
|
+
updatedAt: new Date().toISOString(),
|
|
171
|
+
schemaVersion: 1,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
// No-op friend store for inner dialog. Inner dialog doesn't track token usage per-friend.
|
|
175
|
+
function createNoOpFriendStore() {
|
|
176
|
+
return {
|
|
177
|
+
get: async () => null,
|
|
178
|
+
put: async () => { },
|
|
179
|
+
delete: async () => { },
|
|
180
|
+
findByExternalId: async () => null,
|
|
181
|
+
};
|
|
152
182
|
}
|
|
153
183
|
async function runInnerDialogTurn(options) {
|
|
154
184
|
const now = options?.now ?? (() => new Date());
|
|
155
185
|
const reason = options?.reason ?? "heartbeat";
|
|
156
186
|
const sessionFilePath = innerDialogSessionPath();
|
|
157
187
|
const loaded = (0, context_1.loadSession)(sessionFilePath);
|
|
158
|
-
const
|
|
188
|
+
const existingMessages = loaded?.messages ? [...loaded.messages] : [];
|
|
159
189
|
const instincts = options?.instincts ?? loadInnerDialogInstincts();
|
|
160
190
|
const state = {
|
|
161
191
|
cycleCount: 1,
|
|
162
192
|
resting: false,
|
|
163
193
|
lastHeartbeatAt: now().toISOString(),
|
|
164
194
|
};
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
195
|
+
// ── Adapter concern: build user message ──────────────────────────
|
|
196
|
+
let userContent;
|
|
197
|
+
if (existingMessages.length === 0) {
|
|
198
|
+
// Fresh session: bootstrap message with non-canonical cleanup nudge
|
|
168
199
|
const aspirations = readAspirations((0, identity_1.getAgentRoot)());
|
|
169
200
|
const nonCanonical = (0, bundle_manifest_1.findNonCanonicalBundlePaths)((0, identity_1.getAgentRoot)());
|
|
170
201
|
const cleanupNudge = buildNonCanonicalCleanupNudge(nonCanonical);
|
|
171
|
-
|
|
202
|
+
userContent = [
|
|
172
203
|
buildInnerDialogBootstrapMessage(aspirations, "No prior inner dialog session found."),
|
|
173
204
|
cleanupNudge,
|
|
174
205
|
].filter(Boolean).join("\n\n");
|
|
175
|
-
messages.push({ role: "user", content: bootstrapMessage });
|
|
176
206
|
}
|
|
177
207
|
else {
|
|
178
|
-
|
|
208
|
+
// Resumed session: instinct message with checkpoint context
|
|
209
|
+
const assistantTurns = existingMessages.filter((message) => message.role === "assistant").length;
|
|
179
210
|
state.cycleCount = assistantTurns + 1;
|
|
180
|
-
state.checkpoint = deriveResumeCheckpoint(
|
|
181
|
-
|
|
182
|
-
messages.push({ role: "user", content: instinctPrompt });
|
|
183
|
-
}
|
|
184
|
-
const pendingMessages = (0, pending_1.drainPending)((0, pending_1.getPendingDir)((0, identity_1.getAgentName)(), "self", "inner", "dialog"));
|
|
185
|
-
if (pendingMessages.length > 0) {
|
|
186
|
-
const lastUserIdx = messages.length - 1;
|
|
187
|
-
const lastUser = messages[lastUserIdx];
|
|
188
|
-
/* v8 ignore next -- defensive: all code paths push a user message before here @preserve */
|
|
189
|
-
if (lastUser?.role === "user" && typeof lastUser.content === "string") {
|
|
190
|
-
const section = pendingMessages
|
|
191
|
-
.map((msg) => `- **${msg.from}**: ${msg.content}`)
|
|
192
|
-
.join("\n");
|
|
193
|
-
messages[lastUserIdx] = {
|
|
194
|
-
...lastUser,
|
|
195
|
-
content: `${lastUser.content}\n\n## pending messages\n${section}`,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
211
|
+
state.checkpoint = deriveResumeCheckpoint(existingMessages);
|
|
212
|
+
userContent = buildInstinctUserMessage(instincts, reason, state);
|
|
198
213
|
}
|
|
214
|
+
// ── Adapter concern: inbox drain (inner-dialog-specific) ─────────
|
|
199
215
|
const inboxMessages = options?.drainInbox?.() ?? [];
|
|
200
216
|
if (inboxMessages.length > 0) {
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
const section = inboxMessages
|
|
206
|
-
.map((msg) => `- **${msg.from}**: ${msg.content}`)
|
|
207
|
-
.join("\n");
|
|
208
|
-
messages[lastUserIdx] = {
|
|
209
|
-
...lastUser,
|
|
210
|
-
content: `${lastUser.content}\n\n## incoming messages\n${section}`,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
217
|
+
const section = inboxMessages
|
|
218
|
+
.map((msg) => `- **${msg.from}**: ${msg.content}`)
|
|
219
|
+
.join("\n");
|
|
220
|
+
userContent = `${userContent}\n\n## incoming messages\n${section}`;
|
|
213
221
|
}
|
|
222
|
+
const userMessage = { role: "user", content: userContent };
|
|
223
|
+
// ── Session loader: wraps existing session logic ──────────────────
|
|
224
|
+
const innerCapabilities = (0, channel_1.getChannelCapabilities)("inner");
|
|
225
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
|
|
226
|
+
const selfFriend = createSelfFriend((0, identity_1.getAgentName)());
|
|
227
|
+
const selfContext = { friend: selfFriend, channel: innerCapabilities };
|
|
228
|
+
const sessionLoader = {
|
|
229
|
+
loadOrCreate: async () => {
|
|
230
|
+
if (existingMessages.length > 0) {
|
|
231
|
+
return { messages: existingMessages, sessionPath: sessionFilePath };
|
|
232
|
+
}
|
|
233
|
+
// Fresh session: build system prompt
|
|
234
|
+
const systemPrompt = await (0, prompt_1.buildSystem)("inner", { toolChoiceRequired: true });
|
|
235
|
+
return {
|
|
236
|
+
messages: [{ role: "system", content: systemPrompt }],
|
|
237
|
+
sessionPath: sessionFilePath,
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
// ── Call shared pipeline ──────────────────────────────────────────
|
|
214
242
|
const callbacks = createInnerDialogCallbacks();
|
|
215
243
|
const traceId = (0, nerves_1.createTraceId)();
|
|
216
|
-
const result = await (0,
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
244
|
+
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
245
|
+
channel: "inner",
|
|
246
|
+
capabilities: innerCapabilities,
|
|
247
|
+
messages: [userMessage],
|
|
248
|
+
callbacks,
|
|
249
|
+
friendResolver: { resolve: () => Promise.resolve(selfContext) },
|
|
250
|
+
sessionLoader,
|
|
251
|
+
pendingDir,
|
|
252
|
+
friendStore: createNoOpFriendStore(),
|
|
253
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
254
|
+
drainPending: pending_1.drainPending,
|
|
255
|
+
runAgent: core_1.runAgent,
|
|
256
|
+
postTurn: context_1.postTurn,
|
|
257
|
+
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
258
|
+
signal: options?.signal,
|
|
259
|
+
runAgentOptions: {
|
|
260
|
+
traceId,
|
|
261
|
+
toolChoiceRequired: true,
|
|
262
|
+
skipConfirmation: true,
|
|
263
|
+
},
|
|
220
264
|
});
|
|
221
|
-
(0, context_1.postTurn)(messages, sessionFilePath, result.usage);
|
|
222
265
|
(0, runtime_1.emitNervesEvent)({
|
|
223
266
|
component: "senses",
|
|
224
267
|
event: "senses.inner_dialog_turn",
|
|
@@ -226,8 +269,8 @@ async function runInnerDialogTurn(options) {
|
|
|
226
269
|
meta: { reason, session: sessionFilePath },
|
|
227
270
|
});
|
|
228
271
|
return {
|
|
229
|
-
messages,
|
|
272
|
+
messages: result.messages ?? [],
|
|
230
273
|
usage: result.usage,
|
|
231
|
-
sessionPath: sessionFilePath,
|
|
274
|
+
sessionPath: result.sessionPath ?? sessionFilePath,
|
|
232
275
|
};
|
|
233
276
|
}
|