@pdpp/local-collector 0.0.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.
Files changed (49) hide show
  1. package/README.md +48 -0
  2. package/dist/local-collector/bin/pdpp-local-collector.js +347 -0
  3. package/dist/local-collector/src/errors.d.ts +12 -0
  4. package/dist/local-collector/src/errors.js +20 -0
  5. package/dist/local-collector/src/runner.d.ts +16 -0
  6. package/dist/local-collector/src/runner.js +59 -0
  7. package/dist/polyfill-connectors/connectors/claude_code/index.js +806 -0
  8. package/dist/polyfill-connectors/connectors/claude_code/parsers.js +224 -0
  9. package/dist/polyfill-connectors/connectors/claude_code/schemas.js +120 -0
  10. package/dist/polyfill-connectors/connectors/claude_code/types.js +1 -0
  11. package/dist/polyfill-connectors/connectors/codex/index.js +880 -0
  12. package/dist/polyfill-connectors/connectors/codex/parsers.js +159 -0
  13. package/dist/polyfill-connectors/connectors/codex/schemas.js +118 -0
  14. package/dist/polyfill-connectors/connectors/codex/types.js +1 -0
  15. package/dist/polyfill-connectors/src/auth.js +76 -0
  16. package/dist/polyfill-connectors/src/browser-handoff.js +197 -0
  17. package/dist/polyfill-connectors/src/collector-protocol.d.ts +2 -0
  18. package/dist/polyfill-connectors/src/collector-protocol.js +2 -0
  19. package/dist/polyfill-connectors/src/collector-runner.d.ts +139 -0
  20. package/dist/polyfill-connectors/src/collector-runner.js +1084 -0
  21. package/dist/polyfill-connectors/src/connector-runtime-protocol.d.ts +191 -0
  22. package/dist/polyfill-connectors/src/connector-runtime-protocol.js +1 -0
  23. package/dist/polyfill-connectors/src/connector-runtime.js +879 -0
  24. package/dist/polyfill-connectors/src/fixture-capture.js +237 -0
  25. package/dist/polyfill-connectors/src/is-main-module.d.ts +1 -0
  26. package/dist/polyfill-connectors/src/is-main-module.js +17 -0
  27. package/dist/polyfill-connectors/src/local-device-client.d.ts +126 -0
  28. package/dist/polyfill-connectors/src/local-device-client.js +132 -0
  29. package/dist/polyfill-connectors/src/local-device-envelope.d.ts +26 -0
  30. package/dist/polyfill-connectors/src/local-device-envelope.js +43 -0
  31. package/dist/polyfill-connectors/src/local-device-outbox.d.ts +115 -0
  32. package/dist/polyfill-connectors/src/local-device-outbox.js +509 -0
  33. package/dist/polyfill-connectors/src/local-device-queue.d.ts +34 -0
  34. package/dist/polyfill-connectors/src/local-device-queue.js +133 -0
  35. package/dist/polyfill-connectors/src/local-source-inventory.js +119 -0
  36. package/dist/polyfill-connectors/src/pdpp-safe-text.js +13 -0
  37. package/dist/polyfill-connectors/src/runner/index.d.ts +11 -0
  38. package/dist/polyfill-connectors/src/runner/index.js +10 -0
  39. package/dist/polyfill-connectors/src/runtime-capabilities.d.ts +40 -0
  40. package/dist/polyfill-connectors/src/runtime-capabilities.js +59 -0
  41. package/dist/polyfill-connectors/src/safe-emit.d.ts +3 -0
  42. package/dist/polyfill-connectors/src/safe-emit.js +30 -0
  43. package/dist/polyfill-connectors/src/safe-text-preview.js +156 -0
  44. package/dist/polyfill-connectors/src/schema-registry.js +17 -0
  45. package/dist/polyfill-connectors/src/scope-filters.d.ts +38 -0
  46. package/dist/polyfill-connectors/src/scope-filters.js +80 -0
  47. package/dist/polyfill-connectors/src/shutdown-hook.js +51 -0
  48. package/dist/polyfill-connectors/src/streaming-target-registration.js +161 -0
  49. package/package.json +63 -0
