sentinelayer-cli 0.40.0 → 0.42.0
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/README.md +22 -4
- package/package.json +4 -4
- package/src/auth/admission-scope.js +34 -0
- package/src/auth/gate.js +6 -0
- package/src/auth/http.js +4 -0
- package/src/auth/service.js +6 -0
- package/src/board/done-gate.js +155 -0
- package/src/board/evidence-resolvers.js +138 -0
- package/src/cli.js +24 -1
- package/src/commands/board.js +116 -0
- package/src/commands/session.js +1094 -279
- package/src/engram/build-state.js +183 -0
- package/src/engram/canonical.js +75 -0
- package/src/engram/document.js +190 -0
- package/src/engram/governance.js +67 -0
- package/src/engram/index.js +73 -0
- package/src/engram/ingest-document.js +107 -0
- package/src/engram/namespace.js +89 -0
- package/src/engram/observations.js +34 -0
- package/src/engram/sla.js +104 -0
- package/src/engram/store.js +120 -0
- package/src/engram/tools.js +152 -0
- package/src/engram/trust.js +105 -0
- package/src/events/schema.js +18 -0
- package/src/legacy-cli.js +6 -3
- package/src/mcp/registry.js +27 -10
- package/src/mcp/session-stdio-server.js +209 -18
- package/src/net/test-egress-guard.js +158 -0
- package/src/review/investor-dd-orchestrator.js +14 -2
- package/src/session/admission-auth.js +312 -0
- package/src/session/admission-preimage.js +90 -0
- package/src/session/admission.js +620 -0
- package/src/session/agent-registry.js +22 -63
- package/src/session/checkpoints.js +25 -1
- package/src/session/codebase-context.js +54 -0
- package/src/session/coordination-guidance.js +6 -6
- package/src/session/daemon-spawn.js +20 -2
- package/src/session/daemon.js +26 -125
- package/src/session/event-identity.js +9 -2
- package/src/session/file-lease-integrations.js +1300 -0
- package/src/session/file-locks.js +967 -464
- package/src/session/first-message.js +11 -3
- package/src/session/listener.js +246 -20
- package/src/session/listeners.js +71 -80
- package/src/session/live-source.js +47 -16
- package/src/session/message-edits.js +89 -0
- package/src/session/paths.js +2 -0
- package/src/session/recall/embedder.js +181 -0
- package/src/session/recall/entities.js +0 -0
- package/src/session/recall/index-build.js +38 -0
- package/src/session/recall/index-core.js +108 -0
- package/src/session/recall/index.js +131 -0
- package/src/session/recall/needle-eval.js +279 -0
- package/src/session/recall/observation-core.js +183 -0
- package/src/session/recall/observations.js +21 -0
- package/src/session/recall/provenance.js +153 -0
- package/src/session/recall/retrieve.js +374 -0
- package/src/session/recall/text.js +0 -0
- package/src/session/recall/token-stats.js +81 -0
- package/src/session/recap.js +54 -26
- package/src/session/remote-hydrate.js +12 -20
- package/src/session/store.js +13 -14
- package/src/session/stream.js +23 -3
- package/src/session/sync.js +789 -22
- package/src/session/tickets.js +316 -0
- package/src/session/title-sync.js +2 -1
- package/src/session/usage-report.js +41 -2
- package/src/session/wake/pump.js +8 -3
- package/src/session/wake/resolve-target.js +10 -5
|
@@ -32,8 +32,10 @@ import {
|
|
|
32
32
|
sessionEventIdentityKeys,
|
|
33
33
|
sessionEventHasKnownIdentity,
|
|
34
34
|
sessionEventUpgradesExisting,
|
|
35
|
+
dedupeSessionEvents,
|
|
35
36
|
} from "./event-identity.js";
|
|
36
37
|
import { isSessionControlEvent } from "./control-events.js";
|
|
38
|
+
import { sessionMessageEditSnapshot } from "./message-edits.js";
|
|
37
39
|
|
|
38
40
|
const EVENTS_CURSOR_SUFFIX = "events";
|
|
39
41
|
const DEFAULT_EVENT_PAGE_LIMIT = 200;
|
|
@@ -231,6 +233,10 @@ async function pollSessionEventPages({
|
|
|
231
233
|
const pageEvents = Array.isArray(result.events) ? result.events : [];
|
|
232
234
|
const nextCursor =
|
|
233
235
|
typeof result.cursor === "string" && result.cursor.trim() ? result.cursor.trim() : cursor;
|
|
236
|
+
const scanExhausted =
|
|
237
|
+
typeof (result.scanExhausted ?? result.scan_exhausted) === "boolean"
|
|
238
|
+
? (result.scanExhausted ?? result.scan_exhausted)
|
|
239
|
+
: null;
|
|
234
240
|
const progressed = nextCursor && cursorAdvances(nextCursor, cursor);
|
|
235
241
|
if (nextCursor && cursor && !progressed && pageEvents.length === 0) {
|
|
236
242
|
return {
|
|
@@ -250,7 +256,7 @@ async function pollSessionEventPages({
|
|
|
250
256
|
events.push(...pageEvents);
|
|
251
257
|
cursor = nextCursor || cursor;
|
|
252
258
|
|
|
253
|
-
if (pageEvents.length < normalizedLimit) {
|
|
259
|
+
if (pageEvents.length < normalizedLimit && scanExhausted !== false) {
|
|
254
260
|
return {
|
|
255
261
|
ok: true,
|
|
256
262
|
reason: "",
|
|
@@ -366,25 +372,11 @@ export async function hydrateSessionFromRemote({
|
|
|
366
372
|
|
|
367
373
|
// Dedup across sources — both endpoints can return the same event
|
|
368
374
|
// (e.g. a human relay event). Cursor values are unique per event.
|
|
369
|
-
const
|
|
370
|
-
const
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (c && seenCursors.has(c)) continue;
|
|
375
|
-
if (sessionEventHasKnownIdentity(e, seenKeys)) continue;
|
|
376
|
-
if (c) seenCursors.add(c);
|
|
377
|
-
addSessionEventIdentityKeys(seenKeys, e);
|
|
378
|
-
merged.push(e);
|
|
379
|
-
}
|
|
380
|
-
for (const e of eventsResult?.events || []) {
|
|
381
|
-
const c = (e && typeof e === "object" && typeof e.cursor === "string") ? e.cursor : "";
|
|
382
|
-
if (c && seenCursors.has(c)) continue;
|
|
383
|
-
if (sessionEventHasKnownIdentity(e, seenKeys)) continue;
|
|
384
|
-
if (c) seenCursors.add(c);
|
|
385
|
-
addSessionEventIdentityKeys(seenKeys, e);
|
|
386
|
-
merged.push(e);
|
|
387
|
-
}
|
|
375
|
+
const sourceEvents = [...(humanResult?.events || []), ...(eventsResult?.events || [])];
|
|
376
|
+
const merged = dedupeSessionEvents(sourceEvents.flatMap((event) => {
|
|
377
|
+
const snapshot = sessionMessageEditSnapshot(event);
|
|
378
|
+
return snapshot ? [snapshot, event] : [event];
|
|
379
|
+
}));
|
|
388
380
|
|
|
389
381
|
// If BOTH pollers failed, surface the human-message failure (the
|
|
390
382
|
// legacy contract) so existing callers see no behavior change. If
|
package/src/session/store.js
CHANGED
|
@@ -4,10 +4,10 @@ import path from "node:path";
|
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
|
|
6
6
|
import { buildArtifactLineageIndex, verifyArtifactChain } from "../daemon/artifact-lineage.js";
|
|
7
|
-
import {
|
|
7
|
+
import { readSessionCodebaseContext } from "./codebase-context.js";
|
|
8
8
|
import { computeSessionAnalytics } from "./analytics.js";
|
|
9
9
|
import { resolveSessionPaths, resolveSessionsRoot } from "./paths.js";
|
|
10
|
-
import { appendToStream } from "./stream.js";
|
|
10
|
+
import { appendToStream, withSessionStreamLock } from "./stream.js";
|
|
11
11
|
|
|
12
12
|
const SESSION_SCHEMA_VERSION = "1.0.0";
|
|
13
13
|
const DEFAULT_TTL_SECONDS = 24 * 60 * 60;
|
|
@@ -247,13 +247,7 @@ function normalizeSessionTemplate(raw = null) {
|
|
|
247
247
|
}
|
|
248
248
|
|
|
249
249
|
async function collectSessionCodebaseContext(targetPath) {
|
|
250
|
-
|
|
251
|
-
const cachedIngest = await readJsonFile(cachedIngestPath, { allowMissing: true });
|
|
252
|
-
if (cachedIngest && typeof cachedIngest === "object") {
|
|
253
|
-
return normalizeCodebaseContext(cachedIngest);
|
|
254
|
-
}
|
|
255
|
-
const ingest = await collectCodebaseIngest({ rootPath: targetPath });
|
|
256
|
-
return normalizeCodebaseContext(ingest);
|
|
250
|
+
return normalizeCodebaseContext(await readSessionCodebaseContext(targetPath));
|
|
257
251
|
}
|
|
258
252
|
|
|
259
253
|
async function buildArchiveSidecars(
|
|
@@ -507,11 +501,16 @@ export async function createSession({
|
|
|
507
501
|
}
|
|
508
502
|
);
|
|
509
503
|
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
504
|
+
return withSessionStreamLock(sessionId, async () => {
|
|
505
|
+
// A concurrent materializer may have won while optional context was read.
|
|
506
|
+
// Never reset its title/TTL/status or race its first billing append.
|
|
507
|
+
const existing = await loadMetadata(sessionId, { targetPath: resolvedTargetPath });
|
|
508
|
+
if (existing) return buildSessionPayload(existing.metadata, paths, nowIso);
|
|
509
|
+
await fsp.mkdir(paths.agentsDir, { recursive: true });
|
|
510
|
+
await saveMetadata(metadata, paths);
|
|
511
|
+
await fsp.writeFile(paths.streamPath, "", { encoding: "utf-8", flag: "a" });
|
|
512
|
+
return buildSessionPayload(metadata, paths, nowIso);
|
|
513
|
+
}, { targetPath: resolvedTargetPath });
|
|
515
514
|
}
|
|
516
515
|
|
|
517
516
|
export async function updateSessionTitle(
|
package/src/session/stream.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
sessionEventIdentityKeys,
|
|
9
9
|
} from "./event-identity.js";
|
|
10
10
|
import { enrichEventWithMentions } from "./mentions.js";
|
|
11
|
+
import { messageRevision, projectSessionMessageEdits } from "./message-edits.js";
|
|
11
12
|
import { resolveSessionPaths } from "./paths.js";
|
|
12
13
|
import { redactEventPayload } from "./redact.js";
|
|
13
14
|
import { fetchSessionFromApi, listSessionsFromApi, syncSessionEventToApi } from "./sync.js";
|
|
@@ -287,6 +288,23 @@ async function releaseLock(lockPath) {
|
|
|
287
288
|
await fsp.rm(lockPath, { recursive: true, force: true }).catch(() => {});
|
|
288
289
|
}
|
|
289
290
|
|
|
291
|
+
// Creation and append must coordinate on the SAME lock. In particular, two
|
|
292
|
+
// billing writers may both observe a missing session before either creates it.
|
|
293
|
+
// The callback must not append (append acquires this lock independently).
|
|
294
|
+
export async function withSessionStreamLock(sessionId, operation, {
|
|
295
|
+
targetPath = process.cwd(),
|
|
296
|
+
timeoutMs = DEFAULT_LOCK_TIMEOUT_MS,
|
|
297
|
+
} = {}) {
|
|
298
|
+
const paths = resolveSessionPaths(sessionId, { targetPath });
|
|
299
|
+
await fsp.mkdir(paths.sessionDir, { recursive: true });
|
|
300
|
+
await acquireLock(paths.lockPath, { timeoutMs });
|
|
301
|
+
try {
|
|
302
|
+
return await operation();
|
|
303
|
+
} finally {
|
|
304
|
+
await releaseLock(paths.lockPath);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
290
308
|
async function readEventsFromFile(filePath) {
|
|
291
309
|
try {
|
|
292
310
|
const raw = await fsp.readFile(filePath, "utf-8");
|
|
@@ -359,7 +377,8 @@ function eventsShareIdentity(left = {}, right = {}) {
|
|
|
359
377
|
}
|
|
360
378
|
|
|
361
379
|
async function appendOrMergeCurrentStreamEvent(paths, canonicalEvent, { mergeExisting = false } = {}) {
|
|
362
|
-
|
|
380
|
+
// Keep prior revisions in the raw audit log. Readers project the latest body.
|
|
381
|
+
if (!mergeExisting || messageRevision(canonicalEvent) > 1) {
|
|
363
382
|
await fsp.appendFile(paths.streamPath, `${JSON.stringify(canonicalEvent)}\n`, "utf-8");
|
|
364
383
|
return;
|
|
365
384
|
}
|
|
@@ -427,6 +446,7 @@ export async function appendToStream(
|
|
|
427
446
|
maxEvents = DEFAULT_MAX_STREAM_EVENTS,
|
|
428
447
|
syncRemote = true,
|
|
429
448
|
awaitRemoteSync = false,
|
|
449
|
+
lockTimeoutMs = DEFAULT_LOCK_TIMEOUT_MS,
|
|
430
450
|
mergeExisting = false,
|
|
431
451
|
} = {}
|
|
432
452
|
) {
|
|
@@ -457,7 +477,7 @@ export async function appendToStream(
|
|
|
457
477
|
const nowIso = new Date().toISOString();
|
|
458
478
|
const normalizedMaxEvents = normalizePositiveInteger(maxEvents, DEFAULT_MAX_STREAM_EVENTS);
|
|
459
479
|
|
|
460
|
-
await acquireLock(paths.lockPath);
|
|
480
|
+
await acquireLock(paths.lockPath, { timeoutMs: lockTimeoutMs });
|
|
461
481
|
try {
|
|
462
482
|
await fsp.mkdir(paths.sessionDir, { recursive: true });
|
|
463
483
|
await appendOrMergeCurrentStreamEvent(paths, canonicalEvent, { mergeExisting });
|
|
@@ -490,7 +510,7 @@ export async function readStream(
|
|
|
490
510
|
{ tail = 20, since = null, targetPath = process.cwd() } = {}
|
|
491
511
|
) {
|
|
492
512
|
const paths = resolveSessionPaths(sessionId, { targetPath });
|
|
493
|
-
const events = await readAllEvents(paths);
|
|
513
|
+
const events = projectSessionMessageEdits(await readAllEvents(paths));
|
|
494
514
|
const filtered = filterBySince(events, since);
|
|
495
515
|
const normalizedTail = Number(tail);
|
|
496
516
|
if (!Number.isFinite(normalizedTail) || normalizedTail <= 0) {
|