@ouro.bot/cli 0.1.0-alpha.37 → 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 +22 -0
- package/dist/heart/daemon/daemon-cli.js +444 -32
- package/dist/heart/daemon/daemon.js +90 -0
- package/dist/heart/daemon/specialist-prompt.js +2 -1
- package/dist/heart/daemon/specialist-tools.js +48 -2
- package/dist/heart/kicks.js +1 -1
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/pending.js +8 -0
- package/dist/mind/prompt.js +126 -2
- package/dist/repertoire/tools-base.js +193 -271
- package/dist/repertoire/tools.js +18 -41
- package/dist/senses/bluebubbles-model.js +10 -0
- package/dist/senses/bluebubbles.js +301 -27
- package/dist/senses/cli.js +73 -50
- package/dist/senses/inner-dialog.js +99 -54
- package/dist/senses/pipeline.js +124 -0
- package/dist/senses/teams.js +264 -63
- package/dist/senses/trust-gate.js +113 -2
- package/package.json +2 -1
package/dist/senses/cli.js
CHANGED
|
@@ -34,6 +34,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.MarkdownStreamer = exports.InputController = exports.Spinner = void 0;
|
|
37
|
+
exports.formatPendingPrefix = formatPendingPrefix;
|
|
37
38
|
exports.handleSigint = handleSigint;
|
|
38
39
|
exports.addHistory = addHistory;
|
|
39
40
|
exports.renderMarkdown = renderMarkdown;
|
|
@@ -51,7 +52,6 @@ const format_1 = require("../mind/format");
|
|
|
51
52
|
const config_1 = require("../heart/config");
|
|
52
53
|
const context_1 = require("../mind/context");
|
|
53
54
|
const pending_1 = require("../mind/pending");
|
|
54
|
-
const prompt_refresh_1 = require("../mind/prompt-refresh");
|
|
55
55
|
const commands_1 = require("./commands");
|
|
56
56
|
const identity_1 = require("../heart/identity");
|
|
57
57
|
const nerves_1 = require("../nerves");
|
|
@@ -61,10 +61,25 @@ const tokens_1 = require("../mind/friends/tokens");
|
|
|
61
61
|
const cli_logging_1 = require("../nerves/cli-logging");
|
|
62
62
|
const runtime_1 = require("../nerves/runtime");
|
|
63
63
|
const trust_gate_1 = require("./trust-gate");
|
|
64
|
+
const pipeline_1 = require("./pipeline");
|
|
65
|
+
const channel_1 = require("../mind/friends/channel");
|
|
64
66
|
const session_lock_1 = require("./session-lock");
|
|
65
67
|
const update_hooks_1 = require("../heart/daemon/update-hooks");
|
|
66
68
|
const bundle_meta_1 = require("../heart/daemon/hooks/bundle-meta");
|
|
67
69
|
const bundle_manifest_1 = require("../mind/bundle-manifest");
|
|
70
|
+
/**
|
|
71
|
+
* Format pending messages as content-prefix strings for injection into
|
|
72
|
+
* the next user message. Self-messages (from === agentName) become
|
|
73
|
+
* `[inner thought: {content}]`, inter-agent messages become
|
|
74
|
+
* `[message from {name}: {content}]`.
|
|
75
|
+
*/
|
|
76
|
+
function formatPendingPrefix(messages, agentName) {
|
|
77
|
+
return messages
|
|
78
|
+
.map((msg) => msg.from === agentName
|
|
79
|
+
? `[inner thought: ${msg.content}]`
|
|
80
|
+
: `[message from ${msg.from}: ${msg.content}]`)
|
|
81
|
+
.join("\n");
|
|
82
|
+
}
|
|
68
83
|
// spinner that only touches stderr, cleans up after itself
|
|
69
84
|
// exported for direct testability (stop-without-start branch)
|
|
70
85
|
class Spinner {
|
|
@@ -606,20 +621,29 @@ async function runCliSession(options) {
|
|
|
606
621
|
echoRows += Math.ceil((2 + line.length) / cols);
|
|
607
622
|
}
|
|
608
623
|
process.stdout.write(`\x1b[${echoRows}A\x1b[K` + `\x1b[1m> ${inputLines[0]}${inputLines.length > 1 ? ` (+${inputLines.length - 1} lines)` : ""}\x1b[0m\n\n`);
|
|
609
|
-
messages.push({ role: "user", content: input });
|
|
610
624
|
addHistory(history, input);
|
|
611
625
|
currentAbort = new AbortController();
|
|
612
|
-
const traceId = (0, nerves_1.createTraceId)();
|
|
613
626
|
ctrl.suppress(() => currentAbort.abort());
|
|
614
627
|
let result;
|
|
615
628
|
try {
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
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
|
+
}
|
|
623
647
|
}
|
|
624
648
|
catch (err) {
|
|
625
649
|
// AbortError (Ctrl-C) -- silently return to prompt
|
|
@@ -683,13 +707,6 @@ async function main(agentName, options) {
|
|
|
683
707
|
channel: "cli",
|
|
684
708
|
});
|
|
685
709
|
const resolvedContext = await resolver.resolve();
|
|
686
|
-
const cliToolContext = {
|
|
687
|
-
/* v8 ignore next -- CLI has no OAuth sign-in; this no-op satisfies the interface @preserve */
|
|
688
|
-
signin: async () => undefined,
|
|
689
|
-
context: resolvedContext,
|
|
690
|
-
friendStore,
|
|
691
|
-
summarize: (0, core_1.createSummarize)(),
|
|
692
|
-
};
|
|
693
710
|
const friendId = resolvedContext.friend.id;
|
|
694
711
|
const agentConfig = (0, identity_1.loadAgentConfig)();
|
|
695
712
|
(0, cli_logging_1.configureCliRuntimeLogger)(friendId, {
|
|
@@ -715,49 +732,55 @@ async function main(agentName, options) {
|
|
|
715
732
|
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
716
733
|
? existing.messages
|
|
717
734
|
: [{ role: "system", content: await (0, prompt_1.buildSystem)("cli", undefined, resolvedContext) }];
|
|
718
|
-
//
|
|
735
|
+
// Per-turn pipeline input: CLI capabilities and pending dir
|
|
736
|
+
const cliCapabilities = (0, channel_1.getChannelCapabilities)("cli");
|
|
719
737
|
const pendingDir = (0, pending_1.getPendingDir)((0, identity_1.getAgentName)(), friendId, "cli", "session");
|
|
720
|
-
const
|
|
721
|
-
const pending = (0, pending_1.drainPending)(pendingDir);
|
|
722
|
-
if (pending.length === 0)
|
|
723
|
-
return 0;
|
|
724
|
-
for (const msg of pending) {
|
|
725
|
-
sessionMessages.push({ role: "user", name: "harness", content: `[proactive message from ${msg.from}]` });
|
|
726
|
-
sessionMessages.push({ role: "assistant", content: msg.content });
|
|
727
|
-
}
|
|
728
|
-
return pending.length;
|
|
729
|
-
};
|
|
730
|
-
// Startup drain: deliver offline messages
|
|
731
|
-
const startupCount = drainToMessages();
|
|
732
|
-
if (startupCount > 0) {
|
|
733
|
-
(0, context_1.saveSession)(sessPath, sessionMessages);
|
|
734
|
-
}
|
|
738
|
+
const summarize = (0, core_1.createSummarize)();
|
|
735
739
|
try {
|
|
736
740
|
await runCliSession({
|
|
737
741
|
agentName: (0, identity_1.getAgentName)(),
|
|
738
742
|
pasteDebounceMs,
|
|
739
743
|
messages: sessionMessages,
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
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,
|
|
744
756
|
provider: "local",
|
|
745
757
|
externalId: localExternalId,
|
|
746
|
-
|
|
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
|
+
},
|
|
747
776
|
});
|
|
748
|
-
if
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
}
|
|
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
|
+
}
|
|
753
782
|
}
|
|
754
|
-
return {
|
|
755
|
-
},
|
|
756
|
-
onTurnEnd: async (msgs, result) => {
|
|
757
|
-
(0, context_1.postTurn)(msgs, sessPath, result.usage);
|
|
758
|
-
await (0, tokens_1.accumulateFriendTokens)(friendStore, resolvedContext.friend.id, result.usage);
|
|
759
|
-
drainToMessages();
|
|
760
|
-
await (0, prompt_refresh_1.refreshSystemPrompt)(msgs, "cli", undefined, resolvedContext);
|
|
783
|
+
return { usage: result.usage };
|
|
761
784
|
},
|
|
762
785
|
onNewSession: () => {
|
|
763
786
|
(0, context_1.deleteSession)(sessPath);
|
|
@@ -48,12 +48,17 @@ const identity_1 = require("../heart/identity");
|
|
|
48
48
|
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
|
+
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");
|
|
51
56
|
const nerves_1 = require("../nerves");
|
|
52
57
|
const runtime_1 = require("../nerves/runtime");
|
|
53
58
|
const DEFAULT_INNER_DIALOG_INSTINCTS = [
|
|
54
59
|
{
|
|
55
60
|
id: "heartbeat_checkin",
|
|
56
|
-
prompt: "
|
|
61
|
+
prompt: "...time passing. anything stirring?",
|
|
57
62
|
enabled: true,
|
|
58
63
|
},
|
|
59
64
|
];
|
|
@@ -68,19 +73,8 @@ function readAspirations(agentRoot) {
|
|
|
68
73
|
function loadInnerDialogInstincts() {
|
|
69
74
|
return [...DEFAULT_INNER_DIALOG_INSTINCTS];
|
|
70
75
|
}
|
|
71
|
-
function buildInnerDialogBootstrapMessage(
|
|
72
|
-
|
|
73
|
-
return [
|
|
74
|
-
"Inner dialog boot.",
|
|
75
|
-
"",
|
|
76
|
-
"## aspirations",
|
|
77
|
-
aspirationText,
|
|
78
|
-
"",
|
|
79
|
-
"## current state",
|
|
80
|
-
stateSummary,
|
|
81
|
-
"",
|
|
82
|
-
"Orient yourself, decide what to do next, and make meaningful progress.",
|
|
83
|
-
].join("\n");
|
|
76
|
+
function buildInnerDialogBootstrapMessage(_aspirations, _stateSummary) {
|
|
77
|
+
return "waking up. settling in.\n\nwhat needs my attention?";
|
|
84
78
|
}
|
|
85
79
|
function buildNonCanonicalCleanupNudge(nonCanonicalPaths) {
|
|
86
80
|
if (nonCanonicalPaths.length === 0)
|
|
@@ -95,17 +89,14 @@ function buildNonCanonicalCleanupNudge(nonCanonicalPaths) {
|
|
|
95
89
|
...listed,
|
|
96
90
|
].join("\n");
|
|
97
91
|
}
|
|
98
|
-
function buildInstinctUserMessage(instincts,
|
|
92
|
+
function buildInstinctUserMessage(instincts, _reason, state) {
|
|
99
93
|
const active = instincts.find((instinct) => instinct.enabled !== false) ?? DEFAULT_INNER_DIALOG_INSTINCTS[0];
|
|
100
|
-
const checkpoint = state.checkpoint?.trim()
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
`checkpoint: ${checkpoint}`,
|
|
107
|
-
"resume_instruction: continue from the checkpoint if still valid; otherwise revise and proceed.",
|
|
108
|
-
].join("\n");
|
|
94
|
+
const checkpoint = state.checkpoint?.trim();
|
|
95
|
+
const lines = [active.prompt];
|
|
96
|
+
if (checkpoint) {
|
|
97
|
+
lines.push(`\nlast i remember: ${checkpoint}`);
|
|
98
|
+
}
|
|
99
|
+
return lines.join("\n");
|
|
109
100
|
}
|
|
110
101
|
function contentToText(content) {
|
|
111
102
|
if (typeof content === "string")
|
|
@@ -161,62 +152,116 @@ function createInnerDialogCallbacks() {
|
|
|
161
152
|
};
|
|
162
153
|
}
|
|
163
154
|
function innerDialogSessionPath() {
|
|
164
|
-
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
|
+
};
|
|
165
182
|
}
|
|
166
183
|
async function runInnerDialogTurn(options) {
|
|
167
184
|
const now = options?.now ?? (() => new Date());
|
|
168
185
|
const reason = options?.reason ?? "heartbeat";
|
|
169
186
|
const sessionFilePath = innerDialogSessionPath();
|
|
170
187
|
const loaded = (0, context_1.loadSession)(sessionFilePath);
|
|
171
|
-
const
|
|
188
|
+
const existingMessages = loaded?.messages ? [...loaded.messages] : [];
|
|
172
189
|
const instincts = options?.instincts ?? loadInnerDialogInstincts();
|
|
173
190
|
const state = {
|
|
174
191
|
cycleCount: 1,
|
|
175
192
|
resting: false,
|
|
176
193
|
lastHeartbeatAt: now().toISOString(),
|
|
177
194
|
};
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
195
|
+
// ── Adapter concern: build user message ──────────────────────────
|
|
196
|
+
let userContent;
|
|
197
|
+
if (existingMessages.length === 0) {
|
|
198
|
+
// Fresh session: bootstrap message with non-canonical cleanup nudge
|
|
181
199
|
const aspirations = readAspirations((0, identity_1.getAgentRoot)());
|
|
182
200
|
const nonCanonical = (0, bundle_manifest_1.findNonCanonicalBundlePaths)((0, identity_1.getAgentRoot)());
|
|
183
201
|
const cleanupNudge = buildNonCanonicalCleanupNudge(nonCanonical);
|
|
184
|
-
|
|
202
|
+
userContent = [
|
|
185
203
|
buildInnerDialogBootstrapMessage(aspirations, "No prior inner dialog session found."),
|
|
186
204
|
cleanupNudge,
|
|
187
205
|
].filter(Boolean).join("\n\n");
|
|
188
|
-
messages.push({ role: "user", content: bootstrapMessage });
|
|
189
206
|
}
|
|
190
207
|
else {
|
|
191
|
-
|
|
208
|
+
// Resumed session: instinct message with checkpoint context
|
|
209
|
+
const assistantTurns = existingMessages.filter((message) => message.role === "assistant").length;
|
|
192
210
|
state.cycleCount = assistantTurns + 1;
|
|
193
|
-
state.checkpoint = deriveResumeCheckpoint(
|
|
194
|
-
|
|
195
|
-
messages.push({ role: "user", content: instinctPrompt });
|
|
211
|
+
state.checkpoint = deriveResumeCheckpoint(existingMessages);
|
|
212
|
+
userContent = buildInstinctUserMessage(instincts, reason, state);
|
|
196
213
|
}
|
|
214
|
+
// ── Adapter concern: inbox drain (inner-dialog-specific) ─────────
|
|
197
215
|
const inboxMessages = options?.drainInbox?.() ?? [];
|
|
198
216
|
if (inboxMessages.length > 0) {
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const section = inboxMessages
|
|
204
|
-
.map((msg) => `- **${msg.from}**: ${msg.content}`)
|
|
205
|
-
.join("\n");
|
|
206
|
-
messages[lastUserIdx] = {
|
|
207
|
-
...lastUser,
|
|
208
|
-
content: `${lastUser.content}\n\n## incoming messages\n${section}`,
|
|
209
|
-
};
|
|
210
|
-
}
|
|
217
|
+
const section = inboxMessages
|
|
218
|
+
.map((msg) => `- **${msg.from}**: ${msg.content}`)
|
|
219
|
+
.join("\n");
|
|
220
|
+
userContent = `${userContent}\n\n## incoming messages\n${section}`;
|
|
211
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 ──────────────────────────────────────────
|
|
212
242
|
const callbacks = createInnerDialogCallbacks();
|
|
213
243
|
const traceId = (0, nerves_1.createTraceId)();
|
|
214
|
-
const result = await (0,
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
+
},
|
|
218
264
|
});
|
|
219
|
-
(0, context_1.postTurn)(messages, sessionFilePath, result.usage);
|
|
220
265
|
(0, runtime_1.emitNervesEvent)({
|
|
221
266
|
component: "senses",
|
|
222
267
|
event: "senses.inner_dialog_turn",
|
|
@@ -224,8 +269,8 @@ async function runInnerDialogTurn(options) {
|
|
|
224
269
|
meta: { reason, session: sessionFilePath },
|
|
225
270
|
});
|
|
226
271
|
return {
|
|
227
|
-
messages,
|
|
272
|
+
messages: result.messages ?? [],
|
|
228
273
|
usage: result.usage,
|
|
229
|
-
sessionPath: sessionFilePath,
|
|
274
|
+
sessionPath: result.sessionPath ?? sessionFilePath,
|
|
230
275
|
};
|
|
231
276
|
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Shared per-turn pipeline for all senses.
|
|
3
|
+
// Senses are thin transport adapters; this module owns the common lifecycle:
|
|
4
|
+
// resolve friend -> trust gate -> load session -> drain pending -> runAgent -> postTurn -> token accumulation.
|
|
5
|
+
//
|
|
6
|
+
// Transport-level concerns (BB API calls, Teams cards, readline) stay in sense adapters.
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.handleInboundTurn = handleInboundTurn;
|
|
9
|
+
const runtime_1 = require("../nerves/runtime");
|
|
10
|
+
// ── Pipeline ──────────────────────────────────────────────────────
|
|
11
|
+
async function handleInboundTurn(input) {
|
|
12
|
+
// Step 1: Resolve friend
|
|
13
|
+
const resolvedContext = await input.friendResolver.resolve();
|
|
14
|
+
(0, runtime_1.emitNervesEvent)({
|
|
15
|
+
component: "senses",
|
|
16
|
+
event: "senses.pipeline_start",
|
|
17
|
+
message: "inbound turn pipeline started",
|
|
18
|
+
meta: {
|
|
19
|
+
channel: input.channel,
|
|
20
|
+
friendId: resolvedContext.friend.id,
|
|
21
|
+
senseType: input.capabilities.senseType,
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
// Step 2: Trust gate
|
|
25
|
+
const gateInput = {
|
|
26
|
+
friend: resolvedContext.friend,
|
|
27
|
+
provider: input.provider ?? "local",
|
|
28
|
+
externalId: input.externalId ?? "",
|
|
29
|
+
tenantId: input.tenantId,
|
|
30
|
+
channel: input.channel,
|
|
31
|
+
senseType: input.capabilities.senseType,
|
|
32
|
+
isGroupChat: input.isGroupChat ?? false,
|
|
33
|
+
groupHasFamilyMember: input.groupHasFamilyMember ?? false,
|
|
34
|
+
hasExistingGroupWithFamily: input.hasExistingGroupWithFamily ?? false,
|
|
35
|
+
};
|
|
36
|
+
const gateResult = input.enforceTrustGate(gateInput);
|
|
37
|
+
// Gate rejection: return early, no agent turn
|
|
38
|
+
if (!gateResult.allowed) {
|
|
39
|
+
(0, runtime_1.emitNervesEvent)({
|
|
40
|
+
component: "senses",
|
|
41
|
+
event: "senses.pipeline_gate_reject",
|
|
42
|
+
message: "trust gate rejected inbound turn",
|
|
43
|
+
meta: {
|
|
44
|
+
channel: input.channel,
|
|
45
|
+
friendId: resolvedContext.friend.id,
|
|
46
|
+
reason: gateResult.reason,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
return {
|
|
50
|
+
resolvedContext,
|
|
51
|
+
gateResult,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// Step 3: Load/create session
|
|
55
|
+
const session = await input.sessionLoader.loadOrCreate();
|
|
56
|
+
const sessionMessages = session.messages;
|
|
57
|
+
// Step 4: Drain pending messages
|
|
58
|
+
const pending = input.drainPending(input.pendingDir);
|
|
59
|
+
// Assemble messages: session messages + pending (formatted) + inbound user messages
|
|
60
|
+
if (pending.length > 0) {
|
|
61
|
+
// Format pending messages and prepend to the user content
|
|
62
|
+
const pendingSection = pending
|
|
63
|
+
.map((msg) => `[pending from ${msg.from}]: ${msg.content}`)
|
|
64
|
+
.join("\n");
|
|
65
|
+
// If there are inbound user messages, prepend pending to the first one
|
|
66
|
+
if (input.messages.length > 0) {
|
|
67
|
+
const firstMsg = input.messages[0];
|
|
68
|
+
if (firstMsg.role === "user") {
|
|
69
|
+
if (typeof firstMsg.content === "string") {
|
|
70
|
+
input.messages[0] = {
|
|
71
|
+
...firstMsg,
|
|
72
|
+
content: `## pending messages\n${pendingSection}\n\n${firstMsg.content}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
input.messages[0] = {
|
|
77
|
+
...firstMsg,
|
|
78
|
+
content: [
|
|
79
|
+
{ type: "text", text: `## pending messages\n${pendingSection}\n\n` },
|
|
80
|
+
...firstMsg.content,
|
|
81
|
+
],
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// Append user messages from the inbound turn
|
|
88
|
+
for (const msg of input.messages) {
|
|
89
|
+
sessionMessages.push(msg);
|
|
90
|
+
}
|
|
91
|
+
// Step 5: runAgent
|
|
92
|
+
const existingToolContext = input.runAgentOptions?.toolContext;
|
|
93
|
+
const runAgentOptions = {
|
|
94
|
+
...input.runAgentOptions,
|
|
95
|
+
toolContext: {
|
|
96
|
+
/* v8 ignore next -- default no-op signin satisfies interface; real signin injected by sense adapter @preserve */
|
|
97
|
+
signin: async () => undefined,
|
|
98
|
+
...existingToolContext,
|
|
99
|
+
context: resolvedContext,
|
|
100
|
+
friendStore: input.friendStore,
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
const result = await input.runAgent(sessionMessages, input.callbacks, input.channel, input.signal, runAgentOptions);
|
|
104
|
+
// Step 6: postTurn
|
|
105
|
+
input.postTurn(sessionMessages, session.sessionPath, result.usage);
|
|
106
|
+
// Step 7: Token accumulation
|
|
107
|
+
await input.accumulateFriendTokens(input.friendStore, resolvedContext.friend.id, result.usage);
|
|
108
|
+
(0, runtime_1.emitNervesEvent)({
|
|
109
|
+
component: "senses",
|
|
110
|
+
event: "senses.pipeline_end",
|
|
111
|
+
message: "inbound turn pipeline completed",
|
|
112
|
+
meta: {
|
|
113
|
+
channel: input.channel,
|
|
114
|
+
friendId: resolvedContext.friend.id,
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
return {
|
|
118
|
+
resolvedContext,
|
|
119
|
+
gateResult,
|
|
120
|
+
usage: result.usage,
|
|
121
|
+
sessionPath: session.sessionPath,
|
|
122
|
+
messages: sessionMessages,
|
|
123
|
+
};
|
|
124
|
+
}
|