@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
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Builds token bundles from enabled credential providers by reading
3
+ * `process.env` and credential files from disk.
4
+ *
5
+ * Separated from {@link ./credential-providers.ts} (persistence layer) and
6
+ * {@link ./token-push.ts} (network orchestration) to keep each module
7
+ * focused on a single concern.
8
+ */
9
+ import { existsSync, readFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { join } from "node:path";
12
+ import { create } from "@bufbuild/protobuf";
13
+ import { powerline } from "@grackle-ai/common";
14
+ import { credentialProviders } from "@grackle-ai/database";
15
+ /** Maps each runtime to the credential providers it needs. */
16
+ const RUNTIME_PROVIDERS = {
17
+ "claude-code": ["claude", "github"],
18
+ "copilot": ["copilot", "github"],
19
+ "codex": ["codex", "github"],
20
+ "goose": ["goose", "github"],
21
+ "stub": [],
22
+ // ACP runtimes (experimental) — auth via ACP authenticate method, not credential files
23
+ "claude-code-acp": ["claude", "github"],
24
+ "codex-acp": ["codex", "github"],
25
+ "copilot-acp": ["copilot", "github"],
26
+ };
27
+ /**
28
+ * Build a token bundle containing enabled provider credentials.
29
+ * When `runtime` is a known {@link RuntimeName}, only providers mapped to that runtime are included.
30
+ * When `runtime` is omitted, all enabled providers are included.
31
+ * When `runtime` is provided but not a recognized {@link RuntimeName}, no providers are included
32
+ * (fails safe rather than exposing all credentials for an unrecognized runtime).
33
+ * Reads values fresh from `process.env` or disk at call time.
34
+ */
35
+ export function buildProviderTokenBundle(runtime, database) {
36
+ const config = credentialProviders.getCredentialProviders(database);
37
+ // When runtime is given, look it up in the map. Unknown runtimes get [] (empty, not all providers).
38
+ const runtimeProviders = runtime !== undefined
39
+ ? (Object.hasOwn(RUNTIME_PROVIDERS, runtime) ? RUNTIME_PROVIDERS[runtime] : [])
40
+ : undefined;
41
+ const allowedProviders = runtimeProviders !== undefined
42
+ ? new Set(runtimeProviders)
43
+ : undefined;
44
+ const items = [];
45
+ // Claude provider
46
+ if ((!allowedProviders || allowedProviders.has("claude")) && config.claude === "subscription") {
47
+ const credentialsPath = join(homedir(), ".claude", ".credentials.json");
48
+ if (existsSync(credentialsPath)) {
49
+ const value = readFileSync(credentialsPath, "utf-8");
50
+ if (value.trim()) {
51
+ items.push(create(powerline.TokenItemSchema, {
52
+ name: "claude-credentials",
53
+ type: "file",
54
+ filePath: "~/.claude/.credentials.json",
55
+ value,
56
+ }));
57
+ }
58
+ }
59
+ }
60
+ else if ((!allowedProviders || allowedProviders.has("claude")) && config.claude === "api_key") {
61
+ const apiKey = process.env.ANTHROPIC_API_KEY;
62
+ if (apiKey) {
63
+ items.push(create(powerline.TokenItemSchema, {
64
+ name: "anthropic-api-key",
65
+ type: "env_var",
66
+ envVar: "ANTHROPIC_API_KEY",
67
+ value: apiKey,
68
+ }));
69
+ }
70
+ }
71
+ // GitHub provider
72
+ if ((!allowedProviders || allowedProviders.has("github")) && config.github === "on") {
73
+ for (const varName of ["GITHUB_TOKEN", "GH_TOKEN"]) {
74
+ const value = process.env[varName];
75
+ if (value) {
76
+ items.push(create(powerline.TokenItemSchema, {
77
+ name: varName.toLowerCase().replace(/_/g, "-"),
78
+ type: "env_var",
79
+ envVar: varName,
80
+ value,
81
+ }));
82
+ }
83
+ }
84
+ }
85
+ // Copilot provider — push the config file so the SDK's useLoggedInUser path works.
86
+ // Also forward env vars for explicit token / BYOK scenarios.
87
+ if ((!allowedProviders || allowedProviders.has("copilot")) && config.copilot === "on") {
88
+ const copilotConfigPath = join(homedir(), ".copilot", "config.json");
89
+ if (existsSync(copilotConfigPath)) {
90
+ const value = readFileSync(copilotConfigPath, "utf-8");
91
+ if (value.trim()) {
92
+ items.push(create(powerline.TokenItemSchema, {
93
+ name: "copilot-config",
94
+ type: "file",
95
+ filePath: "~/.copilot/config.json",
96
+ value,
97
+ }));
98
+ }
99
+ }
100
+ for (const varName of [
101
+ "COPILOT_GITHUB_TOKEN",
102
+ "COPILOT_CLI_URL",
103
+ "COPILOT_CLI_PATH",
104
+ "COPILOT_PROVIDER_CONFIG",
105
+ ]) {
106
+ const value = process.env[varName];
107
+ if (value) {
108
+ items.push(create(powerline.TokenItemSchema, {
109
+ name: varName.toLowerCase().replace(/_/g, "-"),
110
+ type: "env_var",
111
+ envVar: varName,
112
+ value,
113
+ }));
114
+ }
115
+ }
116
+ }
117
+ // Codex provider — push the auth file so the SDK's ChatGPT auth path works.
118
+ // Also forward OPENAI_API_KEY env var for API-key scenarios.
119
+ if ((!allowedProviders || allowedProviders.has("codex")) && config.codex === "on") {
120
+ const codexAuthPath = join(homedir(), ".codex", "auth.json");
121
+ if (existsSync(codexAuthPath)) {
122
+ const value = readFileSync(codexAuthPath, "utf-8");
123
+ if (value.trim()) {
124
+ items.push(create(powerline.TokenItemSchema, {
125
+ name: "codex-auth",
126
+ type: "file",
127
+ filePath: "~/.codex/auth.json",
128
+ value,
129
+ }));
130
+ }
131
+ }
132
+ const openaiKey = process.env.OPENAI_API_KEY;
133
+ if (openaiKey) {
134
+ items.push(create(powerline.TokenItemSchema, {
135
+ name: "openai-api-key",
136
+ type: "env_var",
137
+ envVar: "OPENAI_API_KEY",
138
+ value: openaiKey,
139
+ }));
140
+ }
141
+ }
142
+ // Goose provider — forward config file and provider-related env vars.
143
+ // Goose is provider-agnostic so we forward whichever API keys are available.
144
+ if ((!allowedProviders || allowedProviders.has("goose")) && config.goose === "on") {
145
+ const isWindows = process.platform === "win32";
146
+ const gooseConfigPath = isWindows
147
+ ? join(process.env.APPDATA || join(homedir(), "AppData", "Roaming"), "Block", "goose", "config", "config.yaml")
148
+ : join(homedir(), ".config", "goose", "config.yaml");
149
+ const gooseConfigFilePath = isWindows
150
+ ? "%APPDATA%/Block/goose/config/config.yaml"
151
+ : "~/.config/goose/config.yaml";
152
+ if (existsSync(gooseConfigPath)) {
153
+ const value = readFileSync(gooseConfigPath, "utf-8");
154
+ if (value.trim()) {
155
+ items.push(create(powerline.TokenItemSchema, {
156
+ name: "goose-config",
157
+ type: "file",
158
+ filePath: gooseConfigFilePath,
159
+ value,
160
+ }));
161
+ }
162
+ }
163
+ for (const varName of [
164
+ "GOOSE_PROVIDER",
165
+ "GOOSE_MODEL",
166
+ "ANTHROPIC_API_KEY",
167
+ "OPENAI_API_KEY",
168
+ "GOOGLE_API_KEY",
169
+ ]) {
170
+ const value = process.env[varName];
171
+ if (value) {
172
+ items.push(create(powerline.TokenItemSchema, {
173
+ name: varName.toLowerCase().replace(/_/g, "-"),
174
+ type: "env_var",
175
+ envVar: varName,
176
+ value,
177
+ }));
178
+ }
179
+ }
180
+ }
181
+ return create(powerline.TokenBundleSchema, { tokens: items });
182
+ }
183
+ //# sourceMappingURL=credential-bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"credential-bundle.js","sourceRoot":"","sources":["../src/credential-bundle.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,37 @@
1
+ /** All domain event types emitted by the event bus. */
2
+ export type GrackleEventType = "task.created" | "task.updated" | "task.started" | "task.completed" | "task.deleted" | "workspace.created" | "workspace.archived" | "workspace.updated" | "persona.created" | "persona.updated" | "persona.deleted" | "finding.posted" | "environment.added" | "environment.removed" | "environment.changed" | "environment.provision_progress" | "token.changed" | "credential.providers_changed" | "setting.changed";
3
+ /** A domain event emitted by the event bus. */
4
+ export interface GrackleEvent {
5
+ /** ULID — chronologically sortable unique identifier. */
6
+ id: string;
7
+ /** Dot-notation event type (e.g. "task.created"). */
8
+ type: GrackleEventType;
9
+ /** ISO 8601 timestamp. */
10
+ timestamp: string;
11
+ /** Domain-specific payload. */
12
+ payload: Record<string, unknown>;
13
+ }
14
+ /** Callback signature for event subscribers. */
15
+ export type Subscriber = (event: GrackleEvent) => void;
16
+ /**
17
+ * Emit a domain event. Persists to SQLite synchronously, then
18
+ * fans out to all subscribers asynchronously via queueMicrotask.
19
+ *
20
+ * @param type - The dot-notation event type.
21
+ * @param payload - Domain-specific data.
22
+ * @returns The created GrackleEvent.
23
+ */
24
+ export declare function emit(type: GrackleEventType, payload: Record<string, unknown>): GrackleEvent;
25
+ /**
26
+ * Register a subscriber to receive all domain events.
27
+ *
28
+ * @param subscriber - Callback invoked for each emitted event.
29
+ * @returns An unsubscribe function.
30
+ */
31
+ export declare function subscribe(subscriber: Subscriber): () => void;
32
+ /**
33
+ * Reset all subscribers. For use in tests only.
34
+ * @internal
35
+ */
36
+ export declare function _resetForTesting(): void;
37
+ //# sourceMappingURL=event-bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-bus.d.ts","sourceRoot":"","sources":["../src/event-bus.ts"],"names":[],"mappings":"AAMA,uDAAuD;AACvD,MAAM,MAAM,gBAAgB,GACxB,cAAc,GACd,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,cAAc,GACd,mBAAmB,GACnB,oBAAoB,GACpB,mBAAmB,GACnB,iBAAiB,GACjB,iBAAiB,GACjB,iBAAiB,GACjB,gBAAgB,GAChB,mBAAmB,GACnB,qBAAqB,GACrB,qBAAqB,GACrB,gCAAgC,GAChC,eAAe,GACf,8BAA8B,GAC9B,iBAAiB,CAAC;AAEtB,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,EAAE,EAAE,MAAM,CAAC;IACX,qDAAqD;IACrD,IAAI,EAAE,gBAAgB,CAAC;IACvB,0BAA0B;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAClC;AAED,gDAAgD;AAChD,MAAM,MAAM,UAAU,GAAG,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;AAQvD;;;;;;;GAOG;AACH,wBAAgB,IAAI,CAClB,IAAI,EAAE,gBAAgB,EACtB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,YAAY,CA+Bd;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,IAAI,CAK5D;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
@@ -0,0 +1,65 @@
1
+ import { ulid } from "ulid";
2
+ import { persistEvent } from "@grackle-ai/database";
3
+ import { logger } from "./logger.js";
4
+ // ─── Module State ─────────────────────────────────────────
5
+ const subscribers = new Set();
6
+ // ─── Public API ───────────────────────────────────────────
7
+ /**
8
+ * Emit a domain event. Persists to SQLite synchronously, then
9
+ * fans out to all subscribers asynchronously via queueMicrotask.
10
+ *
11
+ * @param type - The dot-notation event type.
12
+ * @param payload - Domain-specific data.
13
+ * @returns The created GrackleEvent.
14
+ */
15
+ export function emit(type, payload) {
16
+ const event = {
17
+ id: ulid(),
18
+ type,
19
+ timestamp: new Date().toISOString(),
20
+ payload,
21
+ };
22
+ // Persist synchronously (SQLite is fast in WAL mode).
23
+ // Intentionally non-fatal: a persistence failure is logged but does not
24
+ // prevent subscribers from receiving the event. Domain events drive live
25
+ // UI updates which must not break if SQLite is temporarily unavailable.
26
+ // Replay/audit consumers should monitor logs for persistence errors.
27
+ try {
28
+ persistEvent(event);
29
+ }
30
+ catch (err) {
31
+ logger.error({ err, event }, "Failed to persist domain event");
32
+ }
33
+ // Fan out asynchronously — subscriber errors never block the emitter
34
+ queueMicrotask(() => {
35
+ for (const subscriber of subscribers) {
36
+ try {
37
+ subscriber(event);
38
+ }
39
+ catch (err) {
40
+ logger.error({ err, eventType: event.type }, "Subscriber error");
41
+ }
42
+ }
43
+ });
44
+ return event;
45
+ }
46
+ /**
47
+ * Register a subscriber to receive all domain events.
48
+ *
49
+ * @param subscriber - Callback invoked for each emitted event.
50
+ * @returns An unsubscribe function.
51
+ */
52
+ export function subscribe(subscriber) {
53
+ subscribers.add(subscriber);
54
+ return () => {
55
+ subscribers.delete(subscriber);
56
+ };
57
+ }
58
+ /**
59
+ * Reset all subscribers. For use in tests only.
60
+ * @internal
61
+ */
62
+ export function _resetForTesting() {
63
+ subscribers.clear();
64
+ }
65
+ //# sourceMappingURL=event-bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-bus.js","sourceRoot":"","sources":["../src/event-bus.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAyCrC,6DAA6D;AAE7D,MAAM,WAAW,GAAoB,IAAI,GAAG,EAAE,CAAC;AAE/C,6DAA6D;AAE7D;;;;;;;GAOG;AACH,MAAM,UAAU,IAAI,CAClB,IAAsB,EACtB,OAAgC;IAEhC,MAAM,KAAK,GAAiB;QAC1B,EAAE,EAAE,IAAI,EAAE;QACV,IAAI;QACJ,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,OAAO;KACR,CAAC;IAEF,sDAAsD;IACtD,wEAAwE;IACxE,yEAAyE;IACzE,wEAAwE;IACxE,qEAAqE;IACrE,IAAI,CAAC;QACH,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,gCAAgC,CAAC,CAAC;IACjE,CAAC;IAED,qEAAqE;IACrE,cAAc,CAAC,GAAG,EAAE;QAClB,KAAK,MAAM,UAAU,IAAI,WAAW,EAAE,CAAC;YACrC,IAAI,CAAC;gBACH,UAAU,CAAC,KAAK,CAAC,CAAC;YACpB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,kBAAkB,CAAC,CAAC;YACnE,CAAC;QACH,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,UAAsB;IAC9C,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAC5B,OAAO,GAAG,EAAE;QACV,WAAW,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACjC,CAAC,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB;IAC9B,WAAW,CAAC,KAAK,EAAE,CAAC;AACtB,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { powerline } from "@grackle-ai/common";
2
+ import type { ProcessorContext } from "./processor-registry.js";
3
+ /** Options for processing an agent event stream. */
4
+ export interface EventStreamOptions {
5
+ sessionId: string;
6
+ logPath: string;
7
+ workspaceId?: string;
8
+ taskId?: string;
9
+ /** System context injected into the agent session. Emitted as the first event in the stream. */
10
+ systemContext?: string;
11
+ /** Initial user prompt sent to the agent. Emitted as a user_input event after systemContext. */
12
+ prompt?: string;
13
+ }
14
+ /**
15
+ * Process a finding event, storing it in the finding store and broadcasting.
16
+ * Shared between live event processing and replay on late-bind.
17
+ */
18
+ export declare function processFindingEvent(ctx: ProcessorContext, content: string, sessionId: string): void;
19
+ /**
20
+ * Process a subtask creation event, creating a child task and broadcasting.
21
+ * Shared between live event processing and replay on late-bind.
22
+ */
23
+ export declare function processSubtaskEvent(ctx: ProcessorContext, content: string, subtaskLocalIdMap: Map<string, string>): void;
24
+ /**
25
+ * Process an async iterable of agent events from a PowerLine spawn or resume stream.
26
+ * Handles event transformation, logging, finding interception, status updates, and cleanup.
27
+ *
28
+ * This function is fire-and-forget: it runs in the background and does not throw.
29
+ * Callers should use `onComplete` callback for post-processing.
30
+ *
31
+ * Supports late-binding: if a task is associated with the session after the stream starts,
32
+ * the processor registry notifies this function via a bind listener, and pre-association
33
+ * events are replayed from the session log.
34
+ */
35
+ export declare function processEventStream(events: AsyncIterable<powerline.AgentEvent>, options: EventStreamOptions): void;
36
+ //# sourceMappingURL=event-processor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-processor.d.ts","sourceRoot":"","sources":["../src/event-processor.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,SAAS,EAA0E,MAAM,oBAAoB,CAAC;AAYhI,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAEhE,oDAAoD;AACpD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gGAAgG;IAChG,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,gBAAgB,EACrB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,GAChB,IAAI,CAmBN;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,gBAAgB,EACrB,OAAO,EAAE,MAAM,EACf,iBAAiB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GACrC,IAAI,CA6FN;AAsCD;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,CAAC,SAAS,CAAC,UAAU,CAAC,EAC3C,OAAO,EAAE,kBAAkB,GAC1B,IAAI,CA+KN"}
@@ -0,0 +1,312 @@
1
+ import { create } from "@bufbuild/protobuf";
2
+ import { grackle, eventTypeToEnum, SESSION_STATUS, TERMINAL_SESSION_STATUSES, END_REASON } from "@grackle-ai/common";
3
+ import { v4 as uuid } from "uuid";
4
+ import { sessionStore, findingStore, taskStore, workspaceStore, slugify } from "@grackle-ai/database";
5
+ import * as streamHub from "./stream-hub.js";
6
+ import * as logWriter from "./log-writer.js";
7
+ import * as processorRegistry from "./processor-registry.js";
8
+ import { writeTranscript } from "./transcript.js";
9
+ import { emit } from "./event-bus.js";
10
+ import { logger } from "./logger.js";
11
+ import { publishChildCompletion } from "./pipe-delivery.js";
12
+ import { cleanupLifecycleStream } from "./lifecycle.js";
13
+ /**
14
+ * Process a finding event, storing it in the finding store and broadcasting.
15
+ * Shared between live event processing and replay on late-bind.
16
+ */
17
+ export function processFindingEvent(ctx, content, sessionId) {
18
+ if (!ctx.workspaceId) {
19
+ return;
20
+ }
21
+ try {
22
+ const data = JSON.parse(content);
23
+ const findingId = uuid();
24
+ findingStore.postFinding(findingId, ctx.workspaceId, ctx.taskId || "", sessionId, data.category || "general", data.title || "Untitled", data.content || "", data.tags || []);
25
+ emit("finding.posted", { workspaceId: ctx.workspaceId, findingId });
26
+ logger.info({ findingId, workspaceId: ctx.workspaceId, title: data.title }, "Finding stored");
27
+ }
28
+ catch (err) {
29
+ logger.error({ err, workspaceId: ctx.workspaceId, taskId: ctx.taskId }, "Failed to store finding");
30
+ }
31
+ }
32
+ /**
33
+ * Process a subtask creation event, creating a child task and broadcasting.
34
+ * Shared between live event processing and replay on late-bind.
35
+ */
36
+ export function processSubtaskEvent(ctx, content, subtaskLocalIdMap) {
37
+ if (!ctx.taskId) {
38
+ return;
39
+ }
40
+ try {
41
+ const data = JSON.parse(content);
42
+ const parentTask = taskStore.getTask(ctx.taskId);
43
+ if (!parentTask) {
44
+ logger.warn({ taskId: ctx.taskId }, "Subtask creation failed: parent task not found");
45
+ return;
46
+ }
47
+ if (!parentTask.canDecompose) {
48
+ logger.warn({ taskId: ctx.taskId }, "Subtask creation failed: parent task cannot decompose");
49
+ return;
50
+ }
51
+ const workspace = parentTask.workspaceId ? workspaceStore.getWorkspace(parentTask.workspaceId) : undefined;
52
+ if (parentTask.workspaceId && !workspace) {
53
+ logger.warn({ workspaceId: parentTask.workspaceId }, "Subtask creation failed: workspace not found");
54
+ return;
55
+ }
56
+ // Validate required fields
57
+ const title = typeof data.title === "string" ? data.title.trim() : "";
58
+ const description = typeof data.description === "string" ? data.description.trim() : "";
59
+ if (!title || !description) {
60
+ logger.warn({ taskId: ctx.taskId, rawTitle: data.title, rawDescription: data.description }, "Subtask creation failed: invalid title or description");
61
+ return;
62
+ }
63
+ // Normalize and validate depends_on, local_id, and can_decompose
64
+ const dependsOn = Array.isArray(data.depends_on)
65
+ ? data.depends_on.filter((d) => typeof d === "string").map(d => d.trim()).filter(Boolean)
66
+ : [];
67
+ const localId = typeof data.local_id === "string" ? data.local_id.trim() : "";
68
+ const canDecompose = typeof data.can_decompose === "boolean" ? data.can_decompose : false;
69
+ // Resolve depends_on local IDs to real task IDs
70
+ const resolvedDeps = [];
71
+ for (const localDep of dependsOn) {
72
+ const realId = subtaskLocalIdMap.get(localDep);
73
+ if (realId) {
74
+ resolvedDeps.push(realId);
75
+ }
76
+ else {
77
+ logger.warn({ localDep, taskId: ctx.taskId }, "Subtask dependency local_id not found, skipping");
78
+ }
79
+ }
80
+ const subtaskId = uuid().slice(0, 8);
81
+ taskStore.createTask(subtaskId, parentTask.workspaceId || undefined, title, description, resolvedDeps, workspace ? slugify(workspace.name) : "", ctx.taskId, canDecompose);
82
+ // Record the local_id → real ID mapping, detecting duplicates
83
+ if (localId) {
84
+ if (subtaskLocalIdMap.has(localId)) {
85
+ logger.warn({
86
+ localId,
87
+ existingSubtaskId: subtaskLocalIdMap.get(localId),
88
+ newSubtaskId: subtaskId,
89
+ parentTaskId: ctx.taskId,
90
+ }, "Duplicate subtask local_id encountered; keeping existing mapping");
91
+ }
92
+ else {
93
+ subtaskLocalIdMap.set(localId, subtaskId);
94
+ }
95
+ }
96
+ emit("task.created", { taskId: subtaskId, workspaceId: parentTask.workspaceId ?? undefined });
97
+ logger.info({ subtaskId, parentTaskId: ctx.taskId, title }, "Subtask created");
98
+ }
99
+ catch (err) {
100
+ logger.error({ err, taskId: ctx.taskId }, "Failed to create subtask");
101
+ }
102
+ }
103
+ /**
104
+ * Replay pre-association events from the session log through finding/subtask interceptors.
105
+ * Called when a session is late-bound to a task. Does not re-publish to streamHub.
106
+ *
107
+ * Note: Uses synchronous readFileSync while the log is written via a buffered WriteStream.
108
+ * Events written very recently may still be in the write buffer and not yet flushed to disk.
109
+ * In practice this is negligible since replay targets events written before the current
110
+ * iteration of the for-await loop, which are already flushed by the time lateBind is called.
111
+ */
112
+ function replayLoggedEvents(ctx, subtaskLocalIdMap) {
113
+ try {
114
+ const entries = logWriter.readLog(ctx.logPath);
115
+ let findingsReplayed = 0;
116
+ let subtasksReplayed = 0;
117
+ for (const entry of entries) {
118
+ if (entry.type === "finding") {
119
+ processFindingEvent(ctx, entry.content, entry.session_id);
120
+ findingsReplayed++;
121
+ }
122
+ else if (entry.type === "subtask_create") {
123
+ processSubtaskEvent(ctx, entry.content, subtaskLocalIdMap);
124
+ subtasksReplayed++;
125
+ }
126
+ }
127
+ if (findingsReplayed > 0 || subtasksReplayed > 0) {
128
+ logger.info({ sessionId: ctx.sessionId, taskId: ctx.taskId, findingsReplayed, subtasksReplayed }, "Replayed pre-association events from session log");
129
+ }
130
+ }
131
+ catch (err) {
132
+ logger.error({ err, sessionId: ctx.sessionId }, "Failed to replay logged events");
133
+ }
134
+ }
135
+ /**
136
+ * Process an async iterable of agent events from a PowerLine spawn or resume stream.
137
+ * Handles event transformation, logging, finding interception, status updates, and cleanup.
138
+ *
139
+ * This function is fire-and-forget: it runs in the background and does not throw.
140
+ * Callers should use `onComplete` callback for post-processing.
141
+ *
142
+ * Supports late-binding: if a task is associated with the session after the stream starts,
143
+ * the processor registry notifies this function via a bind listener, and pre-association
144
+ * events are replayed from the session log.
145
+ */
146
+ export function processEventStream(events, options) {
147
+ const { sessionId, logPath } = options;
148
+ // Create a mutable context that can be updated via the processor registry
149
+ const ctx = {
150
+ sessionId,
151
+ logPath,
152
+ workspaceId: options.workspaceId || "",
153
+ taskId: options.taskId || "",
154
+ };
155
+ /** Maps local_id strings (assigned by the agent) to real task IDs, scoped to this stream. */
156
+ const subtaskLocalIdMap = new Map();
157
+ processorRegistry.register(ctx);
158
+ // Register the bind listener synchronously alongside register() to close the race
159
+ // window where lateBind() could fire between register and the async IIFE starting.
160
+ processorRegistry.onBind(sessionId, () => {
161
+ replayLoggedEvents(ctx, subtaskLocalIdMap);
162
+ });
163
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
164
+ (async () => {
165
+ try {
166
+ logWriter.initLog(logPath);
167
+ sessionStore.updateSessionStatus(sessionId, SESSION_STATUS.RUNNING);
168
+ // Emit system context and initial prompt as the first visible events in the stream.
169
+ // Only for task sessions — ad-hoc spawns show the prompt in the chat input already.
170
+ // Use distinct timestamps so clients can reliably sort/dedup by timestamp+eventType.
171
+ if (options.systemContext && options.taskId) {
172
+ const sysCtxEvent = create(grackle.SessionEventSchema, {
173
+ sessionId,
174
+ type: grackle.EventType.SYSTEM,
175
+ timestamp: new Date().toISOString(),
176
+ content: options.systemContext,
177
+ raw: JSON.stringify({ systemContext: true }),
178
+ });
179
+ logWriter.writeEvent(logPath, sysCtxEvent);
180
+ streamHub.publish(sysCtxEvent);
181
+ }
182
+ if (options.prompt && options.taskId) {
183
+ const promptEvent = create(grackle.SessionEventSchema, {
184
+ sessionId,
185
+ type: grackle.EventType.USER_INPUT,
186
+ timestamp: new Date().toISOString(),
187
+ content: options.prompt,
188
+ });
189
+ logWriter.writeEvent(logPath, promptEvent);
190
+ streamHub.publish(promptEvent);
191
+ }
192
+ for await (const event of events) {
193
+ // runtime_session_id is an internal control event: persist it then skip
194
+ // logging/publishing — it has no proto enum value and is not client-visible.
195
+ if (event.type === "runtime_session_id") {
196
+ if (event.content) {
197
+ sessionStore.updateRuntimeSessionId(sessionId, event.content);
198
+ }
199
+ continue;
200
+ }
201
+ const sessionEvent = create(grackle.SessionEventSchema, {
202
+ sessionId,
203
+ type: eventTypeToEnum(event.type),
204
+ timestamp: event.timestamp,
205
+ content: event.content,
206
+ raw: event.raw,
207
+ });
208
+ logWriter.writeEvent(logPath, sessionEvent);
209
+ streamHub.publish(sessionEvent);
210
+ // Intercept finding events and store + broadcast them
211
+ if (event.type === "finding" && ctx.workspaceId) {
212
+ processFindingEvent(ctx, event.content, sessionId);
213
+ }
214
+ // Intercept subtask creation events and create child tasks
215
+ if (event.type === "subtask_create" && ctx.taskId) {
216
+ processSubtaskEvent(ctx, event.content, subtaskLocalIdMap);
217
+ }
218
+ // Intercept usage events and accumulate token counts on the session record
219
+ if (event.type === "usage") {
220
+ try {
221
+ const data = JSON.parse(event.content);
222
+ const inputTokens = Number.isFinite(data.input_tokens)
223
+ ? Math.max(0, Math.trunc(data.input_tokens))
224
+ : 0;
225
+ const outputTokens = Number.isFinite(data.output_tokens)
226
+ ? Math.max(0, Math.trunc(data.output_tokens))
227
+ : 0;
228
+ const costUsd = Number.isFinite(data.cost_usd)
229
+ ? Math.max(0, data.cost_usd)
230
+ : 0;
231
+ if (inputTokens > 0 || outputTokens > 0 || costUsd > 0) {
232
+ sessionStore.updateSessionUsage(sessionId, inputTokens, outputTokens, costUsd);
233
+ }
234
+ }
235
+ catch (err) {
236
+ logger.error({ err, sessionId }, "Failed to process usage event");
237
+ }
238
+ }
239
+ if (event.type === "status") {
240
+ // Map runtime status strings to our session status model
241
+ if (event.content === "waiting_input") {
242
+ sessionStore.updateSessionStatus(sessionId, SESSION_STATUS.IDLE);
243
+ }
244
+ else if (event.content === "running") {
245
+ sessionStore.updateSessionStatus(sessionId, SESSION_STATUS.RUNNING);
246
+ }
247
+ else if (event.content === "completed") {
248
+ sessionStore.updateSession(sessionId, SESSION_STATUS.STOPPED, undefined, undefined, END_REASON.COMPLETED);
249
+ }
250
+ else if (event.content === "killed") {
251
+ sessionStore.updateSession(sessionId, SESSION_STATUS.STOPPED, undefined, undefined, END_REASON.KILLED);
252
+ }
253
+ else if (event.content === "failed") {
254
+ sessionStore.updateSession(sessionId, SESSION_STATUS.STOPPED, undefined, undefined, END_REASON.INTERRUPTED);
255
+ cleanupLifecycleStream(sessionId);
256
+ }
257
+ // On terminal status: publish child completion to IPC pipe stream.
258
+ // Note: lifecycle streams are NOT cleaned up here — they persist until
259
+ // the spawner explicitly closes the fd (killAgent, closeFd). This is the
260
+ // emergent lifecycle model: fd closure drives hibernation, not status events.
261
+ if (["completed", "killed", "failed"].includes(event.content)) {
262
+ publishChildCompletion(sessionId, event.content);
263
+ }
264
+ // Broadcast task_updated on status changes so frontend re-fetches computed status.
265
+ // This covers both terminal events (completed/killed/failed) and non-terminal
266
+ // transitions (running, waiting_input) that affect the computed task status.
267
+ if (ctx.taskId && ["completed", "killed", "failed", "running", "waiting_input"].includes(event.content)) {
268
+ emit("task.updated", { taskId: ctx.taskId, workspaceId: ctx.workspaceId });
269
+ }
270
+ }
271
+ }
272
+ // Fallback: if stream ended without a terminal status event, emit a UI refresh
273
+ // without changing status. Guard against overwriting terminal or SUSPENDED states.
274
+ const current = sessionStore.getSession(sessionId);
275
+ if (current && !TERMINAL_SESSION_STATUSES.has(current.status) && current.status !== SESSION_STATUS.SUSPENDED) {
276
+ if (ctx.taskId) {
277
+ emit("task.updated", { taskId: ctx.taskId, workspaceId: ctx.workspaceId });
278
+ }
279
+ }
280
+ }
281
+ catch (err) {
282
+ const current = sessionStore.getSession(sessionId);
283
+ if (current && !TERMINAL_SESSION_STATUSES.has(current.status)) {
284
+ // Transport error during active or idle session — suspend for auto-recovery
285
+ // on reconnect. Don't publish child completion (session will resume).
286
+ logger.info({ sessionId, err: String(err) }, "Stream lost — suspending session for recovery");
287
+ sessionStore.suspendSession(sessionId);
288
+ streamHub.publish(create(grackle.SessionEventSchema, {
289
+ sessionId,
290
+ type: grackle.EventType.STATUS,
291
+ timestamp: new Date().toISOString(),
292
+ content: SESSION_STATUS.SUSPENDED,
293
+ }));
294
+ if (ctx.taskId) {
295
+ emit("task.updated", { taskId: ctx.taskId, workspaceId: ctx.workspaceId });
296
+ }
297
+ }
298
+ // If already terminal (killAgent/completed/failed set status before transport
299
+ // died), the session is in its correct final state and task.updated was already
300
+ // emitted — skip the duplicate to avoid interfering with SIGCHLD delivery.
301
+ }
302
+ finally {
303
+ processorRegistry.unregister(sessionId);
304
+ logWriter.endSession(logPath);
305
+ try {
306
+ writeTranscript(logPath);
307
+ }
308
+ catch { /* non-critical */ }
309
+ }
310
+ })();
311
+ }
312
+ //# sourceMappingURL=event-processor.js.map