@grackle-ai/core 0.75.4

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 (130) hide show
  1. package/README.md +30 -0
  2. package/dist/adapter-config.d.ts +6 -0
  3. package/dist/adapter-config.d.ts.map +1 -0
  4. package/dist/adapter-config.js +19 -0
  5. package/dist/adapter-config.js.map +1 -0
  6. package/dist/adapter-manager.d.ts +22 -0
  7. package/dist/adapter-manager.d.ts.map +1 -0
  8. package/dist/adapter-manager.js +81 -0
  9. package/dist/adapter-manager.js.map +1 -0
  10. package/dist/auto-reconnect.d.ts +23 -0
  11. package/dist/auto-reconnect.d.ts.map +1 -0
  12. package/dist/auto-reconnect.js +164 -0
  13. package/dist/auto-reconnect.js.map +1 -0
  14. package/dist/compute-task-status.d.ts +28 -0
  15. package/dist/compute-task-status.d.ts.map +1 -0
  16. package/dist/compute-task-status.js +70 -0
  17. package/dist/compute-task-status.js.map +1 -0
  18. package/dist/credential-bundle.d.ts +12 -0
  19. package/dist/credential-bundle.d.ts.map +1 -0
  20. package/dist/credential-bundle.js +183 -0
  21. package/dist/credential-bundle.js.map +1 -0
  22. package/dist/event-bus.d.ts +37 -0
  23. package/dist/event-bus.d.ts.map +1 -0
  24. package/dist/event-bus.js +65 -0
  25. package/dist/event-bus.js.map +1 -0
  26. package/dist/event-processor.d.ts +36 -0
  27. package/dist/event-processor.d.ts.map +1 -0
  28. package/dist/event-processor.js +312 -0
  29. package/dist/event-processor.js.map +1 -0
  30. package/dist/grpc-service.d.ts +22 -0
  31. package/dist/grpc-service.d.ts.map +1 -0
  32. package/dist/grpc-service.js +1724 -0
  33. package/dist/grpc-service.js.map +1 -0
  34. package/dist/index.d.ts +16 -0
  35. package/dist/index.d.ts.map +1 -0
  36. package/dist/index.js +25 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/knowledge-init.d.ts +27 -0
  39. package/dist/knowledge-init.d.ts.map +1 -0
  40. package/dist/knowledge-init.js +212 -0
  41. package/dist/knowledge-init.js.map +1 -0
  42. package/dist/lifecycle.d.ts +36 -0
  43. package/dist/lifecycle.d.ts.map +1 -0
  44. package/dist/lifecycle.js +112 -0
  45. package/dist/lifecycle.js.map +1 -0
  46. package/dist/log-writer.d.ts +32 -0
  47. package/dist/log-writer.d.ts.map +1 -0
  48. package/dist/log-writer.js +104 -0
  49. package/dist/log-writer.js.map +1 -0
  50. package/dist/logger.d.ts +4 -0
  51. package/dist/logger.d.ts.map +1 -0
  52. package/dist/logger.js +10 -0
  53. package/dist/logger.js.map +1 -0
  54. package/dist/pipe-delivery.d.ts +41 -0
  55. package/dist/pipe-delivery.d.ts.map +1 -0
  56. package/dist/pipe-delivery.js +186 -0
  57. package/dist/pipe-delivery.js.map +1 -0
  58. package/dist/processor-registry.d.ts +25 -0
  59. package/dist/processor-registry.d.ts.map +1 -0
  60. package/dist/processor-registry.js +58 -0
  61. package/dist/processor-registry.js.map +1 -0
  62. package/dist/reanimate-agent.d.ts +12 -0
  63. package/dist/reanimate-agent.d.ts.map +1 -0
  64. package/dist/reanimate-agent.js +76 -0
  65. package/dist/reanimate-agent.js.map +1 -0
  66. package/dist/session-recovery.d.ts +16 -0
  67. package/dist/session-recovery.d.ts.map +1 -0
  68. package/dist/session-recovery.js +129 -0
  69. package/dist/session-recovery.js.map +1 -0
  70. package/dist/signals/sigchld.d.ts +7 -0
  71. package/dist/signals/sigchld.d.ts.map +1 -0
  72. package/dist/signals/sigchld.js +167 -0
  73. package/dist/signals/sigchld.js.map +1 -0
  74. package/dist/signals/signal-delivery.d.ts +14 -0
  75. package/dist/signals/signal-delivery.d.ts.map +1 -0
  76. package/dist/signals/signal-delivery.js +166 -0
  77. package/dist/signals/signal-delivery.js.map +1 -0
  78. package/dist/stream-hub.d.ts +14 -0
  79. package/dist/stream-hub.d.ts.map +1 -0
  80. package/dist/stream-hub.js +95 -0
  81. package/dist/stream-hub.js.map +1 -0
  82. package/dist/stream-registry.d.ts +84 -0
  83. package/dist/stream-registry.d.ts.map +1 -0
  84. package/dist/stream-registry.js +363 -0
  85. package/dist/stream-registry.js.map +1 -0
  86. package/dist/test-utils/integration-setup.d.ts +11 -0
  87. package/dist/test-utils/integration-setup.d.ts.map +1 -0
  88. package/dist/test-utils/integration-setup.js +32 -0
  89. package/dist/test-utils/integration-setup.js.map +1 -0
  90. package/dist/test-utils/mock-database.d.ts +130 -0
  91. package/dist/test-utils/mock-database.d.ts.map +1 -0
  92. package/dist/test-utils/mock-database.js +147 -0
  93. package/dist/test-utils/mock-database.js.map +1 -0
  94. package/dist/token-push.d.ts +22 -0
  95. package/dist/token-push.d.ts.map +1 -0
  96. package/dist/token-push.js +78 -0
  97. package/dist/token-push.js.map +1 -0
  98. package/dist/transcript.d.ts +5 -0
  99. package/dist/transcript.d.ts.map +1 -0
  100. package/dist/transcript.js +71 -0
  101. package/dist/transcript.js.map +1 -0
  102. package/dist/utils/exec.d.ts +17 -0
  103. package/dist/utils/exec.d.ts.map +1 -0
  104. package/dist/utils/exec.js +21 -0
  105. package/dist/utils/exec.js.map +1 -0
  106. package/dist/utils/format-gh-error.d.ts +6 -0
  107. package/dist/utils/format-gh-error.d.ts.map +1 -0
  108. package/dist/utils/format-gh-error.js +30 -0
  109. package/dist/utils/format-gh-error.js.map +1 -0
  110. package/dist/utils/network.d.ts +7 -0
  111. package/dist/utils/network.d.ts.map +1 -0
  112. package/dist/utils/network.js +21 -0
  113. package/dist/utils/network.js.map +1 -0
  114. package/dist/utils/ports.d.ts +3 -0
  115. package/dist/utils/ports.d.ts.map +1 -0
  116. package/dist/utils/ports.js +19 -0
  117. package/dist/utils/ports.js.map +1 -0
  118. package/dist/utils/sleep.d.ts +3 -0
  119. package/dist/utils/sleep.d.ts.map +1 -0
  120. package/dist/utils/sleep.js +5 -0
  121. package/dist/utils/sleep.js.map +1 -0
  122. package/dist/ws-bridge.d.ts +30 -0
  123. package/dist/ws-bridge.d.ts.map +1 -0
  124. package/dist/ws-bridge.js +372 -0
  125. package/dist/ws-bridge.js.map +1 -0
  126. package/dist/ws-broadcast.d.ts +19 -0
  127. package/dist/ws-broadcast.d.ts.map +1 -0
  128. package/dist/ws-broadcast.js +60 -0
  129. package/dist/ws-broadcast.js.map +1 -0
  130. package/package.json +57 -0
