@privos_ai/app-server 0.8.3 → 0.9.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 (47) hide show
  1. package/README.md +39 -1
  2. package/dist/cli/commands/lint.d.ts +8 -0
  3. package/dist/cli/commands/lint.d.ts.map +1 -0
  4. package/dist/cli/commands/lint.js +24 -0
  5. package/dist/cli/commands/lint.js.map +1 -0
  6. package/dist/cli/commands/publish.d.ts +20 -0
  7. package/dist/cli/commands/publish.d.ts.map +1 -0
  8. package/dist/cli/commands/publish.js +365 -0
  9. package/dist/cli/commands/publish.js.map +1 -0
  10. package/dist/cli/index.d.ts +3 -0
  11. package/dist/cli/index.d.ts.map +1 -0
  12. package/dist/cli/index.js +36 -0
  13. package/dist/cli/index.js.map +1 -0
  14. package/dist/cli/lib/authorize.d.ts +80 -0
  15. package/dist/cli/lib/authorize.d.ts.map +1 -0
  16. package/dist/cli/lib/authorize.js +86 -0
  17. package/dist/cli/lib/authorize.js.map +1 -0
  18. package/dist/cli/lib/manifest.d.ts +30 -0
  19. package/dist/cli/lib/manifest.d.ts.map +1 -0
  20. package/dist/cli/lib/manifest.js +61 -0
  21. package/dist/cli/lib/manifest.js.map +1 -0
  22. package/dist/cli/lib/output.d.ts +66 -0
  23. package/dist/cli/lib/output.d.ts.map +1 -0
  24. package/dist/cli/lib/output.js +61 -0
  25. package/dist/cli/lib/output.js.map +1 -0
  26. package/dist/cli/lib/package-source.d.ts +63 -0
  27. package/dist/cli/lib/package-source.d.ts.map +1 -0
  28. package/dist/cli/lib/package-source.js +219 -0
  29. package/dist/cli/lib/package-source.js.map +1 -0
  30. package/dist/cli/lib/portal-client.d.ts +38 -0
  31. package/dist/cli/lib/portal-client.d.ts.map +1 -0
  32. package/dist/cli/lib/portal-client.js +87 -0
  33. package/dist/cli/lib/portal-client.js.map +1 -0
  34. package/dist/cli/lib/upload.d.ts +54 -0
  35. package/dist/cli/lib/upload.d.ts.map +1 -0
  36. package/dist/cli/lib/upload.js +75 -0
  37. package/dist/cli/lib/upload.js.map +1 -0
  38. package/dist/manifest-lint-cli.js +5 -16
  39. package/dist/manifest-lint-cli.js.map +1 -1
  40. package/dist/relay/relay-client.d.ts +9 -0
  41. package/dist/relay/relay-client.d.ts.map +1 -1
  42. package/dist/relay/relay-client.js +64 -0
  43. package/dist/relay/relay-client.js.map +1 -1
  44. package/package.json +3 -1
  45. package/skill/README.md +14 -0
  46. package/skill/SKILL.md +124 -0
  47. package/skill/references/errors.md +68 -0
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Thin fetch wrapper for the `/api/cloud/marketplace/*` publish routes: JSON
3
+ * request/response, retry-after-driven backoff on transient 429/5xx, and a
4
+ * typed {@link PortalError} carrying the Portal's `code` for callers that
5
+ * branch on specific wire-contract error codes (`VERSION_SEMVER_EXISTS`,
6
+ * `PUBLISH_GRANT_EXPIRED`, `PUBLISH_GRANT_MISMATCH`, `LISTING_NOT_BOUND`,
7
+ * `LISTING_UNRESOLVED`, RFC 8628 `slow_down`, …).
8
+ */
9
+ export declare class PortalError extends Error {
10
+ readonly status: number;
11
+ readonly code?: string | undefined;
12
+ readonly retryAfterMs?: number | undefined;
13
+ constructor(message: string, status: number, code?: string | undefined, retryAfterMs?: number | undefined);
14
+ }
15
+ export type PortalRequestInit = Readonly<{
16
+ method?: string;
17
+ /** Bearer token: publisher token (`pvp_…`), publish grant (`pvg_…`), or none (anonymous create). */
18
+ token?: string;
19
+ body?: unknown;
20
+ }>;
21
+ export type PortalClientOptions = Readonly<{
22
+ origin: string;
23
+ basePath?: string;
24
+ fetchImpl?: typeof fetch;
25
+ maxRetries?: number;
26
+ sleepImpl?: (ms: number) => Promise<void>;
27
+ }>;
28
+ export declare function parseRetryAfterMs(header: string | null): number | undefined;
29
+ export declare class PortalClient {
30
+ private readonly origin;
31
+ private readonly basePath;
32
+ private readonly fetchImpl;
33
+ private readonly maxRetries;
34
+ private readonly sleepImpl;
35
+ constructor(options: PortalClientOptions);
36
+ request<T>(path: string, init?: PortalRequestInit): Promise<T>;
37
+ }
38
+ //# sourceMappingURL=portal-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal-client.d.ts","sourceRoot":"","sources":["../../../src/cli/lib/portal-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,qBAAa,WAAY,SAAQ,KAAK;aAGpB,MAAM,EAAE,MAAM;aACd,IAAI,CAAC,EAAE,MAAM;aACb,YAAY,CAAC,EAAE,MAAM;gBAHrC,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,IAAI,CAAC,EAAE,MAAM,YAAA,EACb,YAAY,CAAC,EAAE,MAAM,YAAA;CAKtC;AAED,MAAM,MAAM,iBAAiB,GAAG,QAAQ,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oGAAoG;IACpG,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CACf,CAAC,CAAC;AAEH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1C,CAAC,CAAC;AASH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,SAAS,CAM3E;AAED,qBAAa,YAAY;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAe;IACzC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;gBAE9C,OAAO,EAAE,mBAAmB;IAQlC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,OAAO,CAAC,CAAC,CAAC;CAoCxE"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Thin fetch wrapper for the `/api/cloud/marketplace/*` publish routes: JSON
3
+ * request/response, retry-after-driven backoff on transient 429/5xx, and a
4
+ * typed {@link PortalError} carrying the Portal's `code` for callers that
5
+ * branch on specific wire-contract error codes (`VERSION_SEMVER_EXISTS`,
6
+ * `PUBLISH_GRANT_EXPIRED`, `PUBLISH_GRANT_MISMATCH`, `LISTING_NOT_BOUND`,
7
+ * `LISTING_UNRESOLVED`, RFC 8628 `slow_down`, …).
8
+ */
9
+ export class PortalError extends Error {
10
+ status;
11
+ code;
12
+ retryAfterMs;
13
+ constructor(message, status, code, retryAfterMs) {
14
+ super(message);
15
+ this.status = status;
16
+ this.code = code;
17
+ this.retryAfterMs = retryAfterMs;
18
+ this.name = 'PortalError';
19
+ }
20
+ }
21
+ const DEFAULT_BASE_PATH = '/api/cloud/marketplace';
22
+ const DEFAULT_MAX_RETRIES = 5;
23
+ function defaultSleep(ms) {
24
+ return new Promise((resolve) => setTimeout(resolve, ms));
25
+ }
26
+ export function parseRetryAfterMs(header) {
27
+ if (!header)
28
+ return undefined;
29
+ if (/^\d+$/.test(header))
30
+ return Number(header) * 1000;
31
+ const parsed = Date.parse(header);
32
+ if (Number.isNaN(parsed))
33
+ return undefined;
34
+ return Math.max(0, parsed - Date.now());
35
+ }
36
+ export class PortalClient {
37
+ origin;
38
+ basePath;
39
+ fetchImpl;
40
+ maxRetries;
41
+ sleepImpl;
42
+ constructor(options) {
43
+ this.origin = options.origin.replace(/\/+$/, '');
44
+ this.basePath = options.basePath ?? DEFAULT_BASE_PATH;
45
+ this.fetchImpl = options.fetchImpl ?? fetch;
46
+ this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
47
+ this.sleepImpl = options.sleepImpl ?? defaultSleep;
48
+ }
49
+ async request(path, init = {}) {
50
+ const method = init.method ?? 'GET';
51
+ const url = `${this.origin}${this.basePath}${path}`;
52
+ let attempt = 0;
53
+ for (;;) {
54
+ const response = await this.fetchImpl(url, {
55
+ method,
56
+ headers: {
57
+ ...(init.token ? { Authorization: `Bearer ${init.token}` } : {}),
58
+ ...(init.body === undefined ? {} : { 'Content-Type': 'application/json' }),
59
+ },
60
+ body: init.body === undefined ? undefined : JSON.stringify(init.body),
61
+ });
62
+ const text = await response.text();
63
+ let parsed;
64
+ try {
65
+ parsed = text ? JSON.parse(text) : {};
66
+ }
67
+ catch {
68
+ parsed = { raw: text.slice(0, 500) };
69
+ }
70
+ if (response.ok)
71
+ return parsed;
72
+ const record = parsed;
73
+ const code = typeof record?.code === 'string' ? record.code : undefined;
74
+ const retryAfterMs = parseRetryAfterMs(response.headers.get('retry-after'));
75
+ const transient = response.status === 429 || (response.status >= 500 && response.status < 600);
76
+ if (transient && attempt < this.maxRetries) {
77
+ const backoffMs = retryAfterMs ?? Math.min(30_000, 1_000 * 2 ** attempt);
78
+ await this.sleepImpl(backoffMs);
79
+ attempt += 1;
80
+ continue;
81
+ }
82
+ const message = typeof record?.message === 'string' ? record.message : `${method} ${path} failed (${response.status})`;
83
+ throw new PortalError(message, response.status, code, retryAfterMs);
84
+ }
85
+ }
86
+ }
87
+ //# sourceMappingURL=portal-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portal-client.js","sourceRoot":"","sources":["../../../src/cli/lib/portal-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ import { PortalClient } from './portal-client.js';
2
+ /** Portal's `express.json()` body limit is 10 MB; base64 inflates bytes ~1.37x, so 5 MB parts stay well under it. */
3
+ export declare const DEFAULT_PART_SIZE_BYTES: number;
4
+ export type UploadSession = Readonly<{
5
+ uploadId: string;
6
+ }>;
7
+ export declare function createUploadSession(client: PortalClient, token: string, listingId: string, request: Readonly<{
8
+ fileName: string;
9
+ totalBytes: number;
10
+ paths: readonly string[];
11
+ }>): Promise<UploadSession>;
12
+ /**
13
+ * Uploads `buffer` as base64 parts, one at a time (sequential — never
14
+ * concurrent), retrying transient 5xx/429 per-part via {@link PortalClient}'s
15
+ * own retry-after backoff.
16
+ */
17
+ export declare function uploadPartsSequentially(client: PortalClient, token: string, uploadId: string, buffer: Buffer, partSizeBytes?: number, onProgress?: (part: number, totalParts: number) => void): Promise<number>;
18
+ export type CompleteUploadResult = Readonly<{
19
+ sha256: string;
20
+ uploadId: string;
21
+ manifestDigest: string;
22
+ manifest: Record<string, unknown>;
23
+ }>;
24
+ export declare function completeUpload(client: PortalClient, token: string, uploadId: string): Promise<CompleteUploadResult>;
25
+ export type CreatedVersion = Readonly<{
26
+ id: string;
27
+ semver: string;
28
+ state: string;
29
+ }>;
30
+ export declare function createVersion(client: PortalClient, token: string, listingId: string, request: Readonly<{
31
+ semver: string;
32
+ uploadId: string;
33
+ changelog: string;
34
+ }>): Promise<CreatedVersion>;
35
+ export declare function submitVersion(client: PortalClient, token: string, listingId: string, versionId: string): Promise<void>;
36
+ export type ListingVersion = Readonly<{
37
+ id: string;
38
+ semver: string;
39
+ state: string;
40
+ }> & Record<string, unknown>;
41
+ export type ListingDetail = Readonly<{
42
+ listing: Record<string, unknown>;
43
+ versions: readonly ListingVersion[];
44
+ events?: readonly Record<string, unknown>[];
45
+ }>;
46
+ export declare function getListingDetail(client: PortalClient, token: string, listingId: string): Promise<ListingDetail>;
47
+ export type WaitForPreflightOptions = Readonly<{
48
+ timeoutMs?: number;
49
+ pollMs?: number;
50
+ sleepImpl?: (ms: number) => Promise<void>;
51
+ }>;
52
+ /** Polls listing detail up to `timeoutMs` (default 60 s) while the version sits in `PREFLIGHT_PENDING`. */
53
+ export declare function waitForPreflight(client: PortalClient, token: string, listingId: string, versionId: string, options?: WaitForPreflightOptions): Promise<ListingVersion | undefined>;
54
+ //# sourceMappingURL=upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.d.ts","sourceRoot":"","sources":["../../../src/cli/lib/upload.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /** Portal's `express.json()` body limit is 10 MB; base64 inflates bytes ~1.37x, so 5 MB parts stay well under it. */
2
+ export const DEFAULT_PART_SIZE_BYTES = 5 * 1024 * 1024;
3
+ export async function createUploadSession(client, token, listingId, request) {
4
+ const response = await client.request(`/creator/listings/${listingId}/uploads`, {
5
+ method: 'POST',
6
+ token,
7
+ body: {
8
+ fileName: request.fileName,
9
+ totalBytes: request.totalBytes,
10
+ kind: 'APP_SOURCE',
11
+ paths: request.paths,
12
+ },
13
+ });
14
+ const upload = response.upload;
15
+ const uploadId = upload?.id;
16
+ if (typeof uploadId !== 'string' || !uploadId)
17
+ throw new Error('Portal did not return an upload session id.');
18
+ return { uploadId };
19
+ }
20
+ /**
21
+ * Uploads `buffer` as base64 parts, one at a time (sequential — never
22
+ * concurrent), retrying transient 5xx/429 per-part via {@link PortalClient}'s
23
+ * own retry-after backoff.
24
+ */
25
+ export async function uploadPartsSequentially(client, token, uploadId, buffer, partSizeBytes = DEFAULT_PART_SIZE_BYTES, onProgress) {
26
+ const totalParts = Math.max(1, Math.ceil(buffer.length / partSizeBytes));
27
+ for (let index = 0; index < totalParts; index += 1) {
28
+ const start = index * partSizeBytes;
29
+ const end = Math.min(buffer.length, start + partSizeBytes);
30
+ const chunk = buffer.subarray(start, end);
31
+ await client.request(`/creator/uploads/${uploadId}/parts/${index + 1}`, {
32
+ method: 'PUT',
33
+ token,
34
+ body: { dataBase64: chunk.toString('base64') },
35
+ });
36
+ onProgress?.(index + 1, totalParts);
37
+ }
38
+ return totalParts;
39
+ }
40
+ export async function completeUpload(client, token, uploadId) {
41
+ const response = await client.request(`/creator/uploads/${uploadId}/complete`, { method: 'POST', token, body: {} });
42
+ return {
43
+ sha256: String(response.sha256),
44
+ uploadId: String(response.uploadId ?? uploadId),
45
+ manifestDigest: String(response.manifestDigest),
46
+ manifest: response.manifest ?? {},
47
+ };
48
+ }
49
+ export async function createVersion(client, token, listingId, request) {
50
+ const response = await client.request(`/creator/listings/${listingId}/versions`, { method: 'POST', token, body: request });
51
+ const version = response.version;
52
+ return { id: String(version.id), semver: String(version.semver), state: String(version.state) };
53
+ }
54
+ export async function submitVersion(client, token, listingId, versionId) {
55
+ await client.request(`/creator/listings/${listingId}/submit`, { method: 'POST', token, body: { versionId } });
56
+ }
57
+ export async function getListingDetail(client, token, listingId) {
58
+ return client.request(`/creator/listings/${listingId}`, { method: 'GET', token });
59
+ }
60
+ /** Polls listing detail up to `timeoutMs` (default 60 s) while the version sits in `PREFLIGHT_PENDING`. */
61
+ export async function waitForPreflight(client, token, listingId, versionId, options = {}) {
62
+ const timeoutMs = options.timeoutMs ?? 60_000;
63
+ const pollMs = options.pollMs ?? 1_000;
64
+ const sleepImpl = options.sleepImpl ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
65
+ const deadline = Date.now() + timeoutMs;
66
+ let detail = await getListingDetail(client, token, listingId);
67
+ let version = detail.versions.find((item) => item.id === versionId);
68
+ while (version?.state === 'PREFLIGHT_PENDING' && Date.now() < deadline) {
69
+ await sleepImpl(pollMs);
70
+ detail = await getListingDetail(client, token, listingId);
71
+ version = detail.versions.find((item) => item.id === versionId);
72
+ }
73
+ return version;
74
+ }
75
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"upload.js","sourceRoot":"","sources":["../../../src/cli/lib/upload.ts"],"names":[],"mappings":"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"}
@@ -1,18 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { lintManifest } from './manifest-tools.js';
5
- const manifestPath = path.resolve(process.argv[2] || 'privos-app.json');
6
- let manifest;
7
- try {
8
- manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
9
- }
10
- catch {
11
- console.error(JSON.stringify({ valid: false, errors: [`Unable to read valid JSON from ${manifestPath}`] }, null, 2));
12
- process.exit(1);
13
- }
14
- const result = lintManifest(manifest);
15
- console.log(JSON.stringify({ manifestPath, ...result }, null, 2));
16
- if (!result.valid)
17
- process.exit(1);
2
+ // Compatibility alias for `privos-app lint`: kept as its own bin entry
3
+ // (`privos-app-lint`) with byte-identical output and exit code, delegating
4
+ // to the same implementation so the two can never drift.
5
+ import { runLint } from './cli/commands/lint.js';
6
+ process.exit(runLint(process.argv.slice(2)));
18
7
  //# sourceMappingURL=manifest-lint-cli.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-lint-cli.js","sourceRoot":"","sources":["../src/manifest-lint-cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEnD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,iBAAiB,CAAC,CAAC;AACxE,IAAI,QAAiB,CAAC;AACtB,IAAI,CAAC;IACJ,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;AAC9D,CAAC;AAAC,MAAM,CAAC;IACR,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,kCAAkC,YAAY,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACrH,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC;AAED,MAAM,MAAM,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;AACtC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,YAAY,EAAE,GAAG,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAClE,IAAI,CAAC,MAAM,CAAC,KAAK;IAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"manifest-lint-cli.js","sourceRoot":"","sources":["../src/manifest-lint-cli.ts"],"names":[],"mappings":";AACA,uEAAuE;AACvE,2EAA2E;AAC3E,yDAAyD;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,wBAAwB,CAAC;AAEjD,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -129,6 +129,15 @@ export interface RelayClientOptions {
