@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,80 @@
1
+ export function resourceSet(streamRequest) {
2
+ if (!(streamRequest && Array.isArray(streamRequest.resources) && streamRequest.resources.length)) {
3
+ return null;
4
+ }
5
+ const s = new Set();
6
+ for (const r of streamRequest.resources) {
7
+ s.add(String(r));
8
+ }
9
+ return s;
10
+ }
11
+ export function passesResourceFilter(resSet, primaryKey) {
12
+ if (!resSet) {
13
+ return true;
14
+ }
15
+ const canonical = Array.isArray(primaryKey) ? JSON.stringify(primaryKey.map(String)) : String(primaryKey);
16
+ return resSet.has(canonical);
17
+ }
18
+ export function passesTimeRange(isoValue, timeRange) {
19
+ if (!timeRange) {
20
+ return true;
21
+ }
22
+ if (!isoValue) {
23
+ return true;
24
+ }
25
+ if (timeRange.since && isoValue < timeRange.since) {
26
+ return false;
27
+ }
28
+ if (timeRange.until && isoValue >= timeRange.until) {
29
+ return false;
30
+ }
31
+ return true;
32
+ }
33
+ export function makeEmitGate(emitRecord, streamRequest, { consentTimeField } = {}) {
34
+ const resSet = resourceSet(streamRequest);
35
+ const emitted = new Set();
36
+ const gate = ((stream, data, keyField = "id") => {
37
+ const key = data[keyField];
38
+ if (key == null) {
39
+ return false;
40
+ }
41
+ const canonical = Array.isArray(key) ? JSON.stringify(key.map(String)) : String(key);
42
+ if (resSet && !resSet.has(canonical)) {
43
+ return false;
44
+ }
45
+ if (consentTimeField && streamRequest?.time_range) {
46
+ const v = data[consentTimeField];
47
+ const iso = typeof v === "string" ? v : undefined;
48
+ if (!passesTimeRange(iso, streamRequest.time_range)) {
49
+ return false;
50
+ }
51
+ }
52
+ emitted.add(canonical);
53
+ emitRecord(stream, data);
54
+ return true;
55
+ });
56
+ gate.emittedSet = () => emitted;
57
+ return gate;
58
+ }
59
+ export function emitTombstones({ emit, stream, priorIds, currentIds, emittedAt }) {
60
+ let count = 0;
61
+ for (const id of priorIds || []) {
62
+ if (!currentIds.has(id)) {
63
+ emit({
64
+ type: "RECORD",
65
+ stream,
66
+ key: id,
67
+ data: { id },
68
+ emitted_at: emittedAt,
69
+ op: "delete",
70
+ });
71
+ count++;
72
+ }
73
+ }
74
+ return count;
75
+ }
76
+ export async function requireCredentialsOrAsk({ required, connectorName, sendInteraction, }) {
77
+ const { resolveAuth } = await import("./auth.js");
78
+ const ctx = { sendInteraction, connectorName };
79
+ return resolveAuth({ kind: "env", required }, ctx);
80
+ }
@@ -0,0 +1,51 @@
1
+ const SIGNALS_TO_HOOK = ["SIGTERM", "SIGINT"];
2
+ const SIGNAL_EXIT_CODES = {
3
+ SIGTERM: 128 + 15,
4
+ SIGINT: 128 + 2,
5
+ };
6
+ function writeShutdownStderr(message) {
7
+ try {
8
+ process.stderr.write(message);
9
+ }
10
+ catch {
11
+ }
12
+ }
13
+ async function runStepSwallowing(fn, label, signal) {
14
+ if (!fn) {
15
+ return;
16
+ }
17
+ try {
18
+ await fn();
19
+ }
20
+ catch (err) {
21
+ writeShutdownStderr(`[shutdown-hook] ${label}() rejected during ${signal}: ${err instanceof Error ? err.message : String(err)}\n`);
22
+ }
23
+ }
24
+ export function withShutdownRelease(release, options = {}) {
25
+ let firing = false;
26
+ const { finalize } = options;
27
+ const handle = (signal) => async () => {
28
+ if (firing) {
29
+ return;
30
+ }
31
+ firing = true;
32
+ try {
33
+ await runStepSwallowing(finalize, "finalize", signal);
34
+ await runStepSwallowing(release, "release", signal);
35
+ }
36
+ finally {
37
+ process.exit(SIGNAL_EXIT_CODES[signal]);
38
+ }
39
+ };
40
+ const listeners = [];
41
+ for (const signal of SIGNALS_TO_HOOK) {
42
+ const fn = handle(signal);
43
+ process.on(signal, fn);
44
+ listeners.push({ signal, fn });
45
+ }
46
+ return function dispose() {
47
+ for (const { signal, fn } of listeners) {
48
+ process.removeListener(signal, fn);
49
+ }
50
+ };
51
+ }
@@ -0,0 +1,161 @@
1
+ const REGISTRATION_PATH = (runId, interactionId) => `/admin/runs/${encodeURIComponent(runId)}/interactions/${encodeURIComponent(interactionId)}/streaming-target`;
2
+ const ALLOWED_CDP_TARGET_HOSTS = new Set(["127.0.0.1", "localhost", "neko"]);
3
+ const defaultLogger = {
4
+ warn(message, data) {
5
+ const suffix = data ? ` ${JSON.stringify(data)}` : "";
6
+ process.stderr.write(`[streaming-registration] ${message}${suffix}\n`);
7
+ },
8
+ };
9
+ function isAllowedCdpWsUrl(wsUrl) {
10
+ let parsed;
11
+ try {
12
+ parsed = new URL(wsUrl);
13
+ }
14
+ catch {
15
+ return false;
16
+ }
17
+ if (parsed.protocol !== "ws:" && parsed.protocol !== "wss:") {
18
+ return false;
19
+ }
20
+ return ALLOWED_CDP_TARGET_HOSTS.has(parsed.hostname);
21
+ }
22
+ function isDescriptorObject(value) {
23
+ return typeof value === "object" && value !== null && !Array.isArray(value);
24
+ }
25
+ function addOptionalMetadata(body, { pageUrl, pageTitle, reason }) {
26
+ if (typeof pageUrl === "string" && pageUrl.length > 0) {
27
+ body.page_url = pageUrl;
28
+ }
29
+ if (typeof pageTitle === "string" && pageTitle.length > 0) {
30
+ body.page_title = pageTitle;
31
+ }
32
+ if (typeof reason === "string" && reason.length > 0) {
33
+ body.reason = reason;
34
+ }
35
+ }
36
+ export function createRegistrationClient(options) {
37
+ if (!options.baseUrl) {
38
+ throw new Error("createRegistrationClient: baseUrl required");
39
+ }
40
+ if (!options.deviceToken) {
41
+ throw new Error("createRegistrationClient: deviceToken required");
42
+ }
43
+ const baseUrl = new URL(options.baseUrl);
44
+ const fetchImpl = options.fetch ?? globalThis.fetch;
45
+ const logger = options.logger ?? defaultLogger;
46
+ const authHeader = `Bearer ${options.deviceToken}`;
47
+ if (typeof fetchImpl !== "function") {
48
+ throw new Error("createRegistrationClient: no fetch implementation available");
49
+ }
50
+ return {
51
+ async register(args) {
52
+ const { runId, interactionId } = args;
53
+ if (!runId) {
54
+ logger.warn("register skipped: runId is empty");
55
+ return false;
56
+ }
57
+ if (!interactionId) {
58
+ logger.warn("register skipped: interactionId is empty", { runId });
59
+ return false;
60
+ }
61
+ let method;
62
+ let body;
63
+ if (args.backend === "neko") {
64
+ if (!isDescriptorObject(args.descriptor)) {
65
+ logger.warn("register skipped: neko descriptor is not an object", { runId, interactionId });
66
+ return false;
67
+ }
68
+ method = "POST";
69
+ body = { backend: "neko", descriptor: args.descriptor };
70
+ }
71
+ else {
72
+ const { wsUrl } = args;
73
+ if (!isAllowedCdpWsUrl(wsUrl)) {
74
+ logger.warn("register skipped: wsUrl host is not allowed", { runId, interactionId });
75
+ return false;
76
+ }
77
+ method = "PUT";
78
+ body = { ws_url: wsUrl };
79
+ }
80
+ addOptionalMetadata(body, args);
81
+ let response;
82
+ try {
83
+ response = await fetchImpl(new URL(REGISTRATION_PATH(runId, interactionId), baseUrl), {
84
+ method,
85
+ headers: {
86
+ accept: "application/json",
87
+ authorization: authHeader,
88
+ "content-type": "application/json",
89
+ },
90
+ body: JSON.stringify(body),
91
+ });
92
+ }
93
+ catch (err) {
94
+ const message = err instanceof Error ? err.message : String(err);
95
+ logger.warn("register failed (network error)", { runId, interactionId, error: message });
96
+ return false;
97
+ }
98
+ if (!response.ok) {
99
+ await response.text().catch(() => undefined);
100
+ logger.warn("register failed", { runId, interactionId, status: response.status });
101
+ return false;
102
+ }
103
+ await response.text().catch(() => undefined);
104
+ return true;
105
+ },
106
+ async unregister({ runId, interactionId }) {
107
+ if (!(runId && interactionId)) {
108
+ return false;
109
+ }
110
+ let response;
111
+ try {
112
+ response = await fetchImpl(new URL(REGISTRATION_PATH(runId, interactionId), baseUrl), {
113
+ method: "DELETE",
114
+ headers: {
115
+ accept: "application/json",
116
+ authorization: authHeader,
117
+ },
118
+ });
119
+ }
120
+ catch (err) {
121
+ const message = err instanceof Error ? err.message : String(err);
122
+ logger.warn("unregister failed (network error)", { runId, interactionId, error: message });
123
+ return false;
124
+ }
125
+ if (!response.ok) {
126
+ await response.text().catch(() => undefined);
127
+ if (response.status !== 404) {
128
+ logger.warn("unregister failed", { runId, interactionId, status: response.status });
129
+ }
130
+ return false;
131
+ }
132
+ await response.text().catch(() => undefined);
133
+ return true;
134
+ },
135
+ };
136
+ }
137
+ export { ALLOWED_CDP_TARGET_HOSTS, REGISTRATION_PATH };
138
+ const STREAMING_REGISTRATION_TOKEN_ENV = "PDPP_STREAMING_REGISTRATION_TOKEN";
139
+ const LOCAL_DEVICE_TOKEN_ENV = "PDPP_LOCAL_DEVICE_TOKEN";
140
+ export function resolveStreamingRegistrationFromEnv(env = process.env) {
141
+ const runId = env.PDPP_RUN_ID?.trim();
142
+ const baseUrl = env.PDPP_REFERENCE_BASE_URL?.trim();
143
+ const registrationToken = env[STREAMING_REGISTRATION_TOKEN_ENV]?.trim();
144
+ const deviceToken = env[LOCAL_DEVICE_TOKEN_ENV]?.trim();
145
+ const bearerToken = registrationToken || deviceToken;
146
+ if (!(runId && baseUrl && bearerToken)) {
147
+ if (runId && !(baseUrl && bearerToken)) {
148
+ process.stderr.write("[streaming-registration] PDPP_RUN_ID set but PDPP_REFERENCE_BASE_URL or " +
149
+ `${STREAMING_REGISTRATION_TOKEN_ENV}/${LOCAL_DEVICE_TOKEN_ENV} missing; ` +
150
+ "streaming-companion target not registered for this run.\n");
151
+ }
152
+ return Promise.resolve(undefined);
153
+ }
154
+ const client = createRegistrationClient({ baseUrl, deviceToken: bearerToken });
155
+ return Promise.resolve({
156
+ runId,
157
+ register: (args) => client.register(args),
158
+ unregister: (args) => client.unregister(args),
159
+ });
160
+ }
161
+ export { LOCAL_DEVICE_TOKEN_ENV, STREAMING_REGISTRATION_TOKEN_ENV };
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@pdpp/local-collector",
3
+ "version": "0.0.0",
4
+ "description": "Publishable local collector runtime for PDPP: filesystem-class connectors (Claude Code, Codex) plus the device-exporter ingest client.",
5
+ "type": "module",
6
+ "private": false,
7
+ "main": "./dist/local-collector/src/runner.js",
8
+ "types": "./dist/local-collector/src/runner.d.ts",
9
+ "bin": {
10
+ "pdpp-local-collector": "dist/local-collector/bin/pdpp-local-collector.js"
11
+ },
12
+ "exports": {
13
+ "./runner": {
14
+ "types": "./dist/local-collector/src/runner.d.ts",
15
+ "import": "./dist/local-collector/src/runner.js"
16
+ },
17
+ "./errors": {
18
+ "types": "./dist/local-collector/src/errors.d.ts",
19
+ "import": "./dist/local-collector/src/errors.js"
20
+ },
21
+ "./package.json": "./package.json"
22
+ },
23
+ "files": [
24
+ "dist/",
25
+ "README.md"
26
+ ],
27
+ "scripts": {
28
+ "build": "rm -rf dist && tsc -p tsconfig.build.json && node scripts/postbuild.mjs",
29
+ "prepack": "pnpm build",
30
+ "test": "node --test --import tsx 'test/*.test.js'",
31
+ "pack-install-run": "node scripts/pack-install-run.mjs",
32
+ "validate:package": "pnpm build && node scripts/validate-package.mjs",
33
+ "verify": "pnpm test && pnpm validate:package"
34
+ },
35
+ "dependencies": {
36
+ "zod": "^4.3.6"
37
+ },
38
+ "keywords": [
39
+ "pdpp",
40
+ "personal-data",
41
+ "collector",
42
+ "data-portability"
43
+ ],
44
+ "license": "ISC",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/vana-com/pdpp.git",
48
+ "directory": "packages/local-collector"
49
+ },
50
+ "bugs": {
51
+ "url": "https://github.com/vana-com/pdpp/issues"
52
+ },
53
+ "homepage": "https://github.com/vana-com/pdpp#readme",
54
+ "engines": {
55
+ "node": ">=22.14.0"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public",
59
+ "provenance": false,
60
+ "registry": "https://registry.npmjs.org/",
61
+ "tag": "beta"
62
+ }
63
+ }