@bastani/atomic 0.9.41 → 0.9.42-alpha.1
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/CHANGELOG.md +49 -0
- package/dist/builtin/intercom/CHANGELOG.md +13 -0
- package/dist/builtin/intercom/README.md +7 -3
- package/dist/builtin/intercom/broker/broker-status.ts +48 -0
- package/dist/builtin/intercom/broker/broker.ts +90 -9
- package/dist/builtin/intercom/broker/client.ts +112 -12
- package/dist/builtin/intercom/broker/control.ts +225 -0
- package/dist/builtin/intercom/broker/multinode-router.ts +32 -39
- package/dist/builtin/intercom/broker/paths.ts +4 -0
- package/dist/builtin/intercom/broker/pending-send-registry.ts +27 -5
- package/dist/builtin/intercom/broker/shared-storage-setting.ts +60 -0
- package/dist/builtin/intercom/broker/spawn.ts +13 -4
- package/dist/builtin/intercom/broker/sql-lease-store.ts +23 -0
- package/dist/builtin/intercom/broker/watchdog.ts +105 -0
- package/dist/builtin/intercom/index.bundle.mjs +200 -22
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/types.ts +34 -2
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/extension/index.bundle.mjs +1 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +12 -0
- package/dist/builtin/workflows/README.md +10 -0
- package/dist/builtin/workflows/builtin/{chunk-txbc93t6.js → chunk-3s95f7ry.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-n2gkzkty.js → chunk-edhf30ef.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-8x61e0s7.js → chunk-frqxd4d1.js} +18 -9
- package/dist/builtin/workflows/builtin/goal.js +2 -2
- package/dist/builtin/workflows/builtin/index.js +3 -3
- package/dist/builtin/workflows/builtin/ralph.js +2 -2
- package/dist/builtin/workflows/package.json +6 -1
- package/dist/builtin/workflows/src/authoring.d.ts +3 -1
- package/dist/builtin/workflows/src/durable/journal/internal.d.ts +13 -0
- package/dist/builtin/workflows/src/durable/journal/resolution.d.ts +2 -0
- package/dist/builtin/workflows/src/durable/journal/sql-run-journal.d.ts +1 -0
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +88554 -82820
- package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +72635 -0
- package/dist/builtin/workflows/src/index.js +53824 -5907
- package/dist/builtin/workflows/src/local-worker-entry.mjs +72635 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +7 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +2 -0
- package/dist/builtin/workflows/src/workers/public-options.d.ts +31 -0
- package/dist/bun/app-host-modules.d.ts +9 -0
- package/dist/bun/app-host-modules.d.ts.map +1 -0
- package/dist/bun/app-host-modules.js +10 -0
- package/dist/bun/app-host-modules.js.map +1 -0
- package/dist/bun/cli.d.ts +3 -1
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +15 -1
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/internal-intercom-broker.d.ts +17 -0
- package/dist/bun/internal-intercom-broker.d.ts.map +1 -1
- package/dist/bun/internal-intercom-broker.js +29 -0
- package/dist/bun/internal-intercom-broker.js.map +1 -1
- package/dist/bun/internal-workflow-worker.d.ts +4 -0
- package/dist/bun/internal-workflow-worker.d.ts.map +1 -0
- package/dist/bun/internal-workflow-worker.js +37 -0
- package/dist/bun/internal-workflow-worker.js.map +1 -0
- package/dist/bun/split-loader.js +10 -2
- package/dist/bun/split-loader.js.map +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +3 -1
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/intercom-command.d.ts +15 -0
- package/dist/cli/intercom-command.d.ts.map +1 -0
- package/dist/cli/intercom-command.js +87 -0
- package/dist/cli/intercom-command.js.map +1 -0
- package/dist/cli/storage-command.d.ts +39 -0
- package/dist/cli/storage-command.d.ts.map +1 -0
- package/dist/cli/storage-command.js +367 -0
- package/dist/cli/storage-command.js.map +1 -0
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +5 -1
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +1 -0
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-tasks.d.ts.map +1 -1
- package/dist/core/agent-session-tasks.js +1 -0
- package/dist/core/agent-session-tasks.js.map +1 -1
- package/dist/core/agent-session-types.d.ts +1 -0
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/agent-session.d.ts +1 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +2 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/builtin-install-layout.d.ts +6 -0
- package/dist/core/builtin-install-layout.d.ts.map +1 -1
- package/dist/core/builtin-install-layout.js +9 -0
- package/dist/core/builtin-install-layout.js.map +1 -1
- package/dist/core/child-session-options.d.ts.map +1 -1
- package/dist/core/child-session-options.js +3 -0
- package/dist/core/child-session-options.js.map +1 -1
- package/dist/core/exec.js +2 -2
- package/dist/core/exec.js.map +1 -1
- package/dist/core/export-html/template.css +9 -0
- package/dist/core/export-html/template.js +165 -45
- package/dist/core/extensions/loader-host-modules.d.ts +6 -0
- package/dist/core/extensions/loader-host-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-host-modules.js +12 -1
- package/dist/core/extensions/loader-host-modules.js.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.d.ts +6 -0
- package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.js +18 -0
- package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
- package/dist/core/extensions/message-types.d.ts +4 -0
- package/dist/core/extensions/message-types.d.ts.map +1 -1
- package/dist/core/extensions/message-types.js.map +1 -1
- package/dist/core/fast-model-routing-transport.d.ts.map +1 -1
- package/dist/core/fast-model-routing-transport.js +5 -1
- package/dist/core/fast-model-routing-transport.js.map +1 -1
- package/dist/core/intercom-broker-control.d.ts +67 -0
- package/dist/core/intercom-broker-control.d.ts.map +1 -0
- package/dist/core/intercom-broker-control.js +17 -0
- package/dist/core/intercom-broker-control.js.map +1 -0
- package/dist/core/model-runtime-streaming.d.ts.map +1 -1
- package/dist/core/model-runtime-streaming.js +38 -1
- package/dist/core/model-runtime-streaming.js.map +1 -1
- package/dist/core/package-manager-git.d.ts +2 -2
- package/dist/core/package-manager-git.d.ts.map +1 -1
- package/dist/core/package-manager-git.js.map +1 -1
- package/dist/core/package-manager-manifest.d.ts.map +1 -1
- package/dist/core/package-manager-manifest.js +9 -1
- package/dist/core/package-manager-manifest.js.map +1 -1
- package/dist/core/package-manager-npm.d.ts +2 -2
- package/dist/core/package-manager-npm.d.ts.map +1 -1
- package/dist/core/package-manager-npm.js.map +1 -1
- package/dist/core/package-manager-paths.d.ts +7 -7
- package/dist/core/package-manager-paths.d.ts.map +1 -1
- package/dist/core/package-manager-paths.js.map +1 -1
- package/dist/core/package-manager-storage-adapters.d.ts +7 -0
- package/dist/core/package-manager-storage-adapters.d.ts.map +1 -0
- package/dist/core/package-manager-storage-adapters.js +76 -0
- package/dist/core/package-manager-storage-adapters.js.map +1 -0
- package/dist/core/package-manager-types.d.ts +4 -0
- package/dist/core/package-manager-types.d.ts.map +1 -1
- package/dist/core/package-manager-types.js.map +1 -1
- package/dist/core/provider-rate-limits.d.ts +23 -0
- package/dist/core/provider-rate-limits.d.ts.map +1 -0
- package/dist/core/provider-rate-limits.js +113 -0
- package/dist/core/provider-rate-limits.js.map +1 -0
- package/dist/core/provider-request-admission.d.ts +7 -0
- package/dist/core/provider-request-admission.d.ts.map +1 -0
- package/dist/core/provider-request-admission.js +91 -0
- package/dist/core/provider-request-admission.js.map +1 -0
- package/dist/core/resource-admission-scope.d.ts +4 -0
- package/dist/core/resource-admission-scope.d.ts.map +1 -0
- package/dist/core/resource-admission-scope.js +9 -0
- package/dist/core/resource-admission-scope.js.map +1 -0
- package/dist/core/sdk-types.d.ts +16 -0
- package/dist/core/sdk-types.d.ts.map +1 -1
- package/dist/core/sdk-types.js.map +1 -1
- package/dist/core/sdk.d.ts +2 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +18 -2
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-lifecycle-work.d.ts +1 -0
- package/dist/core/session-lifecycle-work.d.ts.map +1 -1
- package/dist/core/session-lifecycle-work.js +1 -0
- package/dist/core/session-lifecycle-work.js.map +1 -1
- package/dist/core/settings-manager-basic-accessors.d.ts +3 -0
- package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
- package/dist/core/settings-manager-basic-accessors.js +6 -0
- package/dist/core/settings-manager-basic-accessors.js.map +1 -1
- package/dist/core/settings-schema.d.ts +22 -0
- package/dist/core/settings-schema.d.ts.map +1 -1
- package/dist/core/settings-schema.js +18 -1
- package/dist/core/settings-schema.js.map +1 -1
- package/dist/core/storage/index.d.ts +1 -0
- package/dist/core/storage/index.d.ts.map +1 -1
- package/dist/core/storage/index.js +1 -0
- package/dist/core/storage/index.js.map +1 -1
- package/dist/core/storage/node-sqlite.d.ts +2 -1
- package/dist/core/storage/node-sqlite.d.ts.map +1 -1
- package/dist/core/storage/node-sqlite.js +5 -4
- package/dist/core/storage/node-sqlite.js.map +1 -1
- package/dist/core/storage/provider.d.ts +6 -0
- package/dist/core/storage/provider.d.ts.map +1 -1
- package/dist/core/storage/provider.js +27 -7
- package/dist/core/storage/provider.js.map +1 -1
- package/dist/core/storage/sqlite-facade.d.ts +49 -0
- package/dist/core/storage/sqlite-facade.d.ts.map +1 -0
- package/dist/core/storage/sqlite-facade.js +180 -0
- package/dist/core/storage/sqlite-facade.js.map +1 -0
- package/dist/core/storage/storage-status.d.ts +81 -0
- package/dist/core/storage/storage-status.d.ts.map +1 -0
- package/dist/core/storage/storage-status.js +409 -0
- package/dist/core/storage/storage-status.js.map +1 -0
- package/dist/core/tasks/supervisor.d.ts +2 -1
- package/dist/core/tasks/supervisor.d.ts.map +1 -1
- package/dist/core/tasks/supervisor.js +105 -4
- package/dist/core/tasks/supervisor.js.map +1 -1
- package/dist/core/tools/bash-pty-native.d.ts.map +1 -1
- package/dist/core/tools/bash-pty-native.js +1 -1
- package/dist/core/tools/bash-pty-native.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +28 -2
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +3 -1
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +7 -3
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts +9 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.js +16 -8
- package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
- package/dist/modes/interactive/components/login-dialog.d.ts +5 -0
- package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
- package/dist/modes/interactive/components/login-dialog.js +69 -2
- package/dist/modes/interactive/components/login-dialog.js.map +1 -1
- package/dist/modes/interactive/components/markdown-transform.d.ts +1 -1
- package/dist/modes/interactive/components/markdown-transform.d.ts.map +1 -1
- package/dist/modes/interactive/components/markdown-transform.js +2 -2
- package/dist/modes/interactive/components/markdown-transform.js.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.d.ts.map +1 -1
- package/dist/modes/interactive/components/session-selector-search.js +80 -22
- package/dist/modes/interactive/components/session-selector-search.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts +3 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +6 -2
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts +1 -1
- package/dist/modes/interactive/interactive-mode-deps.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-deps.js.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.js +6 -2
- package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
- package/dist/modes/interactive-engine/engine-health.d.ts +8 -4
- package/dist/modes/interactive-engine/engine-health.d.ts.map +1 -1
- package/dist/modes/interactive-engine/engine-health.js +41 -6
- package/dist/modes/interactive-engine/engine-health.js.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.d.ts.map +1 -1
- package/dist/modes/interactive-engine/isolated-runtime.js +19 -17
- package/dist/modes/interactive-engine/isolated-runtime.js.map +1 -1
- package/dist/modes/interactive-engine/remote-queue-pause.js +1 -1
- package/dist/modes/interactive-engine/remote-queue-pause.js.map +1 -1
- package/dist/modes/rpc/rpc-input-scheduler.d.ts +1 -0
- package/dist/modes/rpc/rpc-input-scheduler.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-input-scheduler.js +4 -1
- package/dist/modes/rpc/rpc-input-scheduler.js.map +1 -1
- package/dist/modes/rpc/rpc-input.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-input.js +29 -7
- package/dist/modes/rpc/rpc-input.js.map +1 -1
- package/dist/utils/shell.d.ts.map +1 -1
- package/dist/utils/shell.js +14 -0
- package/dist/utils/shell.js.map +1 -1
- package/docs/custom-provider/oauth.md +8 -1
- package/docs/extensions/api-reference.md +2 -0
- package/docs/intercom/operations.md +17 -8
- package/docs/intercom/reference.md +1 -1
- package/docs/keybindings.md +1 -1
- package/docs/packages/authoring.md +15 -0
- package/docs/packages.md +3 -2
- package/docs/providers.md +2 -0
- package/docs/reference/cli.md +17 -0
- package/docs/sdk.md +37 -0
- package/docs/settings.md +33 -2
- package/docs/storage.md +191 -219
- package/docs/usage.md +1 -1
- package/docs/workflows/api-reference.md +50 -0
- package/docs/workflows/operations.md +44 -1
- package/docs/workflows.md +25 -0
- package/examples/extensions/gondolin/package-lock.json +14 -24
- package/examples/extensions/gondolin/package.json +1 -1
- package/npm-shrinkwrap.json +35 -35
- package/package.json +4 -4
- package/schemas/settings.schema.json +87 -1
|
@@ -5,6 +5,12 @@ export { buildSendSignature } from "./send-signature.js";
|
|
|
5
5
|
const DEFAULT_GENERATION_TTL_MS = 10 * 60 * 1000;
|
|
6
6
|
const DEFAULT_MAX_GENERATIONS = 10_000;
|
|
7
7
|
|
|
8
|
+
export class PendingSendTimeoutError extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super("Send timeout");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
export interface SendOptionsLike extends LogicalSendOptions {}
|
|
9
15
|
|
|
10
16
|
export interface SendResultLike {
|
|
@@ -29,6 +35,7 @@ interface OwnedPendingSend extends PendingSendAttempt {
|
|
|
29
35
|
resolve(result: SendResultLike): void;
|
|
30
36
|
reject(error: Error): void;
|
|
31
37
|
timer?: ReturnType<typeof setTimeout>;
|
|
38
|
+
timedOut?: boolean;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
export interface PendingSendAcquisition {
|
|
@@ -106,7 +113,7 @@ export class PendingSendRegistry {
|
|
|
106
113
|
generation,
|
|
107
114
|
};
|
|
108
115
|
attempt.timer = setTimeout(() => {
|
|
109
|
-
this.
|
|
116
|
+
this.expire(attempt);
|
|
110
117
|
}, timeoutMs);
|
|
111
118
|
this.attempts.set(messageId, attempt);
|
|
112
119
|
return { attempt, owner: true };
|
|
@@ -114,7 +121,7 @@ export class PendingSendRegistry {
|
|
|
114
121
|
|
|
115
122
|
resolve(messageId: string, attemptId: string, result: SendResultLike): boolean {
|
|
116
123
|
const attempt = this.attempts.get(messageId);
|
|
117
|
-
if (!attempt || attempt.attemptId !== attemptId) return false;
|
|
124
|
+
if (!attempt || attempt.timedOut || attempt.attemptId !== attemptId) return false;
|
|
118
125
|
this.attempts.delete(messageId);
|
|
119
126
|
if (attempt.timer) clearTimeout(attempt.timer);
|
|
120
127
|
this.touchGeneration(attempt);
|
|
@@ -128,10 +135,10 @@ export class PendingSendRegistry {
|
|
|
128
135
|
*/
|
|
129
136
|
extend(messageId: string, attemptId: string | undefined, timeoutMs: number): boolean {
|
|
130
137
|
const attempt = this.attempts.get(messageId);
|
|
131
|
-
if (!attempt || (attemptId === undefined ? !attempt.legacyEligible : attempt.attemptId !== attemptId)) return false;
|
|
138
|
+
if (!attempt || attempt.timedOut || (attemptId === undefined ? !attempt.legacyEligible : attempt.attemptId !== attemptId)) return false;
|
|
132
139
|
if (attempt.timer) clearTimeout(attempt.timer);
|
|
133
140
|
attempt.timer = setTimeout(() => {
|
|
134
|
-
this.
|
|
141
|
+
this.expire(attempt);
|
|
135
142
|
}, timeoutMs);
|
|
136
143
|
return true;
|
|
137
144
|
}
|
|
@@ -139,10 +146,25 @@ export class PendingSendRegistry {
|
|
|
139
146
|
/** Resolve a pre-attemptId response only for an ID's first active generation. */
|
|
140
147
|
resolveLegacy(messageId: string, result: SendResultLike): boolean {
|
|
141
148
|
const attempt = this.attempts.get(messageId);
|
|
142
|
-
if (!attempt?.legacyEligible) return false;
|
|
149
|
+
if (!attempt?.legacyEligible || attempt.timedOut) return false;
|
|
143
150
|
return this.resolve(messageId, attempt.attemptId, result);
|
|
144
151
|
}
|
|
145
152
|
|
|
153
|
+
private expire(attempt: OwnedPendingSend): void {
|
|
154
|
+
if (this.attempts.get(attempt.messageId) !== attempt) return;
|
|
155
|
+
attempt.timedOut = true;
|
|
156
|
+
attempt.timer = undefined;
|
|
157
|
+
attempt.reject(new PendingSendTimeoutError());
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
release(attempt: PendingSendAttempt): boolean {
|
|
161
|
+
const current = this.attempts.get(attempt.messageId);
|
|
162
|
+
if (current !== attempt || !current.timedOut) return false;
|
|
163
|
+
this.attempts.delete(attempt.messageId);
|
|
164
|
+
this.touchGeneration(current);
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
|
|
146
168
|
reject(attempt: PendingSendAttempt, error: Error): boolean {
|
|
147
169
|
const current = this.attempts.get(attempt.messageId);
|
|
148
170
|
if (current !== attempt) return false;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import type { JsonWireValue } from "./framing.js";
|
|
5
|
+
import { BROKER_STORAGE_ENV } from "./paths.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The raw shared `storage` selection, read without the host package so the broker stays self-contained: the
|
|
9
|
+
* selection the launching process handed this broker in `ATOMIC_STORAGE` (its `--storage`, `--storage-file` or own
|
|
10
|
+
* `ATOMIC_STORAGE`), otherwise the top-level `storage` value of the agent directory's settings. Relative paths in
|
|
11
|
+
* either resolve against the agent directory's parent, as for sessions and the journal. `undefined` leaves
|
|
12
|
+
* multi-node routing off: no selection, a per-process selection that is not a JSON object (the launching process
|
|
13
|
+
* validated it; settings are never consulted instead), and the selections the storage layer reads as in-memory
|
|
14
|
+
* (`memory: true` without a path, or `path: ":memory:"`), which no other broker can share. `path: ""` is not one of
|
|
15
|
+
* them: it selects the default storage file.
|
|
16
|
+
*/
|
|
17
|
+
export function readSharedStorageSetting(
|
|
18
|
+
agentDir: string,
|
|
19
|
+
env: NodeJS.ProcessEnv = process.env,
|
|
20
|
+
): Record<string, JsonWireValue> | undefined {
|
|
21
|
+
const processSelection = env[BROKER_STORAGE_ENV];
|
|
22
|
+
let parsed: JsonWireValue | undefined;
|
|
23
|
+
try {
|
|
24
|
+
parsed =
|
|
25
|
+
processSelection === undefined || processSelection.length === 0
|
|
26
|
+
? (JSON.parse(readFileSync(join(agentDir, "settings.json"), "utf8")) as { storage?: JsonWireValue }).storage
|
|
27
|
+
: JSON.parse(processSelection);
|
|
28
|
+
} catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return undefined;
|
|
32
|
+
const selection = parsed;
|
|
33
|
+
if (selection.adapter === undefined) {
|
|
34
|
+
if (selection.memory === true && !selection.path) return undefined;
|
|
35
|
+
if (selection.path === ":memory:") return undefined;
|
|
36
|
+
}
|
|
37
|
+
return selection;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Where `readSharedStorageSetting` takes the selection from in this environment. */
|
|
41
|
+
export function sharedStorageSettingSource(env: NodeJS.ProcessEnv = process.env): "env" | "settings" {
|
|
42
|
+
const processSelection = env[BROKER_STORAGE_ENV];
|
|
43
|
+
return processSelection === undefined || processSelection.length === 0 ? "settings" : "env";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function sortedKeys(_key: string, value: JsonWireValue): JsonWireValue {
|
|
47
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) return value;
|
|
48
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => (left < right ? -1 : left > right ? 1 : 0)));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Identifies a shared selection independently of key order, without revealing it: two brokers agree when it matches. */
|
|
52
|
+
export function sharedStorageDigest(setting: Readonly<Record<string, JsonWireValue>>): string {
|
|
53
|
+
return createHash("sha256").update(JSON.stringify(setting, sortedKeys)).digest("hex");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The adapter (`sqlite` for a `path` selection) and table prefix a shared selection names, for logs and status. */
|
|
57
|
+
export function describeSharedStorage(setting: Readonly<Record<string, JsonWireValue>>): { adapter: string; prefix?: string } {
|
|
58
|
+
const adapter = typeof setting.adapter === "string" ? setting.adapter : "sqlite";
|
|
59
|
+
return typeof setting.prefix === "string" && setting.prefix.length > 0 ? { adapter, prefix: setting.prefix } : { adapter };
|
|
60
|
+
}
|
|
@@ -272,13 +272,21 @@ export function getBrokerSpawnOptions(
|
|
|
272
272
|
}
|
|
273
273
|
|
|
274
274
|
/**
|
|
275
|
-
*
|
|
275
|
+
* Preserve a bounded previous log across truncation. Returns false when a
|
|
276
276
|
* Windows broker that is still starting holds the log through its launcher's stderr redirect.
|
|
277
277
|
*/
|
|
278
278
|
function resetBrokerLog(logPath: string = BROKER_LOG): boolean {
|
|
279
279
|
ensurePrivateDirectory(dirname(logPath));
|
|
280
280
|
try {
|
|
281
|
+
const previous = readBrokerLogTail(logPath);
|
|
281
282
|
closeSync(openSync(logPath, "w"));
|
|
283
|
+
if (previous.length > 0) {
|
|
284
|
+
try {
|
|
285
|
+
const previousPath = `${logPath}.previous`;
|
|
286
|
+
writeFileSync(previousPath, Buffer.from(previous).subarray(-BROKER_LOG_TAIL_BYTES), { mode: 0o600 });
|
|
287
|
+
if (process.platform !== "win32") chmodSync(previousPath, 0o600);
|
|
288
|
+
} catch {}
|
|
289
|
+
}
|
|
282
290
|
return true;
|
|
283
291
|
} catch (error) {
|
|
284
292
|
const code = (error as NodeJS.ErrnoException).code;
|
|
@@ -327,6 +335,7 @@ export async function spawnBrokerIfNeeded(
|
|
|
327
335
|
brokerCommand: string,
|
|
328
336
|
brokerArgs: string[],
|
|
329
337
|
readyTimeoutMs: number = BROKER_READY_TIMEOUT_MS,
|
|
338
|
+
extensionDir: string = EXTENSION_DIR,
|
|
330
339
|
): Promise<void> {
|
|
331
340
|
ensurePrivateDirectory(INTERCOM_DIR);
|
|
332
341
|
|
|
@@ -346,8 +355,8 @@ export async function spawnBrokerIfNeeded(
|
|
|
346
355
|
return;
|
|
347
356
|
}
|
|
348
357
|
|
|
349
|
-
const brokerPath = resolveBrokerEntrypoint(
|
|
350
|
-
const launch = getBrokerLaunchSpec(brokerPath, brokerCommand, brokerArgs);
|
|
358
|
+
const brokerPath = resolveBrokerEntrypoint(extensionDir);
|
|
359
|
+
const launch = getBrokerLaunchSpec(brokerPath, brokerCommand, brokerArgs, extensionDir);
|
|
351
360
|
if (launch.kind === "windows-launcher") {
|
|
352
361
|
writeWindowsHiddenLauncher(launch.launcherCommandLine, launch.launcherPath);
|
|
353
362
|
}
|
|
@@ -362,7 +371,7 @@ export async function spawnBrokerIfNeeded(
|
|
|
362
371
|
const logFd = launch.kind === "direct" ? openSync(BROKER_LOG, "a") : undefined;
|
|
363
372
|
let child: ReturnType<typeof spawn>;
|
|
364
373
|
try {
|
|
365
|
-
child = spawn(launch.command, launch.args, getBrokerSpawnOptions(
|
|
374
|
+
child = spawn(launch.command, launch.args, getBrokerSpawnOptions(extensionDir, logFd ?? "ignore"));
|
|
366
375
|
} finally {
|
|
367
376
|
if (logFd !== undefined) closeSync(logFd);
|
|
368
377
|
}
|
|
@@ -62,6 +62,29 @@ function stateFromRow(row: StateRow): LeaseState {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
/**
|
|
66
|
+
* The host names of the brokers whose membership lease is live in `<prefix>intercom_leases`, read without creating
|
|
67
|
+
* the table: `undefined` when no broker has created it yet. For status reports; brokers use `listLive`.
|
|
68
|
+
*/
|
|
69
|
+
export async function readLiveBrokerNodes(
|
|
70
|
+
database: SqliteExecutor,
|
|
71
|
+
prefix: string | undefined,
|
|
72
|
+
staleAfterMs: number,
|
|
73
|
+
): Promise<string[] | undefined> {
|
|
74
|
+
const table = `${validateStoragePrefix(prefix)}intercom_leases`;
|
|
75
|
+
const exists = await database.get("SELECT name FROM sqlite_master WHERE type = 'table' AND name = ?", table);
|
|
76
|
+
if (exists === undefined) return undefined;
|
|
77
|
+
const rows = await database.all<StateRow>(
|
|
78
|
+
`SELECT kind, key, holder_id, fencing_token, subject_json, ${AGE} AS age_ms FROM ${table}
|
|
79
|
+
WHERE kind = 'broker' AND holder_id IS NOT NULL AND ${AGE} <= ? ORDER BY first_acquired_order`,
|
|
80
|
+
staleAfterMs,
|
|
81
|
+
);
|
|
82
|
+
return rows.flatMap((row) => {
|
|
83
|
+
const { subject } = stateFromRow(row);
|
|
84
|
+
return subject.kind === "broker" ? [subject.nodeId] : [];
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
65
88
|
/** Opens the S1 lease store on the shared database's `<prefix>intercom_leases` table; the database stays open. */
|
|
66
89
|
export async function openSqlBrokerLeaseStore(options: IntercomBrokerStateOptions): Promise<BrokerLeaseStore> {
|
|
67
90
|
const { database } = options;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
|
|
3
|
+
export const BROKER_WATCHDOG_INTERVAL_MS = 2_000;
|
|
4
|
+
export const BROKER_WATCHDOG_PROBE_TIMEOUT_MS = 10_000;
|
|
5
|
+
export const BROKER_WATCHDOG_SILENCE_MS = 60_000;
|
|
6
|
+
|
|
7
|
+
const WATCHDOG_SOURCE = `
|
|
8
|
+
const { workerData } = require("node:worker_threads");
|
|
9
|
+
const { connect } = require("node:net");
|
|
10
|
+
const { randomUUID } = require("node:crypto");
|
|
11
|
+
const { writeFileSync } = require("node:fs");
|
|
12
|
+
let lastHealthyAt = performance.now();
|
|
13
|
+
let consecutiveFailures = 0;
|
|
14
|
+
|
|
15
|
+
function probe() {
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
const requestId = randomUUID();
|
|
18
|
+
const socket = connect(workerData.socketPath);
|
|
19
|
+
let buffer = Buffer.alloc(0);
|
|
20
|
+
let settled = false;
|
|
21
|
+
const finish = (failure) => {
|
|
22
|
+
if (settled) return;
|
|
23
|
+
settled = true;
|
|
24
|
+
clearTimeout(timer);
|
|
25
|
+
socket.destroy();
|
|
26
|
+
resolve(failure);
|
|
27
|
+
};
|
|
28
|
+
const timer = setTimeout(() => finish("response-timeout"), workerData.probeTimeoutMs);
|
|
29
|
+
socket.on("error", () => finish("connection-error"));
|
|
30
|
+
socket.on("close", () => finish("connection-closed"));
|
|
31
|
+
socket.on("connect", () => {
|
|
32
|
+
const payload = Buffer.from(JSON.stringify({ type: "health", requestId }));
|
|
33
|
+
const header = Buffer.alloc(4);
|
|
34
|
+
header.writeUInt32BE(payload.length);
|
|
35
|
+
try { socket.write(Buffer.concat([header, payload])); }
|
|
36
|
+
catch { finish("write-error"); }
|
|
37
|
+
});
|
|
38
|
+
socket.on("data", (data) => {
|
|
39
|
+
if (buffer.length + data.length > 1024) return finish("oversized-response");
|
|
40
|
+
buffer = Buffer.concat([buffer, data]);
|
|
41
|
+
if (buffer.length < 4) return;
|
|
42
|
+
const length = buffer.readUInt32BE(0);
|
|
43
|
+
if (length > 1020) return finish("oversized-response");
|
|
44
|
+
if (buffer.length < length + 4) return;
|
|
45
|
+
try {
|
|
46
|
+
const reply = JSON.parse(buffer.subarray(4, length + 4).toString("utf8"));
|
|
47
|
+
if (reply.type !== "health" || reply.requestId !== requestId) return finish("unexpected-response");
|
|
48
|
+
if (reply.instanceId !== workerData.instanceId) return finish("different-broker-instance");
|
|
49
|
+
finish();
|
|
50
|
+
} catch { finish("invalid-response"); }
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function check() {
|
|
56
|
+
const failure = await probe();
|
|
57
|
+
if (failure === undefined) {
|
|
58
|
+
lastHealthyAt = performance.now();
|
|
59
|
+
consecutiveFailures = 0;
|
|
60
|
+
} else {
|
|
61
|
+
consecutiveFailures += 1;
|
|
62
|
+
const silentForMs = Math.round(performance.now() - lastHealthyAt);
|
|
63
|
+
if (consecutiveFailures >= 3 && silentForMs >= workerData.silenceMs) {
|
|
64
|
+
try {
|
|
65
|
+
writeFileSync(workerData.logPath, JSON.stringify({
|
|
66
|
+
at: new Date().toISOString(), pid: process.pid, instanceId: workerData.instanceId,
|
|
67
|
+
reason: "Intercom broker stopped answering health requests; terminating for recovery",
|
|
68
|
+
failure, consecutiveFailures, silentForMs
|
|
69
|
+
}) + "\\n", { mode: 0o600 });
|
|
70
|
+
} catch {}
|
|
71
|
+
process.kill(process.pid, "SIGKILL");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
setTimeout(check, workerData.intervalMs);
|
|
76
|
+
}
|
|
77
|
+
setTimeout(check, workerData.intervalMs);
|
|
78
|
+
`;
|
|
79
|
+
|
|
80
|
+
export function startBrokerWatchdog(socketPath: string, instanceId: string, logPath: string): void {
|
|
81
|
+
try {
|
|
82
|
+
const worker = new Worker(WATCHDOG_SOURCE, {
|
|
83
|
+
eval: true,
|
|
84
|
+
workerData: {
|
|
85
|
+
socketPath,
|
|
86
|
+
instanceId,
|
|
87
|
+
logPath,
|
|
88
|
+
intervalMs: BROKER_WATCHDOG_INTERVAL_MS,
|
|
89
|
+
probeTimeoutMs: BROKER_WATCHDOG_PROBE_TIMEOUT_MS,
|
|
90
|
+
silenceMs: BROKER_WATCHDOG_SILENCE_MS,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
let failed = false;
|
|
94
|
+
worker.on("error", (error) => {
|
|
95
|
+
failed = true;
|
|
96
|
+
console.error("Intercom broker watchdog unavailable:", error);
|
|
97
|
+
});
|
|
98
|
+
worker.on("exit", (code) => {
|
|
99
|
+
if (!failed) console.error(`Intercom broker watchdog exited unexpectedly with code ${code}`);
|
|
100
|
+
});
|
|
101
|
+
worker.unref();
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error("Intercom broker watchdog unavailable:", error);
|
|
104
|
+
}
|
|
105
|
+
}
|