129
129
  oauthTimeoutMs?: number;
130
130
  /** Timeout waiting for WebSocket `open` after create (default 15s). */
131
131
  openHandshakeTimeoutMs?: number;
132
+ /**
133
+ * Interval for client-initiated keepalive pings (default 30s). Each tick pings
134
+ * the Hub and, if the previous ping got no pong before the next tick, terminates
135
+ * the socket so the reconnect path restores it — this is what lets a
136
+ * long-running app self-heal a silently dead (half-open) connection without an
137
+ * external healthcheck. Set to `0` to disable (the app then relies solely on
138
+ * server-driven pings and OS timeouts, matching pre-keepalive behavior).
139
+ */
140
+ keepAliveIntervalMs?: number;
132
141
  /** Injected for tests. */
133
142
  fetchImpl?: typeof fetch;
134
143
  WebSocketImpl?: typeof WebSocket;
@@ -1 +1 @@
1
- {"version":3,"file":"relay-client.d.ts","sourceRoot":"","sources":["../../src/relay/relay-client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"relay-client.d.ts","sourceRoot":"","sources":["../../src/relay/relay-client.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,14 @@ const BACKOFF_MS = [1_000, 2_000, 5_000, 10_000, 30_000];
12
12
  const DEFAULT_PAIRING_TIMEOUT_MS = 30_000;
13
13
  const DEFAULT_OAUTH_TIMEOUT_MS = 15_000;
14
14
  const DEFAULT_OPEN_HANDSHAKE_TIMEOUT_MS = 15_000;
15
+ // Client-initiated keepalive. A live socket whose peer has silently gone away
16
+ // (half-open: no FIN/RST ever arrives — laptop sleep, NAT/idle drop, a Hub that
17
+ // vanished) keeps `readyState === OPEN` forever, so the process stays up while
18
+ // dispatch is dead and nothing triggers the reconnect path. A periodic ping that
19
+ // expects a pong within one interval detects that, terminates the dead socket,
20
+ // and lets the existing `close` → backoff reconnect restore service in-process —
21
+ // no external healthcheck or process restart required.
22
+ const DEFAULT_KEEPALIVE_INTERVAL_MS = 30_000;
15
23
  function sortedUniqueStrings(value, field) {
16
24
  if (!Array.isArray(value) || value.some((item) => typeof item !== 'string' || !item)) {
17
25
  throw new Error(`Pairing response ${field} is invalid.`);
@@ -608,12 +616,14 @@ export function connectRelay(opts) {
608
616
  DEFAULT_MAX_MESSAGE_BYTES;
609
617
  const oauthTimeoutMs = opts.oauthTimeoutMs ?? DEFAULT_OAUTH_TIMEOUT_MS;
610
618
  const openHandshakeTimeoutMs = opts.openHandshakeTimeoutMs ?? DEFAULT_OPEN_HANDSHAKE_TIMEOUT_MS;
619
+ const keepAliveIntervalMs = opts.keepAliveIntervalMs ?? DEFAULT_KEEPALIVE_INTERVAL_MS;
611
620
  const maxBufferedBytes = runtime.getLimits().maxBufferedBytes;
612
621
  let stopped = false;
613
622
  let activeWs = null;
614
623
  let wsAuthenticated = false;
615
624
  let reconnectTimer = null;
616
625
  let openHandshakeTimer = null;
626
+ let keepAliveTimer = null;
617
627
  let backoffIndex = 0;
618
628
  let connectGeneration = 0;
619
629
  let oauthAbort = null;
@@ -642,6 +652,12 @@ export function connectRelay(opts) {
642
652
  openHandshakeTimer = null;
643
653
  }
644
654
  };
655
+ const clearKeepAliveTimer = () => {
656
+ if (keepAliveTimer) {
657
+ clearInterval(keepAliveTimer);
658
+ keepAliveTimer = null;
659
+ }
660
+ };
645
661
  const scheduleReconnect = () => {
646
662
  if (stopped)
647
663
  return;
@@ -720,6 +736,11 @@ export function connectRelay(opts) {
720
736
  }
721
737
  // close handler schedules reconnect when generation still current
722
738
  }, openHandshakeTimeoutMs);
739
+ // Liveness for this socket's keepalive: reset to true whenever the Hub
740
+ // answers our ping (or pings us), false when we send a ping and are
741
+ // still waiting. A tick that finds it already false means a full
742
+ // interval passed with no pong — the peer is gone.
743
+ let isAlive = true;
723
744
  ws.on('open', () => {
724
745
  clearOpenHandshakeTimer();
725
746
  if (stopped || generation !== connectGeneration) {
@@ -738,6 +759,43 @@ export function connectRelay(opts) {
738
759
  connectedOnce = true;
739
760
  connectedResolve?.();
740
761
  }
762
+ // Start client-initiated keepalive for this connection. Only one
763
+ // socket is ever active, so clear any prior interval first.
764
+ clearKeepAliveTimer();
765
+ if (keepAliveIntervalMs > 0) {
766
+ isAlive = true;
767
+ keepAliveTimer = setInterval(() => {
768
+ if (stopped || generation !== connectGeneration) {
769
+ clearKeepAliveTimer();
770
+ return;
771
+ }
772
+ if (!isAlive) {
773
+ // No pong within a full interval: the socket is half-open.
774
+ // Terminate to force a `close` event, which runs the
775
+ // existing backoff reconnect — self-healing in-process.
776
+ log('relay.keepalive_timeout', { generation });
777
+ try {
778
+ if (typeof ws.terminate === 'function')
779
+ ws.terminate();
780
+ else
781
+ ws.close();
782
+ }
783
+ catch {
784
+ /* ignore */
785
+ }
786
+ return;
787
+ }
788
+ isAlive = false;
789
+ try {
790
+ ws.ping();
791
+ }
792
+ catch {
793
+ /* ignore */
794
+ }
795
+ }, keepAliveIntervalMs);
796
+ // Don't let the keepalive interval alone keep the process alive.
797
+ keepAliveTimer.unref?.();
798
+ }
741
799
  });
742
800
  ws.on('message', (raw) => {
743
801
  void handleMessage(ws, generation, raw).catch((err) => {
@@ -749,6 +807,7 @@ export function connectRelay(opts) {
749
807
  });
750
808
  });
751
809
  ws.on('ping', () => {
810
+ isAlive = true;
752
811
  try {
753
812
  ws.pong();
754
813
  }
@@ -756,8 +815,12 @@ export function connectRelay(opts) {
756
815
  /* ignore */
757
816
  }
758
817
  });
818
+ ws.on('pong', () => {
819
+ isAlive = true;
820
+ });
759
821
  ws.on('close', (code) => {
760
822
  clearOpenHandshakeTimer();
823
+ clearKeepAliveTimer();
761
824
  log('relay.disconnected', { code, generation });
762
825
  if (activeWs === ws) {
763
826
  activeWs = null;
@@ -1000,6 +1063,7 @@ export function connectRelay(opts) {
1000
1063
  wsAuthenticated = false;
1001
1064
  clearReconnectTimer();
1002
1065
  clearOpenHandshakeTimer();
1066
+ clearKeepAliveTimer();
1003
1067
  connectGeneration += 1;
1004
1068
  oauthAbort?.abort();
1005
1069
  oauthAbort = null;