@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.
- package/dist/local-collector/bin/pdpp-local-collector.js +580 -22
- package/dist/local-collector/src/runner.d.ts +1 -1
- package/dist/local-collector/src/runner.js +15 -1
- package/dist/polyfill-connectors/connectors/claude_code/index.js +85 -48
- package/dist/polyfill-connectors/connectors/codex/index.js +390 -108
- package/dist/polyfill-connectors/connectors/codex/parsers.js +5 -3
- package/dist/polyfill-connectors/src/bounded-file-preview.js +76 -0
- package/dist/polyfill-connectors/src/browser-handoff.js +38 -5
- package/dist/polyfill-connectors/src/collector-build-info.d.ts +8 -0
- package/dist/polyfill-connectors/src/collector-build-info.js +10 -0
- package/dist/polyfill-connectors/src/collector-runner.d.ts +54 -0
- package/dist/polyfill-connectors/src/collector-runner.js +250 -18
- package/dist/polyfill-connectors/src/connector-exit.js +62 -0
- package/dist/polyfill-connectors/src/connector-runtime-protocol.d.ts +41 -21
- package/dist/polyfill-connectors/src/connector-runtime.js +241 -30
- package/dist/polyfill-connectors/src/fingerprint-cursor.js +107 -0
- package/dist/polyfill-connectors/src/local-device-client.d.ts +17 -0
- package/dist/polyfill-connectors/src/local-device-client.js +69 -9
- package/dist/polyfill-connectors/src/local-device-outbox.d.ts +59 -0
- package/dist/polyfill-connectors/src/local-device-outbox.js +394 -5
- package/dist/polyfill-connectors/src/local-source-inventory.js +8 -1
- package/dist/polyfill-connectors/src/runner/index.d.ts +4 -3
- package/dist/polyfill-connectors/src/runner/index.js +4 -3
- package/dist/polyfill-connectors/src/safe-text-preview.js +13 -0
- package/dist/polyfill-connectors/src/static-secret-injection.js +155 -0
- 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:
|
|
78
|
-
function_call_count:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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;
|