@pdpp/local-collector 0.1.0-beta.6 → 0.1.0-beta.8

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.
Files changed (26) hide show
  1. package/dist/local-collector/bin/pdpp-local-collector.js +580 -22
  2. package/dist/local-collector/src/runner.d.ts +1 -1
  3. package/dist/local-collector/src/runner.js +15 -1
  4. package/dist/polyfill-connectors/connectors/claude_code/index.js +85 -48
  5. package/dist/polyfill-connectors/connectors/codex/index.js +390 -108
  6. package/dist/polyfill-connectors/connectors/codex/parsers.js +5 -3
  7. package/dist/polyfill-connectors/src/bounded-file-preview.js +76 -0
  8. package/dist/polyfill-connectors/src/browser-handoff.js +38 -5
  9. package/dist/polyfill-connectors/src/collector-build-info.d.ts +8 -0
  10. package/dist/polyfill-connectors/src/collector-build-info.js +10 -0
  11. package/dist/polyfill-connectors/src/collector-runner.d.ts +54 -0
  12. package/dist/polyfill-connectors/src/collector-runner.js +250 -18
  13. package/dist/polyfill-connectors/src/connector-exit.js +62 -0
  14. package/dist/polyfill-connectors/src/connector-runtime-protocol.d.ts +41 -21
  15. package/dist/polyfill-connectors/src/connector-runtime.js +241 -30
  16. package/dist/polyfill-connectors/src/fingerprint-cursor.js +107 -0
  17. package/dist/polyfill-connectors/src/local-device-client.d.ts +17 -0
  18. package/dist/polyfill-connectors/src/local-device-client.js +69 -9
  19. package/dist/polyfill-connectors/src/local-device-outbox.d.ts +59 -0
  20. package/dist/polyfill-connectors/src/local-device-outbox.js +394 -5
  21. package/dist/polyfill-connectors/src/local-source-inventory.js +8 -1
  22. package/dist/polyfill-connectors/src/runner/index.d.ts +4 -3
  23. package/dist/polyfill-connectors/src/runner/index.js +4 -3
  24. package/dist/polyfill-connectors/src/safe-text-preview.js +13 -0
  25. package/dist/polyfill-connectors/src/static-secret-injection.js +155 -0
  26. package/package.json +1 -1
