@ouro.bot/cli 0.1.0-alpha.314 → 0.1.0-alpha.316
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 +12 -0
- package/dist/heart/daemon/cli-exec.js +37 -16
- package/dist/heart/daemon/startup-tui.js +68 -30
- package/dist/heart/daemon/up-progress.js +126 -0
- package/dist/heart/outlook/outlook-read.js +55 -78
- package/dist/heart/outlook/outlook-types.js +20 -0
- package/dist/heart/session-activity.js +33 -15
- package/dist/heart/session-events.js +673 -0
- package/dist/heart/session-recall.js +23 -77
- package/dist/heart/start-of-turn-packet.js +2 -0
- package/dist/mind/context.js +67 -182
- package/dist/mind/prompt.js +14 -2
- package/dist/nerves/coverage/file-completeness.js +4 -0
- package/dist/senses/bluebubbles/index.js +1 -0
- package/dist/senses/cli/ouro-tui.js +10 -0
- package/dist/senses/cli.js +4 -0
- package/dist/senses/pipeline.js +4 -0
- package/dist/senses/shared-turn.js +1 -0
- package/dist/senses/teams.js +1 -0
- package/package.json +10 -1
- package/dist/outlook-ui/assets/index-Ck8agNeO.js +0 -61
- package/dist/outlook-ui/assets/index-LwChZTgL.css +0 -1
- package/dist/outlook-ui/index.html +0 -15
|
@@ -1,65 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.recallSession = recallSession;
|
|
37
4
|
exports.searchSessionTranscript = searchSessionTranscript;
|
|
38
|
-
const fs = __importStar(require("fs"));
|
|
39
5
|
const runtime_1 = require("../nerves/runtime");
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.filter((text) => text.length > 0)
|
|
50
|
-
.join("");
|
|
51
|
-
}
|
|
52
|
-
function normalizeSessionMessages(messages) {
|
|
53
|
-
if (!Array.isArray(messages))
|
|
54
|
-
return [];
|
|
55
|
-
return messages
|
|
56
|
-
.map((message) => {
|
|
57
|
-
const record = message && typeof message === "object" ? message : {};
|
|
58
|
-
return {
|
|
59
|
-
role: typeof record.role === "string" ? record.role : "",
|
|
60
|
-
content: normalizeContent(record.content),
|
|
61
|
-
};
|
|
62
|
-
})
|
|
6
|
+
const session_events_1 = require("./session-events");
|
|
7
|
+
function normalizeSessionMessages(events) {
|
|
8
|
+
return events
|
|
9
|
+
.map((event) => ({
|
|
10
|
+
id: event.id,
|
|
11
|
+
role: event.role,
|
|
12
|
+
content: (0, session_events_1.extractEventText)(event),
|
|
13
|
+
timestamp: (0, session_events_1.formatSessionEventTimestamp)(event),
|
|
14
|
+
}))
|
|
63
15
|
.filter((message) => message.role !== "system" && message.content.length > 0);
|
|
64
16
|
}
|
|
65
17
|
function buildSummaryInstruction(friendId, channel, trustLevel) {
|
|
@@ -114,6 +66,10 @@ function buildSearchExcerpts(messages, query, maxMatches) {
|
|
|
114
66
|
const start = Math.max(0, i - 1);
|
|
115
67
|
const end = Math.min(messages.length, i + 2);
|
|
116
68
|
const excerpt = messages
|
|
69
|
+
.slice(start, end)
|
|
70
|
+
.map((message) => `[${message.timestamp} | ${message.role} | ${message.id}] ${clip(message.content, 200)}`)
|
|
71
|
+
.join("\n");
|
|
72
|
+
const signature = messages
|
|
117
73
|
.slice(start, end)
|
|
118
74
|
.map((message) => `[${message.role}] ${clip(message.content, 200)}`)
|
|
119
75
|
.join("\n");
|
|
@@ -121,15 +77,15 @@ function buildSearchExcerpts(messages, query, maxMatches) {
|
|
|
121
77
|
.slice(start, end)
|
|
122
78
|
.filter((message) => message.content.toLowerCase().includes(normalizedQuery))
|
|
123
79
|
.length;
|
|
124
|
-
candidates.push({ excerpt, score, index: i });
|
|
80
|
+
candidates.push({ excerpt, signature, score, index: i });
|
|
125
81
|
}
|
|
126
82
|
const seen = new Set();
|
|
127
83
|
return candidates
|
|
128
84
|
.sort((a, b) => b.score - a.score || a.index - b.index)
|
|
129
85
|
.filter((candidate) => {
|
|
130
|
-
if (seen.has(candidate.
|
|
86
|
+
if (seen.has(candidate.signature))
|
|
131
87
|
return false;
|
|
132
|
-
seen.add(candidate.
|
|
88
|
+
seen.add(candidate.signature);
|
|
133
89
|
return true;
|
|
134
90
|
})
|
|
135
91
|
.slice(0, maxMatches)
|
|
@@ -147,20 +103,15 @@ async function recallSession(options) {
|
|
|
147
103
|
messageCount: options.messageCount,
|
|
148
104
|
},
|
|
149
105
|
});
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
raw = fs.readFileSync(options.sessionPath, "utf-8");
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
106
|
+
const envelope = (0, session_events_1.loadSessionEnvelopeFile)(options.sessionPath);
|
|
107
|
+
if (!envelope)
|
|
155
108
|
return { kind: "missing" };
|
|
156
|
-
|
|
157
|
-
const parsed = JSON.parse(raw);
|
|
158
|
-
const tailMessages = normalizeSessionMessages(parsed.messages).slice(-options.messageCount);
|
|
109
|
+
const tailMessages = normalizeSessionMessages(envelope.events).slice(-options.messageCount);
|
|
159
110
|
if (tailMessages.length === 0) {
|
|
160
111
|
return { kind: "empty" };
|
|
161
112
|
}
|
|
162
113
|
const transcript = tailMessages
|
|
163
|
-
.map((message) => `[${message.role}] ${message.content}`)
|
|
114
|
+
.map((message) => `[${message.timestamp} | ${message.role} | ${message.id}] ${message.content}`)
|
|
164
115
|
.join("\n");
|
|
165
116
|
const summary = options.summarize
|
|
166
117
|
? await options.summarize(transcript, buildSummaryInstruction(options.friendId, options.channel, options.trustLevel ?? "family"))
|
|
@@ -186,15 +137,10 @@ async function searchSessionTranscript(options) {
|
|
|
186
137
|
maxMatches: options.maxMatches ?? 5,
|
|
187
138
|
},
|
|
188
139
|
});
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
raw = fs.readFileSync(options.sessionPath, "utf-8");
|
|
192
|
-
}
|
|
193
|
-
catch {
|
|
140
|
+
const envelope = (0, session_events_1.loadSessionEnvelopeFile)(options.sessionPath);
|
|
141
|
+
if (!envelope)
|
|
194
142
|
return { kind: "missing" };
|
|
195
|
-
|
|
196
|
-
const parsed = JSON.parse(raw);
|
|
197
|
-
const messages = normalizeSessionMessages(parsed.messages);
|
|
143
|
+
const messages = normalizeSessionMessages(envelope.events);
|
|
198
144
|
if (messages.length === 0) {
|
|
199
145
|
return { kind: "empty" };
|
|
200
146
|
}
|
|
@@ -192,6 +192,7 @@ function buildStartOfTurnPacket(view, opts) {
|
|
|
192
192
|
cares: buildCaresSection(view.activeCares),
|
|
193
193
|
presence: buildPresenceSection(view.peerPresence),
|
|
194
194
|
resumeHint: buildResumeHint(view, opts?.canonicalObligations ? effectiveObligations : undefined),
|
|
195
|
+
currentSessionTiming: opts?.currentSessionTiming,
|
|
195
196
|
tempo,
|
|
196
197
|
tokenBudget,
|
|
197
198
|
assembledAt: new Date().toISOString(),
|
|
@@ -230,6 +231,7 @@ function renderStartOfTurnPacket(packet) {
|
|
|
230
231
|
{ label: "bundleState", content: (0, bundle_state_1.renderBundleStateHint)(packet.bundleState ?? []), priority: 7 },
|
|
231
232
|
{ label: "syncFailure", content: packet.syncFailure ?? "", priority: 7 },
|
|
232
233
|
{ label: "resume", content: packet.resumeHint, priority: 6 },
|
|
234
|
+
{ label: "sessionTiming", content: packet.currentSessionTiming ?? "", priority: 5 },
|
|
233
235
|
{ label: "obligations", content: packet.obligations, priority: 5 },
|
|
234
236
|
{ label: "cares", content: packet.cares, priority: 4 },
|
|
235
237
|
{ label: "plot", content: packet.plotLine, priority: 3 },
|
package/dist/mind/context.js
CHANGED
|
@@ -33,20 +33,23 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.validateSessionMessages = exports.repairSessionMessages = exports.migrateToolNames = void 0;
|
|
36
37
|
exports.trimMessages = trimMessages;
|
|
37
|
-
exports.validateSessionMessages = validateSessionMessages;
|
|
38
|
-
exports.repairSessionMessages = repairSessionMessages;
|
|
39
|
-
exports.migrateToolNames = migrateToolNames;
|
|
40
38
|
exports.saveSession = saveSession;
|
|
41
39
|
exports.appendSyntheticAssistantMessage = appendSyntheticAssistantMessage;
|
|
42
40
|
exports.loadSession = loadSession;
|
|
43
41
|
exports.postTurn = postTurn;
|
|
44
42
|
exports.deleteSession = deleteSession;
|
|
45
43
|
const config_1 = require("../heart/config");
|
|
44
|
+
const session_events_1 = require("../heart/session-events");
|
|
46
45
|
const runtime_1 = require("../nerves/runtime");
|
|
47
46
|
const fs = __importStar(require("fs"));
|
|
48
47
|
const path = __importStar(require("path"));
|
|
49
48
|
const token_estimate_1 = require("./token-estimate");
|
|
49
|
+
var session_events_2 = require("../heart/session-events");
|
|
50
|
+
Object.defineProperty(exports, "migrateToolNames", { enumerable: true, get: function () { return session_events_2.migrateToolNames; } });
|
|
51
|
+
Object.defineProperty(exports, "repairSessionMessages", { enumerable: true, get: function () { return session_events_2.repairSessionMessages; } });
|
|
52
|
+
Object.defineProperty(exports, "validateSessionMessages", { enumerable: true, get: function () { return session_events_2.validateSessionMessages; } });
|
|
50
53
|
function buildTrimmableBlocks(messages) {
|
|
51
54
|
const blocks = [];
|
|
52
55
|
let i = 0;
|
|
@@ -175,163 +178,47 @@ function trimMessages(messages, maxTokens, contextMargin, actualTokenCount) {
|
|
|
175
178
|
* user → assistant (with optional tool calls/results) → user → assistant...
|
|
176
179
|
* Never assistant → assistant without a user in between.
|
|
177
180
|
*/
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (msg.role === "system")
|
|
186
|
-
continue;
|
|
187
|
-
if (msg.role === "tool") {
|
|
188
|
-
sawToolResultSincePrevAssistant = true;
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
if (msg.role === "assistant" && prevNonToolRole === "assistant") {
|
|
192
|
-
// assistant → tool(s) → assistant is valid (tool call flow)
|
|
193
|
-
if (!(prevAssistantHadToolCalls && sawToolResultSincePrevAssistant)) {
|
|
194
|
-
violations.push(`back-to-back assistant at index ${i}`);
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
prevAssistantHadToolCalls = msg.role === "assistant" && Array.isArray(msg.tool_calls) && msg.tool_calls.length > 0;
|
|
198
|
-
sawToolResultSincePrevAssistant = false;
|
|
199
|
-
prevNonToolRole = msg.role;
|
|
200
|
-
}
|
|
201
|
-
return violations;
|
|
202
|
-
}
|
|
203
|
-
/**
|
|
204
|
-
* Repairs session invariant violations by merging consecutive assistant messages.
|
|
205
|
-
*/
|
|
206
|
-
function repairSessionMessages(messages) {
|
|
207
|
-
const violations = validateSessionMessages(messages);
|
|
208
|
-
if (violations.length === 0)
|
|
209
|
-
return messages;
|
|
210
|
-
const result = [];
|
|
211
|
-
for (const msg of messages) {
|
|
212
|
-
if (msg.role === "assistant" && result.length > 0) {
|
|
213
|
-
const prev = result[result.length - 1];
|
|
214
|
-
if (prev.role === "assistant" && !("tool_calls" in prev)) {
|
|
215
|
-
const prevContent = typeof prev.content === "string" ? prev.content : "";
|
|
216
|
-
const curContent = typeof msg.content === "string" ? msg.content : "";
|
|
217
|
-
prev.content = `${prevContent}\n\n${curContent}`;
|
|
218
|
-
continue;
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
result.push(msg);
|
|
222
|
-
}
|
|
223
|
-
(0, runtime_1.emitNervesEvent)({
|
|
224
|
-
level: "info",
|
|
225
|
-
event: "mind.session_invariant_repair",
|
|
226
|
-
component: "mind",
|
|
227
|
-
message: "repaired session invariant violations",
|
|
228
|
-
meta: { violations },
|
|
229
|
-
});
|
|
230
|
-
return result;
|
|
231
|
-
}
|
|
232
|
-
function stripOrphanedToolResults(messages) {
|
|
233
|
-
const validCallIds = new Set();
|
|
234
|
-
for (const msg of messages) {
|
|
235
|
-
if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls))
|
|
236
|
-
continue;
|
|
237
|
-
for (const toolCall of msg.tool_calls)
|
|
238
|
-
validCallIds.add(toolCall.id);
|
|
239
|
-
}
|
|
240
|
-
let removed = 0;
|
|
241
|
-
const repaired = messages.filter((msg) => {
|
|
242
|
-
if (msg.role !== "tool")
|
|
243
|
-
return true;
|
|
244
|
-
const keep = validCallIds.has(msg.tool_call_id);
|
|
245
|
-
if (!keep)
|
|
246
|
-
removed++;
|
|
247
|
-
return keep;
|
|
248
|
-
});
|
|
249
|
-
if (removed > 0) {
|
|
250
|
-
(0, runtime_1.emitNervesEvent)({
|
|
251
|
-
level: "info",
|
|
252
|
-
event: "mind.session_orphan_tool_result_repair",
|
|
253
|
-
component: "mind",
|
|
254
|
-
message: "removed orphaned tool results from session history",
|
|
255
|
-
meta: { removed },
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
return repaired;
|
|
259
|
-
}
|
|
260
|
-
// Tool renames that have shipped. Old names in session history confuse the
|
|
261
|
-
// model into calling tools that no longer exist. Applied on session load so
|
|
262
|
-
// the transcript uses the current vocabulary.
|
|
263
|
-
const TOOL_NAME_MIGRATIONS = {
|
|
264
|
-
final_answer: "settle",
|
|
265
|
-
no_response: "observe",
|
|
266
|
-
go_inward: "ponder",
|
|
267
|
-
descend: "ponder",
|
|
268
|
-
memory_save: "diary_write",
|
|
269
|
-
memory_search: "recall",
|
|
270
|
-
};
|
|
271
|
-
function migrateToolNames(messages) {
|
|
272
|
-
let migrated = 0;
|
|
273
|
-
const result = messages.map((msg) => {
|
|
274
|
-
if (msg.role !== "assistant" || !Array.isArray(msg.tool_calls) || msg.tool_calls.length === 0)
|
|
275
|
-
return msg;
|
|
276
|
-
let changed = false;
|
|
277
|
-
const updatedCalls = msg.tool_calls.map((tc) => {
|
|
278
|
-
if (tc.type !== "function")
|
|
279
|
-
return tc;
|
|
280
|
-
const newName = TOOL_NAME_MIGRATIONS[tc.function.name];
|
|
281
|
-
if (!newName)
|
|
282
|
-
return tc;
|
|
283
|
-
changed = true;
|
|
284
|
-
migrated++;
|
|
285
|
-
return { ...tc, function: { ...tc.function, name: newName } };
|
|
286
|
-
});
|
|
287
|
-
return changed ? { ...msg, tool_calls: updatedCalls } : msg;
|
|
288
|
-
});
|
|
289
|
-
if (migrated > 0) {
|
|
290
|
-
(0, runtime_1.emitNervesEvent)({
|
|
291
|
-
level: "info",
|
|
292
|
-
event: "mind.session_tool_name_migration",
|
|
293
|
-
component: "mind",
|
|
294
|
-
message: "migrated deprecated tool names in session history",
|
|
295
|
-
meta: { migrated },
|
|
296
|
-
});
|
|
297
|
-
}
|
|
298
|
-
return result;
|
|
181
|
+
function denormalizeContinuityState(state) {
|
|
182
|
+
if (!state.mustResolveBeforeHandoff && typeof state.lastFriendActivityAt !== "string")
|
|
183
|
+
return undefined;
|
|
184
|
+
return {
|
|
185
|
+
...(state.mustResolveBeforeHandoff ? { mustResolveBeforeHandoff: true } : {}),
|
|
186
|
+
...(typeof state.lastFriendActivityAt === "string" ? { lastFriendActivityAt: state.lastFriendActivityAt } : {}),
|
|
187
|
+
};
|
|
299
188
|
}
|
|
300
|
-
function
|
|
301
|
-
const violations = validateSessionMessages(messages);
|
|
302
|
-
if (violations.length > 0) {
|
|
303
|
-
(0, runtime_1.emitNervesEvent)({
|
|
304
|
-
level: "info",
|
|
305
|
-
event: "mind.session_invariant_violation",
|
|
306
|
-
component: "mind",
|
|
307
|
-
message: "session invariant violated on save",
|
|
308
|
-
meta: { path: filePath, violations },
|
|
309
|
-
});
|
|
310
|
-
messages = repairSessionMessages(messages);
|
|
311
|
-
}
|
|
312
|
-
messages = stripOrphanedToolResults(messages);
|
|
189
|
+
function writeSessionEnvelope(filePath, envelope) {
|
|
313
190
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
314
|
-
const envelope = { version: 1, messages };
|
|
315
|
-
if (lastUsage)
|
|
316
|
-
envelope.lastUsage = lastUsage;
|
|
317
|
-
if (state?.mustResolveBeforeHandoff === true || typeof state?.lastFriendActivityAt === "string") {
|
|
318
|
-
envelope.state = {
|
|
319
|
-
...(state?.mustResolveBeforeHandoff === true ? { mustResolveBeforeHandoff: true } : {}),
|
|
320
|
-
...(typeof state?.lastFriendActivityAt === "string" ? { lastFriendActivityAt: state.lastFriendActivityAt } : {}),
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
191
|
fs.writeFileSync(filePath, JSON.stringify(envelope, null, 2));
|
|
324
192
|
}
|
|
193
|
+
function saveSession(filePath, messages, lastUsage, state) {
|
|
194
|
+
const existing = (0, session_events_1.loadSessionEnvelopeFile)(filePath);
|
|
195
|
+
const previousMessages = existing ? (0, session_events_1.projectProviderMessages)(existing) : [];
|
|
196
|
+
const sanitized = (0, session_events_1.sanitizeProviderMessages)(messages);
|
|
197
|
+
const envelope = (0, session_events_1.buildCanonicalSessionEnvelope)({
|
|
198
|
+
existing,
|
|
199
|
+
previousMessages,
|
|
200
|
+
currentMessages: sanitized,
|
|
201
|
+
trimmedMessages: sanitized,
|
|
202
|
+
recordedAt: new Date().toISOString(),
|
|
203
|
+
lastUsage: lastUsage ?? null,
|
|
204
|
+
state,
|
|
205
|
+
projectionBasis: {
|
|
206
|
+
maxTokens: null,
|
|
207
|
+
contextMargin: null,
|
|
208
|
+
inputTokens: lastUsage?.input_tokens ?? null,
|
|
209
|
+
},
|
|
210
|
+
});
|
|
211
|
+
writeSessionEnvelope(filePath, envelope);
|
|
212
|
+
}
|
|
325
213
|
function appendSyntheticAssistantMessage(filePath, content) {
|
|
326
214
|
try {
|
|
327
215
|
if (!fs.existsSync(filePath))
|
|
328
216
|
return false;
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
if (data.version !== 1 || !Array.isArray(data.messages))
|
|
217
|
+
const envelope = (0, session_events_1.loadSessionEnvelopeFile)(filePath);
|
|
218
|
+
if (!envelope)
|
|
332
219
|
return false;
|
|
333
|
-
|
|
334
|
-
|
|
220
|
+
const updated = (0, session_events_1.appendSyntheticAssistantEvent)(envelope, content, new Date().toISOString());
|
|
221
|
+
writeSessionEnvelope(filePath, updated);
|
|
335
222
|
(0, runtime_1.emitNervesEvent)({
|
|
336
223
|
component: "mind",
|
|
337
224
|
event: "mind.session_synthetic_message_appended",
|
|
@@ -346,44 +233,25 @@ function appendSyntheticAssistantMessage(filePath, content) {
|
|
|
346
233
|
}
|
|
347
234
|
function loadSession(filePath) {
|
|
348
235
|
try {
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
if (data.version !== 1)
|
|
236
|
+
const envelope = (0, session_events_1.loadSessionEnvelopeFile)(filePath);
|
|
237
|
+
if (!envelope)
|
|
352
238
|
return null;
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
component: "mind",
|
|
360
|
-
message: "session invariant violated on load",
|
|
361
|
-
meta: { path: filePath, violations },
|
|
362
|
-
});
|
|
363
|
-
messages = repairSessionMessages(messages);
|
|
364
|
-
}
|
|
365
|
-
messages = stripOrphanedToolResults(messages);
|
|
366
|
-
messages = migrateToolNames(messages);
|
|
367
|
-
const rawState = data?.state && typeof data.state === "object" && data.state !== null
|
|
368
|
-
? data.state
|
|
369
|
-
: undefined;
|
|
370
|
-
const state = rawState && (rawState.mustResolveBeforeHandoff === true
|
|
371
|
-
|| typeof rawState.lastFriendActivityAt === "string")
|
|
372
|
-
? {
|
|
373
|
-
...(rawState.mustResolveBeforeHandoff === true ? { mustResolveBeforeHandoff: true } : {}),
|
|
374
|
-
...(typeof rawState.lastFriendActivityAt === "string" ? { lastFriendActivityAt: rawState.lastFriendActivityAt } : {}),
|
|
375
|
-
}
|
|
376
|
-
: undefined;
|
|
377
|
-
return { messages, lastUsage: data.lastUsage, state };
|
|
239
|
+
return {
|
|
240
|
+
messages: (0, session_events_1.projectProviderMessages)(envelope),
|
|
241
|
+
events: envelope.events,
|
|
242
|
+
lastUsage: envelope.lastUsage ?? undefined,
|
|
243
|
+
state: denormalizeContinuityState(envelope.state),
|
|
244
|
+
};
|
|
378
245
|
}
|
|
379
246
|
catch {
|
|
380
247
|
return null;
|
|
381
248
|
}
|
|
382
249
|
}
|
|
383
250
|
function postTurn(messages, sessPath, usage, hooks, state) {
|
|
251
|
+
const preTrimMessages = [...messages];
|
|
384
252
|
if (hooks?.beforeTrim) {
|
|
385
253
|
try {
|
|
386
|
-
hooks.beforeTrim(
|
|
254
|
+
hooks.beforeTrim(preTrimMessages);
|
|
387
255
|
}
|
|
388
256
|
catch (error) {
|
|
389
257
|
(0, runtime_1.emitNervesEvent)({
|
|
@@ -398,9 +266,26 @@ function postTurn(messages, sessPath, usage, hooks, state) {
|
|
|
398
266
|
}
|
|
399
267
|
}
|
|
400
268
|
const { maxTokens, contextMargin } = (0, config_1.getContextConfig)();
|
|
401
|
-
const
|
|
269
|
+
const currentMessages = (0, session_events_1.sanitizeProviderMessages)(messages);
|
|
270
|
+
const trimmed = trimMessages(currentMessages, maxTokens, contextMargin, usage?.input_tokens);
|
|
402
271
|
messages.splice(0, messages.length, ...trimmed);
|
|
403
|
-
|
|
272
|
+
const existing = (0, session_events_1.loadSessionEnvelopeFile)(sessPath);
|
|
273
|
+
const previousMessages = existing ? (0, session_events_1.projectProviderMessages)(existing) : [];
|
|
274
|
+
const envelope = (0, session_events_1.buildCanonicalSessionEnvelope)({
|
|
275
|
+
existing,
|
|
276
|
+
previousMessages,
|
|
277
|
+
currentMessages,
|
|
278
|
+
trimmedMessages: trimmed,
|
|
279
|
+
recordedAt: new Date().toISOString(),
|
|
280
|
+
lastUsage: usage ?? null,
|
|
281
|
+
state,
|
|
282
|
+
projectionBasis: {
|
|
283
|
+
maxTokens,
|
|
284
|
+
contextMargin,
|
|
285
|
+
inputTokens: usage?.input_tokens ?? null,
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
writeSessionEnvelope(sessPath, envelope);
|
|
404
289
|
}
|
|
405
290
|
function deleteSession(filePath) {
|
|
406
291
|
try {
|
package/dist/mind/prompt.js
CHANGED
|
@@ -127,8 +127,20 @@ function buildSessionSummary(options) {
|
|
|
127
127
|
return "";
|
|
128
128
|
const lines = ["## active sessions"];
|
|
129
129
|
for (const entry of entries) {
|
|
130
|
-
const
|
|
131
|
-
|
|
130
|
+
const parts = [];
|
|
131
|
+
if (entry.lastInboundAt) {
|
|
132
|
+
parts.push(`in ${formatTimeAgo(now - Date.parse(entry.lastInboundAt))}`);
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
parts.push(`last ${formatTimeAgo(now - entry.lastActivityMs)}`);
|
|
136
|
+
}
|
|
137
|
+
if (entry.lastOutboundAt) {
|
|
138
|
+
parts.push(`out ${formatTimeAgo(now - Date.parse(entry.lastOutboundAt))}`);
|
|
139
|
+
}
|
|
140
|
+
if (entry.unansweredInboundCount > 0) {
|
|
141
|
+
parts.push(`${entry.unansweredInboundCount} waiting`);
|
|
142
|
+
}
|
|
143
|
+
lines.push(`- ${entry.friendName}/${entry.channel}/${entry.key} (${parts.join(" · ")})`);
|
|
132
144
|
}
|
|
133
145
|
return lines.join("\n");
|
|
134
146
|
}
|
|
@@ -91,6 +91,10 @@ const DISPATCH_EXEMPT_PATTERNS = [
|
|
|
91
91
|
"heart/attachments/originals",
|
|
92
92
|
"heart/attachments/sources/index",
|
|
93
93
|
"heart/attachments/sources/cli-local-file",
|
|
94
|
+
// Browser-safe Outlook contract helpers: shared types/formatting helpers
|
|
95
|
+
// consumed by server readers and the UI. Outlook read/render modules own
|
|
96
|
+
// the observability for these projections.
|
|
97
|
+
"heart/outlook/outlook-types",
|
|
94
98
|
];
|
|
95
99
|
function isDispatchExempt(filePath) {
|
|
96
100
|
return DISPATCH_EXEMPT_PATTERNS.some((pattern) => filePath.includes(pattern));
|
|
@@ -253,6 +253,16 @@ function InputArea({ onSubmit, onCtrlC, history, queuedInputs, onPopQueue, agent
|
|
|
253
253
|
// PageUp/PageDown: suppress (no text insertion, no action)
|
|
254
254
|
if (key.pageUp || key.pageDown)
|
|
255
255
|
return;
|
|
256
|
+
// Alt+Enter (single data event): Ink checks `return: input === '\r'` before
|
|
257
|
+
// stripping the \x1b prefix, so key.return is false when the terminal sends
|
|
258
|
+
// \x1b\r as one chunk. Detect via the stripped inputChar instead.
|
|
259
|
+
if (inputChar === "\r" && key.meta) {
|
|
260
|
+
lastEscTime.current = 0;
|
|
261
|
+
const before = inputRef.current.slice(0, cursorRef.current);
|
|
262
|
+
const after = inputRef.current.slice(cursorRef.current);
|
|
263
|
+
updateInput(before + "\n" + after, cursorRef.current + 1);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
256
266
|
if (key.return) {
|
|
257
267
|
// Alt+Enter: detect via key.meta OR recent ESC (within 50ms — Ink splits \x1b\r)
|
|
258
268
|
const recentEsc = (Date.now() - lastEscTime.current) < 50;
|
package/dist/senses/cli.js
CHANGED
|
@@ -985,6 +985,7 @@ async function main(agentName, options) {
|
|
|
985
985
|
// Load existing session or start fresh
|
|
986
986
|
const existing = (0, context_1.loadSession)(sessPath);
|
|
987
987
|
let sessionState = existing?.state;
|
|
988
|
+
let sessionEvents = existing?.events ?? [];
|
|
988
989
|
const mcpManager = await (0, mcp_manager_1.getSharedMcpManager)() ?? undefined;
|
|
989
990
|
const sessionMessages = existing?.messages && existing.messages.length > 0
|
|
990
991
|
? existing.messages
|
|
@@ -1006,6 +1007,7 @@ async function main(agentName, options) {
|
|
|
1006
1007
|
_testInputSource: options?._testInputSource,
|
|
1007
1008
|
onAsyncAssistantMessage: async (messages, _assistantMessage) => {
|
|
1008
1009
|
(0, context_1.postTurn)(messages, sessPath, undefined, undefined, sessionState);
|
|
1010
|
+
sessionEvents = (0, context_1.loadSession)(sessPath)?.events ?? sessionEvents;
|
|
1009
1011
|
},
|
|
1010
1012
|
runTurn: async (messages, userInput, callbacks, signal, toolContext, userContent) => {
|
|
1011
1013
|
// Run the full per-turn pipeline: resolve -> gate -> session -> drain -> runAgent -> postTurn -> tokens
|
|
@@ -1044,6 +1046,7 @@ async function main(agentName, options) {
|
|
|
1044
1046
|
messages,
|
|
1045
1047
|
sessionPath: sessPath,
|
|
1046
1048
|
state: sessionState,
|
|
1049
|
+
events: sessionEvents,
|
|
1047
1050
|
}),
|
|
1048
1051
|
},
|
|
1049
1052
|
pendingDir,
|
|
@@ -1065,6 +1068,7 @@ async function main(agentName, options) {
|
|
|
1065
1068
|
postTurn: (turnMessages, sessionPathArg, usage, hooks, state) => {
|
|
1066
1069
|
(0, context_1.postTurn)(turnMessages, sessionPathArg, usage, hooks, state);
|
|
1067
1070
|
sessionState = state;
|
|
1071
|
+
sessionEvents = (0, context_1.loadSession)(sessionPathArg)?.events ?? sessionEvents;
|
|
1068
1072
|
},
|
|
1069
1073
|
accumulateFriendTokens: tokens_1.accumulateFriendTokens,
|
|
1070
1074
|
signal,
|
package/dist/senses/pipeline.js
CHANGED
|
@@ -60,6 +60,7 @@ const start_of_turn_packet_1 = require("../heart/start-of-turn-packet");
|
|
|
60
60
|
const bundle_state_1 = require("../heart/bundle-state");
|
|
61
61
|
const sync_1 = require("../heart/sync");
|
|
62
62
|
const config_1 = require("../heart/config");
|
|
63
|
+
const session_events_1 = require("../heart/session-events");
|
|
63
64
|
const presence_1 = require("../arc/presence");
|
|
64
65
|
const episodes_1 = require("../arc/episodes");
|
|
65
66
|
const turn_context_1 = require("../heart/turn-context");
|
|
@@ -258,6 +259,7 @@ async function handleInboundTurn(input) {
|
|
|
258
259
|
// Step 3: Load/create session
|
|
259
260
|
const session = await input.sessionLoader.loadOrCreate();
|
|
260
261
|
const sessionMessages = session.messages;
|
|
262
|
+
const sessionEvents = session.events ?? [];
|
|
261
263
|
let mustResolveBeforeHandoff = (0, continuity_1.resolveMustResolveBeforeHandoff)(session.state?.mustResolveBeforeHandoff === true, input.continuityIngressTexts);
|
|
262
264
|
const lastFriendActivityAt = input.channel === "inner"
|
|
263
265
|
? session.state?.lastFriendActivityAt
|
|
@@ -272,6 +274,7 @@ async function handleInboundTurn(input) {
|
|
|
272
274
|
key: input.sessionKey ?? "session",
|
|
273
275
|
sessionPath: session.sessionPath,
|
|
274
276
|
};
|
|
277
|
+
const currentSessionTiming = (0, session_events_1.describeCurrentSessionTiming)(sessionEvents);
|
|
275
278
|
// Step 3b: Pre-turn sync pull (opt-in) — MUST happen before any continuity state reads
|
|
276
279
|
// so that obligations, episodes, cares, etc. reflect the latest remote state.
|
|
277
280
|
let syncFailure;
|
|
@@ -384,6 +387,7 @@ async function handleInboundTurn(input) {
|
|
|
384
387
|
primary: activeWorkFrame.primaryObligation,
|
|
385
388
|
all: activeWorkFrame.pendingObligations,
|
|
386
389
|
},
|
|
390
|
+
currentSessionTiming,
|
|
387
391
|
});
|
|
388
392
|
/* v8 ignore next 3 -- syncFailure propagation tested in sync.test.ts @preserve */
|
|
389
393
|
if (syncFailure) {
|
package/dist/senses/teams.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ouro.bot/cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.316",
|
|
4
4
|
"main": "dist/heart/daemon/ouro-entry.js",
|
|
5
5
|
"bin": {
|
|
6
6
|
"cli": "dist/heart/daemon/ouro-bot-entry.js",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"teams": "tsc && node dist/senses/teams-entry.js --agent ouroboros",
|
|
27
27
|
"bluebubbles": "tsc && node dist/senses/bluebubbles/entry.js --agent ouroboros",
|
|
28
28
|
"test": "vitest run",
|
|
29
|
+
"test:outlook-ui": "npm test --prefix packages/outlook-ui",
|
|
29
30
|
"test:coverage:vitest": "vitest run --coverage",
|
|
30
31
|
"test:coverage": "node scripts/run-coverage-gate.cjs",
|
|
31
32
|
"build": "tsc && (cd packages/outlook-ui && npm install --ignore-scripts 2>/dev/null && npm run build && cp -r dist ../../dist/outlook-ui) || echo 'outlook-ui build skipped'",
|
|
@@ -50,11 +51,19 @@
|
|
|
50
51
|
"url": "https://github.com/ouroborosbot/ouroboros"
|
|
51
52
|
},
|
|
52
53
|
"devDependencies": {
|
|
54
|
+
"@testing-library/react": "^16.3.2",
|
|
53
55
|
"@types/semver": "^7.7.1",
|
|
54
56
|
"@vitest/coverage-v8": "^4.0.18",
|
|
55
57
|
"eslint": "^10.0.2",
|
|
58
|
+
"jsdom": "^29.0.2",
|
|
56
59
|
"typescript": "^5.7.0",
|
|
57
60
|
"typescript-eslint": "^8.56.1",
|
|
58
61
|
"vitest": "^4.0.18"
|
|
62
|
+
},
|
|
63
|
+
"overrides": {
|
|
64
|
+
"@testing-library/react": {
|
|
65
|
+
"react": "$react",
|
|
66
|
+
"@types/react": "$@types/react"
|
|
67
|
+
}
|
|
59
68
|
}
|
|
60
69
|
}
|