@ouro.bot/cli 0.1.0-alpha.4 → 0.1.0-alpha.41
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/AdoptionSpecialist.ouro/agent.json +70 -9
- package/AdoptionSpecialist.ouro/psyche/SOUL.md +5 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +117 -188
- package/assets/ouroboros.png +0 -0
- package/changelog.json +170 -0
- package/dist/heart/config.js +81 -8
- package/dist/heart/core.js +78 -45
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/daemon-cli.js +987 -77
- package/dist/heart/daemon/daemon-entry.js +14 -5
- package/dist/heart/daemon/daemon-runtime-sync.js +90 -0
- package/dist/heart/daemon/daemon.js +177 -9
- package/dist/heart/daemon/hatch-animation.js +35 -0
- package/dist/heart/daemon/hatch-flow.js +4 -20
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +134 -0
- package/dist/heart/daemon/message-router.js +15 -6
- package/dist/heart/daemon/ouro-bot-entry.js +0 -0
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-entry.js +0 -0
- package/dist/heart/daemon/ouro-path-installer.js +178 -0
- package/dist/heart/daemon/ouro-uti.js +11 -2
- package/dist/heart/daemon/process-manager.js +1 -1
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-metadata.js +118 -0
- package/dist/heart/daemon/sense-manager.js +266 -0
- package/dist/heart/daemon/specialist-orchestrator.js +129 -0
- package/dist/heart/daemon/specialist-prompt.js +99 -0
- package/dist/heart/daemon/specialist-tools.js +283 -0
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/subagent-installer.js +10 -1
- package/dist/heart/daemon/update-checker.js +111 -0
- package/dist/heart/daemon/update-hooks.js +138 -0
- package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
- package/dist/heart/identity.js +96 -4
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/providers/anthropic.js +16 -2
- package/dist/heart/sense-truth.js +61 -0
- package/dist/heart/streaming.js +96 -21
- package/dist/mind/bundle-manifest.js +70 -0
- package/dist/mind/context.js +7 -7
- package/dist/mind/first-impressions.js +2 -1
- package/dist/mind/friends/channel.js +43 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/types.js +9 -1
- package/dist/mind/memory.js +10 -3
- package/dist/mind/pending.js +10 -2
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +222 -7
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/feedback.js +134 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +62 -4
- package/dist/repertoire/coding/spawner.js +3 -3
- package/dist/repertoire/coding/tools.js +41 -2
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/tasks/index.js +2 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +202 -219
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +55 -35
- package/dist/senses/bluebubbles-client.js +434 -0
- package/dist/senses/bluebubbles-entry.js +11 -0
- package/dist/senses/bluebubbles-media.js +338 -0
- package/dist/senses/bluebubbles-model.js +261 -0
- package/dist/senses/bluebubbles-mutation-log.js +74 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +832 -0
- package/dist/senses/cli.js +327 -138
- package/dist/senses/debug-activity.js +127 -0
- package/dist/senses/inner-dialog.js +103 -55
- package/dist/senses/pipeline.js +124 -0
- package/dist/senses/teams.js +427 -112
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +14 -3
- package/subagents/README.md +40 -53
- package/subagents/work-doer.md +26 -24
- package/subagents/work-merger.md +24 -30
- package/subagents/work-planner.md +34 -25
- package/dist/inner-worker-entry.js +0 -4
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createDebugActivityController = createDebugActivityController;
|
|
4
|
+
const format_1 = require("../mind/format");
|
|
5
|
+
const phrases_1 = require("../mind/phrases");
|
|
6
|
+
const runtime_1 = require("../nerves/runtime");
|
|
7
|
+
function createDebugActivityController(options) {
|
|
8
|
+
let queue = Promise.resolve();
|
|
9
|
+
let statusMessageGuid;
|
|
10
|
+
let typingActive = false;
|
|
11
|
+
let hadToolRun = false;
|
|
12
|
+
let followupShown = false;
|
|
13
|
+
let lastPhrase = "";
|
|
14
|
+
function reportTransportError(operation, error) {
|
|
15
|
+
(0, runtime_1.emitNervesEvent)({
|
|
16
|
+
level: "warn",
|
|
17
|
+
component: "senses",
|
|
18
|
+
event: "senses.debug_activity_transport_error",
|
|
19
|
+
message: "debug activity transport failed",
|
|
20
|
+
meta: {
|
|
21
|
+
operation,
|
|
22
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
options.onTransportError?.(operation, error);
|
|
26
|
+
}
|
|
27
|
+
function enqueue(operation, task) {
|
|
28
|
+
queue = queue
|
|
29
|
+
.then(task)
|
|
30
|
+
.catch((error) => {
|
|
31
|
+
reportTransportError(operation, error);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function nextPhrase(pool) {
|
|
35
|
+
const phrase = (0, phrases_1.pickPhrase)(pool, lastPhrase);
|
|
36
|
+
lastPhrase = phrase;
|
|
37
|
+
return phrase;
|
|
38
|
+
}
|
|
39
|
+
function startTypingNow() {
|
|
40
|
+
if (typingActive) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
typingActive = true;
|
|
44
|
+
enqueue("typing_start", async () => {
|
|
45
|
+
await options.transport.setTyping(true);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function setStatus(text) {
|
|
49
|
+
(0, runtime_1.emitNervesEvent)({
|
|
50
|
+
component: "senses",
|
|
51
|
+
event: "senses.debug_activity_update",
|
|
52
|
+
message: "debug activity status updated",
|
|
53
|
+
meta: {
|
|
54
|
+
hasStatusGuid: Boolean(statusMessageGuid),
|
|
55
|
+
textLength: text.length,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
const shouldStartTyping = !typingActive;
|
|
59
|
+
if (shouldStartTyping) {
|
|
60
|
+
typingActive = true;
|
|
61
|
+
}
|
|
62
|
+
enqueue("status_update", async () => {
|
|
63
|
+
if (statusMessageGuid) {
|
|
64
|
+
await options.transport.editStatus(statusMessageGuid, text);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
statusMessageGuid = await options.transport.sendStatus(text);
|
|
68
|
+
}
|
|
69
|
+
if (shouldStartTyping) {
|
|
70
|
+
await options.transport.setTyping(true);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
onModelStart() {
|
|
76
|
+
const pool = hadToolRun ? options.followupPhrases : options.thinkingPhrases;
|
|
77
|
+
if (options.startTypingOnModelStart) {
|
|
78
|
+
startTypingNow();
|
|
79
|
+
}
|
|
80
|
+
if (options.suppressInitialModelStatus && !statusMessageGuid && !hadToolRun) {
|
|
81
|
+
nextPhrase(pool);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
setStatus(`${nextPhrase(pool)}...`);
|
|
85
|
+
},
|
|
86
|
+
onToolStart(name, args) {
|
|
87
|
+
hadToolRun = true;
|
|
88
|
+
followupShown = false;
|
|
89
|
+
const argSummary = Object.values(args).join(", ");
|
|
90
|
+
const detail = argSummary ? ` (${argSummary})` : "";
|
|
91
|
+
setStatus(`running ${name}${detail}...`);
|
|
92
|
+
},
|
|
93
|
+
onToolEnd(name, summary, success) {
|
|
94
|
+
hadToolRun = true;
|
|
95
|
+
followupShown = false;
|
|
96
|
+
setStatus((0, format_1.formatToolResult)(name, summary, success));
|
|
97
|
+
},
|
|
98
|
+
onTextChunk(text) {
|
|
99
|
+
if (!text || !hadToolRun || followupShown) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
followupShown = true;
|
|
103
|
+
if (options.suppressFollowupPhraseStatus) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
setStatus(`${nextPhrase(options.followupPhrases)}...`);
|
|
107
|
+
},
|
|
108
|
+
onError(error) {
|
|
109
|
+
setStatus((0, format_1.formatError)(error));
|
|
110
|
+
this.finish();
|
|
111
|
+
},
|
|
112
|
+
async drain() {
|
|
113
|
+
await queue;
|
|
114
|
+
},
|
|
115
|
+
async finish() {
|
|
116
|
+
if (!typingActive) {
|
|
117
|
+
await queue;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
typingActive = false;
|
|
121
|
+
enqueue("typing_stop", async () => {
|
|
122
|
+
await options.transport.setTyping(false);
|
|
123
|
+
});
|
|
124
|
+
await queue;
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
@@ -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")
|
|
@@ -144,7 +135,10 @@ function deriveResumeCheckpoint(messages) {
|
|
|
144
135
|
const firstLine = assistantText
|
|
145
136
|
.split("\n")
|
|
146
137
|
.map((line) => line.trim())
|
|
147
|
-
.
|
|
138
|
+
.find((line) => line.length > 0);
|
|
139
|
+
/* v8 ignore next -- unreachable: contentToText().trim() guarantees a non-empty line @preserve */
|
|
140
|
+
if (!firstLine)
|
|
141
|
+
return "no prior checkpoint recorded";
|
|
148
142
|
if (firstLine.length <= 220)
|
|
149
143
|
return firstLine;
|
|
150
144
|
return `${firstLine.slice(0, 217)}...`;
|
|
@@ -161,62 +155,116 @@ function createInnerDialogCallbacks() {
|
|
|
161
155
|
};
|
|
162
156
|
}
|
|
163
157
|
function innerDialogSessionPath() {
|
|
164
|
-
return (0, config_1.sessionPath)(
|
|
158
|
+
return (0, config_1.sessionPath)(pending_1.INNER_DIALOG_PENDING.friendId, pending_1.INNER_DIALOG_PENDING.channel, pending_1.INNER_DIALOG_PENDING.key);
|
|
159
|
+
}
|
|
160
|
+
// Self-referencing friend record for inner dialog (agent talking to itself).
|
|
161
|
+
// No real friend to resolve -- this satisfies the pipeline's friend resolver contract.
|
|
162
|
+
function createSelfFriend(agentName) {
|
|
163
|
+
return {
|
|
164
|
+
id: "self",
|
|
165
|
+
name: agentName,
|
|
166
|
+
trustLevel: "family",
|
|
167
|
+
externalIds: [],
|
|
168
|
+
tenantMemberships: [],
|
|
169
|
+
toolPreferences: {},
|
|
170
|
+
notes: {},
|
|
171
|
+
totalTokens: 0,
|
|
172
|
+
createdAt: new Date().toISOString(),
|
|
173
|
+
updatedAt: new Date().toISOString(),
|
|
174
|
+
schemaVersion: 1,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// No-op friend store for inner dialog. Inner dialog doesn't track token usage per-friend.
|
|
178
|
+
function createNoOpFriendStore() {
|
|
179
|
+
return {
|
|
180
|
+
get: async () => null,
|
|
181
|
+
put: async () => { },
|
|
182
|
+
delete: async () => { },
|
|
183
|
+
findByExternalId: async () => null,
|
|
184
|
+
};
|
|
165
185
|
}
|
|
166
186
|
async function runInnerDialogTurn(options) {
|
|
167
187
|
const now = options?.now ?? (() => new Date());
|
|
168
188
|
const reason = options?.reason ?? "heartbeat";
|
|
169
189
|
const sessionFilePath = innerDialogSessionPath();
|
|
170
190
|
const loaded = (0, context_1.loadSession)(sessionFilePath);
|
|
171
|
-
const
|
|
191
|
+
const existingMessages = loaded?.messages ? [...loaded.messages] : [];
|
|
172
192
|
const instincts = options?.instincts ?? loadInnerDialogInstincts();
|
|
173
193
|
const state = {
|
|
174
194
|
cycleCount: 1,
|
|
175
195
|
resting: false,
|
|
176
196
|
lastHeartbeatAt: now().toISOString(),
|
|
177
197
|
};
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
198
|
+
// ── Adapter concern: build user message ──────────────────────────
|
|
199
|
+
let userContent;
|
|
200
|
+
if (existingMessages.length === 0) {
|
|
201
|
+
// Fresh session: bootstrap message with non-canonical cleanup nudge
|
|
181
202
|
const aspirations = readAspirations((0, identity_1.getAgentRoot)());
|
|
182
203
|
const nonCanonical = (0, bundle_manifest_1.findNonCanonicalBundlePaths)((0, identity_1.getAgentRoot)());
|
|
183
204
|
const cleanupNudge = buildNonCanonicalCleanupNudge(nonCanonical);
|
|
184
|
-
|
|
205
|
+
userContent = [
|
|
185
206
|
buildInnerDialogBootstrapMessage(aspirations, "No prior inner dialog session found."),
|
|
186
207
|
cleanupNudge,
|
|
187
208
|
].filter(Boolean).join("\n\n");
|
|
188
|
-
messages.push({ role: "user", content: bootstrapMessage });
|
|
189
209
|
}
|
|
190
210
|
else {
|
|
191
|
-
|
|
211
|
+
// Resumed session: instinct message with checkpoint context
|
|
212
|
+
const assistantTurns = existingMessages.filter((message) => message.role === "assistant").length;
|
|
192
213
|
state.cycleCount = assistantTurns + 1;
|
|
193
|
-
state.checkpoint = deriveResumeCheckpoint(
|
|
194
|
-
|
|
195
|
-
messages.push({ role: "user", content: instinctPrompt });
|
|
214
|
+
state.checkpoint = deriveResumeCheckpoint(existingMessages);
|
|
215
|
+
userContent = buildInstinctUserMessage(instincts, reason, state);
|
|
196
216
|
}
|
|
217
|
+
// ── Adapter concern: inbox drain (inner-dialog-specific) ─────────
|
|
197
218
|
const inboxMessages = options?.drainInbox?.() ?? [];
|
|
198
219
|
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
|
-
}
|
|
220
|
+
const section = inboxMessages
|
|
221
|
+
.map((msg) => `- **${msg.from}**: ${msg.content}`)
|
|
222
|
+
.join("\n");
|
|
223
|
+
userContent = `${userContent}\n\n## incoming messages\n${section}`;
|
|
211
224
|
}
|
|
225
|
+
const userMessage = { role: "user", content: userContent };
|
|
226
|
+
// ── Session loader: wraps existing session logic ──────────────────
|
|
227
|
+
const innerCapabilities = (0, channel_1.getChannelCapabilities)("inner");
|
|
228
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
|
|
229
|
+
const selfFriend = createSelfFriend((0, identity_1.getAgentName)());
|
|
230
|
+
const selfContext = { friend: selfFriend, channel: innerCapabilities };
|
|
231
|
+
const sessionLoader = {
|
|
232
|
+
loadOrCreate: async () => {
|
|
233
|
+
if (existingMessages.length > 0) {
|
|
234
|
+
return { messages: existingMessages, sessionPath: sessionFilePath };
|
|
235
|
+
}
|
|
236
|
+
// Fresh session: build system prompt
|
|
237
|
+
const systemPrompt = await (0, prompt_1.buildSystem)("inner", { toolChoiceRequired: true });
|
|
238
|
+
return {
|
|
239
|
+
messages: [{ role: "system", content: systemPrompt }],
|
|
240
|
+
sessionPath: sessionFilePath,
|
|
241
|
+
};
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
// ── Call shared pipeline ──────────────────────────────────────────
|
|
212
245
|
const callbacks = createInnerDialogCallbacks();
|
|
213
246
|
const traceId = (0, nerves_1.createTraceId)();
|
|
214
|
-
const result = await (0,
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
247
|
+
const result = await (0, pipeline_1.handleInboundTurn)({
|
|
248
|
+
channel: "inner",
|
|
249
|
+
capabilities: innerCapabilities,
|
|
250
|
+
messages: [userMessage],
|
|
251
|
+
callbacks,
|
|
252
|
+
friendResolver: { resolve: () => Promise.resolve(selfContext) },
|
|
253
|
+
sessionLoader,
|
|
254
|
+
pendingDir,
|
|
255
|
+
friendStore: createNoOpFriendStore(),
|
|
256
|
+
enforceTrustGate: trust_gate_1.enforceTrustGate,
|
|
257
|
+
drainPending: pending_1.drainPending,
|
|
258
|
+
runAgent: core_1.runAgent,
|
|
259
|
+
postTurn: context_1.postTurn,
|
|
260
|
+
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
261
|
+
signal: options?.signal,
|
|
262
|
+
runAgentOptions: {
|
|
263
|
+
traceId,
|
|
264
|
+
toolChoiceRequired: true,
|
|
265
|
+
skipConfirmation: true,
|
|
266
|
+
},
|
|
218
267
|
});
|
|
219
|
-
(0, context_1.postTurn)(messages, sessionFilePath, result.usage);
|
|
220
268
|
(0, runtime_1.emitNervesEvent)({
|
|
221
269
|
component: "senses",
|
|
222
270
|
event: "senses.inner_dialog_turn",
|
|
@@ -224,8 +272,8 @@ async function runInnerDialogTurn(options) {
|
|
|
224
272
|
meta: { reason, session: sessionFilePath },
|
|
225
273
|
});
|
|
226
274
|
return {
|
|
227
|
-
messages,
|
|
275
|
+
messages: result.messages ?? [],
|
|
228
276
|
usage: result.usage,
|
|
229
|
-
sessionPath: sessionFilePath,
|
|
277
|
+
sessionPath: result.sessionPath ?? sessionFilePath,
|
|
230
278
|
};
|
|
231
279
|
}
|
|
@@ -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
|
+
}
|