@@ -62,7 +62,9 @@ export function parseFrontmatter(text) {
62
62
  export function isRolloutFile(name) {
63
63
  return name.startsWith("rollout-") && name.endsWith(".jsonl");
64
64
  }
65
- export function buildThreadSessionRecord(id, t, agg) {
65
+ export function buildThreadSessionRecord(id, t, agg, priorFingerprint) {
66
+ const messageCount = agg?.messageCount ?? priorFingerprint?.message_count ?? null;
67
+ const functionCallCount = agg?.functionCallCount ?? priorFingerprint?.function_call_count ?? null;
66
68
  return {
67
69
  id,
68
70
  cwd: t.cwd || null,
@@ -74,8 +76,8 @@ export function buildThreadSessionRecord(id, t, agg) {
74
76
  repository_url: t.git_origin_url || null,
75
77
  started_at: epochToIso(t.created_at) || agg?.meta?.timestamp || agg?.firstTs || null,
76
78
  last_event_at: epochToIso(t.updated_at) || agg?.lastTs || null,
77
- message_count: agg?.messageCount ?? null,
78
- function_call_count: agg?.functionCallCount ?? null,
79
+ message_count: messageCount,
80
+ function_call_count: functionCallCount,
79
81
  title: textPreview(t.title || null, 500),
80
82
  archived: t.archived === 1 || t.archived === true,
81
83
  tokens_used: t.tokens_used ?? null,
@@ -0,0 +1,76 @@
1
+ import { createReadStream } from "node:fs";
2
+ export const BOUNDED_PREVIEW_MAX_BYTES = 64 * 1024;
3
+ function trailingIncompleteUtf8Bytes(buf) {
4
+ let i = buf.length - 1;
5
+ let continuation = 0;
6
+ while (i >= 0) {
7
+ const byte = buf[i];
8
+ if (byte === undefined || (byte & 0b1100_0000) !== 0b1000_0000) {
9
+ break;
10
+ }
11
+ continuation++;
12
+ i--;
13
+ if (continuation > 3) {
14
+ return 0;
15
+ }
16
+ }
17
+ const lead = i >= 0 ? buf[i] : undefined;
18
+ if (lead === undefined) {
19
+ return 0;
20
+ }
21
+ let expected;
22
+ if ((lead & 0b1000_0000) === 0) {
23
+ expected = 1;
24
+ }
25
+ else if ((lead & 0b1110_0000) === 0b1100_0000) {
26
+ expected = 2;
27
+ }
28
+ else if ((lead & 0b1111_0000) === 0b1110_0000) {
29
+ expected = 3;
30
+ }
31
+ else if ((lead & 0b1111_1000) === 0b1111_0000) {
32
+ expected = 4;
33
+ }
34
+ else {
35
+ return 0;
36
+ }
37
+ const have = continuation + 1;
38
+ return have < expected ? have : 0;
39
+ }
40
+ export async function readBoundedFilePreview(path, maxBytes = BOUNDED_PREVIEW_MAX_BYTES) {
41
+ if (maxBytes <= 0) {
42
+ return { buffer: Buffer.alloc(0), bytesRead: 0, truncated: false };
43
+ }
44
+ return await new Promise((resolve) => {
45
+ const chunks = [];
46
+ let collected = 0;
47
+ let sawMore = false;
48
+ const stream = createReadStream(path, { start: 0, end: maxBytes });
49
+ stream.on("data", (chunk) => {
50
+ const buf = chunk;
51
+ const remaining = maxBytes - collected;
52
+ if (remaining <= 0) {
53
+ sawMore = true;
54
+ return;
55
+ }
56
+ if (buf.length > remaining) {
57
+ chunks.push(buf.subarray(0, remaining));
58
+ collected += remaining;
59
+ sawMore = true;
60
+ }
61
+ else {
62
+ chunks.push(buf);
63
+ collected += buf.length;
64
+ }
65
+ });
66
+ stream.on("error", () => resolve(null));
67
+ stream.on("end", () => {
68
+ let buffer = chunks.length === 1 && chunks[0] ? chunks[0] : Buffer.concat(chunks, collected);
69
+ const trim = trailingIncompleteUtf8Bytes(buffer);
70
+ if (trim > 0) {
71
+ buffer = buffer.subarray(0, buffer.length - trim);
72
+ }
73
+ resolve({ buffer, bytesRead: buffer.length, truncated: sawMore });
74
+ });
75
+ });
76
+ }
@@ -19,6 +19,7 @@ const BROWSER_SURFACE_ID_ENV = "PDPP_BROWSER_SURFACE_ID";
19
19
  const BROWSER_SURFACE_LEASE_ID_ENV = "PDPP_BROWSER_SURFACE_LEASE_ID";
20
20
  const BROWSER_SURFACE_PROFILE_KEY_ENV = "PDPP_BROWSER_SURFACE_PROFILE_KEY";
21
21
  const BROWSER_SURFACE_REQUIRED_ENV = "PDPP_BROWSER_SURFACE_REQUIRED";
22
+ const BROWSER_SURFACE_REMOTE_CDP_URL_ENV = "PDPP_BROWSER_SURFACE_REMOTE_CDP_URL";
22
23
  const BROWSER_SURFACE_STREAM_BASE_URL_ENV = "PDPP_BROWSER_SURFACE_STREAM_BASE_URL";
23
24
  function resolveCdpEndpointFromEnv(env) {
24
25
  const host = env[BROWSER_CDP_HOST_ENV]?.trim();
@@ -43,12 +44,14 @@ function resolveManagedNekoDescriptorFromEnv(env) {
43
44
  const baseUrl = nonEmptyEnv(env, BROWSER_SURFACE_STREAM_BASE_URL_ENV);
44
45
  const leaseId = nonEmptyEnv(env, BROWSER_SURFACE_LEASE_ID_ENV);
45
46
  const profileKey = nonEmptyEnv(env, BROWSER_SURFACE_PROFILE_KEY_ENV);
47
+ const cdpHttpUrl = nonEmptyEnv(env, BROWSER_SURFACE_REMOTE_CDP_URL_ENV);
46
48
  if (!(baseUrl && leaseId && profileKey)) {
47
49
  return;
48
50
  }
49
51
  return {
50
52
  backend: "neko",
51
53
  base_url: baseUrl,
54
+ ...(cdpHttpUrl ? { cdp_http_url: cdpHttpUrl } : {}),
52
55
  lease_id: leaseId,
53
56
  profile_key: profileKey,
54
57
  ...(nonEmptyEnv(env, BROWSER_SURFACE_ID_ENV) ? { surface_id: nonEmptyEnv(env, BROWSER_SURFACE_ID_ENV) } : {}),
@@ -57,7 +60,29 @@ function resolveManagedNekoDescriptorFromEnv(env) {
57
60
  function generateInteractionId() {
58
61
  return `int_${String(Date.now())}_${randomBytes(4).toString("hex")}`;
59
62
  }
60
- async function readManualActionPageMetadata(page) {
63
+ export const DEADLINE_TIMEOUT = Symbol("pdpp.browser-handoff.deadline-timeout");
64
+ const DEFAULT_METADATA_READ_DEADLINE_MS = 2000;
65
+ export async function withDeadline(work, ms, onTimeout) {
66
+ if (!(Number.isFinite(ms) && ms > 0)) {
67
+ return work;
68
+ }
69
+ let timer;
70
+ const deadline = new Promise((resolve) => {
71
+ timer = setTimeout(() => {
72
+ onTimeout?.();
73
+ resolve(DEADLINE_TIMEOUT);
74
+ }, ms);
75
+ });
76
+ try {
77
+ return await Promise.race([work, deadline]);
78
+ }
79
+ finally {
80
+ if (timer) {
81
+ clearTimeout(timer);
82
+ }
83
+ }
84
+ }
85
+ async function readManualActionPageMetadata(page, deadlineMs = DEFAULT_METADATA_READ_DEADLINE_MS) {
61
86
  let pageUrl;
62
87
  let pageTitle;
63
88
  try {
@@ -66,7 +91,12 @@ async function readManualActionPageMetadata(page) {
66
91
  catch {
67
92
  }
68
93
  try {
69
- pageTitle = await page.title();
94
+ const titleResult = await withDeadline(page.title(), deadlineMs, () => {
95
+ process.stderr.write(`[browser-handoff] page.title() timed out after ${String(deadlineMs)}ms; emitting interaction without page title.\n`);
96
+ });
97
+ if (titleResult !== DEADLINE_TIMEOUT) {
98
+ pageTitle = titleResult;
99
+ }
70
100
  }
71
101
  catch {
72
102
  }
@@ -115,11 +145,11 @@ function registerCdpManualActionTarget(args) {
115
145
  ...(args.reason ? { reason: args.reason } : {}),
116
146
  });
117
147
  }
118
- export async function prepareManualAction(args) {
148
+ export async function prepareBrowserInteractionTarget(args) {
119
149
  const env = args.env ?? process.env;
120
150
  const resolveStreamingRegistration = args.resolveStreamingRegistration ?? resolveStreamingRegistrationFromEnv;
121
151
  const resolveWsUrl = args.resolveWsUrl ?? resolveWsUrlForExactPage;
122
- const interactionId = generateInteractionId();
152
+ const interactionId = args.interactionId ?? generateInteractionId();
123
153
  const registration = await resolveStreamingRegistration(env);
124
154
  if (!registration) {
125
155
  return { interactionId, registered: false };
@@ -161,6 +191,9 @@ export async function prepareManualAction(args) {
161
191
  }
162
192
  return { interactionId, registered: true };
163
193
  }
194
+ export function prepareManualAction(args) {
195
+ return prepareBrowserInteractionTarget(args);
196
+ }
164
197
  function captureManualActionFixture(args) {
165
198
  if (!args.capture) {
166
199
  return;
@@ -194,4 +227,4 @@ export async function manualAction(args, sendInteraction) {
194
227
  ...(args.timeoutSeconds === undefined ? {} : { timeout_seconds: args.timeoutSeconds }),
195
228
  });
196
229
  }
197
- export { BROWSER_CDP_HOST_ENV, BROWSER_CDP_PORT_ENV, BROWSER_SURFACE_ID_ENV, BROWSER_SURFACE_LEASE_ID_ENV, BROWSER_SURFACE_PROFILE_KEY_ENV, BROWSER_SURFACE_REQUIRED_ENV, BROWSER_SURFACE_STREAM_BASE_URL_ENV, };
230
+ export { BROWSER_CDP_HOST_ENV, BROWSER_CDP_PORT_ENV, BROWSER_SURFACE_ID_ENV, BROWSER_SURFACE_LEASE_ID_ENV, BROWSER_SURFACE_PROFILE_KEY_ENV, BROWSER_SURFACE_REMOTE_CDP_URL_ENV, BROWSER_SURFACE_REQUIRED_ENV, BROWSER_SURFACE_STREAM_BASE_URL_ENV, };
@@ -0,0 +1,8 @@
1
+ export declare const COLLECTOR_BUILD_SOURCE_SENTINEL = "source";
2
+ export interface CollectorBuildInfo {
3
+ builtAt: string | null;
4
+ revision: string;
5
+ version: string;
6
+ }
7
+ export declare const COLLECTOR_BUILD_INFO: CollectorBuildInfo;
8
+ export declare function buildAgentVersion(info?: CollectorBuildInfo): string;
@@ -0,0 +1,10 @@
1
+ const COLLECTOR_BUILD_SOURCE_SENTINEL = "source";
2
+ const COLLECTOR_BUILD_INFO = {
3
+ builtAt: "2026-06-10T20:53:47.540Z",
4
+ revision: "f9fdc632c3b0",
5
+ version: "0.1.0-beta.8",
6
+ };
7
+ function buildAgentVersion(info = COLLECTOR_BUILD_INFO) {
8
+ return `${info.version}+${info.revision}`;
9
+ }
10
+ export { COLLECTOR_BUILD_INFO, COLLECTOR_BUILD_SOURCE_SENTINEL, buildAgentVersion };
@@ -16,6 +16,38 @@ export interface CollectorOutboxPolicy {
16
16
  retryBackoffMs: number;
17
17
  }
18
18
  export declare const DEFAULT_COLLECTOR_OUTBOX_POLICY: Readonly<CollectorOutboxPolicy>;
19
+ export interface CollectorAutoPrunePolicy {
20
+ enabled: boolean;
21
+ keepRecentCount: number;
22
+ }
23
+ export declare const DEFAULT_COLLECTOR_AUTO_PRUNE_POLICY: Readonly<CollectorAutoPrunePolicy>;
24
+ export declare function resolveCollectorAutoPrunePolicy(override?: Partial<CollectorAutoPrunePolicy>, env?: NodeJS.ProcessEnv): CollectorAutoPrunePolicy;
25
+ export declare function autoPruneSucceededOutbox(input: {
26
+ outbox: Pick<LocalDeviceOutbox, "pruneSent">;
27
+ policy: CollectorAutoPrunePolicy;
28
+ sourceInstanceId: string;
29
+ }): CollectorAutoPruneResult;
30
+ export interface CollectorAutoPruneResult {
31
+ enabled: boolean;
32
+ matched: number;
33
+ pruned: number;
34
+ }
35
+ export interface CollectorAutoCompactPolicy {
36
+ enabled: boolean;
37
+ minReclaimableBytes: number;
38
+ }
39
+ export declare const DEFAULT_COLLECTOR_AUTO_COMPACT_POLICY: Readonly<CollectorAutoCompactPolicy>;
40
+ export declare function resolveCollectorAutoCompactPolicy(override?: Partial<CollectorAutoCompactPolicy>, env?: NodeJS.ProcessEnv): CollectorAutoCompactPolicy;
41
+ export declare function autoCompactOutboxIfBloated(input: {
42
+ outbox: Pick<LocalDeviceOutbox, "compact" | "countNonSucceeded" | "pageStats">;
43
+ policy: CollectorAutoCompactPolicy;
44
+ }): CollectorAutoCompactResult;
45
+ export interface CollectorAutoCompactResult {
46
+ compacted: boolean;
47
+ enabled: boolean;
48
+ reason: "disabled" | "below_threshold" | "lane_not_quiet" | "compacted";
49
+ reclaimedBytes: number;
50
+ }
19
51
  export interface CollectorEnrollmentConfig {
20
52
  baseUrl: string;
21
53
  code: string;
@@ -32,6 +64,8 @@ export interface CollectorConnectorSpec extends ConnectorPlacementInput {
32
64
  }
33
65
  export interface CollectorRunConfig {
34
66
  abortSignal?: AbortSignal;
67
+ autoCompact?: Partial<CollectorAutoCompactPolicy>;
68
+ autoPrune?: Partial<CollectorAutoPrunePolicy>;
35
69
  baseUrl: string;
36
70
  batchSize?: number;
37
71
  collectorHolderId?: string;
@@ -44,7 +78,17 @@ export interface CollectorRunConfig {
44
78
  runId?: string;
45
79
  sourceInstanceId: string;
46
80
  }
81
+ export declare const COLLECTOR_COVERAGE_STATUSES: readonly ["collected", "inventory_only", "excluded", "deferred", "missing", "unsupported", "unaccounted"];
82
+ export type CollectorCoverageStatus = (typeof COLLECTOR_COVERAGE_STATUSES)[number];
83
+ export interface CollectorCompletenessSummary {
84
+ byStore: Readonly<Record<string, CollectorCoverageStatus>>;
85
+ countsByStatus: Readonly<Record<CollectorCoverageStatus, number>>;
86
+ fullyAccounted: boolean;
87
+ storeCount: number;
88
+ unaccountedStores: readonly string[];
89
+ }
47
90
  export interface CollectorRunResult {
91
+ completeness: CollectorCompletenessSummary | null;
48
92
  done: Extract<EmittedMessage, {
49
93
  type: "DONE";
50
94
  }> | null;
@@ -52,6 +96,7 @@ export interface CollectorRunResult {
52
96
  flushedState: Readonly<Record<string, unknown>> | null;
53
97
  outboxSummary: LocalDeviceOutboxSummary;
54
98
  priorState: Readonly<Record<string, unknown>>;
99
+ prunedSent: CollectorAutoPruneResult;
55
100
  recordsQueued: number;
56
101
  recoveredLeases: number;
57
102
  satisfiedBindings: readonly RuntimeBindingName[];
@@ -65,6 +110,7 @@ export declare class CollectorStateReadError extends Error {
65
110
  constructor(message: string, cause: unknown);
66
111
  }
67
112
  export declare function runCollectorConnector(config: CollectorRunConfig): Promise<CollectorRunResult>;
113
+ export declare function summarizeCollectorCompleteness(coverageByStore: Map<string, CollectorCoverageStatus> | null): CollectorCompletenessSummary | null;
68
114
  export declare function buildCollectorStartMessage(streams: readonly string[], streamsToBackfill?: readonly string[], priorState?: Readonly<Record<string, unknown>> | null): StartMessage;
69
115
  export declare function transformRecordsToCollectorEnvelopes(input: {
70
116
  batchId: string;
@@ -118,6 +164,14 @@ export interface DrainCollectorOutboxResult {
118
164
  sentByKind: Readonly<Partial<Record<LocalDeviceOutboxItem["kind"], number>>>;
119
165
  }
120
166
  export declare function drainCollectorOutbox(input: DrainCollectorOutboxInput): Promise<DrainCollectorOutboxResult>;
167
+ export declare const LOCAL_COLLECTOR_LIFECYCLE_STATES: readonly ["healthy_idle", "draining", "retryable_backlog", "dead_letter", "stale_lease", "coverage_missing"];
168
+ export type LocalCollectorLifecycleState = (typeof LOCAL_COLLECTOR_LIFECYCLE_STATES)[number];
169
+ export interface LocalCollectorLifecycleInput {
170
+ coverageObserved: boolean | null;
171
+ recordBatchCount: number;
172
+ summary: LocalDeviceOutboxSummary;
173
+ }
174
+ export declare function deriveLocalCollectorLifecycleState(input: LocalCollectorLifecycleInput): LocalCollectorLifecycleState;
121
175
  export declare function buildHeartbeatOutboxDiagnostics(summary: LocalDeviceOutboxSummary, options?: {
122
176
  backlogOpen?: number;
123
177
  }): HeartbeatOutboxDiagnostics;