@ouro.bot/cli 0.1.0-alpha.13 → 0.1.0-alpha.131
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/psyche/SOUL.md +2 -2
- package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
- package/README.md +147 -205
- package/changelog.json +814 -0
- package/dist/heart/active-work.js +622 -0
- package/dist/heart/bridges/manager.js +358 -0
- package/dist/heart/bridges/state-machine.js +135 -0
- package/dist/heart/bridges/store.js +123 -0
- package/dist/heart/commitments.js +105 -0
- package/dist/heart/config.js +66 -21
- package/dist/heart/core.js +518 -100
- package/dist/heart/cross-chat-delivery.js +146 -0
- package/dist/heart/daemon/agent-discovery.js +81 -0
- package/dist/heart/daemon/auth-flow.js +457 -0
- package/dist/heart/daemon/daemon-cli.js +1516 -195
- package/dist/heart/daemon/daemon-entry.js +43 -2
- package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
- package/dist/heart/daemon/daemon.js +261 -1
- package/dist/heart/daemon/hatch-animation.js +10 -3
- package/dist/heart/daemon/hatch-flow.js +7 -72
- package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
- package/dist/heart/daemon/launchd.js +159 -0
- package/dist/heart/daemon/log-tailer.js +4 -3
- package/dist/heart/daemon/message-router.js +17 -8
- package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
- package/dist/heart/daemon/ouro-path-installer.js +57 -29
- package/dist/heart/daemon/ouro-version-manager.js +171 -0
- package/dist/heart/daemon/process-manager.js +13 -0
- package/dist/heart/daemon/run-hooks.js +37 -0
- package/dist/heart/daemon/runtime-logging.js +58 -15
- package/dist/heart/daemon/runtime-metadata.js +219 -0
- package/dist/heart/daemon/runtime-mode.js +67 -0
- package/dist/heart/daemon/sense-manager.js +50 -2
- package/dist/heart/daemon/skill-management-installer.js +94 -0
- package/dist/heart/daemon/socket-client.js +202 -0
- package/dist/heart/daemon/specialist-orchestrator.js +2 -2
- package/dist/heart/daemon/specialist-prompt.js +7 -4
- package/dist/heart/daemon/specialist-tools.js +52 -3
- package/dist/heart/daemon/staged-restart.js +114 -0
- package/dist/heart/daemon/thoughts.js +507 -0
- 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/delegation.js +62 -0
- package/dist/heart/identity.js +64 -21
- package/dist/heart/kicks.js +1 -19
- package/dist/heart/model-capabilities.js +48 -0
- package/dist/heart/obligations.js +197 -0
- package/dist/heart/progress-story.js +42 -0
- package/dist/heart/provider-failover.js +88 -0
- package/dist/heart/provider-ping.js +159 -0
- package/dist/heart/providers/anthropic-token.js +163 -0
- package/dist/heart/providers/anthropic.js +195 -34
- package/dist/heart/providers/azure.js +115 -9
- package/dist/heart/providers/github-copilot.js +157 -0
- package/dist/heart/providers/minimax.js +33 -3
- package/dist/heart/providers/openai-codex.js +49 -14
- package/dist/heart/safe-workspace.js +381 -0
- package/dist/heart/session-activity.js +173 -0
- package/dist/heart/session-recall.js +216 -0
- package/dist/heart/streaming.js +108 -24
- package/dist/heart/target-resolution.js +123 -0
- package/dist/heart/tool-loop.js +194 -0
- package/dist/heart/turn-coordinator.js +28 -0
- package/dist/mind/associative-recall.js +14 -2
- package/dist/mind/bundle-manifest.js +12 -0
- package/dist/mind/context.js +60 -14
- package/dist/mind/first-impressions.js +16 -2
- package/dist/mind/friends/channel.js +35 -0
- package/dist/mind/friends/group-context.js +144 -0
- package/dist/mind/friends/store-file.js +19 -0
- package/dist/mind/friends/trust-explanation.js +74 -0
- package/dist/mind/friends/types.js +8 -0
- package/dist/mind/memory.js +27 -26
- package/dist/mind/obligation-steering.js +221 -0
- package/dist/mind/pending.js +76 -9
- package/dist/mind/phrases.js +1 -0
- package/dist/mind/prompt.js +456 -77
- package/dist/mind/token-estimate.js +8 -12
- package/dist/nerves/cli-logging.js +15 -2
- package/dist/nerves/coverage/run-artifacts.js +1 -1
- package/dist/nerves/index.js +12 -0
- package/dist/nerves/runtime.js +5 -1
- package/dist/repertoire/ado-client.js +4 -2
- package/dist/repertoire/coding/context-pack.js +254 -0
- package/dist/repertoire/coding/feedback.js +301 -0
- package/dist/repertoire/coding/index.js +4 -1
- package/dist/repertoire/coding/manager.js +210 -4
- package/dist/repertoire/coding/spawner.js +39 -9
- package/dist/repertoire/coding/tools.js +171 -4
- package/dist/repertoire/data/ado-endpoints.json +188 -0
- package/dist/repertoire/guardrails.js +290 -0
- package/dist/repertoire/mcp-client.js +254 -0
- package/dist/repertoire/mcp-manager.js +198 -0
- package/dist/repertoire/skills.js +3 -26
- package/dist/repertoire/tasks/board.js +12 -0
- package/dist/repertoire/tasks/index.js +23 -9
- package/dist/repertoire/tasks/transitions.js +1 -2
- package/dist/repertoire/tools-base.js +925 -250
- package/dist/repertoire/tools-bluebubbles.js +93 -0
- package/dist/repertoire/tools-teams.js +58 -25
- package/dist/repertoire/tools.js +106 -53
- package/dist/senses/bluebubbles-client.js +210 -5
- package/dist/senses/bluebubbles-entry.js +2 -0
- package/dist/senses/bluebubbles-inbound-log.js +109 -0
- package/dist/senses/bluebubbles-media.js +339 -0
- package/dist/senses/bluebubbles-model.js +12 -4
- package/dist/senses/bluebubbles-mutation-log.js +45 -5
- package/dist/senses/bluebubbles-runtime-state.js +109 -0
- package/dist/senses/bluebubbles-session-cleanup.js +72 -0
- package/dist/senses/bluebubbles.js +915 -45
- package/dist/senses/cli-layout.js +187 -0
- package/dist/senses/cli.js +374 -131
- package/dist/senses/continuity.js +94 -0
- package/dist/senses/debug-activity.js +154 -0
- package/dist/senses/inner-dialog-worker.js +47 -18
- package/dist/senses/inner-dialog.js +388 -83
- package/dist/senses/pipeline.js +444 -0
- package/dist/senses/teams.js +607 -129
- package/dist/senses/trust-gate.js +112 -2
- package/package.json +9 -3
- package/subagents/README.md +4 -70
- package/dist/heart/daemon/subagent-installer.js +0 -134
- package/subagents/work-doer.md +0 -233
- package/subagents/work-merger.md +0 -624
- package/subagents/work-planner.md +0 -373
|
@@ -0,0 +1,444 @@
|
|
|
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
|
+
const continuity_1 = require("./continuity");
|
|
11
|
+
const manager_1 = require("../heart/bridges/manager");
|
|
12
|
+
const identity_1 = require("../heart/identity");
|
|
13
|
+
const tasks_1 = require("../repertoire/tasks");
|
|
14
|
+
const coding_1 = require("../repertoire/coding");
|
|
15
|
+
const session_activity_1 = require("../heart/session-activity");
|
|
16
|
+
const active_work_1 = require("../heart/active-work");
|
|
17
|
+
const delegation_1 = require("../heart/delegation");
|
|
18
|
+
const target_resolution_1 = require("../heart/target-resolution");
|
|
19
|
+
const thoughts_1 = require("../heart/daemon/thoughts");
|
|
20
|
+
const pending_1 = require("../mind/pending");
|
|
21
|
+
const obligations_1 = require("../heart/obligations");
|
|
22
|
+
const provider_failover_1 = require("../heart/provider-failover");
|
|
23
|
+
const provider_ping_1 = require("../heart/provider-ping");
|
|
24
|
+
const auth_flow_1 = require("../heart/daemon/auth-flow");
|
|
25
|
+
function emptyTaskBoard() {
|
|
26
|
+
return {
|
|
27
|
+
compact: "",
|
|
28
|
+
full: "",
|
|
29
|
+
byStatus: {
|
|
30
|
+
drafting: [],
|
|
31
|
+
processing: [],
|
|
32
|
+
validating: [],
|
|
33
|
+
collaborating: [],
|
|
34
|
+
paused: [],
|
|
35
|
+
blocked: [],
|
|
36
|
+
done: [],
|
|
37
|
+
},
|
|
38
|
+
actionRequired: [],
|
|
39
|
+
unresolvedDependencies: [],
|
|
40
|
+
activeSessions: [],
|
|
41
|
+
activeBridges: [],
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function isLiveCodingSessionStatus(status) {
|
|
45
|
+
return status === "spawning"
|
|
46
|
+
|| status === "running"
|
|
47
|
+
|| status === "waiting_input"
|
|
48
|
+
|| status === "stalled";
|
|
49
|
+
}
|
|
50
|
+
function prependTurnSections(message, sections) {
|
|
51
|
+
if (message.role !== "user" || sections.length === 0)
|
|
52
|
+
return message;
|
|
53
|
+
const prefix = sections.join("\n\n");
|
|
54
|
+
if (typeof message.content === "string") {
|
|
55
|
+
return {
|
|
56
|
+
...message,
|
|
57
|
+
content: `${prefix}\n\n${message.content}`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return {
|
|
61
|
+
...message,
|
|
62
|
+
content: [
|
|
63
|
+
{ type: "text", text: `${prefix}\n\n` },
|
|
64
|
+
...message.content,
|
|
65
|
+
],
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function readInnerWorkState() {
|
|
69
|
+
const defaultJob = {
|
|
70
|
+
status: "idle",
|
|
71
|
+
content: null,
|
|
72
|
+
origin: null,
|
|
73
|
+
mode: "reflect",
|
|
74
|
+
obligationStatus: null,
|
|
75
|
+
surfacedResult: null,
|
|
76
|
+
queuedAt: null,
|
|
77
|
+
startedAt: null,
|
|
78
|
+
surfacedAt: null,
|
|
79
|
+
};
|
|
80
|
+
try {
|
|
81
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
82
|
+
const pendingDir = (0, pending_1.getInnerDialogPendingDir)((0, identity_1.getAgentName)());
|
|
83
|
+
const sessionPath = (0, thoughts_1.getInnerDialogSessionPath)(agentRoot);
|
|
84
|
+
const { pendingMessages, turns, runtimeState } = (0, thoughts_1.readInnerDialogRawData)(sessionPath, pendingDir);
|
|
85
|
+
const dialogStatus = (0, thoughts_1.deriveInnerDialogStatus)(pendingMessages, turns, runtimeState);
|
|
86
|
+
const job = (0, thoughts_1.deriveInnerJob)(pendingMessages, turns, runtimeState);
|
|
87
|
+
// Derive obligationPending from both the pending message field and the obligation store
|
|
88
|
+
const storeObligationPending = (0, obligations_1.readPendingObligations)(agentRoot).length > 0;
|
|
89
|
+
return {
|
|
90
|
+
status: dialogStatus.processing === "started" ? "running" : "idle",
|
|
91
|
+
hasPending: dialogStatus.queue !== "clear",
|
|
92
|
+
origin: dialogStatus.origin,
|
|
93
|
+
contentSnippet: dialogStatus.contentSnippet,
|
|
94
|
+
obligationPending: dialogStatus.obligationPending || storeObligationPending,
|
|
95
|
+
job,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return {
|
|
100
|
+
status: "idle",
|
|
101
|
+
hasPending: false,
|
|
102
|
+
job: defaultJob,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// ── Pipeline ──────────────────────────────────────────────────────
|
|
107
|
+
async function handleInboundTurn(input) {
|
|
108
|
+
// Step 0: Check for pending failover reply
|
|
109
|
+
if (input.failoverState?.pending) {
|
|
110
|
+
const userText = input.messages
|
|
111
|
+
.filter((m) => m.role === "user")
|
|
112
|
+
.map((m) => typeof m.content === "string" ? m.content : /* v8 ignore next -- defensive: multipart content fallback @preserve */ "")
|
|
113
|
+
.join(" ")
|
|
114
|
+
.trim();
|
|
115
|
+
const pendingContext = input.failoverState.pending;
|
|
116
|
+
const failoverAction = (0, provider_failover_1.handleFailoverReply)(userText, pendingContext);
|
|
117
|
+
const failoverAgentName = pendingContext.agentName;
|
|
118
|
+
input.failoverState.pending = null; // always clear before acting
|
|
119
|
+
if (failoverAction.action === "switch") {
|
|
120
|
+
let switchSucceeded = false;
|
|
121
|
+
try {
|
|
122
|
+
(0, auth_flow_1.writeAgentProviderSelection)(failoverAgentName, failoverAction.provider);
|
|
123
|
+
switchSucceeded = true;
|
|
124
|
+
/* v8 ignore start -- defensive: write failure during provider switch @preserve */
|
|
125
|
+
}
|
|
126
|
+
catch (switchError) {
|
|
127
|
+
(0, runtime_1.emitNervesEvent)({
|
|
128
|
+
level: "error",
|
|
129
|
+
component: "senses",
|
|
130
|
+
event: "senses.failover_switch_error",
|
|
131
|
+
message: `failed to switch provider to ${failoverAction.provider}`,
|
|
132
|
+
meta: { agentName: failoverAgentName, provider: failoverAction.provider, error: switchError instanceof Error ? switchError.message : String(switchError) },
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
/* v8 ignore stop */
|
|
136
|
+
/* v8 ignore next -- false branch: write-failure fallthrough @preserve */
|
|
137
|
+
if (switchSucceeded) {
|
|
138
|
+
(0, runtime_1.emitNervesEvent)({
|
|
139
|
+
component: "senses",
|
|
140
|
+
event: "senses.failover_switch",
|
|
141
|
+
message: `switched provider to ${failoverAction.provider} via failover`,
|
|
142
|
+
meta: { agentName: failoverAgentName, provider: failoverAction.provider },
|
|
143
|
+
});
|
|
144
|
+
// Replace "switch to <provider>" with a context message for the agent.
|
|
145
|
+
// The session already has the user's original question from the failed turn.
|
|
146
|
+
// The agent needs to know what happened so it can respond appropriately.
|
|
147
|
+
const newProviderSecrets = (() => {
|
|
148
|
+
try {
|
|
149
|
+
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(failoverAgentName);
|
|
150
|
+
const cfg = secrets.providers[failoverAction.provider];
|
|
151
|
+
return cfg?.model ?? cfg?.modelName ?? "";
|
|
152
|
+
/* v8 ignore next 2 -- defensive: secrets read failure @preserve */
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return "";
|
|
156
|
+
}
|
|
157
|
+
})();
|
|
158
|
+
const newProviderLabel = newProviderSecrets ? `${failoverAction.provider} (${newProviderSecrets})` : failoverAction.provider;
|
|
159
|
+
input.messages = [{
|
|
160
|
+
role: "user",
|
|
161
|
+
content: `[provider switch: ${pendingContext.errorSummary}. switched to ${newProviderLabel}. your conversation history is intact — respond to the user's last message.]`,
|
|
162
|
+
}];
|
|
163
|
+
input.switchedProvider = failoverAction.provider;
|
|
164
|
+
}
|
|
165
|
+
// Switch failed OR succeeded — either way, fall through to normal processing.
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Step 1: Resolve friend
|
|
169
|
+
const resolvedContext = await input.friendResolver.resolve();
|
|
170
|
+
(0, runtime_1.emitNervesEvent)({
|
|
171
|
+
component: "senses",
|
|
172
|
+
event: "senses.pipeline_start",
|
|
173
|
+
message: "inbound turn pipeline started",
|
|
174
|
+
meta: {
|
|
175
|
+
channel: input.channel,
|
|
176
|
+
friendId: resolvedContext.friend.id,
|
|
177
|
+
senseType: input.capabilities.senseType,
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
// Step 2: Trust gate
|
|
181
|
+
const gateInput = {
|
|
182
|
+
friend: resolvedContext.friend,
|
|
183
|
+
provider: input.provider ?? "local",
|
|
184
|
+
externalId: input.externalId ?? "",
|
|
185
|
+
tenantId: input.tenantId,
|
|
186
|
+
channel: input.channel,
|
|
187
|
+
senseType: input.capabilities.senseType,
|
|
188
|
+
isGroupChat: input.isGroupChat ?? false,
|
|
189
|
+
groupHasFamilyMember: input.groupHasFamilyMember ?? false,
|
|
190
|
+
hasExistingGroupWithFamily: input.hasExistingGroupWithFamily ?? false,
|
|
191
|
+
};
|
|
192
|
+
const gateResult = input.enforceTrustGate(gateInput);
|
|
193
|
+
// Gate rejection: return early, no agent turn
|
|
194
|
+
if (!gateResult.allowed) {
|
|
195
|
+
(0, runtime_1.emitNervesEvent)({
|
|
196
|
+
component: "senses",
|
|
197
|
+
event: "senses.pipeline_gate_reject",
|
|
198
|
+
message: "trust gate rejected inbound turn",
|
|
199
|
+
meta: {
|
|
200
|
+
channel: input.channel,
|
|
201
|
+
friendId: resolvedContext.friend.id,
|
|
202
|
+
reason: gateResult.reason,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
resolvedContext,
|
|
207
|
+
gateResult,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
// Step 3: Load/create session
|
|
211
|
+
const session = await input.sessionLoader.loadOrCreate();
|
|
212
|
+
const sessionMessages = session.messages;
|
|
213
|
+
let mustResolveBeforeHandoff = (0, continuity_1.resolveMustResolveBeforeHandoff)(session.state?.mustResolveBeforeHandoff === true, input.continuityIngressTexts);
|
|
214
|
+
const lastFriendActivityAt = input.channel === "inner"
|
|
215
|
+
? session.state?.lastFriendActivityAt
|
|
216
|
+
: new Date().toISOString();
|
|
217
|
+
const currentObligation = input.continuityIngressTexts
|
|
218
|
+
?.map((text) => text.trim())
|
|
219
|
+
.filter((text) => text.length > 0)
|
|
220
|
+
.at(-1);
|
|
221
|
+
const currentSession = {
|
|
222
|
+
friendId: resolvedContext.friend.id,
|
|
223
|
+
channel: input.channel,
|
|
224
|
+
key: input.sessionKey ?? "session",
|
|
225
|
+
sessionPath: session.sessionPath,
|
|
226
|
+
};
|
|
227
|
+
const activeBridges = (0, manager_1.createBridgeManager)().findBridgesForSession({
|
|
228
|
+
friendId: currentSession.friendId,
|
|
229
|
+
channel: currentSession.channel,
|
|
230
|
+
key: currentSession.key,
|
|
231
|
+
});
|
|
232
|
+
const bridgeContext = (0, manager_1.formatBridgeContext)(activeBridges) || undefined;
|
|
233
|
+
let sessionActivity = [];
|
|
234
|
+
try {
|
|
235
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
236
|
+
sessionActivity = (0, session_activity_1.listSessionActivity)({
|
|
237
|
+
sessionsDir: `${agentRoot}/state/sessions`,
|
|
238
|
+
friendsDir: `${agentRoot}/friends`,
|
|
239
|
+
agentName: (0, identity_1.getAgentName)(),
|
|
240
|
+
currentSession: {
|
|
241
|
+
friendId: currentSession.friendId,
|
|
242
|
+
channel: currentSession.channel,
|
|
243
|
+
key: currentSession.key,
|
|
244
|
+
},
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
sessionActivity = [];
|
|
249
|
+
}
|
|
250
|
+
let targetCandidates = [];
|
|
251
|
+
try {
|
|
252
|
+
if (input.channel !== "inner") {
|
|
253
|
+
const agentRoot = (0, identity_1.getAgentRoot)();
|
|
254
|
+
targetCandidates = await (0, target_resolution_1.listTargetSessionCandidates)({
|
|
255
|
+
sessionsDir: `${agentRoot}/state/sessions`,
|
|
256
|
+
friendsDir: `${agentRoot}/friends`,
|
|
257
|
+
agentName: (0, identity_1.getAgentName)(),
|
|
258
|
+
currentSession: {
|
|
259
|
+
friendId: currentSession.friendId,
|
|
260
|
+
channel: currentSession.channel,
|
|
261
|
+
key: currentSession.key,
|
|
262
|
+
},
|
|
263
|
+
friendStore: input.friendStore,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
catch {
|
|
268
|
+
targetCandidates = [];
|
|
269
|
+
}
|
|
270
|
+
let pendingObligations = [];
|
|
271
|
+
try {
|
|
272
|
+
pendingObligations = (0, obligations_1.readPendingObligations)((0, identity_1.getAgentRoot)());
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
pendingObligations = [];
|
|
276
|
+
}
|
|
277
|
+
let codingSessions = [];
|
|
278
|
+
let otherCodingSessions = [];
|
|
279
|
+
try {
|
|
280
|
+
const liveCodingSessions = (0, coding_1.getCodingSessionManager)()
|
|
281
|
+
.listSessions()
|
|
282
|
+
.filter((session) => isLiveCodingSessionStatus(session.status) && Boolean(session.originSession));
|
|
283
|
+
codingSessions = liveCodingSessions.filter((session) => session.originSession?.friendId === currentSession.friendId
|
|
284
|
+
&& session.originSession.channel === currentSession.channel
|
|
285
|
+
&& session.originSession.key === currentSession.key);
|
|
286
|
+
otherCodingSessions = liveCodingSessions.filter((session) => !(session.originSession?.friendId === currentSession.friendId
|
|
287
|
+
&& session.originSession.channel === currentSession.channel
|
|
288
|
+
&& session.originSession.key === currentSession.key));
|
|
289
|
+
}
|
|
290
|
+
catch {
|
|
291
|
+
codingSessions = [];
|
|
292
|
+
otherCodingSessions = [];
|
|
293
|
+
}
|
|
294
|
+
const activeWorkFrame = (0, active_work_1.buildActiveWorkFrame)({
|
|
295
|
+
currentSession,
|
|
296
|
+
currentObligation,
|
|
297
|
+
mustResolveBeforeHandoff,
|
|
298
|
+
inner: readInnerWorkState(),
|
|
299
|
+
bridges: activeBridges,
|
|
300
|
+
codingSessions,
|
|
301
|
+
otherCodingSessions,
|
|
302
|
+
pendingObligations,
|
|
303
|
+
taskBoard: (() => {
|
|
304
|
+
try {
|
|
305
|
+
return (0, tasks_1.getTaskModule)().getBoard();
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
return emptyTaskBoard();
|
|
309
|
+
}
|
|
310
|
+
})(),
|
|
311
|
+
friendActivity: sessionActivity,
|
|
312
|
+
targetCandidates,
|
|
313
|
+
});
|
|
314
|
+
const delegationDecision = (0, delegation_1.decideDelegation)({
|
|
315
|
+
channel: input.channel,
|
|
316
|
+
ingressTexts: input.continuityIngressTexts ?? [],
|
|
317
|
+
activeWork: activeWorkFrame,
|
|
318
|
+
mustResolveBeforeHandoff,
|
|
319
|
+
});
|
|
320
|
+
// Step 4: Drain deferred friend returns, then ordinary per-session pending.
|
|
321
|
+
const deferredReturns = input.channel === "inner"
|
|
322
|
+
? []
|
|
323
|
+
: (input.drainDeferredReturns?.(resolvedContext.friend.id) ?? []);
|
|
324
|
+
const sessionPending = input.drainPending(input.pendingDir);
|
|
325
|
+
const pending = [...deferredReturns, ...sessionPending];
|
|
326
|
+
// Assemble messages: session messages + live world-state checkpoint + pending + inbound user messages
|
|
327
|
+
const prefixSections = [(0, active_work_1.formatLiveWorldStateCheckpoint)(activeWorkFrame)];
|
|
328
|
+
if (pending.length > 0) {
|
|
329
|
+
const pendingSection = pending
|
|
330
|
+
.map((msg) => `[pending from ${msg.from}]: ${msg.content}`)
|
|
331
|
+
.join("\n");
|
|
332
|
+
prefixSections.push(`## pending messages\n${pendingSection}`);
|
|
333
|
+
}
|
|
334
|
+
if (input.messages.length > 0) {
|
|
335
|
+
input.messages[0] = prependTurnSections(input.messages[0], prefixSections);
|
|
336
|
+
}
|
|
337
|
+
// Append user messages from the inbound turn
|
|
338
|
+
for (const msg of input.messages) {
|
|
339
|
+
sessionMessages.push(msg);
|
|
340
|
+
}
|
|
341
|
+
// Step 5: runAgent
|
|
342
|
+
const existingToolContext = input.runAgentOptions?.toolContext;
|
|
343
|
+
const runAgentOptions = {
|
|
344
|
+
...input.runAgentOptions,
|
|
345
|
+
bridgeContext,
|
|
346
|
+
activeWorkFrame,
|
|
347
|
+
delegationDecision,
|
|
348
|
+
currentSessionKey: currentSession.key,
|
|
349
|
+
currentObligation,
|
|
350
|
+
mustResolveBeforeHandoff,
|
|
351
|
+
setMustResolveBeforeHandoff: (value) => {
|
|
352
|
+
mustResolveBeforeHandoff = value;
|
|
353
|
+
},
|
|
354
|
+
toolContext: {
|
|
355
|
+
/* v8 ignore next -- default no-op signin satisfies interface; real signin injected by sense adapter @preserve */
|
|
356
|
+
signin: async () => undefined,
|
|
357
|
+
...existingToolContext,
|
|
358
|
+
context: resolvedContext,
|
|
359
|
+
friendStore: input.friendStore,
|
|
360
|
+
currentSession,
|
|
361
|
+
activeBridges,
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
const result = await input.runAgent(sessionMessages, input.callbacks, input.channel, input.signal, runAgentOptions);
|
|
365
|
+
// Step 5b: Failover on terminal error
|
|
366
|
+
if (result.outcome === "errored" && input.failoverState) {
|
|
367
|
+
try {
|
|
368
|
+
const agentName = (0, identity_1.getAgentName)();
|
|
369
|
+
const agentConfig = (0, identity_1.loadAgentConfig)();
|
|
370
|
+
const currentProvider = agentConfig.provider;
|
|
371
|
+
/* v8 ignore next -- defensive: errorClassification always set when errored @preserve */
|
|
372
|
+
const classification = result.errorClassification ?? "unknown";
|
|
373
|
+
const inventory = await (0, provider_ping_1.runHealthInventory)(agentName, currentProvider);
|
|
374
|
+
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(agentName);
|
|
375
|
+
const providerModels = {};
|
|
376
|
+
for (const [p, cfg] of Object.entries(secrets.providers)) {
|
|
377
|
+
const model = cfg.model ?? cfg.modelName;
|
|
378
|
+
if (typeof model === "string" && model)
|
|
379
|
+
providerModels[p] = model;
|
|
380
|
+
}
|
|
381
|
+
/* v8 ignore next -- defensive: current provider always in secrets @preserve */
|
|
382
|
+
const currentModel = providerModels[currentProvider] ?? "";
|
|
383
|
+
const failoverContext = (0, provider_failover_1.buildFailoverContext)(
|
|
384
|
+
/* v8 ignore next -- defensive: error always set when errored @preserve */
|
|
385
|
+
result.error?.message ?? "unknown error", classification, currentProvider, currentModel, agentName, inventory, providerModels);
|
|
386
|
+
input.failoverState.pending = failoverContext;
|
|
387
|
+
input.postTurn(sessionMessages, session.sessionPath, result.usage);
|
|
388
|
+
return {
|
|
389
|
+
resolvedContext,
|
|
390
|
+
gateResult,
|
|
391
|
+
usage: result.usage,
|
|
392
|
+
turnOutcome: result.outcome,
|
|
393
|
+
sessionPath: session.sessionPath,
|
|
394
|
+
messages: sessionMessages,
|
|
395
|
+
drainedPending: pending,
|
|
396
|
+
failoverMessage: failoverContext.userMessage,
|
|
397
|
+
};
|
|
398
|
+
/* v8 ignore start -- failover catch: tested via pipeline failover sequence throws test but v8 under-reports catch coverage @preserve */
|
|
399
|
+
}
|
|
400
|
+
catch (failoverError) {
|
|
401
|
+
(0, runtime_1.emitNervesEvent)({
|
|
402
|
+
level: "warn",
|
|
403
|
+
component: "senses",
|
|
404
|
+
event: "senses.failover_error",
|
|
405
|
+
message: "failover sequence failed, falling through",
|
|
406
|
+
meta: { error: failoverError instanceof Error ? failoverError.message : String(failoverError) },
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
/* v8 ignore stop */
|
|
410
|
+
}
|
|
411
|
+
// Step 6: postTurn
|
|
412
|
+
const continuingState = {
|
|
413
|
+
...(mustResolveBeforeHandoff ? { mustResolveBeforeHandoff: true } : {}),
|
|
414
|
+
...(typeof lastFriendActivityAt === "string" ? { lastFriendActivityAt } : {}),
|
|
415
|
+
};
|
|
416
|
+
const nextState = result.outcome === "complete" || result.outcome === "blocked" || result.outcome === "superseded" || result.outcome === "no_response"
|
|
417
|
+
? (typeof lastFriendActivityAt === "string"
|
|
418
|
+
? { lastFriendActivityAt }
|
|
419
|
+
: undefined)
|
|
420
|
+
: (Object.keys(continuingState).length > 0 ? continuingState : undefined);
|
|
421
|
+
input.postTurn(sessionMessages, session.sessionPath, result.usage, undefined, nextState);
|
|
422
|
+
// Step 7: Token accumulation
|
|
423
|
+
await input.accumulateFriendTokens(input.friendStore, resolvedContext.friend.id, result.usage);
|
|
424
|
+
(0, runtime_1.emitNervesEvent)({
|
|
425
|
+
component: "senses",
|
|
426
|
+
event: "senses.pipeline_end",
|
|
427
|
+
message: "inbound turn pipeline completed",
|
|
428
|
+
meta: {
|
|
429
|
+
channel: input.channel,
|
|
430
|
+
friendId: resolvedContext.friend.id,
|
|
431
|
+
},
|
|
432
|
+
});
|
|
433
|
+
return {
|
|
434
|
+
resolvedContext,
|
|
435
|
+
gateResult,
|
|
436
|
+
usage: result.usage,
|
|
437
|
+
turnOutcome: result.outcome,
|
|
438
|
+
completion: result.completion,
|
|
439
|
+
sessionPath: session.sessionPath,
|
|
440
|
+
messages: sessionMessages,
|
|
441
|
+
drainedPending: pending,
|
|
442
|
+
...(input.switchedProvider ? { switchedProvider: input.switchedProvider } : {}),
|
|
443
|
+
};
|
|
444
|
+
}
|