@@ -0,0 +1,159 @@
1
+ import { PDPP_PREVIEW_MAX_CHARS, safeTextPreview } from "../../src/safe-text-preview.js";
2
+ const FRONTMATTER_RE = /^---\s*\r?\n([\s\S]*?)\r?\n---\s*\r?\n?([\s\S]*)$/;
3
+ export const YEAR_DIR_RE = /^\d{4}$/;
4
+ export const TWO_DIGIT_DIR_RE = /^\d{2}$/;
5
+ const LINE_SPLIT_RE = /\r?\n/;
6
+ const FRONTMATTER_KV_RE = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/;
7
+ export const RULES_SUFFIX_RE = /\.rules$/;
8
+ export const MD_SUFFIX_RE = /\.md$/;
9
+ export function textPreview(s, max = PDPP_PREVIEW_MAX_CHARS) {
10
+ const r = safeTextPreview(s, max);
11
+ return r.preview;
12
+ }
13
+ export function extractMessageText(payload) {
14
+ if (!(payload?.content && Array.isArray(payload.content))) {
15
+ return null;
16
+ }
17
+ const parts = payload.content.map((p) => p?.text).filter(Boolean);
18
+ return parts.join("\n") || null;
19
+ }
20
+ export function payloadOutputPreview(output, max = PDPP_PREVIEW_MAX_CHARS) {
21
+ let toPreview = output;
22
+ if (typeof output !== "string" && output !== null && output !== undefined) {
23
+ toPreview = JSON.stringify(output);
24
+ }
25
+ const r = safeTextPreview(toPreview, max);
26
+ return {
27
+ preview: r.preview,
28
+ binaryReason: r.kind === "binary" ? r.reason : null,
29
+ };
30
+ }
31
+ export function epochToIso(sec) {
32
+ return Number.isFinite(sec) && typeof sec === "number" && sec > 0 ? new Date(sec * 1000).toISOString() : null;
33
+ }
34
+ function stripSurroundingQuotes(value) {
35
+ if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
36
+ return value.slice(1, -1);
37
+ }
38
+ return value;
39
+ }
40
+ function parseFrontmatterLine(line, meta) {
41
+ const kv = line.match(FRONTMATTER_KV_RE);
42
+ if (!kv) {
43
+ return;
44
+ }
45
+ const key = kv[1];
46
+ if (!key) {
47
+ return;
48
+ }
49
+ meta[key] = stripSurroundingQuotes((kv[2] ?? "").trim());
50
+ }
51
+ export function parseFrontmatter(text) {
52
+ const m = text.match(FRONTMATTER_RE);
53
+ if (!m) {
54
+ return { meta: {}, body: text };
55
+ }
56
+ const meta = {};
57
+ for (const line of (m[1] ?? "").split(LINE_SPLIT_RE)) {
58
+ parseFrontmatterLine(line, meta);
59
+ }
60
+ return { meta, body: m[2] ?? "" };
61
+ }
62
+ export function isRolloutFile(name) {
63
+ return name.startsWith("rollout-") && name.endsWith(".jsonl");
64
+ }
65
+ export function buildThreadSessionRecord(id, t, agg) {
66
+ return {
67
+ id,
68
+ cwd: t.cwd || null,
69
+ originator: t.source || null,
70
+ cli_version: t.cli_version || null,
71
+ model_provider: t.model_provider || null,
72
+ git_commit: t.git_sha || null,
73
+ git_branch: t.git_branch || null,
74
+ repository_url: t.git_origin_url || null,
75
+ started_at: epochToIso(t.created_at) || agg?.meta?.timestamp || agg?.firstTs || null,
76
+ last_event_at: epochToIso(t.updated_at) || agg?.lastTs || null,
77
+ message_count: agg?.messageCount ?? null,
78
+ function_call_count: agg?.functionCallCount ?? null,
79
+ title: textPreview(t.title || null, 500),
80
+ archived: t.archived === 1 || t.archived === true,
81
+ tokens_used: t.tokens_used ?? null,
82
+ first_user_message: textPreview(t.first_user_message || null, 2000),
83
+ sandbox_policy: t.sandbox_policy || null,
84
+ approval_mode: t.approval_mode || null,
85
+ rollout_path: t.rollout_path || agg?.rolloutPath || null,
86
+ };
87
+ }
88
+ export function buildRolloutOnlySessionRecord(id, agg) {
89
+ const meta = agg.meta || {};
90
+ return {
91
+ id,
92
+ cwd: meta.cwd || null,
93
+ originator: meta.originator || null,
94
+ cli_version: meta.cli_version || null,
95
+ model_provider: meta.model_provider || null,
96
+ git_commit: meta.git?.commit_hash || null,
97
+ git_branch: meta.git?.branch || null,
98
+ repository_url: meta.git?.repository_url || null,
99
+ started_at: meta.timestamp || agg.firstTs,
100
+ last_event_at: agg.lastTs,
101
+ message_count: agg.messageCount,
102
+ function_call_count: agg.functionCallCount,
103
+ title: null,
104
+ archived: null,
105
+ tokens_used: null,
106
+ first_user_message: null,
107
+ sandbox_policy: null,
108
+ approval_mode: null,
109
+ rollout_path: agg.rolloutPath || null,
110
+ };
111
+ }
112
+ export function splitRulesLines(text) {
113
+ return text.split(LINE_SPLIT_RE);
114
+ }
115
+ export function isSkippableRulesLine(line) {
116
+ return !line || line.startsWith("#");
117
+ }
118
+ export function buildRuleRecord(args) {
119
+ return {
120
+ id: `rules:${args.ruleset}:${args.index}`,
121
+ ruleset: args.ruleset,
122
+ rule_text: textPreview(args.line, 4000),
123
+ rule_index: args.index,
124
+ path: args.path,
125
+ mtime_epoch: args.mtime,
126
+ };
127
+ }
128
+ export function buildPromptRecord(args) {
129
+ const name = args.meta.name || args.fileName.replace(MD_SUFFIX_RE, "");
130
+ return {
131
+ id: `prompts:${args.fileName}`,
132
+ name,
133
+ description: args.meta.description || null,
134
+ content: textPreview(args.body, 20_000),
135
+ path: args.path,
136
+ mtime_epoch: Math.floor(args.mtimeMs / 1000),
137
+ };
138
+ }
139
+ export function buildSkillRecord(args) {
140
+ return {
141
+ id: `skills:${args.dirName}`,
142
+ name: args.meta.name || args.dirName,
143
+ description: args.meta.description || null,
144
+ content: textPreview(args.body, 20_000),
145
+ path: args.path,
146
+ mtime_epoch: Math.floor(args.mtimeMs / 1000),
147
+ };
148
+ }
149
+ export function extendTimestampRange(range, ts) {
150
+ if (!ts) {
151
+ return;
152
+ }
153
+ if (!range.firstTs || ts < range.firstTs) {
154
+ range.firstTs = ts;
155
+ }
156
+ if (!range.lastTs || ts > range.lastTs) {
157
+ range.lastTs = ts;
158
+ }
159
+ }
@@ -0,0 +1,118 @@
1
+ import { z } from "zod";
2
+ import { pdppSafeText } from "../../src/pdpp-safe-text.js";
3
+ import { PDPP_PREVIEW_MAX_CHARS, safeTextPreview } from "../../src/safe-text-preview.js";
4
+ import { makeValidateRecord } from "../../src/schema-registry.js";
5
+ const SESSION_ID_RE = /^[0-9a-f-]{36}$|^[0-9a-z]{26}$/;
6
+ const MESSAGE_ID_RE = /^([0-9a-f-]{36}|[0-9a-z]{26})(:\d+)?(:output)?$|^call_[A-Za-z0-9]{24}$/;
7
+ const FUNCTION_CALL_ID_RE = /^call_[A-Za-z0-9]{24}$|^[0-9a-f-]{36}(:\d+)?(:output)?$|^[0-9a-z]{26}(:\d+)?(:output)?$/;
8
+ const ISO_Z_RE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
9
+ const isoDateTimeSchema = z.string().regex(ISO_Z_RE, "must be ISO-8601 with millis and Z suffix").nullable();
10
+ const nullableBoolSchema = z.boolean().nullable();
11
+ const nullableIntSchema = z.number().int().nullable();
12
+ export const sessionsSchema = z.object({
13
+ id: z.string().regex(SESSION_ID_RE, "session id must be uuid or ulid"),
14
+ cwd: pdppSafeText.nullable(),
15
+ originator: pdppSafeText.nullable(),
16
+ cli_version: pdppSafeText.nullable(),
17
+ model_provider: pdppSafeText.nullable(),
18
+ git_commit: pdppSafeText.nullable(),
19
+ git_branch: pdppSafeText.nullable(),
20
+ repository_url: pdppSafeText.nullable(),
21
+ started_at: isoDateTimeSchema,
22
+ last_event_at: isoDateTimeSchema,
23
+ message_count: nullableIntSchema,
24
+ function_call_count: nullableIntSchema,
25
+ title: pdppSafeText.max(1_000_000).nullable(),
26
+ archived: nullableBoolSchema,
27
+ tokens_used: nullableIntSchema,
28
+ first_user_message: pdppSafeText.max(1_000_000).nullable(),
29
+ sandbox_policy: pdppSafeText.nullable(),
30
+ approval_mode: pdppSafeText.nullable(),
31
+ rollout_path: pdppSafeText.nullable(),
32
+ });
33
+ export const messagesSchema = z.object({
34
+ id: z.string().regex(MESSAGE_ID_RE, "message id must be uuid or uuid:line or uuid:line:output"),
35
+ session_id: z.string().regex(SESSION_ID_RE, "session_id must be uuid or ulid"),
36
+ role: pdppSafeText.nullable(),
37
+ type: pdppSafeText.nullable(),
38
+ content: pdppSafeText.max(10_000_000).nullable(),
39
+ timestamp: isoDateTimeSchema,
40
+ });
41
+ export const functionCallsSchema = z.object({
42
+ id: z.string().regex(FUNCTION_CALL_ID_RE, "function_call id must be call_* or uuid or composite"),
43
+ session_id: z.string().regex(SESSION_ID_RE, "session_id must be uuid or ulid"),
44
+ call_id: pdppSafeText.nullable(),
45
+ name: pdppSafeText.nullable(),
46
+ arguments: pdppSafeText.max(10_000_000).nullable(),
47
+ output_preview: z
48
+ .string()
49
+ .max(PDPP_PREVIEW_MAX_CHARS + 1)
50
+ .refine((val) => {
51
+ const result = safeTextPreview(val, PDPP_PREVIEW_MAX_CHARS);
52
+ return result.kind === "text" || result.kind === "empty";
53
+ }, "output_preview contains forbidden control characters")
54
+ .nullable(),
55
+ output_binary_reason: pdppSafeText.max(200).nullable().optional(),
56
+ timestamp: isoDateTimeSchema,
57
+ });
58
+ export const rulesSchema = z.object({
59
+ id: pdppSafeText,
60
+ ruleset: pdppSafeText,
61
+ rule_text: pdppSafeText.max(4000),
62
+ rule_index: z.number().int().min(0),
63
+ path: pdppSafeText.nullable(),
64
+ mtime_epoch: z.number().int().nullable(),
65
+ });
66
+ export const promptsSchema = z.object({
67
+ id: pdppSafeText,
68
+ name: pdppSafeText,
69
+ description: pdppSafeText.nullable(),
70
+ content: pdppSafeText.max(10_000_000).nullable(),
71
+ path: pdppSafeText.nullable(),
72
+ mtime_epoch: z.number().int().nullable(),
73
+ });
74
+ export const skillsSchema = z.object({
75
+ id: pdppSafeText,
76
+ name: pdppSafeText,
77
+ description: pdppSafeText.nullable(),
78
+ content: pdppSafeText.max(10_000_000).nullable(),
79
+ path: pdppSafeText.nullable(),
80
+ mtime_epoch: z.number().int().nullable(),
81
+ });
82
+ const inventoryClassificationSchema = z.enum(["inventory_only", "defer"]);
83
+ const inventoryTypeSchema = z.enum(["directory", "file", "missing", "other"]);
84
+ const coverageStatusSchema = z.enum(["collected", "inventory_only", "excluded", "deferred", "missing", "unsupported"]);
85
+ export const inventorySchema = z.object({
86
+ id: pdppSafeText,
87
+ store: pdppSafeText,
88
+ relative_path: pdppSafeText.max(2048),
89
+ path_hash: z.string().regex(/^[a-f0-9]{64}$/),
90
+ type: inventoryTypeSchema,
91
+ size_bytes: z.number().int().min(0).nullable(),
92
+ mtime_epoch: z.number().int().min(0).nullable(),
93
+ classification: inventoryClassificationSchema,
94
+ reason: pdppSafeText.max(512),
95
+ });
96
+ export const coverageDiagnosticsSchema = z.object({
97
+ id: pdppSafeText,
98
+ store: pdppSafeText,
99
+ stream: pdppSafeText.nullable(),
100
+ status: coverageStatusSchema,
101
+ reason: pdppSafeText.max(512),
102
+ });
103
+ export const SCHEMAS = {
104
+ sessions: sessionsSchema,
105
+ messages: messagesSchema,
106
+ function_calls: functionCallsSchema,
107
+ rules: rulesSchema,
108
+ prompts: promptsSchema,
109
+ skills: skillsSchema,
110
+ history: inventorySchema,
111
+ session_index: inventorySchema,
112
+ logs: inventorySchema,
113
+ shell_snapshots: inventorySchema,
114
+ config_inventory: inventorySchema,
115
+ cache_inventory: inventorySchema,
116
+ coverage_diagnostics: coverageDiagnosticsSchema,
117
+ };
118
+ export const validateRecord = makeValidateRecord(SCHEMAS);
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,76 @@
1
+ const strategies = new Map();
2
+ export function registerAuthStrategy(kind, resolver) {
3
+ strategies.set(kind, resolver);
4
+ }
5
+ export function hasAuthStrategy(kind) {
6
+ return strategies.has(kind);
7
+ }
8
+ export function resolveAuth(config, runtime) {
9
+ if (!config) {
10
+ return Promise.resolve({});
11
+ }
12
+ const resolver = strategies.get(config.kind);
13
+ if (!resolver) {
14
+ return Promise.reject(new Error(`auth_strategy_unknown: ${config.kind}`));
15
+ }
16
+ return resolver(config, runtime);
17
+ }
18
+ const SECRET_NAME = /PASSWORD|SECRET|TOKEN/i;
19
+ function resolveEnvEntry(entry) {
20
+ const aliases = Array.isArray(entry) ? entry : [entry];
21
+ const primary = aliases[0];
22
+ if (!primary) {
23
+ return null;
24
+ }
25
+ for (const name of aliases) {
26
+ const candidate = process.env[name];
27
+ if (candidate) {
28
+ return { primary, value: candidate };
29
+ }
30
+ }
31
+ return { primary, value: undefined };
32
+ }
33
+ function buildCredentialSchema(missing, connectorName) {
34
+ const properties = {};
35
+ for (const name of missing) {
36
+ const base = {
37
+ type: "string",
38
+ description: `${name} for ${connectorName}`,
39
+ };
40
+ properties[name] = SECRET_NAME.test(name) ? { ...base, format: "password" } : base;
41
+ }
42
+ return { type: "object", properties, required: missing };
43
+ }
44
+ registerAuthStrategy("env", async (config, runtime) => {
45
+ const { required } = config;
46
+ if (!Array.isArray(required) || required.length === 0) {
47
+ throw new Error("auth_env_required_missing: auth.required must be a non-empty array");
48
+ }
49
+ const have = {};
50
+ const missing = [];
51
+ for (const entry of required) {
52
+ const resolved = resolveEnvEntry(entry);
53
+ if (!resolved) {
54
+ continue;
55
+ }
56
+ if (resolved.value === undefined) {
57
+ missing.push(resolved.primary);
58
+ }
59
+ else {
60
+ have[resolved.primary] = resolved.value;
61
+ }
62
+ }
63
+ if (missing.length === 0) {
64
+ return have;
65
+ }
66
+ const resp = await runtime.sendInteraction({
67
+ kind: "credentials",
68
+ message: `${runtime.connectorName} needs: ${missing.join(", ")}. Set in .env.local for persistence.`,
69
+ schema: buildCredentialSchema(missing, runtime.connectorName),
70
+ timeout_seconds: 1800,
71
+ });
72
+ if (resp.status !== "success" || !resp.data) {
73
+ throw new Error(`${runtime.connectorName}_credentials_missing`);
74
+ }
75
+ return { ...have, ...resp.data };
76
+ });
@@ -0,0 +1,197 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { resolveStreamingRegistrationFromEnv, } from "./streaming-target-registration.js";
3
+ export async function resolveWsUrlForExactPage(page, opts) {
4
+ const session = await page.context().newCDPSession(page);
5
+ try {
6
+ const { targetInfo } = (await session.send("Target.getTargetInfo"));
7
+ if (targetInfo.type !== "page") {
8
+ throw new Error(`expected page target, got type=${targetInfo.type}`);
9
+ }
10
+ return `ws://${opts.host}:${String(opts.port)}/devtools/page/${targetInfo.targetId}`;
11
+ }
12
+ finally {
13
+ await session.detach().catch(() => undefined);
14
+ }
15
+ }
16
+ const BROWSER_CDP_HOST_ENV = "PDPP_BROWSER_CDP_HOST";
17
+ const BROWSER_CDP_PORT_ENV = "PDPP_BROWSER_CDP_PORT";
18
+ const BROWSER_SURFACE_ID_ENV = "PDPP_BROWSER_SURFACE_ID";
19
+ const BROWSER_SURFACE_LEASE_ID_ENV = "PDPP_BROWSER_SURFACE_LEASE_ID";
20
+ const BROWSER_SURFACE_PROFILE_KEY_ENV = "PDPP_BROWSER_SURFACE_PROFILE_KEY";
21
+ const BROWSER_SURFACE_REQUIRED_ENV = "PDPP_BROWSER_SURFACE_REQUIRED";
22
+ const BROWSER_SURFACE_STREAM_BASE_URL_ENV = "PDPP_BROWSER_SURFACE_STREAM_BASE_URL";
23
+ function resolveCdpEndpointFromEnv(env) {
24
+ const host = env[BROWSER_CDP_HOST_ENV]?.trim();
25
+ const portRaw = env[BROWSER_CDP_PORT_ENV]?.trim();
26
+ if (!(host && portRaw)) {
27
+ return;
28
+ }
29
+ const port = Number.parseInt(portRaw, 10);
30
+ if (!(Number.isFinite(port) && port > 0)) {
31
+ return;
32
+ }
33
+ return { host, port };
34
+ }
35
+ function nonEmptyEnv(env, key) {
36
+ const value = env[key]?.trim();
37
+ return value ? value : undefined;
38
+ }
39
+ function isManagedNekoRequired(env) {
40
+ return nonEmptyEnv(env, BROWSER_SURFACE_REQUIRED_ENV)?.toLowerCase() === "neko";
41
+ }
42
+ function resolveManagedNekoDescriptorFromEnv(env) {
43
+ const baseUrl = nonEmptyEnv(env, BROWSER_SURFACE_STREAM_BASE_URL_ENV);
44
+ const leaseId = nonEmptyEnv(env, BROWSER_SURFACE_LEASE_ID_ENV);
45
+ const profileKey = nonEmptyEnv(env, BROWSER_SURFACE_PROFILE_KEY_ENV);
46
+ if (!(baseUrl && leaseId && profileKey)) {
47
+ return;
48
+ }
49
+ return {
50
+ backend: "neko",
51
+ base_url: baseUrl,
52
+ lease_id: leaseId,
53
+ profile_key: profileKey,
54
+ ...(nonEmptyEnv(env, BROWSER_SURFACE_ID_ENV) ? { surface_id: nonEmptyEnv(env, BROWSER_SURFACE_ID_ENV) } : {}),
55
+ };
56
+ }
57
+ function generateInteractionId() {
58
+ return `int_${String(Date.now())}_${randomBytes(4).toString("hex")}`;
59
+ }
60
+ async function readManualActionPageMetadata(page) {
61
+ let pageUrl;
62
+ let pageTitle;
63
+ try {
64
+ pageUrl = page.url();
65
+ }
66
+ catch {
67
+ }
68
+ try {
69
+ pageTitle = await page.title();
70
+ }
71
+ catch {
72
+ }
73
+ return {
74
+ ...(pageUrl ? { pageUrl } : {}),
75
+ ...(pageTitle ? { pageTitle } : {}),
76
+ };
77
+ }
78
+ function registerManagedNekoManualActionTarget(args) {
79
+ const nekoDescriptor = resolveManagedNekoDescriptorFromEnv(args.env);
80
+ if (!nekoDescriptor) {
81
+ process.stderr.write(`[browser-handoff] managed n.eko surface env is incomplete; streaming-companion target not registered for interaction ${args.interactionId}.\n`);
82
+ return Promise.resolve(false);
83
+ }
84
+ return args.registration.register({
85
+ backend: "neko",
86
+ runId: args.registration.runId,
87
+ interactionId: args.interactionId,
88
+ descriptor: {
89
+ ...nekoDescriptor,
90
+ interaction_id: args.interactionId,
91
+ ...(args.metadata.pageUrl ? { start_url: args.metadata.pageUrl } : {}),
92
+ },
93
+ ...(args.metadata.pageUrl ? { pageUrl: args.metadata.pageUrl } : {}),
94
+ ...(args.metadata.pageTitle ? { pageTitle: args.metadata.pageTitle } : {}),
95
+ ...(args.reason ? { reason: args.reason } : {}),
96
+ });
97
+ }
98
+ async function resolveCdpWsUrlForManualAction(args) {
99
+ try {
100
+ return await args.resolveWsUrl(args.page, args.endpoint);
101
+ }
102
+ catch (err) {
103
+ const message = err instanceof Error ? err.message : String(err);
104
+ process.stderr.write(`[browser-handoff] could not resolve CDP page-target wsUrl for interaction ${args.interactionId}: ${message}; continuing without streaming.\n`);
105
+ return;
106
+ }
107
+ }
108
+ function registerCdpManualActionTarget(args) {
109
+ return args.registration.register({
110
+ runId: args.registration.runId,
111
+ interactionId: args.interactionId,
112
+ wsUrl: args.wsUrl,
113
+ ...(args.metadata.pageUrl ? { pageUrl: args.metadata.pageUrl } : {}),
114
+ ...(args.metadata.pageTitle ? { pageTitle: args.metadata.pageTitle } : {}),
115
+ ...(args.reason ? { reason: args.reason } : {}),
116
+ });
117
+ }
118
+ export async function prepareManualAction(args) {
119
+ const env = args.env ?? process.env;
120
+ const resolveStreamingRegistration = args.resolveStreamingRegistration ?? resolveStreamingRegistrationFromEnv;
121
+ const resolveWsUrl = args.resolveWsUrl ?? resolveWsUrlForExactPage;
122
+ const interactionId = generateInteractionId();
123
+ const registration = await resolveStreamingRegistration(env);
124
+ if (!registration) {
125
+ return { interactionId, registered: false };
126
+ }
127
+ const metadata = await readManualActionPageMetadata(args.page);
128
+ if (isManagedNekoRequired(env)) {
129
+ const ok = await registerManagedNekoManualActionTarget({
130
+ env,
131
+ interactionId,
132
+ metadata,
133
+ registration,
134
+ ...(args.reason ? { reason: args.reason } : {}),
135
+ });
136
+ return { interactionId, registered: ok };
137
+ }
138
+ const endpoint = resolveCdpEndpointFromEnv(env);
139
+ if (!endpoint) {
140
+ process.stderr.write(`[browser-handoff] ${BROWSER_CDP_HOST_ENV}/${BROWSER_CDP_PORT_ENV} not set; streaming-companion target not registered for interaction ${interactionId}.\n`);
141
+ return { interactionId, registered: false };
142
+ }
143
+ const wsUrl = await resolveCdpWsUrlForManualAction({
144
+ endpoint,
145
+ interactionId,
146
+ page: args.page,
147
+ resolveWsUrl,
148
+ });
149
+ if (!wsUrl) {
150
+ return { interactionId, registered: false };
151
+ }
152
+ const ok = await registerCdpManualActionTarget({
153
+ interactionId,
154
+ metadata,
155
+ registration,
156
+ wsUrl,
157
+ ...(args.reason ? { reason: args.reason } : {}),
158
+ });
159
+ if (!ok) {
160
+ return { interactionId, registered: false };
161
+ }
162
+ return { interactionId, registered: true };
163
+ }
164
+ function captureManualActionFixture(args) {
165
+ if (!args.capture) {
166
+ return;
167
+ }
168
+ try {
169
+ const capture = args.capture.captureDom(args.page, `manual-action-${args.reason ?? "manual_action"}-${args.interactionId}`);
170
+ capture.catch(() => undefined);
171
+ }
172
+ catch {
173
+ }
174
+ }
175
+ export async function manualAction(args, sendInteraction) {
176
+ const { interactionId } = await prepareManualAction({
177
+ page: args.page,
178
+ ...(args.reason ? { reason: args.reason } : {}),
179
+ ...(args.env ? { env: args.env } : {}),
180
+ ...(args.resolveStreamingRegistration ? { resolveStreamingRegistration: args.resolveStreamingRegistration } : {}),
181
+ ...(args.resolveWsUrl ? { resolveWsUrl: args.resolveWsUrl } : {}),
182
+ });
183
+ captureManualActionFixture({
184
+ ...(args.capture ? { capture: args.capture } : {}),
185
+ interactionId,
186
+ page: args.page,
187
+ ...(args.reason ? { reason: args.reason } : {}),
188
+ });
189
+ return await sendInteraction({
190
+ kind: "manual_action",
191
+ request_id: interactionId,
192
+ message: args.message,
193
+ ...(args.schema ? { schema: args.schema } : {}),
194
+ ...(args.timeoutSeconds === undefined ? {} : { timeout_seconds: args.timeoutSeconds }),
195
+ });
196
+ }
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, };
@@ -0,0 +1,2 @@
1
+ export declare const COLLECTOR_PROTOCOL_VERSION = "1";
2
+ export declare const COLLECTOR_PROTOCOL_HEADER = "X-PDPP-Collector-Protocol";
@@ -0,0 +1,2 @@
1
+ export const COLLECTOR_PROTOCOL_VERSION = "1";
2
+ export const COLLECTOR_PROTOCOL_HEADER = "X-PDPP-Collector-Protocol";