sentinelayer-cli 0.8.0 → 0.8.2
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 +23 -2
- package/package.json +4 -4
- package/src/agents/ai-governance/index.js +12 -0
- package/src/agents/ai-governance/tools/base.js +171 -0
- package/src/agents/ai-governance/tools/eval-regression.js +47 -0
- package/src/agents/ai-governance/tools/hitl-audit.js +81 -0
- package/src/agents/ai-governance/tools/index.js +52 -0
- package/src/agents/ai-governance/tools/prompt-drift.js +42 -0
- package/src/agents/ai-governance/tools/provenance-check.js +69 -0
- package/src/agents/backend/index.js +12 -0
- package/src/agents/backend/tools/base.js +189 -0
- package/src/agents/backend/tools/circuit-breaker-check.js +123 -0
- package/src/agents/backend/tools/idempotency-audit.js +105 -0
- package/src/agents/backend/tools/index.js +87 -0
- package/src/agents/backend/tools/retry-audit.js +132 -0
- package/src/agents/backend/tools/timeout-audit.js +144 -0
- package/src/agents/code-quality/index.js +12 -0
- package/src/agents/code-quality/tools/base.js +159 -0
- package/src/agents/code-quality/tools/complexity-measure.js +197 -0
- package/src/agents/code-quality/tools/coupling-analysis.js +81 -0
- package/src/agents/code-quality/tools/cycle-detect.js +49 -0
- package/src/agents/code-quality/tools/dep-graph.js +196 -0
- package/src/agents/code-quality/tools/index.js +89 -0
- package/src/agents/data-layer/index.js +12 -0
- package/src/agents/data-layer/tools/base.js +181 -0
- package/src/agents/data-layer/tools/index-audit.js +165 -0
- package/src/agents/data-layer/tools/index.js +83 -0
- package/src/agents/data-layer/tools/migration-scan.js +135 -0
- package/src/agents/data-layer/tools/query-explain.js +120 -0
- package/src/agents/data-layer/tools/tenancy-scan.js +166 -0
- package/src/agents/documentation/index.js +12 -0
- package/src/agents/documentation/tools/api-diff.js +91 -0
- package/src/agents/documentation/tools/base.js +151 -0
- package/src/agents/documentation/tools/dead-link-check.js +58 -0
- package/src/agents/documentation/tools/docstring-coverage.js +78 -0
- package/src/agents/documentation/tools/index.js +52 -0
- package/src/agents/documentation/tools/readme-freshness.js +61 -0
- package/src/agents/envelope/fix-cycle.js +45 -0
- package/src/agents/envelope/index.js +31 -0
- package/src/agents/envelope/loop.js +150 -0
- package/src/agents/envelope/pulse.js +18 -0
- package/src/agents/envelope/stream.js +40 -0
- package/src/agents/infrastructure/index.js +12 -0
- package/src/agents/infrastructure/tools/base.js +171 -0
- package/src/agents/infrastructure/tools/checkov-run.js +32 -0
- package/src/agents/infrastructure/tools/drift-detect.js +59 -0
- package/src/agents/infrastructure/tools/iam-least-priv-check.js +78 -0
- package/src/agents/infrastructure/tools/index.js +52 -0
- package/src/agents/infrastructure/tools/tflint-run.js +31 -0
- package/src/agents/jules/loop.js +7 -4
- package/src/agents/jules/swarm/sub-agent.js +5 -1
- package/src/agents/jules/tools/auth-audit.js +10 -1
- package/src/agents/mode.js +113 -0
- package/src/agents/observability/index.js +12 -0
- package/src/agents/observability/tools/alert-audit.js +39 -0
- package/src/agents/observability/tools/base.js +181 -0
- package/src/agents/observability/tools/dashboard-gap.js +42 -0
- package/src/agents/observability/tools/index.js +54 -0
- package/src/agents/observability/tools/log-schema-check.js +74 -0
- package/src/agents/observability/tools/span-coverage.js +74 -0
- package/src/agents/persona-visuals.js +38 -0
- package/src/agents/release/index.js +12 -0
- package/src/agents/release/tools/base.js +181 -0
- package/src/agents/release/tools/changelog-diff.js +86 -0
- package/src/agents/release/tools/feature-flag-audit.js +126 -0
- package/src/agents/release/tools/index.js +61 -0
- package/src/agents/release/tools/rollback-verify.js +129 -0
- package/src/agents/release/tools/semver-check.js +109 -0
- package/src/agents/reliability/index.js +12 -0
- package/src/agents/reliability/tools/backpressure-check.js +129 -0
- package/src/agents/reliability/tools/base.js +181 -0
- package/src/agents/reliability/tools/chaos-probe.js +109 -0
- package/src/agents/reliability/tools/graceful-degradation-check.js +114 -0
- package/src/agents/reliability/tools/health-check-audit.js +111 -0
- package/src/agents/reliability/tools/index.js +87 -0
- package/src/agents/run-persona.js +109 -0
- package/src/agents/security/index.js +12 -0
- package/src/agents/security/tools/authz-audit.js +134 -0
- package/src/agents/security/tools/base.js +190 -0
- package/src/agents/security/tools/crypto-review.js +175 -0
- package/src/agents/security/tools/index.js +97 -0
- package/src/agents/security/tools/sast-scan.js +175 -0
- package/src/agents/security/tools/secrets-scan.js +216 -0
- package/src/agents/supply-chain/index.js +12 -0
- package/src/agents/supply-chain/tools/attestation-check.js +42 -0
- package/src/agents/supply-chain/tools/base.js +151 -0
- package/src/agents/supply-chain/tools/index.js +52 -0
- package/src/agents/supply-chain/tools/lockfile-integrity.js +73 -0
- package/src/agents/supply-chain/tools/package-verify.js +56 -0
- package/src/agents/supply-chain/tools/sbom-diff.js +34 -0
- package/src/agents/testing/index.js +12 -0
- package/src/agents/testing/tools/base.js +202 -0
- package/src/agents/testing/tools/coverage-gap.js +144 -0
- package/src/agents/testing/tools/flake-detect.js +125 -0
- package/src/agents/testing/tools/index.js +85 -0
- package/src/agents/testing/tools/mutation-test.js +143 -0
- package/src/agents/testing/tools/snapshot-diff.js +103 -0
- package/src/auth/gate.js +65 -37
- package/src/cli.js +1 -1
- package/src/commands/chat.js +3 -10
- package/src/commands/legacy-args.js +10 -0
- package/src/commands/omargate.js +36 -2
- package/src/commands/persona.js +46 -1
- package/src/commands/scan.js +3 -10
- package/src/commands/session.js +654 -6
- package/src/commands/spec.js +3 -10
- package/src/coord/events-log.js +141 -0
- package/src/coord/handshake.js +719 -0
- package/src/coord/index.js +35 -0
- package/src/coord/paths.js +84 -0
- package/src/coord/priority.js +62 -0
- package/src/coord/tarjan.js +157 -0
- package/src/cost/tokenizer.js +160 -0
- package/src/cost/tracker.js +61 -0
- package/src/daemon/artifact-lineage.js +362 -0
- package/src/daemon/assignment-ledger.js +117 -0
- package/src/daemon/ast-drift.js +496 -0
- package/src/daemon/ingest-refresh.js +69 -2
- package/src/ingest/engine.js +15 -0
- package/src/ingest/ownership.js +380 -0
- package/src/legacy-cli.js +68 -1
- package/src/orchestrator/kai-chen.js +126 -0
- package/src/review/ai-review.js +3 -10
- package/src/review/compliance-pack.js +389 -0
- package/src/review/investor-dd-config.js +54 -0
- package/src/review/investor-dd-file-loop.js +303 -0
- package/src/review/investor-dd-file-router.js +406 -0
- package/src/review/investor-dd-html-report.js +233 -0
- package/src/review/investor-dd-notification.js +120 -0
- package/src/review/investor-dd-orchestrator.js +405 -0
- package/src/review/investor-dd-persona-runner.js +275 -0
- package/src/review/live-validator.js +253 -0
- package/src/review/omargate-orchestrator.js +90 -2
- package/src/review/persona-prompts.js +244 -56
- package/src/review/reconciliation-rules.js +329 -0
- package/src/review/reproducibility-chain.js +136 -0
- package/src/review/scan-modes.js +102 -3
- package/src/session/agent-registry.js +7 -0
- package/src/session/analytics.js +479 -0
- package/src/session/daemon.js +609 -14
- package/src/session/file-locks.js +666 -0
- package/src/session/paths.js +4 -0
- package/src/session/recap.js +567 -0
- package/src/session/redact.js +82 -0
- package/src/session/runtime-bridge.js +24 -1
- package/src/session/scoring.js +406 -0
- package/src/session/setup-guides.js +304 -0
- package/src/session/store.js +318 -2
- package/src/session/stream.js +9 -1
- package/src/session/sync.js +753 -0
- package/src/session/tasks.js +1054 -0
- package/src/session/templates.js +188 -0
- package/src/swarm/runtime.js +1 -8
package/src/session/daemon.js
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import process from "node:process";
|
|
3
|
+
import fsp from "node:fs/promises";
|
|
3
4
|
|
|
5
|
+
import { invokeViaProxy } from "../ai/proxy.js";
|
|
4
6
|
import { createAgentEvent } from "../events/schema.js";
|
|
7
|
+
import {
|
|
8
|
+
buildDocumentsFromBlackboardEntries,
|
|
9
|
+
buildLocalHybridIndex,
|
|
10
|
+
buildSharedMemoryCorpus,
|
|
11
|
+
queryLocalHybridIndex,
|
|
12
|
+
} from "../memory/retrieval.js";
|
|
13
|
+
import {
|
|
14
|
+
endSession as endTelemetrySession,
|
|
15
|
+
recordLlmUsage,
|
|
16
|
+
startSession as startTelemetrySession,
|
|
17
|
+
} from "../telemetry/session-tracker.js";
|
|
5
18
|
import {
|
|
6
19
|
detectStaleAgents,
|
|
7
20
|
heartbeatAgent,
|
|
@@ -9,18 +22,36 @@ import {
|
|
|
9
22
|
registerAgent,
|
|
10
23
|
unregisterAgent,
|
|
11
24
|
} from "./agent-registry.js";
|
|
25
|
+
import {
|
|
26
|
+
DEFAULT_FILE_LOCK_TTL_SECONDS,
|
|
27
|
+
lockFile,
|
|
28
|
+
unlockFile,
|
|
29
|
+
} from "./file-locks.js";
|
|
12
30
|
import { resolveSessionPaths } from "./paths.js";
|
|
31
|
+
import {
|
|
32
|
+
DEFAULT_RECAP_INACTIVITY_MS,
|
|
33
|
+
DEFAULT_RECAP_INTERVAL_MS,
|
|
34
|
+
emitPeriodicRecap,
|
|
35
|
+
} from "./recap.js";
|
|
13
36
|
import { stopRuntimeRunsForSession } from "./runtime-bridge.js";
|
|
37
|
+
import { pollHumanMessages } from "./sync.js";
|
|
14
38
|
import { getSession, renewSession } from "./store.js";
|
|
15
39
|
import { appendToStream, readStream, tailStream } from "./stream.js";
|
|
40
|
+
import { handleTaskDirective } from "./tasks.js";
|
|
16
41
|
|
|
17
42
|
const DAEMON_TICK_INTERVAL_MS = 30_000;
|
|
18
|
-
const HELP_REQUEST_TIMEOUT_MS =
|
|
43
|
+
const HELP_REQUEST_TIMEOUT_MS = 1_200;
|
|
44
|
+
const HELP_MODEL_TIMEOUT_MS = 3_000;
|
|
45
|
+
const HELP_CONTEXT_EVENT_TAIL = 50;
|
|
46
|
+
const HELP_CONTEXT_RESULT_LIMIT = 6;
|
|
47
|
+
const HELP_BLACKBOARD_ENTRY_LIMIT = 40;
|
|
19
48
|
const FILE_CONFLICT_WINDOW_MS = 60_000;
|
|
20
49
|
const RENEWAL_WINDOW_MS = 60 * 60 * 1000;
|
|
21
50
|
const RENEWAL_THRESHOLD_EVENTS = 10;
|
|
22
51
|
const RENEWAL_LEAD_MS = 60 * 60 * 1000;
|
|
23
52
|
const DEFAULT_STALE_AGENT_SECONDS = 90;
|
|
53
|
+
const DEFAULT_RECAP_INTERVAL_MS_OVERRIDE = DEFAULT_RECAP_INTERVAL_MS;
|
|
54
|
+
const DEFAULT_RECAP_INACTIVITY_MS_OVERRIDE = DEFAULT_RECAP_INACTIVITY_MS;
|
|
24
55
|
|
|
25
56
|
const SENTI_MODEL = "gpt-5.4-mini";
|
|
26
57
|
const SENTI_IDENTITY = Object.freeze({
|
|
@@ -136,7 +167,11 @@ function createSentiState({
|
|
|
136
167
|
staleAgentSeconds,
|
|
137
168
|
helpRequestTimeoutMs,
|
|
138
169
|
tickIntervalMs,
|
|
170
|
+
recapIntervalMs,
|
|
171
|
+
recapInactivityMs,
|
|
139
172
|
helpResponder,
|
|
173
|
+
llmInvoker,
|
|
174
|
+
telemetrySessionId,
|
|
140
175
|
}) {
|
|
141
176
|
return {
|
|
142
177
|
daemonKey,
|
|
@@ -147,7 +182,11 @@ function createSentiState({
|
|
|
147
182
|
staleAgentSeconds,
|
|
148
183
|
helpRequestTimeoutMs,
|
|
149
184
|
tickIntervalMs,
|
|
185
|
+
recapIntervalMs,
|
|
186
|
+
recapInactivityMs,
|
|
150
187
|
helpResponder,
|
|
188
|
+
llmInvoker,
|
|
189
|
+
telemetrySessionId,
|
|
151
190
|
running: true,
|
|
152
191
|
tickTimer: null,
|
|
153
192
|
helpAbortController: new AbortController(),
|
|
@@ -157,6 +196,9 @@ function createSentiState({
|
|
|
157
196
|
conflictAlertAt: new Map(),
|
|
158
197
|
lastTickAt: null,
|
|
159
198
|
lastTickSummary: null,
|
|
199
|
+
recapEmitter: null,
|
|
200
|
+
humanMessageCursor: null,
|
|
201
|
+
humanMessagePollInFlight: false,
|
|
160
202
|
};
|
|
161
203
|
}
|
|
162
204
|
|
|
@@ -188,6 +230,105 @@ async function hasHelpResponseFromPeer(
|
|
|
188
230
|
});
|
|
189
231
|
}
|
|
190
232
|
|
|
233
|
+
function normalizeUsageNumber(value) {
|
|
234
|
+
const normalized = Number(value);
|
|
235
|
+
if (!Number.isFinite(normalized) || normalized < 0) {
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
return normalized;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function buildStreamContextDocuments(events = []) {
|
|
242
|
+
return (events || [])
|
|
243
|
+
.map((event, index) => {
|
|
244
|
+
const payload = event && typeof event.payload === "object" ? event.payload : {};
|
|
245
|
+
const text = [
|
|
246
|
+
normalizeString(event.event),
|
|
247
|
+
normalizeString(event.agent?.id || event.agentId),
|
|
248
|
+
normalizeString(payload.message),
|
|
249
|
+
normalizeString(payload.response),
|
|
250
|
+
normalizeString(payload.alert),
|
|
251
|
+
normalizeString(payload.reason),
|
|
252
|
+
normalizeString(payload.file),
|
|
253
|
+
]
|
|
254
|
+
.filter(Boolean)
|
|
255
|
+
.join(" ")
|
|
256
|
+
.trim();
|
|
257
|
+
if (!text) {
|
|
258
|
+
return null;
|
|
259
|
+
}
|
|
260
|
+
return {
|
|
261
|
+
documentId: `stream:${index + 1}:${normalizeIsoTimestamp(event.ts, new Date().toISOString())}`,
|
|
262
|
+
sourceType: "session-stream",
|
|
263
|
+
sourcePath: "",
|
|
264
|
+
severity: "P3",
|
|
265
|
+
updatedAt: normalizeIsoTimestamp(event.ts, new Date().toISOString()),
|
|
266
|
+
text,
|
|
267
|
+
metadata: {
|
|
268
|
+
category: "session-stream",
|
|
269
|
+
event: normalizeString(event.event),
|
|
270
|
+
agentId: normalizeString(event.agent?.id || event.agentId),
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
})
|
|
274
|
+
.filter(Boolean);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
async function loadLatestBlackboardEntries(targetPath, { limit = HELP_BLACKBOARD_ENTRY_LIMIT } = {}) {
|
|
278
|
+
const memoryDirectory = path.join(targetPath, ".sentinelayer", "memory");
|
|
279
|
+
let entries = [];
|
|
280
|
+
try {
|
|
281
|
+
entries = await fsp.readdir(memoryDirectory, { withFileTypes: true });
|
|
282
|
+
} catch {
|
|
283
|
+
return [];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const files = entries
|
|
287
|
+
.filter((entry) => entry.isFile() && entry.name.startsWith("blackboard-") && entry.name.endsWith(".json"))
|
|
288
|
+
.map((entry) => entry.name)
|
|
289
|
+
.sort((left, right) => right.localeCompare(left));
|
|
290
|
+
for (const fileName of files) {
|
|
291
|
+
const filePath = path.join(memoryDirectory, fileName);
|
|
292
|
+
try {
|
|
293
|
+
const payload = JSON.parse(await fsp.readFile(filePath, "utf-8"));
|
|
294
|
+
if (!Array.isArray(payload.entries)) {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
return payload.entries.slice(-Math.max(1, Math.floor(Number(limit) || HELP_BLACKBOARD_ENTRY_LIMIT)));
|
|
298
|
+
} catch {
|
|
299
|
+
// Ignore malformed artifacts and continue searching older files.
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return [];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function buildFallbackHelpResponse({ requestMessage = "", synopsis = "context unavailable", contextHints = [] } = {}) {
|
|
306
|
+
const topHints = contextHints.slice(0, 2).join(" | ");
|
|
307
|
+
if (topHints) {
|
|
308
|
+
return `I saw your help_request ("${requestMessage}"). Quick context: ${synopsis}. Top hints: ${topHints}. Share the failing file or stack frame and I can route next steps.`;
|
|
309
|
+
}
|
|
310
|
+
return `I saw your help_request ("${requestMessage}"). Quick context: ${synopsis}. Share the failing file or stack frame and I can route next steps.`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async function runWithTimeout(promise, timeoutMs, timeoutMessage) {
|
|
314
|
+
let timeoutHandle = null;
|
|
315
|
+
try {
|
|
316
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
317
|
+
timeoutHandle = setTimeout(() => {
|
|
318
|
+
reject(new Error(timeoutMessage));
|
|
319
|
+
}, timeoutMs);
|
|
320
|
+
if (typeof timeoutHandle.unref === "function") {
|
|
321
|
+
timeoutHandle.unref();
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
return await Promise.race([promise, timeoutPromise]);
|
|
325
|
+
} finally {
|
|
326
|
+
if (timeoutHandle) {
|
|
327
|
+
clearTimeout(timeoutHandle);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
191
332
|
async function buildHelpResponseMessage(
|
|
192
333
|
daemonState,
|
|
193
334
|
requestEvent,
|
|
@@ -195,6 +336,11 @@ async function buildHelpResponseMessage(
|
|
|
195
336
|
targetPath = process.cwd(),
|
|
196
337
|
} = {}
|
|
197
338
|
) {
|
|
339
|
+
const requestMessage =
|
|
340
|
+
normalizeString(requestEvent?.payload?.message) ||
|
|
341
|
+
normalizeString(requestEvent?.payload?.request) ||
|
|
342
|
+
"help request received";
|
|
343
|
+
|
|
198
344
|
if (typeof daemonState.helpResponder === "function") {
|
|
199
345
|
const custom = await daemonState.helpResponder({
|
|
200
346
|
daemonState,
|
|
@@ -203,19 +349,170 @@ async function buildHelpResponseMessage(
|
|
|
203
349
|
});
|
|
204
350
|
const normalizedCustom = normalizeString(custom);
|
|
205
351
|
if (normalizedCustom) {
|
|
206
|
-
return
|
|
352
|
+
return {
|
|
353
|
+
message: normalizedCustom,
|
|
354
|
+
usage: {
|
|
355
|
+
inputTokens: 0,
|
|
356
|
+
outputTokens: 0,
|
|
357
|
+
costUsd: 0,
|
|
358
|
+
model: daemonState.model,
|
|
359
|
+
provider: "custom-responder",
|
|
360
|
+
latencyMs: 0,
|
|
361
|
+
},
|
|
362
|
+
fallbackPath: false,
|
|
363
|
+
fallbackReason: "",
|
|
364
|
+
contextSignals: {
|
|
365
|
+
documentCount: 0,
|
|
366
|
+
memoryHits: 0,
|
|
367
|
+
blackboardEntries: 0,
|
|
368
|
+
recentEvents: 0,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
207
371
|
}
|
|
208
372
|
}
|
|
209
373
|
|
|
210
|
-
const session = await getSession(daemonState.sessionId, {
|
|
211
|
-
targetPath,
|
|
212
|
-
});
|
|
374
|
+
const session = await getSession(daemonState.sessionId, { targetPath });
|
|
213
375
|
const synopsis = session ? formatCodebaseSynopsis(session) : "codebase context unavailable";
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
376
|
+
const outputRoot = path.join(targetPath, ".sentinelayer");
|
|
377
|
+
|
|
378
|
+
const [recentEvents, blackboardEntries, sharedMemory] = await Promise.all([
|
|
379
|
+
readStream(daemonState.sessionId, {
|
|
380
|
+
targetPath,
|
|
381
|
+
tail: HELP_CONTEXT_EVENT_TAIL,
|
|
382
|
+
}).catch(() => []),
|
|
383
|
+
loadLatestBlackboardEntries(targetPath, {
|
|
384
|
+
limit: HELP_BLACKBOARD_ENTRY_LIMIT,
|
|
385
|
+
}),
|
|
386
|
+
buildSharedMemoryCorpus({
|
|
387
|
+
outputRoot,
|
|
388
|
+
targetPath,
|
|
389
|
+
ingest: session?.codebaseContext || {},
|
|
390
|
+
maxAuditRuns: 2,
|
|
391
|
+
}).catch(() => ({
|
|
392
|
+
documents: [],
|
|
393
|
+
sourceCounts: {},
|
|
394
|
+
})),
|
|
395
|
+
]);
|
|
396
|
+
|
|
397
|
+
const documents = [
|
|
398
|
+
...(sharedMemory.documents || []),
|
|
399
|
+
...buildStreamContextDocuments(recentEvents),
|
|
400
|
+
...buildDocumentsFromBlackboardEntries(blackboardEntries),
|
|
401
|
+
];
|
|
402
|
+
const localIndex = buildLocalHybridIndex(documents);
|
|
403
|
+
const memoryQuery = queryLocalHybridIndex(localIndex, {
|
|
404
|
+
query: requestMessage,
|
|
405
|
+
limit: HELP_CONTEXT_RESULT_LIMIT,
|
|
406
|
+
minScore: 0.05,
|
|
407
|
+
});
|
|
408
|
+
const memoryHits = memoryQuery.results || [];
|
|
409
|
+
const contextHints = memoryHits
|
|
410
|
+
.slice(0, HELP_CONTEXT_RESULT_LIMIT)
|
|
411
|
+
.map((result) => {
|
|
412
|
+
const source = normalizeString(result.sourceType) || "memory";
|
|
413
|
+
const snippet = normalizeString(result.snippet || "").replace(/\s+/g, " ").trim();
|
|
414
|
+
if (!snippet) {
|
|
415
|
+
return "";
|
|
416
|
+
}
|
|
417
|
+
return `${source}: ${snippet}`;
|
|
418
|
+
})
|
|
419
|
+
.filter(Boolean);
|
|
420
|
+
|
|
421
|
+
const systemPrompt = [
|
|
422
|
+
"You are Senti, SentinelLayer's session daemon.",
|
|
423
|
+
"Answer the requesting agent with concise, actionable engineering guidance.",
|
|
424
|
+
"Prioritize concrete next steps and reference available context snippets.",
|
|
425
|
+
"Never invent repository files or runtime behavior.",
|
|
426
|
+
].join(" ");
|
|
427
|
+
const userPrompt = [
|
|
428
|
+
`Agent request: ${requestMessage}`,
|
|
429
|
+
`Codebase synopsis: ${synopsis}`,
|
|
430
|
+
"Context snippets:",
|
|
431
|
+
contextHints.length > 0 ? contextHints.map((line, index) => `${index + 1}. ${line}`).join("\n") : "none",
|
|
432
|
+
"Respond in 2-4 short sentences.",
|
|
433
|
+
].join("\n");
|
|
434
|
+
|
|
435
|
+
const startedAt = Date.now();
|
|
436
|
+
let llmText = "";
|
|
437
|
+
let fallbackPath = false;
|
|
438
|
+
let fallbackReason = "";
|
|
439
|
+
let usage = {
|
|
440
|
+
inputTokens: 0,
|
|
441
|
+
outputTokens: 0,
|
|
442
|
+
costUsd: 0,
|
|
443
|
+
model: daemonState.model,
|
|
444
|
+
provider: "local-fallback",
|
|
445
|
+
latencyMs: 0,
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const llmTimeoutMs = Math.max(
|
|
449
|
+
80,
|
|
450
|
+
Math.min(
|
|
451
|
+
HELP_MODEL_TIMEOUT_MS,
|
|
452
|
+
normalizePositiveInteger(daemonState.helpRequestTimeoutMs, HELP_REQUEST_TIMEOUT_MS) * 2
|
|
453
|
+
)
|
|
454
|
+
);
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
const llmResult = await runWithTimeout(
|
|
458
|
+
Promise.resolve(
|
|
459
|
+
daemonState.llmInvoker({
|
|
460
|
+
model: daemonState.model,
|
|
461
|
+
systemPrompt,
|
|
462
|
+
prompt: userPrompt,
|
|
463
|
+
maxTokens: 320,
|
|
464
|
+
temperature: 0.1,
|
|
465
|
+
})
|
|
466
|
+
),
|
|
467
|
+
llmTimeoutMs,
|
|
468
|
+
"Senti model response timeout."
|
|
469
|
+
);
|
|
470
|
+
llmText = normalizeString(llmResult?.text);
|
|
471
|
+
usage = {
|
|
472
|
+
inputTokens: normalizeUsageNumber(llmResult?.usage?.inputTokens),
|
|
473
|
+
outputTokens: normalizeUsageNumber(llmResult?.usage?.outputTokens),
|
|
474
|
+
costUsd: normalizeUsageNumber(llmResult?.usage?.costUsd),
|
|
475
|
+
model: normalizeString(llmResult?.usage?.model) || daemonState.model,
|
|
476
|
+
provider: normalizeString(llmResult?.usage?.provider) || "sentinelayer",
|
|
477
|
+
latencyMs: normalizeUsageNumber(llmResult?.usage?.latencyMs),
|
|
478
|
+
};
|
|
479
|
+
if (!llmText) {
|
|
480
|
+
fallbackPath = true;
|
|
481
|
+
fallbackReason = "Senti model returned an empty response.";
|
|
482
|
+
}
|
|
483
|
+
} catch (error) {
|
|
484
|
+
fallbackPath = true;
|
|
485
|
+
fallbackReason = normalizeString(error?.message || error) || "Senti model invocation failed.";
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
if (!usage.latencyMs) {
|
|
489
|
+
usage.latencyMs = Math.max(1, Date.now() - startedAt);
|
|
490
|
+
}
|
|
491
|
+
recordLlmUsage({
|
|
492
|
+
sessionId: daemonState.telemetrySessionId,
|
|
493
|
+
inputTokens: usage.inputTokens,
|
|
494
|
+
outputTokens: usage.outputTokens,
|
|
495
|
+
costUsd: usage.costUsd,
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
const message = llmText ||
|
|
499
|
+
buildFallbackHelpResponse({
|
|
500
|
+
requestMessage,
|
|
501
|
+
synopsis,
|
|
502
|
+
contextHints,
|
|
503
|
+
});
|
|
504
|
+
return {
|
|
505
|
+
message,
|
|
506
|
+
usage,
|
|
507
|
+
fallbackPath,
|
|
508
|
+
fallbackReason,
|
|
509
|
+
contextSignals: {
|
|
510
|
+
documentCount: documents.length,
|
|
511
|
+
memoryHits: memoryHits.length,
|
|
512
|
+
blackboardEntries: blackboardEntries.length,
|
|
513
|
+
recentEvents: recentEvents.length,
|
|
514
|
+
},
|
|
515
|
+
};
|
|
219
516
|
}
|
|
220
517
|
|
|
221
518
|
async function maybeRespondToHelpRequest(
|
|
@@ -238,23 +535,47 @@ async function maybeRespondToHelpRequest(
|
|
|
238
535
|
if (hasPeerResponse) {
|
|
239
536
|
return null;
|
|
240
537
|
}
|
|
241
|
-
const
|
|
538
|
+
const response = await buildHelpResponseMessage(daemonState, requestEvent, {
|
|
242
539
|
targetPath,
|
|
243
540
|
});
|
|
244
|
-
|
|
541
|
+
const nowIso = new Date().toISOString();
|
|
542
|
+
const responseEvent = await emitSentiEvent(
|
|
245
543
|
daemonState.sessionId,
|
|
246
544
|
"help_response",
|
|
247
545
|
{
|
|
248
546
|
requestId,
|
|
249
547
|
targetAgentId: normalizeString(requestEvent.agent?.id) || null,
|
|
250
|
-
response:
|
|
548
|
+
response: response.message,
|
|
549
|
+
sourceEvent: "help_request",
|
|
550
|
+
contextSignals: response.contextSignals,
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
targetPath,
|
|
554
|
+
nowIso,
|
|
555
|
+
}
|
|
556
|
+
);
|
|
557
|
+
await emitSentiEvent(
|
|
558
|
+
daemonState.sessionId,
|
|
559
|
+
"model_span",
|
|
560
|
+
{
|
|
251
561
|
sourceEvent: "help_request",
|
|
562
|
+
requestId,
|
|
563
|
+
model: response.usage.model || daemonState.model,
|
|
564
|
+
provider: response.usage.provider || "sentinelayer",
|
|
565
|
+
inputTokens: response.usage.inputTokens,
|
|
566
|
+
outputTokens: response.usage.outputTokens,
|
|
567
|
+
costUsd: response.usage.costUsd,
|
|
568
|
+
latencyMs: response.usage.latencyMs,
|
|
569
|
+
fallbackPath: Boolean(response.fallbackPath),
|
|
570
|
+
fallbackReason: response.fallbackReason || null,
|
|
571
|
+
contextSignals: response.contextSignals,
|
|
252
572
|
},
|
|
253
573
|
{
|
|
254
574
|
targetPath,
|
|
255
|
-
nowIso
|
|
575
|
+
nowIso,
|
|
256
576
|
}
|
|
257
577
|
);
|
|
578
|
+
return responseEvent;
|
|
258
579
|
}
|
|
259
580
|
|
|
260
581
|
function queueHelpResponse(daemonState, requestEvent) {
|
|
@@ -309,6 +630,176 @@ async function runHelpWatcher(daemonState) {
|
|
|
309
630
|
}
|
|
310
631
|
}
|
|
311
632
|
|
|
633
|
+
function splitFileAndIntent(raw = "") {
|
|
634
|
+
const normalized = normalizeString(raw);
|
|
635
|
+
if (!normalized) {
|
|
636
|
+
return {
|
|
637
|
+
filePath: "",
|
|
638
|
+
intent: "",
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
const separatorMatch = /\s(?:—|–|-)\s/.exec(normalized);
|
|
642
|
+
if (!separatorMatch) {
|
|
643
|
+
return {
|
|
644
|
+
filePath: normalizeString(normalized),
|
|
645
|
+
intent: "",
|
|
646
|
+
};
|
|
647
|
+
}
|
|
648
|
+
const separatorIndex = Number(separatorMatch.index || 0);
|
|
649
|
+
return {
|
|
650
|
+
filePath: normalizeString(normalized.slice(0, separatorIndex)),
|
|
651
|
+
intent: normalizeString(normalized.slice(separatorIndex + separatorMatch[0].length)),
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function parseSessionDirective(event = {}) {
|
|
656
|
+
if (normalizeString(event.event) !== "session_message") {
|
|
657
|
+
return null;
|
|
658
|
+
}
|
|
659
|
+
const message = normalizeString(event.payload?.message);
|
|
660
|
+
if (!message) {
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
const directive = /^(lock|unlock)\s*:\s*(.+)$/i.exec(message);
|
|
664
|
+
if (!directive) {
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
const action = normalizeString(directive[1]).toLowerCase();
|
|
668
|
+
const body = normalizeString(directive[2]);
|
|
669
|
+
const parsed = splitFileAndIntent(body);
|
|
670
|
+
if (!parsed.filePath) {
|
|
671
|
+
return null;
|
|
672
|
+
}
|
|
673
|
+
return {
|
|
674
|
+
action,
|
|
675
|
+
filePath: parsed.filePath,
|
|
676
|
+
intent: parsed.intent,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
async function maybeHandleSessionDirective(daemonState, event) {
|
|
681
|
+
const agentId = normalizeString(event.agent?.id);
|
|
682
|
+
if (!agentId || agentId === SENTI_IDENTITY.id) {
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
const nowIso = normalizeIsoTimestamp(event.ts, new Date().toISOString());
|
|
686
|
+
const fileDirective = parseSessionDirective(event);
|
|
687
|
+
if (fileDirective) {
|
|
688
|
+
if (fileDirective.action === "lock") {
|
|
689
|
+
const result = await lockFile(
|
|
690
|
+
daemonState.sessionId,
|
|
691
|
+
agentId,
|
|
692
|
+
fileDirective.filePath,
|
|
693
|
+
{
|
|
694
|
+
intent: fileDirective.intent,
|
|
695
|
+
ttlSeconds: DEFAULT_FILE_LOCK_TTL_SECONDS,
|
|
696
|
+
targetPath: daemonState.targetPath,
|
|
697
|
+
nowIso,
|
|
698
|
+
}
|
|
699
|
+
);
|
|
700
|
+
if (!result.locked) {
|
|
701
|
+
await emitSentiEvent(
|
|
702
|
+
daemonState.sessionId,
|
|
703
|
+
"daemon_alert",
|
|
704
|
+
{
|
|
705
|
+
alert: "file_lock_denied",
|
|
706
|
+
file: result.file || fileDirective.filePath,
|
|
707
|
+
requestedBy: agentId,
|
|
708
|
+
heldBy: result.heldBy || null,
|
|
709
|
+
since: result.since || null,
|
|
710
|
+
suggestion: `${fileDirective.filePath} is locked by ${result.heldBy || "another agent"} (${result.since || "recently"}). Coordinate before editing.`,
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
targetPath: daemonState.targetPath,
|
|
714
|
+
nowIso,
|
|
715
|
+
}
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
return result;
|
|
719
|
+
}
|
|
720
|
+
if (fileDirective.action === "unlock") {
|
|
721
|
+
const result = await unlockFile(
|
|
722
|
+
daemonState.sessionId,
|
|
723
|
+
agentId,
|
|
724
|
+
fileDirective.filePath,
|
|
725
|
+
{
|
|
726
|
+
reason: "session_message_unlock",
|
|
727
|
+
targetPath: daemonState.targetPath,
|
|
728
|
+
nowIso,
|
|
729
|
+
}
|
|
730
|
+
);
|
|
731
|
+
if (!result.unlocked && result.reason === "held_by_other_agent") {
|
|
732
|
+
await emitSentiEvent(
|
|
733
|
+
daemonState.sessionId,
|
|
734
|
+
"daemon_alert",
|
|
735
|
+
{
|
|
736
|
+
alert: "file_unlock_denied",
|
|
737
|
+
file: result.file || fileDirective.filePath,
|
|
738
|
+
requestedBy: agentId,
|
|
739
|
+
heldBy: result.heldBy || null,
|
|
740
|
+
since: result.since || null,
|
|
741
|
+
suggestion: `${fileDirective.filePath} is locked by ${result.heldBy || "another agent"}. Only the lock holder can release it.`,
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
targetPath: daemonState.targetPath,
|
|
745
|
+
nowIso,
|
|
746
|
+
}
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
return result;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
try {
|
|
754
|
+
return await handleTaskDirective(daemonState.sessionId, event, {
|
|
755
|
+
targetPath: daemonState.targetPath,
|
|
756
|
+
nowIso,
|
|
757
|
+
});
|
|
758
|
+
} catch (error) {
|
|
759
|
+
await emitSentiEvent(
|
|
760
|
+
daemonState.sessionId,
|
|
761
|
+
"daemon_alert",
|
|
762
|
+
{
|
|
763
|
+
alert: "task_directive_error",
|
|
764
|
+
requestedBy: agentId,
|
|
765
|
+
reason: normalizeString(error?.message) || "Task directive failed.",
|
|
766
|
+
message: normalizeString(event.payload?.message) || null,
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
targetPath: daemonState.targetPath,
|
|
770
|
+
nowIso,
|
|
771
|
+
}
|
|
772
|
+
);
|
|
773
|
+
return null;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
async function runSessionDirectiveWatcher(daemonState) {
|
|
778
|
+
const signal = daemonState.helpAbortController.signal;
|
|
779
|
+
try {
|
|
780
|
+
for await (const event of tailStream(daemonState.sessionId, {
|
|
781
|
+
targetPath: daemonState.targetPath,
|
|
782
|
+
signal,
|
|
783
|
+
since: daemonState.startedAt,
|
|
784
|
+
replayTail: 0,
|
|
785
|
+
pollMs: 100,
|
|
786
|
+
})) {
|
|
787
|
+
if (!daemonState.running) {
|
|
788
|
+
return;
|
|
789
|
+
}
|
|
790
|
+
if (normalizeString(event.event) !== "session_message") {
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
await maybeHandleSessionDirective(daemonState, event);
|
|
794
|
+
}
|
|
795
|
+
} catch (error) {
|
|
796
|
+
if (error && typeof error === "object" && error.name === "AbortError") {
|
|
797
|
+
return;
|
|
798
|
+
}
|
|
799
|
+
throw error;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
|
|
312
803
|
function buildConflictSignature(agentA, agentB, filePath) {
|
|
313
804
|
const pair = [normalizeString(agentA), normalizeString(agentB)].filter(Boolean).sort().join("|");
|
|
314
805
|
return `${pair}::${normalizeString(filePath).replace(/\\/g, "/")}`;
|
|
@@ -327,6 +818,12 @@ function createHealthSummaryBase(nowIso, session, agents) {
|
|
|
327
818
|
staleAgents: [],
|
|
328
819
|
conflictAlerts: [],
|
|
329
820
|
renewed: null,
|
|
821
|
+
humanMessages: {
|
|
822
|
+
relayed: 0,
|
|
823
|
+
dropped: 0,
|
|
824
|
+
cursor: null,
|
|
825
|
+
reason: "",
|
|
826
|
+
},
|
|
330
827
|
};
|
|
331
828
|
}
|
|
332
829
|
|
|
@@ -490,6 +987,66 @@ async function maybeRenewActiveSession(
|
|
|
490
987
|
};
|
|
491
988
|
}
|
|
492
989
|
|
|
990
|
+
async function pollAndRelayHumanMessages(
|
|
991
|
+
daemonState,
|
|
992
|
+
summary,
|
|
993
|
+
nowIso = new Date().toISOString()
|
|
994
|
+
) {
|
|
995
|
+
if (daemonState.humanMessagePollInFlight) {
|
|
996
|
+
summary.humanMessages.reason = "poll_in_flight";
|
|
997
|
+
return;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
daemonState.humanMessagePollInFlight = true;
|
|
1001
|
+
try {
|
|
1002
|
+
const polled = await pollHumanMessages(daemonState.sessionId, {
|
|
1003
|
+
targetPath: daemonState.targetPath,
|
|
1004
|
+
since: daemonState.humanMessageCursor,
|
|
1005
|
+
});
|
|
1006
|
+
if (!polled.ok) {
|
|
1007
|
+
summary.humanMessages.reason = normalizeString(polled.reason) || "poll_failed";
|
|
1008
|
+
summary.humanMessages.cursor = daemonState.humanMessageCursor;
|
|
1009
|
+
return;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const relayedEvents = [];
|
|
1013
|
+
for (const event of polled.events || []) {
|
|
1014
|
+
const persisted = await appendToStream(daemonState.sessionId, event, {
|
|
1015
|
+
targetPath: daemonState.targetPath,
|
|
1016
|
+
});
|
|
1017
|
+
relayedEvents.push(persisted);
|
|
1018
|
+
}
|
|
1019
|
+
daemonState.humanMessageCursor = normalizeString(polled.cursor) || daemonState.humanMessageCursor;
|
|
1020
|
+
|
|
1021
|
+
summary.humanMessages.relayed = relayedEvents.length;
|
|
1022
|
+
summary.humanMessages.dropped = Array.isArray(polled.dropped) ? polled.dropped.length : 0;
|
|
1023
|
+
summary.humanMessages.cursor = daemonState.humanMessageCursor;
|
|
1024
|
+
summary.humanMessages.reason = "";
|
|
1025
|
+
|
|
1026
|
+
if (relayedEvents.length > 0) {
|
|
1027
|
+
await emitSentiEvent(
|
|
1028
|
+
daemonState.sessionId,
|
|
1029
|
+
"daemon_alert",
|
|
1030
|
+
{
|
|
1031
|
+
alert: "human_directive_received",
|
|
1032
|
+
relayedCount: relayedEvents.length,
|
|
1033
|
+
droppedCount: summary.humanMessages.dropped,
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
targetPath: daemonState.targetPath,
|
|
1037
|
+
nowIso,
|
|
1038
|
+
}
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
} catch (error) {
|
|
1042
|
+
summary.humanMessages.reason =
|
|
1043
|
+
normalizeString(error?.message) || "poll_relay_failed";
|
|
1044
|
+
summary.humanMessages.cursor = daemonState.humanMessageCursor;
|
|
1045
|
+
} finally {
|
|
1046
|
+
daemonState.humanMessagePollInFlight = false;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
493
1050
|
export async function runSentiHealthTick(
|
|
494
1051
|
sessionId,
|
|
495
1052
|
{
|
|
@@ -522,7 +1079,11 @@ export async function runSentiHealthTick(
|
|
|
522
1079
|
staleAgentSeconds,
|
|
523
1080
|
helpRequestTimeoutMs: HELP_REQUEST_TIMEOUT_MS,
|
|
524
1081
|
tickIntervalMs: DAEMON_TICK_INTERVAL_MS,
|
|
1082
|
+
recapIntervalMs: DEFAULT_RECAP_INTERVAL_MS_OVERRIDE,
|
|
1083
|
+
recapInactivityMs: DEFAULT_RECAP_INACTIVITY_MS_OVERRIDE,
|
|
525
1084
|
helpResponder: null,
|
|
1085
|
+
llmInvoker: invokeViaProxy,
|
|
1086
|
+
telemetrySessionId: null,
|
|
526
1087
|
});
|
|
527
1088
|
const normalizedNow = normalizeIsoTimestamp(nowIso, new Date().toISOString());
|
|
528
1089
|
const activeAgents = await listAgents(normalizedSessionId, {
|
|
@@ -542,6 +1103,7 @@ export async function runSentiHealthTick(
|
|
|
542
1103
|
await emitStaleAndRecoveryAlerts(resolvedDaemonState, summary, staleAgents, normalizedNow);
|
|
543
1104
|
await emitConflictAlerts(resolvedDaemonState, summary, filteredAgents, normalizedNow);
|
|
544
1105
|
await maybeRenewActiveSession(resolvedDaemonState, summary, session, normalizedNow);
|
|
1106
|
+
await pollAndRelayHumanMessages(resolvedDaemonState, summary, normalizedNow);
|
|
545
1107
|
return summary;
|
|
546
1108
|
}
|
|
547
1109
|
|
|
@@ -554,7 +1116,10 @@ export async function startSenti(
|
|
|
554
1116
|
tickIntervalMs = DAEMON_TICK_INTERVAL_MS,
|
|
555
1117
|
staleAgentSeconds = DEFAULT_STALE_AGENT_SECONDS,
|
|
556
1118
|
helpRequestTimeoutMs = HELP_REQUEST_TIMEOUT_MS,
|
|
1119
|
+
recapIntervalMs = DEFAULT_RECAP_INTERVAL_MS_OVERRIDE,
|
|
1120
|
+
recapInactivityMs = DEFAULT_RECAP_INACTIVITY_MS_OVERRIDE,
|
|
557
1121
|
helpResponder = null,
|
|
1122
|
+
llmInvoker = invokeViaProxy,
|
|
558
1123
|
} = {}
|
|
559
1124
|
) {
|
|
560
1125
|
const normalizedSessionId = normalizeString(sessionId);
|
|
@@ -584,7 +1149,16 @@ export async function startSenti(
|
|
|
584
1149
|
staleAgentSeconds,
|
|
585
1150
|
DEFAULT_STALE_AGENT_SECONDS
|
|
586
1151
|
);
|
|
1152
|
+
const normalizedRecapIntervalMs = normalizePositiveInteger(
|
|
1153
|
+
recapIntervalMs,
|
|
1154
|
+
DEFAULT_RECAP_INTERVAL_MS_OVERRIDE
|
|
1155
|
+
);
|
|
1156
|
+
const normalizedRecapInactivityMs = normalizePositiveInteger(
|
|
1157
|
+
recapInactivityMs,
|
|
1158
|
+
DEFAULT_RECAP_INACTIVITY_MS_OVERRIDE
|
|
1159
|
+
);
|
|
587
1160
|
const nowIso = new Date().toISOString();
|
|
1161
|
+
const telemetrySession = startTelemetrySession(`session daemon ${normalizedSessionId}`);
|
|
588
1162
|
const daemonState = createSentiState({
|
|
589
1163
|
daemonKey,
|
|
590
1164
|
sessionId: normalizedSessionId,
|
|
@@ -594,7 +1168,11 @@ export async function startSenti(
|
|
|
594
1168
|
staleAgentSeconds: normalizedStaleSeconds,
|
|
595
1169
|
helpRequestTimeoutMs: normalizedHelpTimeoutMs,
|
|
596
1170
|
tickIntervalMs: normalizedTickIntervalMs,
|
|
1171
|
+
recapIntervalMs: normalizedRecapIntervalMs,
|
|
1172
|
+
recapInactivityMs: normalizedRecapInactivityMs,
|
|
597
1173
|
helpResponder,
|
|
1174
|
+
llmInvoker: typeof llmInvoker === "function" ? llmInvoker : invokeViaProxy,
|
|
1175
|
+
telemetrySessionId: telemetrySession?.id || null,
|
|
598
1176
|
});
|
|
599
1177
|
|
|
600
1178
|
await upsertSentiAgent(normalizedSessionId, {
|
|
@@ -661,6 +1239,10 @@ export async function startSenti(
|
|
|
661
1239
|
clearTimeout(timer);
|
|
662
1240
|
}
|
|
663
1241
|
daemonState.pendingHelpTimers.clear();
|
|
1242
|
+
if (daemonState.recapEmitter && daemonState.recapEmitter.isRunning()) {
|
|
1243
|
+
daemonState.recapEmitter.stop("daemon_stop");
|
|
1244
|
+
daemonState.recapEmitter = null;
|
|
1245
|
+
}
|
|
664
1246
|
|
|
665
1247
|
let runtimeStopSummary = null;
|
|
666
1248
|
try {
|
|
@@ -700,6 +1282,9 @@ export async function startSenti(
|
|
|
700
1282
|
}
|
|
701
1283
|
);
|
|
702
1284
|
ACTIVE_SENTI_DAEMONS.delete(daemonKey);
|
|
1285
|
+
if (daemonState.telemetrySessionId) {
|
|
1286
|
+
endTelemetrySession({ sessionId: daemonState.telemetrySessionId });
|
|
1287
|
+
}
|
|
703
1288
|
return {
|
|
704
1289
|
stopped: true,
|
|
705
1290
|
daemonKey,
|
|
@@ -729,6 +1314,8 @@ export async function startSenti(
|
|
|
729
1314
|
lastTickAt: daemonState.lastTickAt,
|
|
730
1315
|
staleAlertedAgents: [...daemonState.staleAlertedAgents],
|
|
731
1316
|
pendingHelpRequests: daemonState.pendingHelpTimers.size,
|
|
1317
|
+
recapRunning: Boolean(daemonState.recapEmitter?.isRunning?.()),
|
|
1318
|
+
humanMessageCursor: daemonState.humanMessageCursor,
|
|
732
1319
|
}),
|
|
733
1320
|
};
|
|
734
1321
|
|
|
@@ -736,6 +1323,12 @@ export async function startSenti(
|
|
|
736
1323
|
ACTIVE_SENTI_DAEMONS.set(daemonKey, daemonState);
|
|
737
1324
|
|
|
738
1325
|
void runHelpWatcher(daemonState).catch(() => {});
|
|
1326
|
+
void runSessionDirectiveWatcher(daemonState).catch(() => {});
|
|
1327
|
+
daemonState.recapEmitter = emitPeriodicRecap(normalizedSessionId, {
|
|
1328
|
+
targetPath: normalizedTargetPath,
|
|
1329
|
+
intervalMs: daemonState.recapIntervalMs,
|
|
1330
|
+
inactivityMs: daemonState.recapInactivityMs,
|
|
1331
|
+
});
|
|
739
1332
|
|
|
740
1333
|
if (autoStart) {
|
|
741
1334
|
await runTick(nowIso);
|
|
@@ -790,6 +1383,8 @@ export function getSentiDaemon(
|
|
|
790
1383
|
export {
|
|
791
1384
|
ACTIVE_SENTI_DAEMONS,
|
|
792
1385
|
DAEMON_TICK_INTERVAL_MS,
|
|
1386
|
+
DEFAULT_RECAP_INACTIVITY_MS_OVERRIDE,
|
|
1387
|
+
DEFAULT_RECAP_INTERVAL_MS_OVERRIDE,
|
|
793
1388
|
DEFAULT_STALE_AGENT_SECONDS,
|
|
794
1389
|
FILE_CONFLICT_WINDOW_MS,
|
|
795
1390
|
HELP_REQUEST_TIMEOUT_MS,
|