@ouro.bot/cli 0.1.0-alpha.314 → 0.1.0-alpha.315
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 +6 -0
- package/dist/heart/daemon/cli-exec.js +22 -8
- package/dist/heart/daemon/startup-tui.js +68 -30
- 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/outlook-ui/assets/{index-Ck8agNeO.js → index-BiYn3Fwj.js} +13 -13
- package/dist/outlook-ui/index.html +1 -1
- package/dist/senses/bluebubbles/index.js +1 -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 +1 -1
package/changelog.json
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
|
|
3
3
|
"versions": [
|
|
4
|
+
{
|
|
5
|
+
"version": "0.1.0-alpha.315",
|
|
6
|
+
"changes": [
|
|
7
|
+
"fix(tui): fix startup TUI ANSI overwrite, daemon death detection, and log visibility. Resolves visual corruption from overlapping ANSI escape sequences, improves process death detection reliability, and ensures daemon log output is consistently visible during startup."
|
|
8
|
+
]
|
|
9
|
+
},
|
|
4
10
|
{
|
|
5
11
|
"version": "0.1.0-alpha.314",
|
|
6
12
|
"changes": [
|
|
@@ -118,24 +118,38 @@ async function ensureDaemonRunning(deps) {
|
|
|
118
118
|
const stability = await (0, startup_tui_1.pollDaemonStartup)({
|
|
119
119
|
sendCommand: deps.sendCommand,
|
|
120
120
|
socketPath: deps.socketPath,
|
|
121
|
-
|
|
121
|
+
daemonPid: started.pid ?? null,
|
|
122
|
+
/* v8 ignore next -- thin wrapper: raw process.stdout.write for ANSI cursor control @preserve */
|
|
123
|
+
writeRaw: (text) => process.stdout.write(text),
|
|
122
124
|
/* v8 ignore next -- thin wrapper: real Date.now() injected for testability @preserve */
|
|
123
125
|
now: () => Date.now(),
|
|
124
126
|
/* v8 ignore next -- thin wrapper: real setTimeout injected for testability @preserve */
|
|
125
127
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
126
|
-
/* v8 ignore start -- daemon log tail: reads real filesystem, tested via deployment @preserve */
|
|
128
|
+
/* v8 ignore start -- daemon log tail + pid check: reads real filesystem, tested via deployment @preserve */
|
|
127
129
|
readLatestDaemonEvent: () => {
|
|
128
130
|
try {
|
|
129
|
-
|
|
131
|
+
// The daemon writes structured events to daemon.ndjson in the first
|
|
132
|
+
// agent bundle's state/daemon/logs/ directory. Read the last line to
|
|
133
|
+
// surface what it's currently doing (e.g., "starting auto-start agents").
|
|
134
|
+
const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
|
|
135
|
+
if (!fs.existsSync(bundlesRoot))
|
|
136
|
+
return null;
|
|
137
|
+
const agents = fs.readdirSync(bundlesRoot).filter((d) => d.endsWith(".ouro"));
|
|
130
138
|
for (const agent of agents) {
|
|
131
|
-
const logPath = path.join(
|
|
139
|
+
const logPath = path.join(bundlesRoot, agent, "state", "daemon", "logs", "daemon.ndjson");
|
|
132
140
|
if (!fs.existsSync(logPath))
|
|
133
141
|
continue;
|
|
134
|
-
const
|
|
142
|
+
const stat = fs.statSync(logPath);
|
|
143
|
+
if (stat.size === 0)
|
|
144
|
+
continue;
|
|
145
|
+
// Only read logs from the last 30 seconds (daemon just started)
|
|
146
|
+
const mtime = stat.mtimeMs;
|
|
147
|
+
if (Date.now() - mtime > 30_000)
|
|
148
|
+
continue;
|
|
149
|
+
const buf = Buffer.alloc(4096);
|
|
135
150
|
const fd = fs.openSync(logPath, "r");
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
fs.readSync(fd, buf, 0, 2048, readFrom);
|
|
151
|
+
const readFrom = Math.max(0, stat.size - 4096);
|
|
152
|
+
fs.readSync(fd, buf, 0, 4096, readFrom);
|
|
139
153
|
fs.closeSync(fd);
|
|
140
154
|
const lines = buf.toString("utf-8").trim().split("\n").filter(Boolean);
|
|
141
155
|
const last = lines[lines.length - 1];
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
14
|
exports.assessStability = assessStability;
|
|
15
15
|
exports.renderStartupProgress = renderStartupProgress;
|
|
16
|
+
exports.renderWaitingForDaemon = renderWaitingForDaemon;
|
|
16
17
|
exports.pollDaemonStartup = pollDaemonStartup;
|
|
17
18
|
const cli_render_1 = require("./cli-render");
|
|
18
19
|
const runtime_1 = require("../../nerves/runtime");
|
|
@@ -66,19 +67,13 @@ function assessStability(payload, now) {
|
|
|
66
67
|
/**
|
|
67
68
|
* Build an ANSI string for in-place terminal display during polling.
|
|
68
69
|
* Uses cursor-up and line-clear escapes to overwrite previous output.
|
|
69
|
-
*
|
|
70
|
-
* @param payload Current daemon status
|
|
71
|
-
* @param elapsed Milliseconds since polling started
|
|
72
|
-
* @param prevLineCount Number of lines written in the previous render (0 on first)
|
|
73
70
|
*/
|
|
74
71
|
function renderStartupProgress(payload, elapsed, prevLineCount = 0) {
|
|
75
72
|
const frameIndex = Math.floor(elapsed / 100) % SPINNER_FRAMES.length;
|
|
76
73
|
const spinner = SPINNER_FRAMES[frameIndex];
|
|
77
74
|
const lines = [];
|
|
78
|
-
// Header line
|
|
79
75
|
const elapsedSec = (elapsed / 1000).toFixed(1);
|
|
80
76
|
lines.push(`${spinner} ${BOLD}waiting for agents${RESET} ${DIM}(${elapsedSec}s)${RESET}`);
|
|
81
|
-
// Per-worker status lines
|
|
82
77
|
for (const worker of payload.workers) {
|
|
83
78
|
const statusColor = worker.status === "running" ? GREEN
|
|
84
79
|
: worker.status === "crashed" ? RED
|
|
@@ -86,7 +81,27 @@ function renderStartupProgress(payload, elapsed, prevLineCount = 0) {
|
|
|
86
81
|
const statusText = `${statusColor}${worker.status}${RESET}`;
|
|
87
82
|
lines.push(` ${worker.agent}/${worker.worker}: ${statusText}`);
|
|
88
83
|
}
|
|
89
|
-
|
|
84
|
+
let output = "";
|
|
85
|
+
if (prevLineCount > 0) {
|
|
86
|
+
output += `\x1b[${prevLineCount}A`;
|
|
87
|
+
}
|
|
88
|
+
for (const line of lines) {
|
|
89
|
+
output += `\x1b[2K${line}\n`;
|
|
90
|
+
}
|
|
91
|
+
return output;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Render a pre-socket status line showing what the daemon is doing.
|
|
95
|
+
*/
|
|
96
|
+
function renderWaitingForDaemon(elapsed, latestEvent, prevLineCount = 0) {
|
|
97
|
+
const elapsedSec = (elapsed / 1000).toFixed(1);
|
|
98
|
+
const frameIndex = Math.floor(elapsed / 100) % SPINNER_FRAMES.length;
|
|
99
|
+
const spinner = SPINNER_FRAMES[frameIndex];
|
|
100
|
+
const lines = [];
|
|
101
|
+
lines.push(`${spinner} ${BOLD}waiting for daemon${RESET} ${DIM}(${elapsedSec}s)${RESET}`);
|
|
102
|
+
if (latestEvent) {
|
|
103
|
+
lines.push(` ${DIM}${latestEvent}${RESET}`);
|
|
104
|
+
}
|
|
90
105
|
let output = "";
|
|
91
106
|
if (prevLineCount > 0) {
|
|
92
107
|
output += `\x1b[${prevLineCount}A`;
|
|
@@ -119,15 +134,19 @@ function renderFinalSummary(result) {
|
|
|
119
134
|
/**
|
|
120
135
|
* Poll the daemon's status socket until all agents are stable or definitively
|
|
121
136
|
* failed, rendering real-time progress to the terminal.
|
|
137
|
+
*
|
|
138
|
+
* Detects daemon process death: if the spawned PID is no longer alive and the
|
|
139
|
+
* socket never came up, reports the failure immediately instead of spinning.
|
|
122
140
|
*/
|
|
123
141
|
async function pollDaemonStartup(deps) {
|
|
124
142
|
const startTime = deps.now();
|
|
125
143
|
let prevLineCount = 0;
|
|
144
|
+
const isAlive = deps.isProcessAlive ?? defaultIsProcessAlive;
|
|
126
145
|
(0, runtime_1.emitNervesEvent)({
|
|
127
146
|
component: "daemon",
|
|
128
147
|
event: "daemon.startup_poll_start",
|
|
129
148
|
message: "beginning startup stability polling",
|
|
130
|
-
meta: { socketPath: deps.socketPath },
|
|
149
|
+
meta: { socketPath: deps.socketPath, daemonPid: deps.daemonPid },
|
|
131
150
|
});
|
|
132
151
|
while (true) {
|
|
133
152
|
const now = deps.now();
|
|
@@ -138,39 +157,47 @@ async function pollDaemonStartup(deps) {
|
|
|
138
157
|
payload = (0, cli_render_1.parseStatusPayload)(response.data);
|
|
139
158
|
}
|
|
140
159
|
catch {
|
|
141
|
-
// Socket not yet available —
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
160
|
+
// Socket not yet available — check if the daemon process is still alive
|
|
161
|
+
if (deps.daemonPid !== null && !isAlive(deps.daemonPid)) {
|
|
162
|
+
const latestEvent = deps.readLatestDaemonEvent?.() ?? null;
|
|
163
|
+
const errorMsg = latestEvent ?? "daemon process died during startup";
|
|
164
|
+
(0, runtime_1.emitNervesEvent)({
|
|
165
|
+
level: "error",
|
|
166
|
+
component: "daemon",
|
|
167
|
+
event: "daemon.startup_process_died",
|
|
168
|
+
message: "daemon process died before socket came up",
|
|
169
|
+
meta: { pid: deps.daemonPid, lastEvent: latestEvent },
|
|
170
|
+
});
|
|
171
|
+
// Clear the waiting line
|
|
172
|
+
if (prevLineCount > 0) {
|
|
173
|
+
let clear = `\x1b[${prevLineCount}A`;
|
|
174
|
+
for (let i = 0; i < prevLineCount; i++)
|
|
175
|
+
clear += `\x1b[2K\n`;
|
|
176
|
+
deps.writeRaw(clear);
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
stable: [],
|
|
180
|
+
degraded: [{ agent: "daemon", errorReason: errorMsg, fixHint: "check daemon logs or run `ouro doctor`" }],
|
|
181
|
+
};
|
|
157
182
|
}
|
|
158
|
-
|
|
159
|
-
|
|
183
|
+
// Show what the daemon is doing from its log
|
|
184
|
+
const latestEvent = deps.readLatestDaemonEvent?.() ?? null;
|
|
185
|
+
const output = renderWaitingForDaemon(elapsed, latestEvent, prevLineCount);
|
|
186
|
+
deps.writeRaw(output);
|
|
187
|
+
prevLineCount = latestEvent ? 2 : 1;
|
|
160
188
|
}
|
|
161
189
|
if (payload) {
|
|
162
190
|
const output = renderStartupProgress(payload, elapsed, prevLineCount);
|
|
163
|
-
deps.
|
|
164
|
-
prevLineCount = payload.workers.length + 1;
|
|
191
|
+
deps.writeRaw(output);
|
|
192
|
+
prevLineCount = payload.workers.length + 1;
|
|
165
193
|
const assessment = assessStability(payload, now);
|
|
166
194
|
if (assessment.resolved) {
|
|
167
195
|
const result = {
|
|
168
196
|
stable: assessment.stable,
|
|
169
197
|
degraded: assessment.degraded,
|
|
170
198
|
};
|
|
171
|
-
// Clear progress lines and render final summary
|
|
172
199
|
const summary = renderFinalSummary(result);
|
|
173
|
-
deps.
|
|
200
|
+
deps.writeRaw(summary);
|
|
174
201
|
(0, runtime_1.emitNervesEvent)({
|
|
175
202
|
component: "daemon",
|
|
176
203
|
event: "daemon.startup_poll_end",
|
|
@@ -187,3 +214,14 @@ async function pollDaemonStartup(deps) {
|
|
|
187
214
|
await deps.sleep(POLL_INTERVAL_MS);
|
|
188
215
|
}
|
|
189
216
|
}
|
|
217
|
+
/* v8 ignore start -- process liveness check: uses real process.kill(0), tested via deployment @preserve */
|
|
218
|
+
function defaultIsProcessAlive(pid) {
|
|
219
|
+
try {
|
|
220
|
+
process.kill(pid, 0);
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/* v8 ignore stop */
|
|
@@ -68,6 +68,7 @@ const outlook_types_1 = require("./outlook-types");
|
|
|
68
68
|
const presence_1 = require("../../arc/presence");
|
|
69
69
|
const cares_1 = require("../../arc/cares");
|
|
70
70
|
const episodes_1 = require("../../arc/episodes");
|
|
71
|
+
const session_events_1 = require("../session-events");
|
|
71
72
|
const LIVE_TASK_STATUSES = ["processing", "validating", "collaborating", "blocked"];
|
|
72
73
|
const ACTIVE_CODING_STATUSES = new Set(["spawning", "running", "waiting_input", "stalled"]);
|
|
73
74
|
const BLOCKED_CODING_STATUSES = new Set(["waiting_input", "stalled"]);
|
|
@@ -378,14 +379,18 @@ function readOutlookMachineState(options = {}) {
|
|
|
378
379
|
agents: agentStates.map(summarizeAgent),
|
|
379
380
|
};
|
|
380
381
|
}
|
|
382
|
+
// ---------------------------------------------------------------------------
|
|
383
|
+
// Session inventory — enumerate all sessions with summary metadata
|
|
384
|
+
// ---------------------------------------------------------------------------
|
|
381
385
|
/* v8 ignore start — session envelope parsing utilities */
|
|
382
386
|
function parseSessionUsage(raw) {
|
|
383
|
-
if (!raw)
|
|
387
|
+
if (!raw || typeof raw !== "object")
|
|
384
388
|
return null;
|
|
385
|
-
const
|
|
386
|
-
const
|
|
387
|
-
const
|
|
388
|
-
const
|
|
389
|
+
const record = raw;
|
|
390
|
+
const inputTokens = typeof record.input_tokens === "number" ? record.input_tokens : 0;
|
|
391
|
+
const outputTokens = typeof record.output_tokens === "number" ? record.output_tokens : 0;
|
|
392
|
+
const reasoningTokens = typeof record.reasoning_tokens === "number" ? record.reasoning_tokens : 0;
|
|
393
|
+
const totalTokens = typeof record.total_tokens === "number" ? record.total_tokens : 0;
|
|
389
394
|
if (inputTokens === 0 && outputTokens === 0 && totalTokens === 0)
|
|
390
395
|
return null;
|
|
391
396
|
return { input_tokens: inputTokens, output_tokens: outputTokens, reasoning_tokens: reasoningTokens, total_tokens: totalTokens };
|
|
@@ -393,33 +398,29 @@ function parseSessionUsage(raw) {
|
|
|
393
398
|
function parseSessionContinuity(raw) {
|
|
394
399
|
if (!raw)
|
|
395
400
|
return null;
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
401
|
+
if (typeof raw !== "object")
|
|
402
|
+
return null;
|
|
403
|
+
const record = raw;
|
|
404
|
+
const continuity = {
|
|
405
|
+
mustResolveBeforeHandoff: record.mustResolveBeforeHandoff === true,
|
|
406
|
+
lastFriendActivityAt: typeof record.lastFriendActivityAt === "string" ? record.lastFriendActivityAt : null,
|
|
399
407
|
};
|
|
408
|
+
if (!continuity.mustResolveBeforeHandoff && continuity.lastFriendActivityAt === null)
|
|
409
|
+
return null;
|
|
410
|
+
return continuity;
|
|
400
411
|
}
|
|
401
|
-
function extractContent(
|
|
402
|
-
if (
|
|
403
|
-
return
|
|
404
|
-
|
|
412
|
+
function extractContent(event) {
|
|
413
|
+
if (!event)
|
|
414
|
+
return null;
|
|
415
|
+
const text = (0, session_events_1.extractEventText)(event);
|
|
416
|
+
return text.length > 0 ? text : null;
|
|
405
417
|
}
|
|
406
|
-
function extractToolCallNames(
|
|
407
|
-
|
|
408
|
-
if (!Array.isArray(toolCalls))
|
|
418
|
+
function extractToolCallNames(event) {
|
|
419
|
+
if (!event)
|
|
409
420
|
return [];
|
|
410
|
-
return toolCalls
|
|
411
|
-
.map((call) =>
|
|
412
|
-
|
|
413
|
-
const fn = call.function;
|
|
414
|
-
if (fn && typeof fn === "object" && "name" in fn) {
|
|
415
|
-
return typeof fn.name === "string" ? fn.name : null;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
/* v8 ignore start */
|
|
419
|
-
return null;
|
|
420
|
-
/* v8 ignore stop */
|
|
421
|
-
})
|
|
422
|
-
.filter((name) => name !== null);
|
|
421
|
+
return event.toolCalls
|
|
422
|
+
.map((call) => call.function.name)
|
|
423
|
+
.filter((name) => typeof name === "string" && name.length > 0);
|
|
423
424
|
}
|
|
424
425
|
/* v8 ignore stop */
|
|
425
426
|
function estimateTokenCount(messages) {
|
|
@@ -428,21 +429,13 @@ function estimateTokenCount(messages) {
|
|
|
428
429
|
const content = extractContent(msg);
|
|
429
430
|
if (content)
|
|
430
431
|
charCount += content.length;
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
charCount += JSON.stringify(toolCalls).length;
|
|
434
|
-
}
|
|
432
|
+
if (msg.toolCalls.length > 0)
|
|
433
|
+
charCount += JSON.stringify(msg.toolCalls).length;
|
|
435
434
|
}
|
|
436
435
|
return Math.ceil(charCount / 4);
|
|
437
436
|
}
|
|
438
437
|
function readSessionEnvelope(sessionPath) {
|
|
439
|
-
|
|
440
|
-
const raw = fs.readFileSync(sessionPath, "utf-8");
|
|
441
|
-
return JSON.parse(raw);
|
|
442
|
-
}
|
|
443
|
-
catch {
|
|
444
|
-
return null;
|
|
445
|
-
}
|
|
438
|
+
return (0, session_events_1.loadSessionEnvelopeFile)(sessionPath);
|
|
446
439
|
}
|
|
447
440
|
/* v8 ignore start — filesystem traversal with defensive isDirectory checks */
|
|
448
441
|
function resolveAllSessionPaths(sessionsDir) {
|
|
@@ -516,18 +509,26 @@ function readSessionInventory(agentName, options = {}) {
|
|
|
516
509
|
if (friendId === "self" && channel === "inner")
|
|
517
510
|
continue;
|
|
518
511
|
const envelope = readSessionEnvelope(sessionPath);
|
|
519
|
-
const
|
|
512
|
+
const events = envelope?.events ?? [];
|
|
513
|
+
const chronology = (0, session_events_1.deriveSessionChronology)(events);
|
|
520
514
|
const lastUsage = parseSessionUsage(envelope?.lastUsage);
|
|
521
515
|
const continuity = parseSessionContinuity(envelope?.state);
|
|
522
|
-
const
|
|
523
|
-
const
|
|
524
|
-
|
|
525
|
-
|
|
516
|
+
const hasObservedEventTiming = events.some((event) => event.time.authoredAt !== null || event.time.observedAt !== null);
|
|
517
|
+
const lastActivityAt = hasObservedEventTiming
|
|
518
|
+
? (chronology.lastActivityAt ?? continuity?.lastFriendActivityAt ?? safeFileMtime(sessionPath) ?? now.toISOString())
|
|
519
|
+
: (continuity?.lastFriendActivityAt ?? safeFileMtime(sessionPath) ?? now.toISOString());
|
|
520
|
+
const activitySource = hasObservedEventTiming && chronology.lastActivityAt
|
|
521
|
+
? "event-timeline"
|
|
522
|
+
: continuity?.lastFriendActivityAt
|
|
523
|
+
? "friend-facing"
|
|
524
|
+
: "mtime-fallback";
|
|
525
|
+
const userMessages = events.filter((m) => m.role === "user");
|
|
526
|
+
const assistantMessages = events.filter((m) => m.role === "assistant");
|
|
526
527
|
const lastUser = userMessages.length > 0 ? userMessages[userMessages.length - 1] : null;
|
|
527
528
|
const lastAssistant = assistantMessages.length > 0 ? assistantMessages[assistantMessages.length - 1] : null;
|
|
528
529
|
const latestToolCallNames = [];
|
|
529
|
-
for (let i =
|
|
530
|
-
const names = extractToolCallNames(
|
|
530
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
531
|
+
const names = extractToolCallNames(events[i]);
|
|
531
532
|
if (names.length > 0) {
|
|
532
533
|
latestToolCallNames.push(...names);
|
|
533
534
|
break;
|
|
@@ -535,7 +536,7 @@ function readSessionInventory(agentName, options = {}) {
|
|
|
535
536
|
}
|
|
536
537
|
const friendName = resolveFriendName(friendsDir, friendId);
|
|
537
538
|
// Derive reply state from message pattern
|
|
538
|
-
const lastMsg =
|
|
539
|
+
const lastMsg = events.length > 0 ? events[events.length - 1] : null;
|
|
539
540
|
const mustResolve = continuity?.mustResolveBeforeHandoff === true;
|
|
540
541
|
let replyState = "idle";
|
|
541
542
|
if (mustResolve) {
|
|
@@ -544,7 +545,7 @@ function readSessionInventory(agentName, options = {}) {
|
|
|
544
545
|
else if (lastMsg?.role === "user") {
|
|
545
546
|
replyState = "needs-reply";
|
|
546
547
|
}
|
|
547
|
-
else if (
|
|
548
|
+
else if (events.length > 0) {
|
|
548
549
|
replyState = "monitoring";
|
|
549
550
|
}
|
|
550
551
|
items.push({
|
|
@@ -556,13 +557,13 @@ function readSessionInventory(agentName, options = {}) {
|
|
|
556
557
|
lastActivityAt,
|
|
557
558
|
activitySource,
|
|
558
559
|
replyState,
|
|
559
|
-
messageCount:
|
|
560
|
+
messageCount: events.length,
|
|
560
561
|
lastUsage,
|
|
561
562
|
continuity,
|
|
562
|
-
latestUserExcerpt: truncateExcerpt(extractContent(lastUser
|
|
563
|
-
latestAssistantExcerpt: truncateExcerpt(extractContent(lastAssistant
|
|
563
|
+
latestUserExcerpt: truncateExcerpt(extractContent(lastUser)),
|
|
564
|
+
latestAssistantExcerpt: truncateExcerpt(extractContent(lastAssistant)),
|
|
564
565
|
latestToolCallNames,
|
|
565
|
-
estimatedTokens:
|
|
566
|
+
estimatedTokens: events.length > 0 ? estimateTokenCount(events) : null,
|
|
566
567
|
});
|
|
567
568
|
}
|
|
568
569
|
items.sort((a, b) => b.lastActivityAt.localeCompare(a.lastActivityAt));
|
|
@@ -606,34 +607,10 @@ function readSessionTranscript(agentName, friendId, channel, key, options = {})
|
|
|
606
607
|
const envelope = readSessionEnvelope(sessionPath);
|
|
607
608
|
if (!envelope)
|
|
608
609
|
return null;
|
|
609
|
-
const rawMessages =
|
|
610
|
+
const rawMessages = envelope.events;
|
|
610
611
|
const friendsDir = path.join(agentRoot, "friends");
|
|
611
612
|
const friendName = resolveFriendName(friendsDir, friendId);
|
|
612
|
-
const messages = rawMessages
|
|
613
|
-
const role = typeof msg.role === "string" ? msg.role : "user";
|
|
614
|
-
const content = extractContent(msg);
|
|
615
|
-
const result = { index, role, content };
|
|
616
|
-
if (typeof msg.name === "string")
|
|
617
|
-
result.name = msg.name;
|
|
618
|
-
if (typeof msg.tool_call_id === "string")
|
|
619
|
-
result.tool_call_id = msg.tool_call_id;
|
|
620
|
-
if (Array.isArray(msg.tool_calls)) {
|
|
621
|
-
result.tool_calls = msg.tool_calls
|
|
622
|
-
.filter((call) => call != null && typeof call === "object")
|
|
623
|
-
.map((call) => {
|
|
624
|
-
const fn = call.function;
|
|
625
|
-
return {
|
|
626
|
-
id: typeof call.id === "string" ? call.id : "",
|
|
627
|
-
type: typeof call.type === "string" ? call.type : "function",
|
|
628
|
-
function: {
|
|
629
|
-
name: typeof fn?.name === "string" ? fn.name : "unknown",
|
|
630
|
-
arguments: typeof fn?.arguments === "string" ? fn.arguments : JSON.stringify(fn?.arguments ?? ""),
|
|
631
|
-
},
|
|
632
|
-
};
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
return result;
|
|
636
|
-
});
|
|
613
|
+
const messages = rawMessages;
|
|
637
614
|
return {
|
|
638
615
|
friendId,
|
|
639
616
|
friendName,
|
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.OUTLOOK_DEFAULT_PORT = exports.OUTLOOK_DEFAULT_INNER_VISIBILITY = exports.OUTLOOK_RELEASE_INTERACTION_MODEL = exports.OUTLOOK_PRODUCT_NAME = void 0;
|
|
4
|
+
exports.getOutlookTranscriptMessageText = getOutlookTranscriptMessageText;
|
|
5
|
+
exports.getOutlookTranscriptTimestamp = getOutlookTranscriptTimestamp;
|
|
4
6
|
exports.OUTLOOK_PRODUCT_NAME = "Ouro Outlook";
|
|
5
7
|
exports.OUTLOOK_RELEASE_INTERACTION_MODEL = "read-only";
|
|
6
8
|
exports.OUTLOOK_DEFAULT_INNER_VISIBILITY = "summary";
|
|
7
9
|
exports.OUTLOOK_DEFAULT_PORT = 6876;
|
|
10
|
+
function transcriptContentText(content) {
|
|
11
|
+
if (typeof content === "string")
|
|
12
|
+
return content;
|
|
13
|
+
if (!Array.isArray(content))
|
|
14
|
+
return "";
|
|
15
|
+
return content
|
|
16
|
+
.map((part) => (part.type === "text" && typeof part.text === "string"
|
|
17
|
+
? part.text
|
|
18
|
+
: ""))
|
|
19
|
+
.filter((text) => text.length > 0)
|
|
20
|
+
.join("");
|
|
21
|
+
}
|
|
22
|
+
function getOutlookTranscriptMessageText(message) {
|
|
23
|
+
return transcriptContentText(message.content);
|
|
24
|
+
}
|
|
25
|
+
function getOutlookTranscriptTimestamp(message) {
|
|
26
|
+
return message.time.authoredAt ?? message.time.observedAt ?? message.time.recordedAt;
|
|
27
|
+
}
|
|
@@ -39,6 +39,7 @@ const fs = __importStar(require("fs"));
|
|
|
39
39
|
const path = __importStar(require("path"));
|
|
40
40
|
const runtime_1 = require("../nerves/runtime");
|
|
41
41
|
const config_1 = require("./config");
|
|
42
|
+
const session_events_1 = require("./session-events");
|
|
42
43
|
const DEFAULT_ACTIVE_THRESHOLD_MS = 24 * 60 * 60 * 1000;
|
|
43
44
|
function activityPriority(source) {
|
|
44
45
|
return source === "friend-facing" ? 0 : 1;
|
|
@@ -63,28 +64,42 @@ function parseFriendActivity(sessionPath) {
|
|
|
63
64
|
catch {
|
|
64
65
|
return null;
|
|
65
66
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
67
|
+
const envelope = (0, session_events_1.loadSessionEnvelopeFile)(sessionPath);
|
|
68
|
+
const chronology = envelope ? (0, session_events_1.deriveSessionChronology)(envelope.events) : null;
|
|
69
|
+
const explicit = envelope?.state.lastFriendActivityAt;
|
|
70
|
+
if (typeof explicit === "string") {
|
|
71
|
+
const parsedMs = Date.parse(explicit);
|
|
72
|
+
if (Number.isFinite(parsedMs)) {
|
|
73
|
+
return {
|
|
74
|
+
lastActivityMs: parsedMs,
|
|
75
|
+
lastActivityAt: new Date(parsedMs).toISOString(),
|
|
76
|
+
activitySource: "friend-facing",
|
|
77
|
+
lastInboundAt: chronology?.lastInboundAt ?? null,
|
|
78
|
+
lastOutboundAt: chronology?.lastOutboundAt ?? null,
|
|
79
|
+
unansweredInboundCount: chronology?.unansweredInboundCount ?? 0,
|
|
80
|
+
};
|
|
79
81
|
}
|
|
80
82
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
+
if (chronology?.lastInboundAt) {
|
|
84
|
+
const parsedMs = Date.parse(chronology.lastInboundAt);
|
|
85
|
+
if (Number.isFinite(parsedMs)) {
|
|
86
|
+
return {
|
|
87
|
+
lastActivityMs: parsedMs,
|
|
88
|
+
lastActivityAt: new Date(parsedMs).toISOString(),
|
|
89
|
+
activitySource: "friend-facing",
|
|
90
|
+
lastInboundAt: chronology.lastInboundAt,
|
|
91
|
+
lastOutboundAt: chronology.lastOutboundAt,
|
|
92
|
+
unansweredInboundCount: chronology.unansweredInboundCount,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
83
95
|
}
|
|
84
96
|
return {
|
|
85
97
|
lastActivityMs: mtimeMs,
|
|
86
98
|
lastActivityAt: new Date(mtimeMs).toISOString(),
|
|
87
99
|
activitySource: "mtime-fallback",
|
|
100
|
+
lastInboundAt: chronology?.lastInboundAt ?? null,
|
|
101
|
+
lastOutboundAt: chronology?.lastOutboundAt ?? null,
|
|
102
|
+
unansweredInboundCount: chronology?.unansweredInboundCount ?? 0,
|
|
88
103
|
};
|
|
89
104
|
}
|
|
90
105
|
function listSessionActivity(query) {
|
|
@@ -151,6 +166,9 @@ function listSessionActivity(query) {
|
|
|
151
166
|
lastActivityAt: activity.lastActivityAt,
|
|
152
167
|
lastActivityMs: activity.lastActivityMs,
|
|
153
168
|
activitySource: activity.activitySource,
|
|
169
|
+
lastInboundAt: activity.lastInboundAt,
|
|
170
|
+
lastOutboundAt: activity.lastOutboundAt,
|
|
171
|
+
unansweredInboundCount: activity.unansweredInboundCount,
|
|
154
172
|
});
|
|
155
173
|
}
|
|
156
174
|
}
|