@phigon/observer 8.1.0 → 8.3.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 +244 -18
- package/dist/bin/guardian-runner-darwin-amd64 +0 -0
- package/dist/bin/guardian-runner-darwin-arm64 +0 -0
- package/dist/bin/guardian-runner-linux-amd64 +0 -0
- package/dist/bin/guardian-runner-linux-arm64 +0 -0
- package/dist/bin/guardian-runner-windows-amd64.exe +0 -0
- package/dist/bin/guardian-runner-windows-arm64.exe +0 -0
- package/dist/src/abortable-delay.d.ts +10 -0
- package/dist/src/abortable-delay.d.ts.map +1 -0
- package/dist/src/abortable-delay.js +21 -0
- package/dist/src/abortable-delay.js.map +1 -0
- package/dist/src/action-policy.d.ts.map +1 -1
- package/dist/src/action-policy.js +4 -2
- package/dist/src/action-policy.js.map +1 -1
- package/dist/src/activity/adapters/claude.d.ts.map +1 -1
- package/dist/src/activity/adapters/claude.js +28 -2
- package/dist/src/activity/adapters/claude.js.map +1 -1
- package/dist/src/activity/adapters/cline-task.d.ts +7 -0
- package/dist/src/activity/adapters/cline-task.d.ts.map +1 -0
- package/dist/src/activity/adapters/cline-task.js +197 -0
- package/dist/src/activity/adapters/cline-task.js.map +1 -0
- package/dist/src/activity/adapters/cline.d.ts +3 -0
- package/dist/src/activity/adapters/cline.d.ts.map +1 -0
- package/dist/src/activity/adapters/cline.js +102 -0
- package/dist/src/activity/adapters/cline.js.map +1 -0
- package/dist/src/activity/adapters/codex.d.ts.map +1 -1
- package/dist/src/activity/adapters/codex.js +76 -0
- package/dist/src/activity/adapters/codex.js.map +1 -1
- package/dist/src/activity/adapters/copilot-chat.d.ts +3 -0
- package/dist/src/activity/adapters/copilot-chat.d.ts.map +1 -0
- package/dist/src/activity/adapters/copilot-chat.js +176 -0
- package/dist/src/activity/adapters/copilot-chat.js.map +1 -0
- package/dist/src/activity/adapters/copilot.d.ts +2 -1
- package/dist/src/activity/adapters/copilot.d.ts.map +1 -1
- package/dist/src/activity/adapters/copilot.js +173 -2
- package/dist/src/activity/adapters/copilot.js.map +1 -1
- package/dist/src/activity/adapters/cursor.d.ts +3 -0
- package/dist/src/activity/adapters/cursor.d.ts.map +1 -0
- package/dist/src/activity/adapters/cursor.js +86 -0
- package/dist/src/activity/adapters/cursor.js.map +1 -0
- package/dist/src/activity/adapters/factory-droid.d.ts +3 -0
- package/dist/src/activity/adapters/factory-droid.d.ts.map +1 -0
- package/dist/src/activity/adapters/factory-droid.js +126 -0
- package/dist/src/activity/adapters/factory-droid.js.map +1 -0
- package/dist/src/activity/adapters/gemini.d.ts +3 -0
- package/dist/src/activity/adapters/gemini.d.ts.map +1 -0
- package/dist/src/activity/adapters/gemini.js +177 -0
- package/dist/src/activity/adapters/gemini.js.map +1 -0
- package/dist/src/activity/adapters/goose.d.ts +3 -0
- package/dist/src/activity/adapters/goose.d.ts.map +1 -0
- package/dist/src/activity/adapters/goose.js +116 -0
- package/dist/src/activity/adapters/goose.js.map +1 -0
- package/dist/src/activity/adapters/kiro.d.ts +3 -0
- package/dist/src/activity/adapters/kiro.d.ts.map +1 -0
- package/dist/src/activity/adapters/kiro.js +47 -0
- package/dist/src/activity/adapters/kiro.js.map +1 -0
- package/dist/src/activity/adapters/openai-compatible.d.ts.map +1 -1
- package/dist/src/activity/adapters/openai-compatible.js +9 -1
- package/dist/src/activity/adapters/openai-compatible.js.map +1 -1
- package/dist/src/activity/adapters/opencode.d.ts.map +1 -1
- package/dist/src/activity/adapters/opencode.js +108 -1
- package/dist/src/activity/adapters/opencode.js.map +1 -1
- package/dist/src/activity/adapters/zed.d.ts +3 -0
- package/dist/src/activity/adapters/zed.d.ts.map +1 -0
- package/dist/src/activity/adapters/zed.js +134 -0
- package/dist/src/activity/adapters/zed.js.map +1 -0
- package/dist/src/activity/collector.d.ts +43 -1
- package/dist/src/activity/collector.d.ts.map +1 -1
- package/dist/src/activity/collector.js +490 -77
- package/dist/src/activity/collector.js.map +1 -1
- package/dist/src/activity/goose-sqlite-source.d.ts +33 -0
- package/dist/src/activity/goose-sqlite-source.d.ts.map +1 -0
- package/dist/src/activity/goose-sqlite-source.js +274 -0
- package/dist/src/activity/goose-sqlite-source.js.map +1 -0
- package/dist/src/activity/index.d.ts +3 -0
- package/dist/src/activity/index.d.ts.map +1 -1
- package/dist/src/activity/index.js +3 -0
- package/dist/src/activity/index.js.map +1 -1
- package/dist/src/activity/ingest.d.ts +9 -0
- package/dist/src/activity/ingest.d.ts.map +1 -1
- package/dist/src/activity/ingest.js +94 -4
- package/dist/src/activity/ingest.js.map +1 -1
- package/dist/src/activity/json-document-source.d.ts +151 -0
- package/dist/src/activity/json-document-source.d.ts.map +1 -0
- package/dist/src/activity/json-document-source.js +586 -0
- package/dist/src/activity/json-document-source.js.map +1 -0
- package/dist/src/activity/native-capabilities.d.ts.map +1 -1
- package/dist/src/activity/native-capabilities.js +8 -12
- package/dist/src/activity/native-capabilities.js.map +1 -1
- package/dist/src/activity/normalize.d.ts.map +1 -1
- package/dist/src/activity/normalize.js +64 -6
- package/dist/src/activity/normalize.js.map +1 -1
- package/dist/src/activity/opencode-message-tables.d.ts +87 -0
- package/dist/src/activity/opencode-message-tables.d.ts.map +1 -0
- package/dist/src/activity/opencode-message-tables.js +546 -0
- package/dist/src/activity/opencode-message-tables.js.map +1 -0
- package/dist/src/activity/opencode-sqlite-source.d.ts +7 -0
- package/dist/src/activity/opencode-sqlite-source.d.ts.map +1 -1
- package/dist/src/activity/opencode-sqlite-source.js +154 -63
- package/dist/src/activity/opencode-sqlite-source.js.map +1 -1
- package/dist/src/activity/source-reader.d.ts +60 -4
- package/dist/src/activity/source-reader.d.ts.map +1 -1
- package/dist/src/activity/source-reader.js +593 -75
- package/dist/src/activity/source-reader.js.map +1 -1
- package/dist/src/activity/sqlite-connections.d.ts +77 -0
- package/dist/src/activity/sqlite-connections.d.ts.map +1 -0
- package/dist/src/activity/sqlite-connections.js +220 -0
- package/dist/src/activity/sqlite-connections.js.map +1 -0
- package/dist/src/activity/zed-sqlite-source.d.ts +65 -0
- package/dist/src/activity/zed-sqlite-source.d.ts.map +1 -0
- package/dist/src/activity/zed-sqlite-source.js +547 -0
- package/dist/src/activity/zed-sqlite-source.js.map +1 -0
- package/dist/src/agent-action-executor.d.ts +18 -1
- package/dist/src/agent-action-executor.d.ts.map +1 -1
- package/dist/src/agent-action-executor.js +113 -58
- package/dist/src/agent-action-executor.js.map +1 -1
- package/dist/src/agentic-guardian.d.ts.map +1 -1
- package/dist/src/agentic-guardian.js +6 -5
- package/dist/src/agentic-guardian.js.map +1 -1
- package/dist/src/atomic-replace.d.ts +7 -0
- package/dist/src/atomic-replace.d.ts.map +1 -1
- package/dist/src/atomic-replace.js +28 -3
- package/dist/src/atomic-replace.js.map +1 -1
- package/dist/src/bridge-journal.d.ts +32 -0
- package/dist/src/bridge-journal.d.ts.map +1 -0
- package/dist/src/bridge-journal.js +113 -0
- package/dist/src/bridge-journal.js.map +1 -0
- package/dist/src/cli.d.ts +7 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +534 -50
- package/dist/src/cli.js.map +1 -1
- package/dist/src/collection-notice.d.ts +34 -0
- package/dist/src/collection-notice.d.ts.map +1 -0
- package/dist/src/collection-notice.js +73 -0
- package/dist/src/collection-notice.js.map +1 -0
- package/dist/src/config.d.ts +39 -0
- package/dist/src/config.d.ts.map +1 -1
- package/dist/src/config.js +187 -36
- package/dist/src/config.js.map +1 -1
- package/dist/src/connection-config.d.ts.map +1 -1
- package/dist/src/connection-config.js +5 -0
- package/dist/src/connection-config.js.map +1 -1
- package/dist/src/content-redaction.d.ts +17 -1
- package/dist/src/content-redaction.d.ts.map +1 -1
- package/dist/src/content-redaction.js +51 -3
- package/dist/src/content-redaction.js.map +1 -1
- package/dist/src/credential-store.d.ts +20 -1
- package/dist/src/credential-store.d.ts.map +1 -1
- package/dist/src/credential-store.js +29 -4
- package/dist/src/credential-store.js.map +1 -1
- package/dist/src/custom-agent-registry.d.ts.map +1 -1
- package/dist/src/custom-agent-registry.js +2 -1
- package/dist/src/custom-agent-registry.js.map +1 -1
- package/dist/src/discovery/adapters/generic.d.ts.map +1 -1
- package/dist/src/discovery/adapters/generic.js +160 -2
- package/dist/src/discovery/adapters/generic.js.map +1 -1
- package/dist/src/discovery/engine.d.ts +7 -1
- package/dist/src/discovery/engine.d.ts.map +1 -1
- package/dist/src/discovery/engine.js +21 -16
- package/dist/src/discovery/engine.js.map +1 -1
- package/dist/src/discovery/known-agent.d.ts +7 -0
- package/dist/src/discovery/known-agent.d.ts.map +1 -1
- package/dist/src/discovery/known-agent.js +9 -4
- package/dist/src/discovery/known-agent.js.map +1 -1
- package/dist/src/discovery/system-evidence.d.ts +40 -1
- package/dist/src/discovery/system-evidence.d.ts.map +1 -1
- package/dist/src/discovery/system-evidence.js +189 -16
- package/dist/src/discovery/system-evidence.js.map +1 -1
- package/dist/src/doctor-check.d.ts +51 -0
- package/dist/src/doctor-check.d.ts.map +1 -0
- package/dist/src/doctor-check.js +174 -0
- package/dist/src/doctor-check.js.map +1 -0
- package/dist/src/enroll.d.ts +86 -0
- package/dist/src/enroll.d.ts.map +1 -0
- package/dist/src/enroll.js +245 -0
- package/dist/src/enroll.js.map +1 -0
- package/dist/src/entry.d.ts +3 -0
- package/dist/src/entry.d.ts.map +1 -0
- package/dist/src/entry.js +14 -0
- package/dist/src/entry.js.map +1 -0
- package/dist/src/event-queue.d.ts +13 -1
- package/dist/src/event-queue.d.ts.map +1 -1
- package/dist/src/event-queue.js +137 -42
- package/dist/src/event-queue.js.map +1 -1
- package/dist/src/event-spool.d.ts +61 -3
- package/dist/src/event-spool.d.ts.map +1 -1
- package/dist/src/event-spool.js +341 -44
- package/dist/src/event-spool.js.map +1 -1
- package/dist/src/generated/phigon/v1/identity_pb.d.ts +7 -0
- package/dist/src/generated/phigon/v1/identity_pb.d.ts.map +1 -1
- package/dist/src/generated/phigon/v1/identity_pb.js +1 -1
- package/dist/src/generated/phigon/v1/identity_pb.js.map +1 -1
- package/dist/src/guardian-containment.d.ts +2 -6
- package/dist/src/guardian-containment.d.ts.map +1 -1
- package/dist/src/guardian-containment.js +12 -63
- package/dist/src/guardian-containment.js.map +1 -1
- package/dist/src/guardian-decision-journal.d.ts.map +1 -1
- package/dist/src/guardian-decision-journal.js +4 -19
- package/dist/src/guardian-decision-journal.js.map +1 -1
- package/dist/src/guardian-managed-exec.d.ts.map +1 -1
- package/dist/src/guardian-managed-exec.js +6 -18
- package/dist/src/guardian-managed-exec.js.map +1 -1
- package/dist/src/guardian-model-bridge.d.ts.map +1 -1
- package/dist/src/guardian-model-bridge.js +2 -1
- package/dist/src/guardian-model-bridge.js.map +1 -1
- package/dist/src/guardian-native.d.ts +0 -1
- package/dist/src/guardian-native.d.ts.map +1 -1
- package/dist/src/guardian-native.js +0 -6
- package/dist/src/guardian-native.js.map +1 -1
- package/dist/src/guardian-process-monitor.d.ts +4 -0
- package/dist/src/guardian-process-monitor.d.ts.map +1 -1
- package/dist/src/guardian-process-monitor.js +82 -26
- package/dist/src/guardian-process-monitor.js.map +1 -1
- package/dist/src/health.d.ts +19 -1
- package/dist/src/health.d.ts.map +1 -1
- package/dist/src/health.js +22 -1
- package/dist/src/health.js.map +1 -1
- package/dist/src/historical-identity-store.d.ts +8 -1
- package/dist/src/historical-identity-store.d.ts.map +1 -1
- package/dist/src/historical-identity-store.js +28 -10
- package/dist/src/historical-identity-store.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/installation.d.ts +2 -0
- package/dist/src/installation.d.ts.map +1 -1
- package/dist/src/installation.js +6 -3
- package/dist/src/installation.js.map +1 -1
- package/dist/src/instance-lock.d.ts +45 -0
- package/dist/src/instance-lock.d.ts.map +1 -0
- package/dist/src/instance-lock.js +243 -0
- package/dist/src/instance-lock.js.map +1 -0
- package/dist/src/machine-command-executor.d.ts +22 -7
- package/dist/src/machine-command-executor.d.ts.map +1 -1
- package/dist/src/machine-command-executor.js +80 -19
- package/dist/src/machine-command-executor.js.map +1 -1
- package/dist/src/node-version.d.ts +8 -0
- package/dist/src/node-version.d.ts.map +1 -0
- package/dist/src/node-version.js +17 -0
- package/dist/src/node-version.js.map +1 -0
- package/dist/src/observer-log.d.ts +50 -0
- package/dist/src/observer-log.d.ts.map +1 -0
- package/dist/src/observer-log.js +141 -0
- package/dist/src/observer-log.js.map +1 -0
- package/dist/src/pair.d.ts +68 -1
- package/dist/src/pair.d.ts.map +1 -1
- package/dist/src/pair.js +204 -65
- package/dist/src/pair.js.map +1 -1
- package/dist/src/private-directory.d.ts +10 -0
- package/dist/src/private-directory.d.ts.map +1 -0
- package/dist/src/private-directory.js +66 -0
- package/dist/src/private-directory.js.map +1 -0
- package/dist/src/process-tree.d.ts +73 -0
- package/dist/src/process-tree.d.ts.map +1 -0
- package/dist/src/process-tree.js +256 -0
- package/dist/src/process-tree.js.map +1 -0
- package/dist/src/proxy.d.ts +68 -0
- package/dist/src/proxy.d.ts.map +1 -0
- package/dist/src/proxy.js +209 -0
- package/dist/src/proxy.js.map +1 -0
- package/dist/src/purge.d.ts +17 -0
- package/dist/src/purge.d.ts.map +1 -0
- package/dist/src/purge.js +84 -0
- package/dist/src/purge.js.map +1 -0
- package/dist/src/remove-machine.d.ts +5 -1
- package/dist/src/remove-machine.d.ts.map +1 -1
- package/dist/src/remove-machine.js +28 -14
- package/dist/src/remove-machine.js.map +1 -1
- package/dist/src/server-authority.d.ts +29 -0
- package/dist/src/server-authority.d.ts.map +1 -0
- package/dist/src/server-authority.js +106 -0
- package/dist/src/server-authority.js.map +1 -0
- package/dist/src/server-clock.d.ts +32 -0
- package/dist/src/server-clock.d.ts.map +1 -0
- package/dist/src/server-clock.js +72 -0
- package/dist/src/server-clock.js.map +1 -0
- package/dist/src/service-command.d.ts +20 -0
- package/dist/src/service-command.d.ts.map +1 -0
- package/dist/src/service-command.js +136 -0
- package/dist/src/service-command.js.map +1 -0
- package/dist/src/service.d.ts +98 -0
- package/dist/src/service.d.ts.map +1 -0
- package/dist/src/service.js +472 -0
- package/dist/src/service.js.map +1 -0
- package/dist/src/stream-client.d.ts +21 -1
- package/dist/src/stream-client.d.ts.map +1 -1
- package/dist/src/stream-client.js +137 -24
- package/dist/src/stream-client.js.map +1 -1
- package/dist/src/stream-state.d.ts +2 -0
- package/dist/src/stream-state.d.ts.map +1 -1
- package/dist/src/stream-state.js +5 -2
- package/dist/src/stream-state.js.map +1 -1
- package/dist/src/system-inspection.d.ts +78 -0
- package/dist/src/system-inspection.d.ts.map +1 -0
- package/dist/src/system-inspection.js +281 -0
- package/dist/src/system-inspection.js.map +1 -0
- package/dist/src/update.d.ts.map +1 -1
- package/dist/src/update.js +9 -3
- package/dist/src/update.js.map +1 -1
- package/dist/src/user-notices.d.ts +64 -0
- package/dist/src/user-notices.d.ts.map +1 -0
- package/dist/src/user-notices.js +141 -0
- package/dist/src/user-notices.js.map +1 -0
- package/package.json +7 -7
- package/dist/src/windows-process.d.ts +0 -7
- package/dist/src/windows-process.d.ts.map +0 -1
- package/dist/src/windows-process.js +0 -18
- package/dist/src/windows-process.js.map +0 -1
|
@@ -1,27 +1,47 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
|
+
import { readdir, stat, unlink } from "node:fs/promises";
|
|
2
3
|
import { join } from "node:path";
|
|
3
4
|
import { create, toBinary } from "@bufbuild/protobuf";
|
|
4
5
|
import { timestampFromDate } from "@bufbuild/protobuf/wkt";
|
|
6
|
+
import { abortableDelay } from "../abortable-delay.js";
|
|
5
7
|
import { ActivityKind, AgentKind, AgentState, } from "../generated/phigon/v1/common_pb.js";
|
|
6
8
|
import { AgentIdentitySchema, } from "../generated/phigon/v1/identity_pb.js";
|
|
7
9
|
import { mergeInventoryWithHistoricalIdentities } from "../historical-identity-store.js";
|
|
8
10
|
import { EventEnvelopeSchema, } from "../generated/phigon/v1/telemetry_pb.js";
|
|
9
11
|
import { claudeActivityAdapter } from "./adapters/claude.js";
|
|
12
|
+
import { clineExtensionActivityAdapter, clineTaskFirstMessageField, kiloCodeActivityAdapter, rooCodeActivityAdapter, } from "./adapters/cline-task.js";
|
|
13
|
+
import { clineActivityAdapter } from "./adapters/cline.js";
|
|
10
14
|
import { codexActivityAdapter } from "./adapters/codex.js";
|
|
11
15
|
import { copilotActivityAdapter } from "./adapters/copilot.js";
|
|
16
|
+
import { copilotChatActivityAdapter } from "./adapters/copilot-chat.js";
|
|
17
|
+
import { cursorActivityAdapter } from "./adapters/cursor.js";
|
|
18
|
+
import { factoryDroidActivityAdapter } from "./adapters/factory-droid.js";
|
|
19
|
+
import { geminiActivityAdapter } from "./adapters/gemini.js";
|
|
20
|
+
import { gooseActivityAdapter } from "./adapters/goose.js";
|
|
12
21
|
import { hermesActivityAdapter } from "./adapters/hermes.js";
|
|
22
|
+
import { kiroActivityAdapter } from "./adapters/kiro.js";
|
|
13
23
|
import { openClawActivityAdapter } from "./adapters/openclaw.js";
|
|
14
24
|
import { openCodeActivityAdapter } from "./adapters/opencode.js";
|
|
25
|
+
import { zedActivityAdapter } from "./adapters/zed.js";
|
|
15
26
|
import { createOpenAICompatibleAdapter } from "./adapters/openai-compatible.js";
|
|
16
27
|
import { activityBridgeAgents } from "./bridges.js";
|
|
17
28
|
import { genericActivityAdapter } from "./adapters/generic.js";
|
|
18
29
|
import { LocalPolicyEngine } from "./policy.js";
|
|
19
30
|
import { vendorDraft } from "./normalize.js";
|
|
20
31
|
import { objectValue, recordObservedAt, stringField, } from "./record.js";
|
|
21
|
-
import { JSONLSourceReader, } from "./source-reader.js";
|
|
32
|
+
import { JSONLSourceReader, fileReadThrough, } from "./source-reader.js";
|
|
33
|
+
import { bridgeWritingSuffix, ingestedBridgeFile } from "./ingest.js";
|
|
34
|
+
import { closedJournalSegments } from "../bridge-journal.js";
|
|
22
35
|
import { processKeyCapability } from "../discovery/process-identity.js";
|
|
23
36
|
const maxEnvelopeBytes = 1 << 20;
|
|
24
37
|
const maxPendingCheckpoints = 256;
|
|
38
|
+
// Bridge files are cleaned up this often, and kept at most this long.
|
|
39
|
+
const bridgePruneIntervalMs = 5 * 60_000;
|
|
40
|
+
const bridgeFileRetentionMs = 7 * 86_400_000;
|
|
41
|
+
// A closed journal segment untouched this long has no writer left.
|
|
42
|
+
const closedSegmentQuietMs = 60_000;
|
|
43
|
+
// Saved live checkpoints remembered while their events await acknowledgement.
|
|
44
|
+
const retainedLiveCheckpoints = 8;
|
|
25
45
|
export const historySyncQueueHeadroom = 1_024;
|
|
26
46
|
const maxDraftsPerRecord = 32;
|
|
27
47
|
const adapters = new Map([
|
|
@@ -37,8 +57,37 @@ const adapters = new Map([
|
|
|
37
57
|
// Keep the flexible generic parser after the generated bridge adapters so
|
|
38
58
|
// the generic bridge cannot accidentally replace it with a fixed label.
|
|
39
59
|
["generic", genericActivityAdapter],
|
|
60
|
+
// Deliberately replaces the generated OpenCode bridge parser: the native
|
|
61
|
+
// adapter reads both OpenCode's database events and the structured CLI
|
|
62
|
+
// records (step_start, tool_use, text, step_finish) that
|
|
63
|
+
// `ingest --agent opencode` receives, as docs/activity-collection.md says.
|
|
40
64
|
["opencode", openCodeActivityAdapter],
|
|
65
|
+
// Native recordings use their own adapter IDs so the OpenAI-compatible
|
|
66
|
+
// `ingest --agent` bridges for the same products keep their parser.
|
|
67
|
+
["gemini-cli", geminiActivityAdapter],
|
|
68
|
+
["cline-cli", clineActivityAdapter],
|
|
69
|
+
["goose-session", gooseActivityAdapter],
|
|
70
|
+
["cursor-transcript", cursorActivityAdapter],
|
|
71
|
+
["copilot-chat", copilotChatActivityAdapter],
|
|
72
|
+
["kiro-session", kiroActivityAdapter],
|
|
73
|
+
["zed-thread", zedActivityAdapter],
|
|
74
|
+
["factory-droid", factoryDroidActivityAdapter],
|
|
75
|
+
["cline-task", clineExtensionActivityAdapter],
|
|
76
|
+
["roo-code-task", rooCodeActivityAdapter],
|
|
77
|
+
["kilo-code-task", kiloCodeActivityAdapter],
|
|
41
78
|
]);
|
|
79
|
+
/**
|
|
80
|
+
* `emit` refused an event. The reader was put back to where the scan
|
|
81
|
+
* started, so its records are read again once delivery has room.
|
|
82
|
+
*/
|
|
83
|
+
class DeliveryRefusedError extends Error {
|
|
84
|
+
}
|
|
85
|
+
const sourceIdentityFailureKey = "agent identities";
|
|
86
|
+
/** The kind of an error, never its message, which may quote a record. */
|
|
87
|
+
function errorName(error) {
|
|
88
|
+
const name = error instanceof Error ? error.name : "";
|
|
89
|
+
return /^[A-Za-z][A-Za-z0-9]{0,63}$/u.test(name) ? name : "unknown error";
|
|
90
|
+
}
|
|
42
91
|
export class ActivityCollector {
|
|
43
92
|
#config;
|
|
44
93
|
#credential;
|
|
@@ -52,6 +101,15 @@ export class ActivityCollector {
|
|
|
52
101
|
#canCollect;
|
|
53
102
|
#canSyncHistory;
|
|
54
103
|
#onSourceIdentity;
|
|
104
|
+
#onDeliveryRefused;
|
|
105
|
+
#onSourceFailure;
|
|
106
|
+
#reportedFailures = new WeakSet();
|
|
107
|
+
// Failures in a row of record conversion, per adapter, and of saving
|
|
108
|
+
// source identities. The keys are a fixed, small set.
|
|
109
|
+
#collectionFailures = new Map();
|
|
110
|
+
#registeredSourcesFailing = false;
|
|
111
|
+
#deliveryRefused = false;
|
|
112
|
+
#refusedDeliveries = 0;
|
|
55
113
|
#policy;
|
|
56
114
|
#limiter;
|
|
57
115
|
#agentsByKind = new Map();
|
|
@@ -65,6 +123,11 @@ export class ActivityCollector {
|
|
|
65
123
|
#oversizedEvents = 0;
|
|
66
124
|
#acknowledgedSequence;
|
|
67
125
|
#durableDelivery;
|
|
126
|
+
// Saved live-reader checkpoints, oldest first, with the sequence that
|
|
127
|
+
// their events end at. Bridge files they read through can be deleted once
|
|
128
|
+
// the server acknowledges that sequence.
|
|
129
|
+
#committedLive = [];
|
|
130
|
+
#flushDelivery;
|
|
68
131
|
#pendingCheckpoints = [];
|
|
69
132
|
#checkpointSave = Promise.resolve();
|
|
70
133
|
#sourceIdentitySaves = [];
|
|
@@ -84,6 +147,8 @@ export class ActivityCollector {
|
|
|
84
147
|
this.#canCollect = options.canCollect ?? (() => true);
|
|
85
148
|
this.#canSyncHistory = options.canSyncHistory ?? this.#canCollect;
|
|
86
149
|
this.#onSourceIdentity = options.onSourceIdentity ?? (() => undefined);
|
|
150
|
+
this.#onDeliveryRefused = options.onDeliveryRefused ?? (() => undefined);
|
|
151
|
+
this.#onSourceFailure = options.onSourceFailure ?? (() => undefined);
|
|
87
152
|
this.#policy = new LocalPolicyEngine(options.policy ?? options.config.policy, options.config.redactionPathPatterns);
|
|
88
153
|
this.#limiter = new MinuteRateLimiter(options.config.activityRateLimitPerMinute);
|
|
89
154
|
this.#lastSequence = options.initialSequence;
|
|
@@ -94,6 +159,7 @@ export class ActivityCollector {
|
|
|
94
159
|
throw new Error("Initial activity acknowledgement is invalid.");
|
|
95
160
|
}
|
|
96
161
|
this.#durableDelivery = options.durableDelivery ?? false;
|
|
162
|
+
this.#flushDelivery = options.flushDelivery ?? (() => undefined);
|
|
97
163
|
}
|
|
98
164
|
updateInventory(inventory) {
|
|
99
165
|
this.#agentsByKind.clear();
|
|
@@ -107,6 +173,9 @@ export class ActivityCollector {
|
|
|
107
173
|
if (acknowledgedSequence < 0n || allocationSequence < 0n) {
|
|
108
174
|
throw new Error("Activity sequence must not be negative.");
|
|
109
175
|
}
|
|
176
|
+
// The server lost acknowledgements: no saved checkpoint is known to be
|
|
177
|
+
// delivered any more.
|
|
178
|
+
this.#committedLive = [];
|
|
110
179
|
this.#acknowledgedSequence = acknowledgedSequence;
|
|
111
180
|
this.#lastSequence =
|
|
112
181
|
allocationSequence < acknowledgedSequence
|
|
@@ -140,22 +209,35 @@ export class ActivityCollector {
|
|
|
140
209
|
throw new Error("History sync expired before all available records were uploaded.");
|
|
141
210
|
}
|
|
142
211
|
if (!this.#canSyncHistory()) {
|
|
143
|
-
await abortableDelay(250, signal
|
|
212
|
+
await abortableDelay(250, signal);
|
|
144
213
|
continue;
|
|
145
214
|
}
|
|
146
215
|
// Release the collection lock between bounded batches. A large archive
|
|
147
216
|
// must not prevent the live reader from following current work.
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
217
|
+
const started = performance.now();
|
|
218
|
+
let pass;
|
|
219
|
+
try {
|
|
220
|
+
pass = await this.#withScanLock(async () => {
|
|
221
|
+
const before = this.#emittedEvents;
|
|
222
|
+
const result = await this.#collectOnce(this.#historyReader, true);
|
|
223
|
+
eventsUploaded += this.#emittedEvents - before;
|
|
224
|
+
return result;
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
catch (error) {
|
|
228
|
+
if (!(error instanceof DeliveryRefusedError))
|
|
229
|
+
throw error;
|
|
230
|
+
await abortableDelay(250, signal);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
154
233
|
recordsScanned += pass.sourceRecords;
|
|
155
234
|
filesDiscovered = Math.max(filesDiscovered, pass.discoveredFiles);
|
|
156
235
|
complete = pass.complete;
|
|
157
236
|
if (!complete && pass.sourceRecords === 0) {
|
|
158
|
-
await abortableDelay(100, signal
|
|
237
|
+
await abortableDelay(100, signal);
|
|
238
|
+
}
|
|
239
|
+
else if (!complete) {
|
|
240
|
+
await abortableDelay(historyRest(performance.now() - started), signal);
|
|
159
241
|
}
|
|
160
242
|
}
|
|
161
243
|
return Object.freeze({
|
|
@@ -186,10 +268,12 @@ export class ActivityCollector {
|
|
|
186
268
|
discoveredFiles: 0,
|
|
187
269
|
};
|
|
188
270
|
}
|
|
271
|
+
const start = reader.position();
|
|
189
272
|
const scan = await reader.scan([
|
|
190
273
|
...this.#sources,
|
|
191
|
-
...await this.#
|
|
274
|
+
...await this.#registeredSources(),
|
|
192
275
|
], Math.max(1, Math.floor(allowance / maxDraftsPerRecord)));
|
|
276
|
+
this.#reportSourceFailures(reader);
|
|
193
277
|
this.#sourceRecords += scan.metrics.records;
|
|
194
278
|
this.#malformedRecords += scan.metrics.malformed;
|
|
195
279
|
this.#oversizedRecords += scan.metrics.oversized;
|
|
@@ -198,61 +282,47 @@ export class ActivityCollector {
|
|
|
198
282
|
const adapter = adapters.get(record.adapterId);
|
|
199
283
|
if (adapter === undefined)
|
|
200
284
|
continue;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
const nativeDraft = vendorDraft(adapter.agentKind, context, genericAgent?.hint !== undefined
|
|
216
|
-
? genericAgent.hint
|
|
217
|
-
: adapter.id, vendorType(record.value), record.value);
|
|
218
|
-
const candidateDrafts = [nativeDraft, ...drafts.slice(0, 31)];
|
|
219
|
-
const permittedDrafts = !historical && record.initialReplay === true &&
|
|
220
|
-
this.#config.activityInitialReplayMode === "messages"
|
|
221
|
-
? candidateDrafts.filter((draft) => draft.payload.case === "activity" &&
|
|
222
|
-
(draft.payload.value.kind === ActivityKind.PROMPT ||
|
|
223
|
-
draft.payload.value.kind === ActivityKind.RESPONSE))
|
|
224
|
-
: candidateDrafts;
|
|
285
|
+
let converted;
|
|
286
|
+
try {
|
|
287
|
+
converted = this.#convertRecord(record, adapter, historical, now);
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
// A record this observer cannot convert is skipped like a malformed
|
|
291
|
+
// one: reading it again would fail the same way, and stopping
|
|
292
|
+
// would end collection from every source.
|
|
293
|
+
this.#malformedRecords++;
|
|
294
|
+
this.#reportCollectionFailure(record.adapterId, `one record could not be converted and was skipped (${errorName(error)})`);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
this.#collectionFailures.delete(record.adapterId);
|
|
298
|
+
this.#oversizedEvents += converted.oversized;
|
|
225
299
|
// A source record is atomic: never advance its cursor after dropping
|
|
226
300
|
// part of its normalized activity. One record may use the final budget
|
|
227
301
|
// plus a bounded burst; the next scan waits for replenishment.
|
|
228
302
|
if (!historical)
|
|
229
|
-
this.#limiter.consume(now,
|
|
230
|
-
for (const
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
tenantId: this.#credential.tenantId,
|
|
234
|
-
machineId: this.#credential.machineId,
|
|
235
|
-
agentId: this.#agentID(draft.agentKind, record.adapterId, record.sourceId, record.value, record.observedAt, genericAgent?.displayName),
|
|
236
|
-
sessionId: draft.sessionId,
|
|
237
|
-
sequence: nextSequence,
|
|
238
|
-
observedAt: timestampFromDate(clampObservedAt(draft.observedAt, now)),
|
|
239
|
-
schemaVersion: 1,
|
|
240
|
-
privacyClass: draft.privacyClass,
|
|
241
|
-
sourceEventId: createHash("sha256")
|
|
242
|
-
.update(`${record.sourceRecordId}\u0000${draftIndex}\u0000${draft.payload.case}`)
|
|
243
|
-
.digest("hex"),
|
|
244
|
-
payload: draft.payload,
|
|
245
|
-
});
|
|
246
|
-
if (toBinary(EventEnvelopeSchema, event).byteLength >
|
|
247
|
-
maxEnvelopeBytes) {
|
|
248
|
-
this.#oversizedEvents++;
|
|
249
|
-
continue;
|
|
303
|
+
this.#limiter.consume(now, converted.drafts);
|
|
304
|
+
for (const event of converted.events) {
|
|
305
|
+
try {
|
|
306
|
+
this.#emit(event);
|
|
250
307
|
}
|
|
251
|
-
|
|
252
|
-
|
|
308
|
+
catch (error) {
|
|
309
|
+
// The reader already moved past this scan's records. Put it back
|
|
310
|
+
// so none is skipped; the ones delivered before this event are
|
|
311
|
+
// sent again with the same source event IDs, which the server
|
|
312
|
+
// deduplicates. The sequence stays unused, leaving no gap.
|
|
313
|
+
reader.rewind(start);
|
|
314
|
+
this.#refusedDeliveries++;
|
|
315
|
+
if (!this.#deliveryRefused) {
|
|
316
|
+
this.#deliveryRefused = true;
|
|
317
|
+
this.#onDeliveryRefused(error);
|
|
318
|
+
}
|
|
319
|
+
throw new DeliveryRefusedError("Telemetry delivery refused an activity event.", { cause: error });
|
|
320
|
+
}
|
|
321
|
+
this.#lastSequence = event.sequence;
|
|
253
322
|
this.#emittedEvents++;
|
|
254
323
|
}
|
|
255
324
|
}
|
|
325
|
+
this.#deliveryRefused = false;
|
|
256
326
|
await this.#flushSourceIdentitySaves();
|
|
257
327
|
const pending = {
|
|
258
328
|
throughSequence: this.#lastSequence,
|
|
@@ -274,12 +344,163 @@ export class ActivityCollector {
|
|
|
274
344
|
discoveredFiles: scan.metrics.discoveredFiles,
|
|
275
345
|
};
|
|
276
346
|
}
|
|
347
|
+
/**
|
|
348
|
+
* The events for one source record, numbered from the next sequence, and
|
|
349
|
+
* how many normalized drafts they came from. Throws when the record cannot
|
|
350
|
+
* be converted; nothing is emitted for it then.
|
|
351
|
+
*/
|
|
352
|
+
#convertRecord(record, adapter, historical, now) {
|
|
353
|
+
const sessionID = reportedSessionID(record.adapterId, record.value, record.sourceId);
|
|
354
|
+
const drafts = adapter.parse(record.value, {
|
|
355
|
+
sessionId: sessionID,
|
|
356
|
+
observedAt: record.observedAt,
|
|
357
|
+
policy: this.#policy,
|
|
358
|
+
});
|
|
359
|
+
const context = {
|
|
360
|
+
sessionId: sessionID,
|
|
361
|
+
observedAt: recordObservedAt(record.value, record.observedAt),
|
|
362
|
+
policy: this.#policy,
|
|
363
|
+
};
|
|
364
|
+
const genericAgent = record.adapterId === "generic"
|
|
365
|
+
? reportedGenericAgentIdentity(record.value, record.sourceDisplayName)
|
|
366
|
+
: undefined;
|
|
367
|
+
const nativeDraft = vendorDraft(adapter.agentKind, context, genericAgent?.hint !== undefined
|
|
368
|
+
? genericAgent.hint
|
|
369
|
+
: adapter.id, vendorType(record.value), record.value);
|
|
370
|
+
const candidateDrafts = [nativeDraft, ...drafts.slice(0, 31)];
|
|
371
|
+
const permittedDrafts = !historical && record.initialReplay === true &&
|
|
372
|
+
this.#config.activityInitialReplayMode === "messages"
|
|
373
|
+
? candidateDrafts.filter((draft) => draft.payload.case === "activity" &&
|
|
374
|
+
(draft.payload.value.kind === ActivityKind.PROMPT ||
|
|
375
|
+
draft.payload.value.kind === ActivityKind.RESPONSE))
|
|
376
|
+
: candidateDrafts;
|
|
377
|
+
const events = [];
|
|
378
|
+
let oversized = 0;
|
|
379
|
+
for (const [draftIndex, draft] of permittedDrafts.entries()) {
|
|
380
|
+
const event = create(EventEnvelopeSchema, {
|
|
381
|
+
tenantId: this.#credential.tenantId,
|
|
382
|
+
machineId: this.#credential.machineId,
|
|
383
|
+
agentId: this.#agentID(draft.agentKind, record.adapterId, record.sourceId, record.value, record.observedAt, genericAgent?.displayName),
|
|
384
|
+
sessionId: draft.sessionId,
|
|
385
|
+
sequence: this.#lastSequence + 1n + BigInt(events.length),
|
|
386
|
+
observedAt: timestampFromDate(clampObservedAt(draft.observedAt, now)),
|
|
387
|
+
schemaVersion: 1,
|
|
388
|
+
privacyClass: draft.privacyClass,
|
|
389
|
+
sourceEventId: createHash("sha256")
|
|
390
|
+
.update(`${record.sourceRecordId}\u0000${draftIndex}\u0000${draft.payload.case}`)
|
|
391
|
+
.digest("hex"),
|
|
392
|
+
payload: draft.payload,
|
|
393
|
+
});
|
|
394
|
+
if (toBinary(EventEnvelopeSchema, event).byteLength >
|
|
395
|
+
maxEnvelopeBytes) {
|
|
396
|
+
oversized++;
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
events.push(event);
|
|
400
|
+
}
|
|
401
|
+
return { events, drafts: permittedDrafts.length, oversized };
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Says that collection work keeps failing, at the 1st, 2nd, 4th, 8th...
|
|
405
|
+
* failure in a row, so it stays visible without flooding the log. The
|
|
406
|
+
* message never includes activity content.
|
|
407
|
+
*/
|
|
408
|
+
#reportCollectionFailure(key, message) {
|
|
409
|
+
const failures = (this.#collectionFailures.get(key) ?? 0) + 1;
|
|
410
|
+
this.#collectionFailures.set(key, failures);
|
|
411
|
+
if ((failures & (failures - 1)) !== 0)
|
|
412
|
+
return;
|
|
413
|
+
const now = this.#clock();
|
|
414
|
+
this.#onSourceFailure(Object.freeze({
|
|
415
|
+
adapterId: key,
|
|
416
|
+
failures,
|
|
417
|
+
message,
|
|
418
|
+
failedAt: now,
|
|
419
|
+
retryAt: now,
|
|
420
|
+
}));
|
|
421
|
+
}
|
|
277
422
|
async run(signal) {
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
423
|
+
let prunedAt = 0;
|
|
424
|
+
try {
|
|
425
|
+
while (!signal.aborted) {
|
|
426
|
+
try {
|
|
427
|
+
await this.scanOnce();
|
|
428
|
+
}
|
|
429
|
+
catch (error) {
|
|
430
|
+
// A full queue or spool waits for acknowledgements like any
|
|
431
|
+
// other backpressure; the refused records are read again.
|
|
432
|
+
if (!(error instanceof DeliveryRefusedError))
|
|
433
|
+
throw error;
|
|
434
|
+
}
|
|
435
|
+
if (this.#clock().getTime() - prunedAt >= bridgePruneIntervalMs) {
|
|
436
|
+
prunedAt = this.#clock().getTime();
|
|
437
|
+
// Cleanup only frees disk space; it retries on the next pass.
|
|
438
|
+
await this.pruneBridgeFiles().catch(() => undefined);
|
|
439
|
+
}
|
|
440
|
+
await abortableDelay(this.#config.activityScanIntervalMs, signal);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
finally {
|
|
444
|
+
this.close();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Deletes bridge files this observer is done with: an `ingest` file or a
|
|
449
|
+
* closed journal segment whose records were all read under a checkpoint
|
|
450
|
+
* the server has acknowledged through, or any of them after seven days.
|
|
451
|
+
* A file an `ingest` run is still writing, and each journal's newest
|
|
452
|
+
* segment, are never deleted while in use.
|
|
453
|
+
*/
|
|
454
|
+
async pruneBridgeFiles() {
|
|
455
|
+
const now = this.#clock().getTime();
|
|
456
|
+
const acknowledged = this.#acknowledgedSequence;
|
|
457
|
+
const settled = this.#committedLive.findLast((checkpoint) => checkpoint.throughSequence <= acknowledged);
|
|
458
|
+
for (const agent of activityBridgeAgents) {
|
|
459
|
+
const root = join(this.#config.dataDirectory, "bridges", agent.id);
|
|
460
|
+
let names;
|
|
461
|
+
try {
|
|
462
|
+
names = await readdir(root);
|
|
463
|
+
}
|
|
464
|
+
catch {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
const present = new Set(names);
|
|
468
|
+
const closedSegments = new Set(closedJournalSegments(names));
|
|
469
|
+
for (const name of names) {
|
|
470
|
+
const ingested = ingestedBridgeFile.test(name);
|
|
471
|
+
if (!ingested && !closedSegments.has(name))
|
|
472
|
+
continue;
|
|
473
|
+
const path = join(root, name);
|
|
474
|
+
try {
|
|
475
|
+
const modifiedMs = (await stat(path)).mtimeMs;
|
|
476
|
+
const expired = now - modifiedMs >= bridgeFileRetentionMs;
|
|
477
|
+
const inUse = ingested
|
|
478
|
+
? present.has(`${name}${bridgeWritingSuffix}`)
|
|
479
|
+
// A segment closes when its journal starts the next one; a
|
|
480
|
+
// writer that chose it just before may still be appending.
|
|
481
|
+
: now - modifiedMs < closedSegmentQuietMs;
|
|
482
|
+
if (expired ||
|
|
483
|
+
(!inUse && settled !== undefined &&
|
|
484
|
+
await fileReadThrough(path, settled.cursors))) {
|
|
485
|
+
await unlink(path);
|
|
486
|
+
if (ingested) {
|
|
487
|
+
await unlink(`${path}${bridgeWritingSuffix}`).catch(() => undefined);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
catch {
|
|
492
|
+
// Gone, or locked by another program: retried on the next pass.
|
|
493
|
+
}
|
|
494
|
+
}
|
|
281
495
|
}
|
|
282
496
|
}
|
|
497
|
+
/**
|
|
498
|
+
* Release the database connections the live reader keeps open. A later
|
|
499
|
+
* scan opens them again.
|
|
500
|
+
*/
|
|
501
|
+
close() {
|
|
502
|
+
this.#reader.close();
|
|
503
|
+
}
|
|
283
504
|
metrics() {
|
|
284
505
|
return Object.freeze({
|
|
285
506
|
sourceRecords: this.#sourceRecords,
|
|
@@ -291,8 +512,53 @@ export class ActivityCollector {
|
|
|
291
512
|
lastSequence: this.#lastSequence,
|
|
292
513
|
pendingCheckpoints: this.#pendingCheckpoints.length,
|
|
293
514
|
backpressuredScans: this.#backpressuredScans,
|
|
515
|
+
refusedDeliveries: this.#refusedDeliveries,
|
|
516
|
+
failingSources: this.#reader.sourceFailures().length,
|
|
294
517
|
});
|
|
295
518
|
}
|
|
519
|
+
/**
|
|
520
|
+
* Sources of registered custom agents. When their list cannot be read,
|
|
521
|
+
* this scan goes on with the built-in sources.
|
|
522
|
+
*/
|
|
523
|
+
async #registeredSources() {
|
|
524
|
+
try {
|
|
525
|
+
const sources = await this.#additionalSources();
|
|
526
|
+
this.#registeredSourcesFailing = false;
|
|
527
|
+
return sources;
|
|
528
|
+
}
|
|
529
|
+
catch (error) {
|
|
530
|
+
if (!this.#registeredSourcesFailing) {
|
|
531
|
+
this.#registeredSourcesFailing = true;
|
|
532
|
+
const now = this.#clock();
|
|
533
|
+
this.#onSourceFailure(Object.freeze({
|
|
534
|
+
adapterId: "registered agents",
|
|
535
|
+
failures: 1,
|
|
536
|
+
message: (error instanceof Error ? error.message : String(error))
|
|
537
|
+
.replace(/\s+/gu, " ")
|
|
538
|
+
.slice(0, 200),
|
|
539
|
+
failedAt: now,
|
|
540
|
+
// Read again on the next scan.
|
|
541
|
+
retryAt: now,
|
|
542
|
+
}));
|
|
543
|
+
}
|
|
544
|
+
return [];
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
#reportSourceFailures(reader) {
|
|
548
|
+
for (const failure of reader.sourceFailures()) {
|
|
549
|
+
if (failure.failures !== 1 || this.#reportedFailures.has(failure))
|
|
550
|
+
continue;
|
|
551
|
+
this.#reportedFailures.add(failure);
|
|
552
|
+
this.#onSourceFailure(failure);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
/** Sources and files skipped after a failure, by the live and history readers. */
|
|
556
|
+
sourceFailures() {
|
|
557
|
+
return [
|
|
558
|
+
...this.#reader.sourceFailures(),
|
|
559
|
+
...this.#historyReader.sourceFailures(),
|
|
560
|
+
];
|
|
561
|
+
}
|
|
296
562
|
sourceIdentities() {
|
|
297
563
|
return [...this.#sourceIdentities.values()].sort((left, right) => left.agentId.localeCompare(right.agentId));
|
|
298
564
|
}
|
|
@@ -306,15 +572,34 @@ export class ActivityCollector {
|
|
|
306
572
|
this.#checkpointSave = this.#checkpointSave
|
|
307
573
|
.catch(() => undefined)
|
|
308
574
|
.then(async () => {
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
575
|
+
let currentDurableThrough = this.#acknowledgedSequence;
|
|
576
|
+
if (this.#durableDelivery) {
|
|
577
|
+
const emittedThrough = this.#lastSequence;
|
|
578
|
+
try {
|
|
579
|
+
await this.#flushDelivery();
|
|
580
|
+
currentDurableThrough = emittedThrough;
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
// Not written: only checkpoints the server acknowledged move.
|
|
584
|
+
// The rest stay pending for the next save, and delivery refuses
|
|
585
|
+
// new events until the spool can be written again.
|
|
586
|
+
}
|
|
587
|
+
}
|
|
312
588
|
const committed = new Set();
|
|
313
589
|
for (const pending of eligible) {
|
|
314
590
|
if (pending.throughSequence > currentDurableThrough)
|
|
315
591
|
continue;
|
|
316
592
|
await pending.reader.commit(pending.checkpoint);
|
|
317
593
|
committed.add(pending);
|
|
594
|
+
if (pending.reader === this.#reader) {
|
|
595
|
+
this.#committedLive = [
|
|
596
|
+
...this.#committedLive.slice(-(retainedLiveCheckpoints - 1)),
|
|
597
|
+
{
|
|
598
|
+
cursors: pending.checkpoint.cursors,
|
|
599
|
+
throughSequence: pending.throughSequence,
|
|
600
|
+
},
|
|
601
|
+
];
|
|
602
|
+
}
|
|
318
603
|
}
|
|
319
604
|
this.#pendingCheckpoints = this.#pendingCheckpoints.filter((candidate) => !committed.has(candidate));
|
|
320
605
|
});
|
|
@@ -366,21 +651,45 @@ export class ActivityCollector {
|
|
|
366
651
|
capabilities: ["activity", "source-isolated", "unattributed"],
|
|
367
652
|
});
|
|
368
653
|
this.#sourceIdentities.set(agentID, identity);
|
|
654
|
+
// An identity that could not be saved is forgotten, so the source's
|
|
655
|
+
// next record saves it again.
|
|
656
|
+
const forget = (error) => {
|
|
657
|
+
if (this.#sourceIdentities.get(agentID) === identity) {
|
|
658
|
+
this.#sourceIdentities.delete(agentID);
|
|
659
|
+
}
|
|
660
|
+
throw error;
|
|
661
|
+
};
|
|
662
|
+
let save;
|
|
369
663
|
try {
|
|
370
|
-
|
|
664
|
+
save = Promise.resolve(this.#onSourceIdentity(identity)).catch(forget);
|
|
371
665
|
}
|
|
372
666
|
catch (error) {
|
|
373
|
-
|
|
667
|
+
save = Promise.reject(error).catch(forget);
|
|
374
668
|
}
|
|
669
|
+
// Handled here too: a scan that stops early leaves the save for the
|
|
670
|
+
// next flush, and an unhandled rejection would end the process.
|
|
671
|
+
save.catch(() => undefined);
|
|
672
|
+
this.#sourceIdentitySaves.push(save);
|
|
375
673
|
}
|
|
674
|
+
/**
|
|
675
|
+
* Waits for the identity saves this scan started. A failed save is
|
|
676
|
+
* reported and retried with a later record; it never stops collection,
|
|
677
|
+
* because the events already carry the agent ID.
|
|
678
|
+
*/
|
|
376
679
|
async #flushSourceIdentitySaves() {
|
|
377
680
|
const pending = this.#sourceIdentitySaves.splice(0);
|
|
378
681
|
if (pending.length === 0)
|
|
379
682
|
return;
|
|
380
683
|
const results = await Promise.allSettled(pending);
|
|
381
684
|
const failure = results.find((result) => result.status === "rejected");
|
|
382
|
-
if (failure
|
|
383
|
-
|
|
685
|
+
if (failure === undefined) {
|
|
686
|
+
this.#collectionFailures.delete(sourceIdentityFailureKey);
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
const reason = failure.reason;
|
|
690
|
+
this.#reportCollectionFailure(sourceIdentityFailureKey, (reason instanceof Error ? reason.message : String(reason))
|
|
691
|
+
.replace(/\s+/gu, " ")
|
|
692
|
+
.slice(0, 200));
|
|
384
693
|
}
|
|
385
694
|
}
|
|
386
695
|
export function mergeInventoryWithSourceIdentities(inventory, sourceIdentities) {
|
|
@@ -557,6 +866,99 @@ function configuredSources(config) {
|
|
|
557
866
|
roots: config.activitySourceDirectories.opencode,
|
|
558
867
|
format: "opencode-sqlite",
|
|
559
868
|
},
|
|
869
|
+
{
|
|
870
|
+
adapterId: "gemini-cli",
|
|
871
|
+
roots: config.activitySourceDirectories.gemini,
|
|
872
|
+
// <project>/chats/session-*.jsonl and subagent <parent>/<id>.jsonl.
|
|
873
|
+
pathPattern: "^[^/]+/chats/",
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
adapterId: "cline-cli",
|
|
877
|
+
roots: config.activitySourceDirectories.cline,
|
|
878
|
+
format: "json-messages",
|
|
879
|
+
// <session>/<session>.messages.json, rewritten in place per turn.
|
|
880
|
+
pathPattern: "^[^/]+/[^/]+\\.messages\\.json$",
|
|
881
|
+
messagesField: "messages",
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
adapterId: "goose-session",
|
|
885
|
+
roots: config.activitySourceDirectories.goose,
|
|
886
|
+
format: "goose-sqlite",
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
adapterId: "cursor-transcript",
|
|
890
|
+
roots: config.activitySourceDirectories.cursor,
|
|
891
|
+
format: "json-messages",
|
|
892
|
+
// <project>/agent-transcripts/<id>/<id>.jsonl and
|
|
893
|
+
// <id>/subagents/<sub>.jsonl. The final line often has no newline yet.
|
|
894
|
+
pathPattern: "^[^/]+/agent-transcripts/.+\\.jsonl$",
|
|
895
|
+
document: {
|
|
896
|
+
documentFormat: "jsonl",
|
|
897
|
+
directoryPattern: "^[^/]+(?:/agent-transcripts(?:/[^/]+(?:/subagents)?)?)?$",
|
|
898
|
+
},
|
|
899
|
+
},
|
|
900
|
+
// VS Code chat sessions: <workspace>/chatSessions/<id>.json documents
|
|
901
|
+
// and, with operation-log storage, <id>.jsonl logs of the same session.
|
|
902
|
+
...["json", "jsonl"].map((extension) => ({
|
|
903
|
+
adapterId: "copilot-chat",
|
|
904
|
+
roots: config.activitySourceDirectories.copilotChat,
|
|
905
|
+
format: "json-messages",
|
|
906
|
+
pathPattern: `^(?:[^/]+/chatSessions/)?[^/]+\\.${extension}$`,
|
|
907
|
+
messagesField: "requests",
|
|
908
|
+
document: {
|
|
909
|
+
documentFormat: extension === "json"
|
|
910
|
+
? "json"
|
|
911
|
+
: "mutation-log",
|
|
912
|
+
directoryPattern: "^[^/]+(?:/chatSessions)?$",
|
|
913
|
+
messageIdField: "requestId",
|
|
914
|
+
completeFields: ["result", "isCanceled", "modelState.completedAt"],
|
|
915
|
+
sessionIdField: "sessionId",
|
|
916
|
+
},
|
|
917
|
+
})),
|
|
918
|
+
{
|
|
919
|
+
adapterId: "kiro-session",
|
|
920
|
+
roots: config.activitySourceDirectories.kiro,
|
|
921
|
+
format: "json-messages",
|
|
922
|
+
// <workspace>/<session id>.json beside the <workspace>/sessions.json index.
|
|
923
|
+
pathPattern: "^[^/]+/(?!sessions[.]json$)[^/]+[.]json$",
|
|
924
|
+
messagesField: "history",
|
|
925
|
+
document: {
|
|
926
|
+
directoryPattern: "^[^/]+$",
|
|
927
|
+
messageIdField: "message.id",
|
|
928
|
+
sessionIdField: "sessionId",
|
|
929
|
+
},
|
|
930
|
+
},
|
|
931
|
+
{
|
|
932
|
+
adapterId: "zed-thread",
|
|
933
|
+
roots: config.activitySourceDirectories.zed,
|
|
934
|
+
format: "zed-sqlite",
|
|
935
|
+
},
|
|
936
|
+
{
|
|
937
|
+
adapterId: "factory-droid",
|
|
938
|
+
roots: config.activitySourceDirectories.droid,
|
|
939
|
+
// <project>/<session id>.jsonl, appended as the session runs, beside
|
|
940
|
+
// <session id>.settings.json.
|
|
941
|
+
pathPattern: "^(?:[^/]+/)?[^/]+\\.jsonl$",
|
|
942
|
+
},
|
|
943
|
+
// Cline, Roo Code and Kilo Code tasks: <task id>/ui_messages.json, the
|
|
944
|
+
// list of messages the extension shows, rewritten as the task runs.
|
|
945
|
+
...[
|
|
946
|
+
["cline-task", config.activitySourceDirectories.clineTasks],
|
|
947
|
+
["roo-code-task", config.activitySourceDirectories.rooCode],
|
|
948
|
+
["kilo-code-task", config.activitySourceDirectories.kiloCode],
|
|
949
|
+
].map(([adapterId, roots]) => ({
|
|
950
|
+
adapterId,
|
|
951
|
+
roots,
|
|
952
|
+
format: "json-messages",
|
|
953
|
+
pathPattern: "^[^/]+/ui_messages[.]json$",
|
|
954
|
+
messagesField: "",
|
|
955
|
+
document: {
|
|
956
|
+
directoryPattern: "^[^/]+$",
|
|
957
|
+
messageIdField: "ts",
|
|
958
|
+
pendingField: "partial",
|
|
959
|
+
firstMessageField: clineTaskFirstMessageField,
|
|
960
|
+
},
|
|
961
|
+
})),
|
|
560
962
|
{
|
|
561
963
|
adapterId: "generic",
|
|
562
964
|
roots: config.activitySourceDirectories.generic,
|
|
@@ -602,6 +1004,17 @@ function sourceIdentityDisplayName(adapterID, sourceID, sourceDisplayName) {
|
|
|
602
1004
|
case "hermes": return "Hermes Agent";
|
|
603
1005
|
case "openclaw": return "OpenClaw";
|
|
604
1006
|
case "opencode": return "OpenCode";
|
|
1007
|
+
case "gemini-cli": return "Gemini CLI";
|
|
1008
|
+
case "cline-cli": return "Cline";
|
|
1009
|
+
case "goose-session": return "Goose";
|
|
1010
|
+
case "cursor-transcript": return "Cursor";
|
|
1011
|
+
case "copilot-chat": return "GitHub Copilot";
|
|
1012
|
+
case "kiro-session": return "Kiro";
|
|
1013
|
+
case "zed-thread": return "Zed";
|
|
1014
|
+
case "factory-droid": return "Factory Droid";
|
|
1015
|
+
case "cline-task": return "Cline";
|
|
1016
|
+
case "roo-code-task": return "Roo Code";
|
|
1017
|
+
case "kilo-code-task": return "Kilo Code";
|
|
605
1018
|
default: return "Agent activity";
|
|
606
1019
|
}
|
|
607
1020
|
})();
|
|
@@ -692,15 +1105,15 @@ function clampObservedAt(value, now) {
|
|
|
692
1105
|
return now;
|
|
693
1106
|
return value;
|
|
694
1107
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1108
|
+
/**
|
|
1109
|
+
* How long a history import rests after a batch that took `spentMs`. Resting
|
|
1110
|
+
* as long as the batch worked keeps a large first import near half a core,
|
|
1111
|
+
* and leaves the other half of the event loop to live collection; a full
|
|
1112
|
+
* catch-up once held a whole core for minutes. One second at most.
|
|
1113
|
+
*/
|
|
1114
|
+
export function historyRest(spentMs) {
|
|
1115
|
+
if (!Number.isFinite(spentMs) || spentMs <= 0)
|
|
1116
|
+
return 0;
|
|
1117
|
+
return Math.min(1_000, Math.round(spentMs));
|
|
705
1118
|
}
|
|
706
1119
|
//# sourceMappingURL=collector.js.map
|