package/README.md ADDED
@@ -0,0 +1,30 @@
1
+ # @grackle-ai/core
2
+
3
+ Core gRPC business logic for [Grackle](https://github.com/nick-pape/grackle).
4
+
5
+ ## Overview
6
+
7
+ This package contains the runtime business logic for the Grackle server:
8
+
9
+ - **gRPC Service** — ConnectRPC handlers for environment, session, task, workspace, persona, and knowledge operations
10
+ - **Event System** — Domain event bus with pub/sub, event processing, and persistence
11
+ - **Streaming** — Stream multiplexing, registry, and pipe delivery for real-time agent output
12
+ - **WebSocket Bridge** — Event broadcast to connected web clients
13
+ - **Session Lifecycle** — Recovery, auto-hibernate, and reanimate for agent sessions
14
+ - **Adapter Management** — Registry and health-check heartbeat for environment adapters
15
+ - **Signals** — SIGCHLD handling for parent-child task coordination
16
+ - **Credentials** — Token push and credential bundling for remote environments
17
+
18
+ ## Usage
19
+
20
+ This package is consumed by `@grackle-ai/server` (the orchestrator) which wires it together with the web server, MCP server, and PowerLine.
21
+
22
+ ```typescript
23
+ import {
24
+ registerGrackleRoutes,
25
+ registerAdapter, startHeartbeat,
26
+ emit, subscribe,
27
+ createWsBridge,
28
+ logger,
29
+ } from "@grackle-ai/core";
30
+ ```
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Parse a JSON adapter configuration string, throwing a gRPC Internal error
3
+ * if the value is not valid JSON or not a plain object.
4
+ */
5
+ export declare function parseAdapterConfig(raw: string): Record<string, unknown>;
6
+ //# sourceMappingURL=adapter-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-config.d.ts","sourceRoot":"","sources":["../src/adapter-config.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAWvE"}
@@ -0,0 +1,19 @@
1
+ import { ConnectError, Code } from "@connectrpc/connect";
2
+ /**
3
+ * Parse a JSON adapter configuration string, throwing a gRPC Internal error
4
+ * if the value is not valid JSON or not a plain object.
5
+ */
6
+ export function parseAdapterConfig(raw) {
7
+ let parsed;
8
+ try {
9
+ parsed = JSON.parse(raw);
10
+ }
11
+ catch {
12
+ throw new ConnectError("Invalid adapter configuration", Code.Internal);
13
+ }
14
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
15
+ throw new ConnectError("Invalid adapter configuration", Code.Internal);
16
+ }
17
+ return parsed;
18
+ }
19
+ //# sourceMappingURL=adapter-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-config.js","sourceRoot":"","sources":["../src/adapter-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,qBAAqB,CAAC;AAEzD;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAW;IAC5C,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,YAAY,CAAC,+BAA+B,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;QAC3E,MAAM,IAAI,YAAY,CAAC,+BAA+B,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzE,CAAC;IACD,OAAO,MAAiC,CAAC;AAC3C,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { EnvironmentAdapter, PowerLineConnection } from "@grackle-ai/adapter-sdk";
2
+ /** Register an environment adapter so it can be looked up by type. */
3
+ export declare function registerAdapter(adapter: EnvironmentAdapter): void;
4
+ /** Retrieve a registered adapter by its type name. */
5
+ export declare function getAdapter(type: string): EnvironmentAdapter | undefined;
6
+ /** Store an active PowerLine connection for an environment. */
7
+ export declare function setConnection(environmentId: string, conn: PowerLineConnection): void;
8
+ /** Get the active PowerLine connection for an environment, if connected. */
9
+ export declare function getConnection(environmentId: string): PowerLineConnection | undefined;
10
+ /** Remove the stored connection for an environment. */
11
+ export declare function removeConnection(environmentId: string): void;
12
+ /** Return the map of all active environment connections. */
13
+ export declare function listConnections(): Map<string, PowerLineConnection>;
14
+ /**
15
+ * Start a periodic health-check loop that calls `onDisconnect` when a
16
+ * PowerLine becomes unreachable. Optionally calls `onHeartbeatComplete`
17
+ * after each tick (used for auto-reconnect of disconnected environments).
18
+ */
19
+ export declare function startHeartbeat(onDisconnect: (environmentId: string) => void, onHeartbeatComplete?: () => Promise<void>): void;
20
+ /** Stop the periodic health-check loop. */
21
+ export declare function stopHeartbeat(): void;
22
+ //# sourceMappingURL=adapter-manager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-manager.d.ts","sourceRoot":"","sources":["../src/adapter-manager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAUvF,sEAAsE;AACtE,wBAAgB,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAEjE;AAED,sDAAsD;AACtD,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,CAEvE;AAED,+DAA+D;AAC/D,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,mBAAmB,GAAG,IAAI,CAEpF;AAED,4EAA4E;AAC5E,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS,CAEpF;AAED,uDAAuD;AACvD,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,IAAI,GAAG,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAElE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAC5B,YAAY,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,EAC7C,mBAAmB,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GACxC,IAAI,CAsCN;AAED,2CAA2C;AAC3C,wBAAgB,aAAa,IAAI,IAAI,CAKpC"}
@@ -0,0 +1,81 @@
1
+ import { envRegistry } from "@grackle-ai/database";
2
+ import { logger } from "./logger.js";
3
+ const HEARTBEAT_INTERVAL_MS = 30_000;
4
+ const adapters = new Map();
5
+ const connections = new Map();
6
+ let heartbeatInterval = undefined;
7
+ /** Register an environment adapter so it can be looked up by type. */
8
+ export function registerAdapter(adapter) {
9
+ adapters.set(adapter.type, adapter);
10
+ }
11
+ /** Retrieve a registered adapter by its type name. */
12
+ export function getAdapter(type) {
13
+ return adapters.get(type);
14
+ }
15
+ /** Store an active PowerLine connection for an environment. */
16
+ export function setConnection(environmentId, conn) {
17
+ connections.set(environmentId, conn);
18
+ }
19
+ /** Get the active PowerLine connection for an environment, if connected. */
20
+ export function getConnection(environmentId) {
21
+ return connections.get(environmentId);
22
+ }
23
+ /** Remove the stored connection for an environment. */
24
+ export function removeConnection(environmentId) {
25
+ connections.delete(environmentId);
26
+ }
27
+ /** Return the map of all active environment connections. */
28
+ export function listConnections() {
29
+ return connections;
30
+ }
31
+ /**
32
+ * Start a periodic health-check loop that calls `onDisconnect` when a
33
+ * PowerLine becomes unreachable. Optionally calls `onHeartbeatComplete`
34
+ * after each tick (used for auto-reconnect of disconnected environments).
35
+ */
36
+ export function startHeartbeat(onDisconnect, onHeartbeatComplete) {
37
+ if (heartbeatInterval !== undefined) {
38
+ return;
39
+ }
40
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
41
+ heartbeatInterval = setInterval(async () => {
42
+ for (const [environmentId, conn] of connections) {
43
+ const env = envRegistry.getEnvironment(environmentId);
44
+ if (!env) {
45
+ continue;
46
+ }
47
+ const adapter = adapters.get(env.adapterType);
48
+ if (!adapter) {
49
+ continue;
50
+ }
51
+ try {
52
+ const ok = await adapter.healthCheck(conn);
53
+ if (!ok) {
54
+ logger.warn({ environmentId }, "Health check failed");
55
+ onDisconnect(environmentId);
56
+ }
57
+ }
58
+ catch {
59
+ logger.warn({ environmentId }, "Connection lost");
60
+ onDisconnect(environmentId);
61
+ }
62
+ }
63
+ // After health checks, attempt reconnection of disconnected environments
64
+ if (onHeartbeatComplete) {
65
+ try {
66
+ await onHeartbeatComplete();
67
+ }
68
+ catch (err) {
69
+ logger.error({ err }, "onHeartbeatComplete callback failed");
70
+ }
71
+ }
72
+ }, HEARTBEAT_INTERVAL_MS);
73
+ }
74
+ /** Stop the periodic health-check loop. */
75
+ export function stopHeartbeat() {
76
+ if (heartbeatInterval !== undefined) {
77
+ clearInterval(heartbeatInterval);
78
+ heartbeatInterval = undefined;
79
+ }
80
+ }
81
+ //# sourceMappingURL=adapter-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter-manager.js","sourceRoot":"","sources":["../src/adapter-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Scan for disconnected environments and attempt to reconnect eligible ones.
3
+ * Called after each heartbeat tick. Uses exponential backoff per environment
4
+ * and a concurrency lock to prevent overlapping attempts.
5
+ *
6
+ * Fire-and-forget for each environment — logs errors but does not throw.
7
+ */
8
+ export declare function attemptReconnects(): Promise<void>;
9
+ /**
10
+ * Clear reconnect state for an environment. Call when the environment is
11
+ * manually provisioned, removed, or otherwise taken out of the reconnect cycle.
12
+ */
13
+ export declare function clearReconnectState(environmentId: string): void;
14
+ /**
15
+ * Reset reconnect state so the environment is immediately eligible on the
16
+ * next heartbeat tick. Unlike {@link clearReconnectState} (which deletes state,
17
+ * causing the next tick to re-initialize with an initial delay), this sets
18
+ * attempts to zero and nextRetryAt to now.
19
+ */
20
+ export declare function resetReconnectState(environmentId: string): void;
21
+ /** @internal Reset all reconnect state for testing. */
22
+ export declare function _resetForTesting(): void;
23
+ //# sourceMappingURL=auto-reconnect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-reconnect.d.ts","sourceRoot":"","sources":["../src/auto-reconnect.ts"],"names":[],"mappings":"AAyCA;;;;;;GAMG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA4CvD;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAK/D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI,CAE/D;AAED,uDAAuD;AACvD,wBAAgB,gBAAgB,IAAI,IAAI,CAGvC"}
@@ -0,0 +1,164 @@
1
+ import { reconnectOrProvision } from "@grackle-ai/adapter-sdk";
2
+ import { envRegistry } from "@grackle-ai/database";
3
+ import * as adapterManager from "./adapter-manager.js";
4
+ import * as tokenPush from "./token-push.js";
5
+ import { recoverSuspendedSessions } from "./session-recovery.js";
6
+ import { parseAdapterConfig } from "./adapter-config.js";
7
+ import { emit } from "./event-bus.js";
8
+ import { logger } from "./logger.js";
9
+ // ─── Constants ──────────────────────────────────────────────
10
+ /** Initial delay before first reconnect attempt (milliseconds). */
11
+ const RECONNECT_INITIAL_DELAY_MS = 10_000;
12
+ /** Maximum number of reconnect attempts before giving up. */
13
+ const RECONNECT_MAX_RETRIES = 5;
14
+ /** Maximum delay between reconnect attempts (milliseconds). */
15
+ const RECONNECT_MAX_DELAY_MS = 120_000;
16
+ /** Multiplier for exponential backoff. */
17
+ const RECONNECT_BACKOFF_MULTIPLIER = 2;
18
+ /** Tracks reconnect backoff state per environment. */
19
+ const reconnectStates = new Map();
20
+ /** Prevents concurrent reconnect attempts for the same environment. */
21
+ const reconnecting = new Set();
22
+ // ─── Public API ─────────────────────────────────────────────
23
+ /**
24
+ * Scan for disconnected environments and attempt to reconnect eligible ones.
25
+ * Called after each heartbeat tick. Uses exponential backoff per environment
26
+ * and a concurrency lock to prevent overlapping attempts.
27
+ *
28
+ * Fire-and-forget for each environment — logs errors but does not throw.
29
+ */
30
+ export async function attemptReconnects() {
31
+ const environments = envRegistry.listEnvironments();
32
+ const disconnected = environments.filter((env) => env.status === "disconnected");
33
+ for (const env of disconnected) {
34
+ const state = reconnectStates.get(env.id);
35
+ // First time seeing this environment disconnected — initialize state with delay
36
+ if (!state) {
37
+ reconnectStates.set(env.id, {
38
+ attempts: 0,
39
+ nextRetryAt: Date.now() + RECONNECT_INITIAL_DELAY_MS,
40
+ });
41
+ continue;
42
+ }
43
+ // Backoff not elapsed yet
44
+ if (Date.now() < state.nextRetryAt) {
45
+ continue;
46
+ }
47
+ // Max retries exhausted
48
+ if (state.attempts >= RECONNECT_MAX_RETRIES) {
49
+ continue;
50
+ }
51
+ // Already reconnecting
52
+ if (reconnecting.has(env.id)) {
53
+ continue;
54
+ }
55
+ // Attempt reconnect (fire-and-forget)
56
+ tryReconnect(env.id).catch((err) => {
57
+ logger.error({ environmentId: env.id, err }, "Unhandled error during auto-reconnect");
58
+ });
59
+ }
60
+ // Clean up state for environments that are no longer disconnected
61
+ for (const [envId] of reconnectStates) {
62
+ const env = environments.find((e) => e.id === envId);
63
+ if (env?.status !== "disconnected") {
64
+ reconnectStates.delete(envId);
65
+ }
66
+ }
67
+ }
68
+ /**
69
+ * Clear reconnect state for an environment. Call when the environment is
70
+ * manually provisioned, removed, or otherwise taken out of the reconnect cycle.
71
+ */
72
+ export function clearReconnectState(environmentId) {
73
+ reconnectStates.delete(environmentId);
74
+ // Note: does not cancel an in-flight tryReconnect — the lock will
75
+ // prevent a new attempt from starting, and the in-flight one will
76
+ // complete or fail on its own.
77
+ }
78
+ /**
79
+ * Reset reconnect state so the environment is immediately eligible on the
80
+ * next heartbeat tick. Unlike {@link clearReconnectState} (which deletes state,
81
+ * causing the next tick to re-initialize with an initial delay), this sets
82
+ * attempts to zero and nextRetryAt to now.
83
+ */
84
+ export function resetReconnectState(environmentId) {
85
+ reconnectStates.set(environmentId, { attempts: 0, nextRetryAt: Date.now() });
86
+ }
87
+ /** @internal Reset all reconnect state for testing. */
88
+ export function _resetForTesting() {
89
+ reconnectStates.clear();
90
+ reconnecting.clear();
91
+ }
92
+ // ─── Internal ───────────────────────────────────────────────
93
+ /**
94
+ * Attempt to reconnect a single disconnected environment.
95
+ * Uses the existing `reconnectOrProvision` flow from adapter-sdk.
96
+ */
97
+ async function tryReconnect(environmentId) {
98
+ reconnecting.add(environmentId);
99
+ try {
100
+ // Re-fetch environment in case it was removed while waiting
101
+ const env = envRegistry.getEnvironment(environmentId);
102
+ if (!env) {
103
+ reconnectStates.delete(environmentId);
104
+ return;
105
+ }
106
+ const adapter = adapterManager.getAdapter(env.adapterType);
107
+ if (!adapter) {
108
+ logger.warn({ environmentId, adapterType: env.adapterType }, "No adapter registered — skipping reconnect");
109
+ return;
110
+ }
111
+ logger.info({ environmentId, adapterType: env.adapterType }, "Attempting auto-reconnect");
112
+ envRegistry.updateEnvironmentStatus(environmentId, "connecting");
113
+ emit("environment.changed", {});
114
+ const config = parseAdapterConfig(env.adapterConfig);
115
+ config.defaultRuntime = env.defaultRuntime;
116
+ const powerlineToken = env.powerlineToken;
117
+ // Run reconnectOrProvision — tries fast reconnect if supported, falls back to provision
118
+ for await (const event of reconnectOrProvision(environmentId, adapter, config, powerlineToken, !!env.bootstrapped)) {
119
+ logger.debug({ environmentId, stage: event.stage, message: event.message }, "Reconnect progress");
120
+ }
121
+ // Establish gRPC connection
122
+ const conn = await adapter.connect(environmentId, config, powerlineToken);
123
+ adapterManager.setConnection(environmentId, conn);
124
+ // Push tokens (local environments exclude file tokens)
125
+ if (env.adapterType === "local") {
126
+ await tokenPush.pushToEnv(environmentId, { excludeFileTokens: true });
127
+ }
128
+ else {
129
+ await tokenPush.pushToEnv(environmentId);
130
+ }
131
+ envRegistry.updateEnvironmentStatus(environmentId, "connected");
132
+ envRegistry.markBootstrapped(environmentId);
133
+ emit("environment.changed", {});
134
+ // Auto-recover suspended sessions (fire-and-forget)
135
+ recoverSuspendedSessions(environmentId, conn).catch((err) => {
136
+ logger.error({ environmentId, err }, "Session recovery failed after auto-reconnect");
137
+ });
138
+ logger.info({ environmentId }, "Auto-reconnect successful");
139
+ reconnectStates.delete(environmentId);
140
+ }
141
+ catch (err) {
142
+ // Clean up any partially-established connection to avoid leaking state
143
+ adapterManager.removeConnection(environmentId);
144
+ const state = reconnectStates.get(environmentId) ?? { attempts: 0, nextRetryAt: 0 };
145
+ state.attempts++;
146
+ if (state.attempts >= RECONNECT_MAX_RETRIES) {
147
+ logger.error({ environmentId, attempts: state.attempts, err }, "Auto-reconnect exhausted all retries — giving up");
148
+ envRegistry.updateEnvironmentStatus(environmentId, "error");
149
+ emit("environment.changed", {});
150
+ }
151
+ else {
152
+ const delay = Math.min(RECONNECT_INITIAL_DELAY_MS * Math.pow(RECONNECT_BACKOFF_MULTIPLIER, state.attempts), RECONNECT_MAX_DELAY_MS);
153
+ state.nextRetryAt = Date.now() + delay;
154
+ reconnectStates.set(environmentId, state);
155
+ logger.info({ environmentId, attempts: state.attempts, nextRetryInMs: delay, err }, "Auto-reconnect failed — will retry");
156
+ envRegistry.updateEnvironmentStatus(environmentId, "disconnected");
157
+ emit("environment.changed", {});
158
+ }
159
+ }
160
+ finally {
161
+ reconnecting.delete(environmentId);
162
+ }
163
+ }
164
+ //# sourceMappingURL=auto-reconnect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-reconnect.js","sourceRoot":"","sources":["../src/auto-reconnect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { SessionRow } from "@grackle-ai/database";
2
+ /** Result of computing a task's effective status from its session history. */
3
+ export interface TaskStatusResult {
4
+ /** The computed effective status string (e.g. "working", "paused", "not_started"). */
5
+ status: string;
6
+ /** The ID of the most recent session (by startedAt), or empty string if none. */
7
+ latestSessionId: string;
8
+ }
9
+ /**
10
+ * Compute the effective task status from the stored DB status and the task's
11
+ * session history. Pure function — no DB access, no side effects.
12
+ *
13
+ * Rules:
14
+ * 1. "complete" is sticky — once a human marks done, always returned as-is.
15
+ * 2. "failed" is sticky when no sessions exist (preserves human-set failure).
16
+ * With active sessions, active status takes precedence.
17
+ * 3. No sessions → "not_started" (clamp any stale transient status).
18
+ * 4. Any active session (pending/running/idle):
19
+ * - Any "idle" → "paused"
20
+ * - Otherwise → "working"
21
+ * 5. All sessions terminal → "paused" (work was done, now stopped)
22
+ *
23
+ * @param storedStatus - The task's status as stored in the DB.
24
+ * @param sessions - All sessions for this task, in any order.
25
+ * @returns Computed status and the ID of the latest session.
26
+ */
27
+ export declare function computeTaskStatus(storedStatus: string, sessions: Pick<SessionRow, "id" | "status" | "startedAt">[]): TaskStatusResult;
28
+ //# sourceMappingURL=compute-task-status.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute-task-status.d.ts","sourceRoot":"","sources":["../src/compute-task-status.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAGvD,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,sFAAsF;IACtF,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,eAAe,EAAE,MAAM,CAAC;CACzB;AASD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,iBAAiB,CAC/B,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,IAAI,CAAC,UAAU,EAAE,IAAI,GAAG,QAAQ,GAAG,WAAW,CAAC,EAAE,GAC1D,gBAAgB,CAsClB"}
@@ -0,0 +1,70 @@
1
+ import { SESSION_STATUS, TASK_STATUS } from "@grackle-ai/common";
2
+ /** Session statuses that indicate the session is actively running. */
3
+ const ACTIVE_SESSION_STATUSES = new Set([
4
+ SESSION_STATUS.PENDING,
5
+ SESSION_STATUS.RUNNING,
6
+ SESSION_STATUS.IDLE,
7
+ ]);
8
+ /**
9
+ * Compute the effective task status from the stored DB status and the task's
10
+ * session history. Pure function — no DB access, no side effects.
11
+ *
12
+ * Rules:
13
+ * 1. "complete" is sticky — once a human marks done, always returned as-is.
14
+ * 2. "failed" is sticky when no sessions exist (preserves human-set failure).
15
+ * With active sessions, active status takes precedence.
16
+ * 3. No sessions → "not_started" (clamp any stale transient status).
17
+ * 4. Any active session (pending/running/idle):
18
+ * - Any "idle" → "paused"
19
+ * - Otherwise → "working"
20
+ * 5. All sessions terminal → "paused" (work was done, now stopped)
21
+ *
22
+ * @param storedStatus - The task's status as stored in the DB.
23
+ * @param sessions - All sessions for this task, in any order.
24
+ * @returns Computed status and the ID of the latest session.
25
+ */
26
+ export function computeTaskStatus(storedStatus, sessions) {
27
+ // "complete" and "failed" are sticky — human-authoritative when no sessions contradict
28
+ if (storedStatus === TASK_STATUS.COMPLETE || storedStatus === TASK_STATUS.FAILED) {
29
+ const latestSessionId = sessions.length > 0
30
+ ? getLatestSession(sessions).id
31
+ : "";
32
+ // If there are active sessions, they take precedence over failed (but not complete)
33
+ if (storedStatus === TASK_STATUS.COMPLETE) {
34
+ return { status: TASK_STATUS.COMPLETE, latestSessionId };
35
+ }
36
+ // For "failed" without sessions, keep it; with active sessions, fall through
37
+ if (sessions.length === 0) {
38
+ return { status: TASK_STATUS.FAILED, latestSessionId: "" };
39
+ }
40
+ }
41
+ // No sessions → not_started (clamp any stale transient status)
42
+ if (sessions.length === 0) {
43
+ return { status: TASK_STATUS.NOT_STARTED, latestSessionId: "" };
44
+ }
45
+ // Check for any active sessions
46
+ const activeSessions = sessions.filter((s) => ACTIVE_SESSION_STATUSES.has(s.status));
47
+ if (activeSessions.length > 0) {
48
+ const hasIdle = activeSessions.some((s) => s.status === SESSION_STATUS.IDLE);
49
+ return {
50
+ status: hasIdle ? TASK_STATUS.PAUSED : TASK_STATUS.WORKING,
51
+ latestSessionId: getLatestSession(sessions).id,
52
+ };
53
+ }
54
+ // All sessions are terminal — task is paused (work was done, now stopped)
55
+ const latest = getLatestSession(sessions);
56
+ return { status: TASK_STATUS.PAUSED, latestSessionId: latest.id };
57
+ }
58
+ /** Get the most recent session by startedAt (descending), breaking ties by ID. */
59
+ function getLatestSession(sessions) {
60
+ return sessions.reduce((latest, current) => {
61
+ if (current.startedAt > latest.startedAt) {
62
+ return current;
63
+ }
64
+ if (current.startedAt === latest.startedAt && current.id > latest.id) {
65
+ return current;
66
+ }
67
+ return latest;
68
+ });
69
+ }
70
+ //# sourceMappingURL=compute-task-status.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compute-task-status.js","sourceRoot":"","sources":["../src/compute-task-status.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAUjE,sEAAsE;AACtE,MAAM,uBAAuB,GAAwB,IAAI,GAAG,CAAC;IAC3D,cAAc,CAAC,OAAO;IACtB,cAAc,CAAC,OAAO;IACtB,cAAc,CAAC,IAAI;CACpB,CAAC,CAAC;AAEH;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,iBAAiB,CAC/B,YAAoB,EACpB,QAA2D;IAE3D,uFAAuF;IACvF,IAAI,YAAY,KAAK,WAAW,CAAC,QAAQ,IAAI,YAAY,KAAK,WAAW,CAAC,MAAM,EAAE,CAAC;QACjF,MAAM,eAAe,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC;YACzC,CAAC,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC,EAAE;YAC/B,CAAC,CAAC,EAAE,CAAC;QACP,oFAAoF;QACpF,IAAI,YAAY,KAAK,WAAW,CAAC,QAAQ,EAAE,CAAC;YAC1C,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,QAAQ,EAAE,eAAe,EAAE,CAAC;QAC3D,CAAC;QACD,6EAA6E;QAC7E,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC;QAC7D,CAAC;IACH,CAAC;IAED,+DAA+D;IAC/D,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,WAAW,EAAE,eAAe,EAAE,EAAE,EAAE,CAAC;IAClE,CAAC;IAED,gCAAgC;IAChC,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAC3C,uBAAuB,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CACtC,CAAC;IAEF,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,cAAc,CAAC,IAAI,CAAC,CAAC;QAC7E,OAAO;YACL,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,CAAC,OAAO;YAC1D,eAAe,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAC,EAAE;SAC/C,CAAC;IACJ,CAAC;IAED,0EAA0E;IAC1E,MAAM,MAAM,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAE1C,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,eAAe,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC;AACpE,CAAC;AAED,kFAAkF;AAClF,SAAS,gBAAgB,CACvB,QAA2D;IAE3D,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE;QACzC,IAAI,OAAO,CAAC,SAAS,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC;YACzC,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,IAAI,OAAO,CAAC,SAAS,KAAK,MAAM,CAAC,SAAS,IAAI,OAAO,CAAC,EAAE,GAAG,MAAM,CAAC,EAAE,EAAE,CAAC;YACrE,OAAO,OAAO,CAAC;QACjB,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { powerline } from "@grackle-ai/common";
2
+ import { type DatabaseInstance } from "@grackle-ai/database";
3
+ /**
4
+ * Build a token bundle containing enabled provider credentials.
5
+ * When `runtime` is a known {@link RuntimeName}, only providers mapped to that runtime are included.
6
+ * When `runtime` is omitted, all enabled providers are included.
7
+ * When `runtime` is provided but not a recognized {@link RuntimeName}, no providers are included
8
+ * (fails safe rather than exposing all credentials for an unrecognized runtime).
9
+ * Reads values fresh from `process.env` or disk at call time.
10
+ */
11
+ export declare function buildProviderTokenBundle(runtime?: string, database?: DatabaseInstance): powerline.TokenBundle;
12
+ //# sourceMappingURL=credential-bundle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-bundle.d.ts","sourceRoot":"","sources":["../src/credential-bundle.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,SAAS,EAAoB,MAAM,oBAAoB,CAAC;AACjE,OAAO,EAAsD,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAejH;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAAC,WAAW,CA0K7G"}