sentinelayer-cli 0.11.3 โ 0.11.4
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/package.json
CHANGED
package/src/commands/session.js
CHANGED
|
@@ -69,6 +69,7 @@ import { hydrateSessionFromRemote } from "../session/remote-hydrate.js";
|
|
|
69
69
|
import { mergeLiveSources } from "../session/live-source.js";
|
|
70
70
|
import { listenSessionEvents } from "../session/listener.js";
|
|
71
71
|
import { buildSessionRecap } from "../session/recap.js";
|
|
72
|
+
import { computeTranscriptStats } from "../session/transcript.js";
|
|
72
73
|
import { deriveSessionTitle } from "../session/senti-naming.js";
|
|
73
74
|
import { pushSessionTitleToApi } from "../session/title-sync.js";
|
|
74
75
|
import {
|
|
@@ -96,6 +97,68 @@ function normalizeString(value) {
|
|
|
96
97
|
return String(value || "").trim();
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
function compareIsoDesc(left = "", right = "") {
|
|
101
|
+
return normalizeString(right).localeCompare(normalizeString(left));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function buildSessionParticipants({ statsAgents = [], registeredAgents = [] } = {}) {
|
|
105
|
+
const byAgentId = new Map();
|
|
106
|
+
for (const agent of Array.isArray(statsAgents) ? statsAgents : []) {
|
|
107
|
+
const agentId = normalizeString(agent?.agentId || agent?.id);
|
|
108
|
+
if (!agentId) continue;
|
|
109
|
+
byAgentId.set(agentId, {
|
|
110
|
+
...agent,
|
|
111
|
+
agentId,
|
|
112
|
+
registered: false,
|
|
113
|
+
source: "events",
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
for (const agent of Array.isArray(registeredAgents) ? registeredAgents : []) {
|
|
118
|
+
const agentId = normalizeString(agent?.agentId || agent?.id);
|
|
119
|
+
if (!agentId) continue;
|
|
120
|
+
const existing = byAgentId.get(agentId);
|
|
121
|
+
if (existing) {
|
|
122
|
+
byAgentId.set(agentId, {
|
|
123
|
+
...existing,
|
|
124
|
+
model: existing.model || normalizeString(agent.model),
|
|
125
|
+
role: normalizeString(agent.role) || existing.role,
|
|
126
|
+
status: normalizeString(agent.status) || existing.status,
|
|
127
|
+
registered: true,
|
|
128
|
+
source: "events+registry",
|
|
129
|
+
joinedAt: normalizeString(agent.joinedAt) || existing.joinedAt,
|
|
130
|
+
lastActivityAt: normalizeString(agent.lastActivityAt) || existing.lastActivityAt,
|
|
131
|
+
active: agent.active,
|
|
132
|
+
});
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
byAgentId.set(agentId, {
|
|
136
|
+
agentId,
|
|
137
|
+
displayName: normalizeString(agent.displayName) || agentId,
|
|
138
|
+
model: normalizeString(agent.model),
|
|
139
|
+
role: normalizeString(agent.role),
|
|
140
|
+
status: normalizeString(agent.status),
|
|
141
|
+
firstSeen: normalizeString(agent.joinedAt) || null,
|
|
142
|
+
lastSeen: normalizeString(agent.lastActivityAt) || normalizeString(agent.joinedAt) || null,
|
|
143
|
+
joinedAt: normalizeString(agent.joinedAt) || null,
|
|
144
|
+
lastActivityAt: normalizeString(agent.lastActivityAt) || null,
|
|
145
|
+
active: agent.active,
|
|
146
|
+
eventCount: 0,
|
|
147
|
+
activeSeconds: 0,
|
|
148
|
+
tokens: 0,
|
|
149
|
+
costUsd: 0,
|
|
150
|
+
registered: true,
|
|
151
|
+
source: "registry",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return [...byAgentId.values()].sort((left, right) => {
|
|
156
|
+
const eventDelta = Number(right.eventCount || 0) - Number(left.eventCount || 0);
|
|
157
|
+
if (eventDelta !== 0) return eventDelta;
|
|
158
|
+
return compareIsoDesc(left.lastSeen || left.lastActivityAt, right.lastSeen || right.lastActivityAt);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
99
162
|
function parsePositiveInteger(rawValue, field, fallbackValue) {
|
|
100
163
|
if (rawValue === undefined || rawValue === null || String(rawValue).trim() === "") {
|
|
101
164
|
return fallbackValue;
|
|
@@ -1291,6 +1354,8 @@ export function registerSessionCommand(program) {
|
|
|
1291
1354
|
agentId: resolvedAgentId,
|
|
1292
1355
|
model,
|
|
1293
1356
|
role,
|
|
1357
|
+
trackProcessExit: false,
|
|
1358
|
+
awaitRemoteSync: Boolean(explicitAgent),
|
|
1294
1359
|
});
|
|
1295
1360
|
const agentJoinRelayed =
|
|
1296
1361
|
Boolean(explicitAgent) &&
|
|
@@ -2269,12 +2334,23 @@ export function registerSessionCommand(program) {
|
|
|
2269
2334
|
limit: 5_000,
|
|
2270
2335
|
}),
|
|
2271
2336
|
]);
|
|
2337
|
+
const stats = computeTranscriptStats({
|
|
2338
|
+
sessionMeta: sessionPayload,
|
|
2339
|
+
events,
|
|
2340
|
+
});
|
|
2341
|
+
const participants = buildSessionParticipants({
|
|
2342
|
+
statsAgents: stats.agents,
|
|
2343
|
+
registeredAgents: agents,
|
|
2344
|
+
});
|
|
2272
2345
|
|
|
2273
2346
|
let output;
|
|
2274
2347
|
if (format === "ndjson") {
|
|
2275
2348
|
const lines = [];
|
|
2276
2349
|
lines.push(JSON.stringify({ kind: "session", value: sessionPayload }));
|
|
2277
2350
|
for (const agent of agents) lines.push(JSON.stringify({ kind: "agent", value: agent }));
|
|
2351
|
+
for (const participant of participants) {
|
|
2352
|
+
lines.push(JSON.stringify({ kind: "participant", value: participant }));
|
|
2353
|
+
}
|
|
2278
2354
|
for (const event of events) lines.push(JSON.stringify({ kind: "event", value: event }));
|
|
2279
2355
|
for (const task of tasks.tasks || []) lines.push(JSON.stringify({ kind: "task", value: task }));
|
|
2280
2356
|
output = `${lines.join("\n")}\n`;
|
|
@@ -2285,13 +2361,18 @@ export function registerSessionCommand(program) {
|
|
|
2285
2361
|
exportedAt: new Date().toISOString(),
|
|
2286
2362
|
session: sessionPayload,
|
|
2287
2363
|
agents,
|
|
2364
|
+
participants,
|
|
2288
2365
|
events,
|
|
2289
2366
|
tasks: tasks.tasks || [],
|
|
2290
2367
|
counts: {
|
|
2291
|
-
agents:
|
|
2368
|
+
agents: participants.length,
|
|
2369
|
+
participants: participants.length,
|
|
2370
|
+
derivedAgents: stats.agents.length,
|
|
2371
|
+
registeredAgents: agents.length,
|
|
2292
2372
|
events: events.length,
|
|
2293
2373
|
tasks: (tasks.tasks || []).length,
|
|
2294
2374
|
},
|
|
2375
|
+
totals: stats.totals,
|
|
2295
2376
|
},
|
|
2296
2377
|
null,
|
|
2297
2378
|
2,
|
|
@@ -2305,7 +2386,7 @@ export function registerSessionCommand(program) {
|
|
|
2305
2386
|
await fsp.writeFile(outPath, output, "utf-8");
|
|
2306
2387
|
console.log(
|
|
2307
2388
|
pc.gray(
|
|
2308
|
-
`Exported ${events.length} events / ${agents.length} agents / ${
|
|
2389
|
+
`Exported ${events.length} events / ${participants.length} participants (${agents.length} registered agents) / ${
|
|
2309
2390
|
(tasks.tasks || []).length
|
|
2310
2391
|
} tasks โ ${outPath}`,
|
|
2311
2392
|
),
|
|
@@ -2399,6 +2480,10 @@ export function registerSessionCommand(program) {
|
|
|
2399
2480
|
includeSystemEvents: options.systemEvents !== false,
|
|
2400
2481
|
},
|
|
2401
2482
|
});
|
|
2483
|
+
const participants = buildSessionParticipants({
|
|
2484
|
+
statsAgents: stats.agents,
|
|
2485
|
+
registeredAgents: agents,
|
|
2486
|
+
});
|
|
2402
2487
|
|
|
2403
2488
|
const outArg = normalizeString(options.out);
|
|
2404
2489
|
const outPath = outArg
|
|
@@ -2413,7 +2498,11 @@ export function registerSessionCommand(program) {
|
|
|
2413
2498
|
outPath,
|
|
2414
2499
|
bytes: Buffer.byteLength(markdown, "utf-8"),
|
|
2415
2500
|
eventCount: events.length,
|
|
2416
|
-
agentCount:
|
|
2501
|
+
agentCount: participants.length,
|
|
2502
|
+
participantCount: participants.length,
|
|
2503
|
+
derivedAgentCount: stats.agents.length,
|
|
2504
|
+
registeredAgentCount: agents.length,
|
|
2505
|
+
participants,
|
|
2417
2506
|
sessionLiveSeconds: stats.sessionLiveSeconds,
|
|
2418
2507
|
sentiActions: stats.sentiActions,
|
|
2419
2508
|
totals: stats.totals,
|
|
@@ -2426,7 +2515,7 @@ export function registerSessionCommand(program) {
|
|
|
2426
2515
|
console.log(pc.bold(`Downloaded session ${normalizedSessionId} โ ${outPath}`));
|
|
2427
2516
|
console.log(
|
|
2428
2517
|
pc.gray(
|
|
2429
|
-
`${events.length} events ยท ${agents.length} agents ยท live ${stats.sessionLiveSeconds}s ยท senti=${stats.sentiActions} ยท tokens=${stats.totals.tokenTotal} ยท cost=$${stats.totals.costTotalUsd.toFixed(4)}`,
|
|
2518
|
+
`${events.length} events ยท ${participants.length} participants (${agents.length} registered agents) ยท live ${stats.sessionLiveSeconds}s ยท senti=${stats.sentiActions} ยท tokens=${stats.totals.tokenTotal} ยท cost=$${stats.totals.costTotalUsd.toFixed(4)}`,
|
|
2430
2519
|
),
|
|
2431
2520
|
);
|
|
2432
2521
|
});
|
|
@@ -156,14 +156,19 @@ async function writeAgentSnapshot(snapshotPath, snapshot) {
|
|
|
156
156
|
await fsp.rename(tmpPath, snapshotPath);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
async function emitAgentEvent(
|
|
159
|
+
async function emitAgentEvent(
|
|
160
|
+
sessionId,
|
|
161
|
+
event,
|
|
162
|
+
payload,
|
|
163
|
+
{ targetPath = process.cwd(), awaitRemoteSync = false } = {}
|
|
164
|
+
) {
|
|
160
165
|
const envelope = createAgentEvent({
|
|
161
166
|
event,
|
|
162
167
|
agentId: payload.agentId,
|
|
163
168
|
sessionId,
|
|
164
169
|
payload,
|
|
165
170
|
});
|
|
166
|
-
await appendToStream(sessionId, envelope, { targetPath });
|
|
171
|
+
await appendToStream(sessionId, envelope, { targetPath, awaitRemoteSync });
|
|
167
172
|
}
|
|
168
173
|
|
|
169
174
|
function buildAgentSnapshotPath(paths, agentId) {
|
|
@@ -238,7 +243,14 @@ function _ensureExitHooksInstalled() {
|
|
|
238
243
|
|
|
239
244
|
export async function registerAgent(
|
|
240
245
|
sessionId,
|
|
241
|
-
{
|
|
246
|
+
{
|
|
247
|
+
agentId = "",
|
|
248
|
+
model = "",
|
|
249
|
+
role = "observer",
|
|
250
|
+
targetPath = process.cwd(),
|
|
251
|
+
trackProcessExit = true,
|
|
252
|
+
awaitRemoteSync = false,
|
|
253
|
+
} = {}
|
|
242
254
|
) {
|
|
243
255
|
const paths = resolveSessionPaths(sessionId, { targetPath });
|
|
244
256
|
const nowIso = new Date().toISOString();
|
|
@@ -289,8 +301,10 @@ export async function registerAgent(
|
|
|
289
301
|
model: snapshot.model,
|
|
290
302
|
role: snapshot.role,
|
|
291
303
|
status: snapshot.status,
|
|
292
|
-
}, { targetPath });
|
|
293
|
-
|
|
304
|
+
}, { targetPath, awaitRemoteSync });
|
|
305
|
+
if (trackProcessExit) {
|
|
306
|
+
_trackLocalAgent(paths.sessionId, snapshot.agentId, targetPath);
|
|
307
|
+
}
|
|
294
308
|
|
|
295
309
|
if (renamedFrom) {
|
|
296
310
|
const welcome = buildSentiWelcome({
|
|
@@ -310,6 +324,7 @@ export async function registerAgent(
|
|
|
310
324
|
await emitContextBriefing(paths.sessionId, {
|
|
311
325
|
forAgentId: snapshot.agentId,
|
|
312
326
|
targetPath,
|
|
327
|
+
awaitRemoteSync,
|
|
313
328
|
}).catch(() => {});
|
|
314
329
|
}
|
|
315
330
|
|
package/src/session/recap.js
CHANGED
|
@@ -559,6 +559,7 @@ export async function emitContextBriefing(
|
|
|
559
559
|
targetPath = process.cwd(),
|
|
560
560
|
nowIso = new Date().toISOString(),
|
|
561
561
|
includeJoinRules = true,
|
|
562
|
+
awaitRemoteSync = false,
|
|
562
563
|
} = {}
|
|
563
564
|
) {
|
|
564
565
|
const recap = await buildSessionRecap(sessionId, {
|
|
@@ -588,6 +589,7 @@ export async function emitContextBriefing(
|
|
|
588
589
|
});
|
|
589
590
|
const persisted = await appendToStream(sessionId, event, {
|
|
590
591
|
targetPath,
|
|
592
|
+
awaitRemoteSync,
|
|
591
593
|
});
|
|
592
594
|
return {
|
|
593
595
|
recap,
|
|
@@ -166,14 +166,25 @@ async function pollSessionEventPages({
|
|
|
166
166
|
};
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
const pageEvents = Array.isArray(result.events) ? result.events : [];
|
|
169
170
|
const nextCursor =
|
|
170
171
|
typeof result.cursor === "string" && result.cursor.trim() ? result.cursor.trim() : cursor;
|
|
171
172
|
const progressed = nextCursor && cursorAdvances(nextCursor, cursor);
|
|
173
|
+
if (nextCursor && cursor && !progressed && pageEvents.length === 0) {
|
|
174
|
+
return {
|
|
175
|
+
ok: true,
|
|
176
|
+
reason: "",
|
|
177
|
+
events,
|
|
178
|
+
cursor,
|
|
179
|
+
pageCount,
|
|
180
|
+
complete: true,
|
|
181
|
+
truncated: false,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
172
184
|
if (nextCursor && cursor && !progressed) {
|
|
173
185
|
reason = "cursor_not_advanced";
|
|
174
186
|
break;
|
|
175
187
|
}
|
|
176
|
-
const pageEvents = Array.isArray(result.events) ? result.events : [];
|
|
177
188
|
events.push(...pageEvents);
|
|
178
189
|
cursor = nextCursor || cursor;
|
|
179
190
|
|
package/src/session/stream.js
CHANGED
|
@@ -337,7 +337,12 @@ function filterBySince(events = [], since) {
|
|
|
337
337
|
export async function appendToStream(
|
|
338
338
|
sessionId,
|
|
339
339
|
event,
|
|
340
|
-
{
|
|
340
|
+
{
|
|
341
|
+
targetPath = process.cwd(),
|
|
342
|
+
maxEvents = DEFAULT_MAX_STREAM_EVENTS,
|
|
343
|
+
syncRemote = true,
|
|
344
|
+
awaitRemoteSync = false,
|
|
345
|
+
} = {}
|
|
341
346
|
) {
|
|
342
347
|
const paths = resolveSessionPaths(sessionId, { targetPath });
|
|
343
348
|
let metadata = await readSessionMetadata(paths);
|
|
@@ -381,9 +386,14 @@ export async function appendToStream(
|
|
|
381
386
|
|
|
382
387
|
if (syncRemote) {
|
|
383
388
|
// Best-effort dashboard sync. Never block local stream durability on API state.
|
|
384
|
-
|
|
389
|
+
const syncPromise = syncSessionEventToApi(paths.sessionId, canonicalEvent, {
|
|
385
390
|
targetPath,
|
|
386
391
|
}).catch(() => {});
|
|
392
|
+
if (awaitRemoteSync) {
|
|
393
|
+
await syncPromise;
|
|
394
|
+
} else {
|
|
395
|
+
void syncPromise;
|
|
396
|
+
}
|
|
387
397
|
}
|
|
388
398
|
|
|
389
399
|
return canonicalEvent;
|
|
@@ -359,26 +359,32 @@ export function buildTranscriptMarkdown({
|
|
|
359
359
|
`| ${avatarMd(identity)} | **${agent.displayName}** \`${agent.agentId}\` | ${agent.family} | ${formatDuration(agent.activeSeconds)} | ${agent.eventCount} | ${agent.tokens.toLocaleString("en-US")} | $${agent.costUsd.toFixed(4)} |`,
|
|
360
360
|
);
|
|
361
361
|
}
|
|
362
|
-
if (stats.agents.length === 0) {
|
|
363
|
-
lines.push("| ๐ค | (no agents joined) | โ | 0s | 0 | 0 | $0.00 |");
|
|
364
|
-
}
|
|
365
362
|
// Surface registered-but-silent agents at the bottom of the table so
|
|
366
363
|
// the participants list is comprehensive even if they never emitted
|
|
367
364
|
// a stream event.
|
|
368
365
|
const seenIds = new Set(stats.agents.map((a) => a.agentId));
|
|
366
|
+
const silentRegisteredAgents = [];
|
|
369
367
|
for (const registered of agents || []) {
|
|
370
368
|
const id = normalize(registered?.agentId);
|
|
371
369
|
if (!id || seenIds.has(id)) continue;
|
|
370
|
+
seenIds.add(id);
|
|
372
371
|
const profile = speakerProfiles.get(id) || null;
|
|
373
372
|
const identity = resolveSpeakerIdentity({
|
|
374
373
|
agentId: id,
|
|
375
374
|
agentModel: registered.model || "",
|
|
376
375
|
profile,
|
|
377
376
|
});
|
|
377
|
+
silentRegisteredAgents.push({ id, identity });
|
|
378
|
+
}
|
|
379
|
+
if (stats.agents.length === 0 && silentRegisteredAgents.length === 0) {
|
|
380
|
+
lines.push("| ๐ค | (no agents joined) | โ | 0s | 0 | 0 | $0.00 |");
|
|
381
|
+
}
|
|
382
|
+
for (const registered of silentRegisteredAgents) {
|
|
378
383
|
lines.push(
|
|
379
|
-
`| ${avatarMd(identity)} | **${identity.displayName}** \`${id}\` | ${identity.family} | 0s ยท idle | 0 | 0 | $0.0000 |`,
|
|
384
|
+
`| ${avatarMd(registered.identity)} | **${registered.identity.displayName}** \`${registered.id}\` | ${registered.identity.family} | 0s ยท idle | 0 | 0 | $0.0000 |`,
|
|
380
385
|
);
|
|
381
386
|
}
|
|
387
|
+
stats.participantCount = stats.agents.length + silentRegisteredAgents.length;
|
|
382
388
|
lines.push("");
|
|
383
389
|
|
|
384
390
|
// Conversation
|