@grackle-ai/server 0.40.0 → 0.41.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/db.d.ts.map +1 -1
  2. package/dist/db.js +10 -0
  3. package/dist/db.js.map +1 -1
  4. package/dist/event-bus.d.ts +37 -0
  5. package/dist/event-bus.d.ts.map +1 -0
  6. package/dist/event-bus.js +65 -0
  7. package/dist/event-bus.js.map +1 -0
  8. package/dist/event-processor.d.ts.map +1 -1
  9. package/dist/event-processor.js +14 -11
  10. package/dist/event-processor.js.map +1 -1
  11. package/dist/event-store.d.ts +9 -0
  12. package/dist/event-store.d.ts.map +1 -0
  13. package/dist/event-store.js +16 -0
  14. package/dist/event-store.js.map +1 -0
  15. package/dist/github-import.js +3 -5
  16. package/dist/github-import.js.map +1 -1
  17. package/dist/grpc-service.d.ts.map +1 -1
  18. package/dist/grpc-service.js +28 -84
  19. package/dist/grpc-service.js.map +1 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +9 -6
  22. package/dist/index.js.map +1 -1
  23. package/dist/reanimate-agent.d.ts +12 -0
  24. package/dist/reanimate-agent.d.ts.map +1 -0
  25. package/dist/reanimate-agent.js +78 -0
  26. package/dist/reanimate-agent.js.map +1 -0
  27. package/dist/schema.d.ts +85 -0
  28. package/dist/schema.d.ts.map +1 -1
  29. package/dist/schema.js +7 -0
  30. package/dist/schema.js.map +1 -1
  31. package/dist/session-store.d.ts +6 -1
  32. package/dist/session-store.d.ts.map +1 -1
  33. package/dist/session-store.js +23 -5
  34. package/dist/session-store.js.map +1 -1
  35. package/dist/ws-bridge.d.ts.map +1 -1
  36. package/dist/ws-bridge.js +81 -121
  37. package/dist/ws-bridge.js.map +1 -1
  38. package/dist/ws-broadcast.d.ts +5 -0
  39. package/dist/ws-broadcast.d.ts.map +1 -1
  40. package/dist/ws-broadcast.js +24 -0
  41. package/dist/ws-broadcast.js.map +1 -1
  42. package/package.json +7 -6
package/dist/ws-bridge.js CHANGED
@@ -26,8 +26,11 @@ import { buildTaskSystemContext } from "./utils/system-context.js";
26
26
  import { slugify } from "./utils/slugify.js";
27
27
  import { processEventStream } from "./event-processor.js";
28
28
  import * as processorRegistry from "./processor-registry.js";
29
- import { broadcast, setWssInstance, broadcastEnvironments, envRowToWs } from "./ws-broadcast.js";
29
+ import { setWssInstance, envRowToWs } from "./ws-broadcast.js";
30
+ import { emit } from "./event-bus.js";
30
31
  import { buildMcpServersJson } from "./grpc-service.js";
32
+ import { reanimateAgent } from "./reanimate-agent.js";
33
+ import { ConnectError } from "@connectrpc/connect";
31
34
  import { computeTaskStatus } from "./compute-task-status.js";
32
35
  import { exec } from "./utils/exec.js";
33
36
  import { formatGhError } from "./utils/format-gh-error.js";
@@ -111,20 +114,17 @@ async function autoProvisionEnvironment(ws, environmentId, env, logContext) {
111
114
  }
112
115
  logger.info({ environmentId, ...logContext }, "Auto-provisioning environment");
113
116
  envRegistry.updateEnvironmentStatus(environmentId, "connecting");
114
- broadcastEnvironments();
117
+ emit("environment.changed", {});
115
118
  try {
116
119
  const config = safeParseAdapterConfig(env.adapterConfig, environmentId);
117
120
  const powerlineToken = env.powerlineToken || "";
118
121
  for await (const provEvent of reconnectOrProvision(environmentId, adapter, config, powerlineToken, !!env.bootstrapped)) {
119
122
  logger.info({ environmentId, stage: provEvent.stage, ...logContext }, "Auto-provision progress");
120
- broadcast({
121
- type: "provision_progress",
122
- payload: {
123
- environmentId,
124
- stage: provEvent.stage,
125
- message: provEvent.message,
126
- progress: provEvent.progress,
127
- },
123
+ emit("environment.provision_progress", {
124
+ environmentId,
125
+ stage: provEvent.stage,
126
+ message: provEvent.message,
127
+ progress: provEvent.progress,
128
128
  });
129
129
  }
130
130
  conn = await adapter.connect(environmentId, config, powerlineToken);
@@ -133,32 +133,26 @@ async function autoProvisionEnvironment(ws, environmentId, env, logContext) {
133
133
  await tokenBroker.pushToEnv(environmentId);
134
134
  envRegistry.updateEnvironmentStatus(environmentId, "connected");
135
135
  envRegistry.markBootstrapped(environmentId);
136
- broadcastEnvironments();
136
+ emit("environment.changed", {});
137
137
  logger.info({ environmentId, ...logContext }, "Auto-provision complete");
138
- broadcast({
139
- type: "provision_progress",
140
- payload: {
141
- environmentId,
142
- stage: "ready",
143
- message: "Environment connected",
144
- progress: 1,
145
- },
138
+ emit("environment.provision_progress", {
139
+ environmentId,
140
+ stage: "ready",
141
+ message: "Environment connected",
142
+ progress: 1,
146
143
  });
147
144
  return conn;
148
145
  }
149
146
  catch (err) {
150
147
  logger.error({ environmentId, ...logContext, err }, "Auto-provision failed");
151
148
  envRegistry.updateEnvironmentStatus(environmentId, "error");
152
- broadcastEnvironments();
149
+ emit("environment.changed", {});
153
150
  const errorMessage = err instanceof Error ? err.message : String(err);
154
- broadcast({
155
- type: "provision_progress",
156
- payload: {
157
- environmentId,
158
- stage: "error",
159
- message: `Auto-provision failed: ${errorMessage}`,
160
- progress: 0,
161
- },
151
+ emit("environment.provision_progress", {
152
+ environmentId,
153
+ stage: "error",
154
+ message: `Auto-provision failed: ${errorMessage}`,
155
+ progress: 0,
162
156
  });
163
157
  sendWs(ws, {
164
158
  type: "error",
@@ -212,13 +206,10 @@ async function startTaskSession(ws, task, options) {
212
206
  systemContext = systemPrompt + "\n\n" + systemContext;
213
207
  }
214
208
  sessionStore.createSession(sessionId, environmentId, runtime, freshTask.title, model, logPath, freshTask.id, resolved.personaId);
215
- broadcast({
216
- type: "task_started",
217
- payload: {
218
- taskId: freshTask.id,
219
- sessionId,
220
- projectId: freshTask.projectId,
221
- },
209
+ emit("task.started", {
210
+ taskId: freshTask.id,
211
+ sessionId,
212
+ projectId: freshTask.projectId,
222
213
  });
223
214
  // Re-push stored tokens + provider credentials (scoped to runtime) so they're fresh for this session.
224
215
  // For local envs, skip file tokens — the PowerLine is on the same machine.
@@ -529,11 +520,27 @@ async function handleMessage(ws, msg, subscriptions) {
529
520
  if (session.taskId) {
530
521
  const task = taskStore.getTask(session.taskId);
531
522
  if (task) {
532
- broadcast({ type: "task_updated", payload: { taskId: task.id, projectId: task.projectId } });
523
+ emit("task.updated", { taskId: task.id, projectId: task.projectId });
533
524
  }
534
525
  }
535
526
  break;
536
527
  }
528
+ case "resume_agent": {
529
+ const resumeSessionId = msg.payload?.sessionId;
530
+ if (!resumeSessionId) {
531
+ sendWs(ws, { type: "error", payload: { message: "sessionId required" } });
532
+ return;
533
+ }
534
+ try {
535
+ reanimateAgent(resumeSessionId);
536
+ sendWs(ws, { type: "agent_resumed", payload: { sessionId: resumeSessionId } });
537
+ }
538
+ catch (err) {
539
+ const message = err instanceof ConnectError ? err.message : String(err);
540
+ sendWs(ws, { type: "error", payload: { message } });
541
+ }
542
+ break;
543
+ }
537
544
  // ─── Projects ──────────────────────────────────────────
538
545
  case "list_projects": {
539
546
  const rows = projectStore.listProjects();
@@ -574,15 +581,14 @@ async function handleMessage(ws, msg, subscriptions) {
574
581
  // useWorktrees defaults to true when not specified
575
582
  const createUseWorktrees = msg.payload?.useWorktrees ?? true;
576
583
  projectStore.createProject(id, name, msg.payload?.description || "", msg.payload?.repoUrl || "", msg.payload?.defaultEnvironmentId || "", createUseWorktrees, typeof msg.payload?.worktreeBasePath === "string" ? msg.payload.worktreeBasePath.trim() : "", msg.payload?.defaultPersonaId || "");
577
- const row = projectStore.getProject(id);
578
- broadcast({ type: "project_created", payload: { project: row } });
584
+ emit("project.created", { projectId: id });
579
585
  break;
580
586
  }
581
587
  case "archive_project": {
582
588
  const projectId = msg.payload?.projectId;
583
589
  if (projectId)
584
590
  projectStore.archiveProject(projectId);
585
- broadcast({ type: "project_archived", payload: { projectId } });
591
+ emit("project.archived", { projectId });
586
592
  break;
587
593
  }
588
594
  case "update_project": {
@@ -620,7 +626,7 @@ async function handleMessage(ws, msg, subscriptions) {
620
626
  worktreeBasePath: worktreeBasePathVal,
621
627
  defaultPersonaId: defaultPersonaIdVal,
622
628
  });
623
- broadcast({ type: "project_updated", payload: { projectId } });
629
+ emit("project.updated", { projectId });
624
630
  break;
625
631
  }
626
632
  // ─── Personas ──────────────────────────────────────────
@@ -666,7 +672,7 @@ async function handleMessage(ws, msg, subscriptions) {
666
672
  personaId = `${slugify(personaName) || "persona"}-${uuid().slice(0, 4)}`;
667
673
  }
668
674
  personaStore.createPersona(personaId, personaName, msg.payload?.description || "", personaSystemPrompt, msg.payload?.toolConfig || "{}", msg.payload?.runtime || "", msg.payload?.model || "", msg.payload?.maxTurns || 0, msg.payload?.mcpServers || "[]");
669
- broadcast({ type: "persona_created", payload: { personaId } });
675
+ emit("persona.created", { personaId });
670
676
  break;
671
677
  }
672
678
  case "get_persona": {
@@ -702,10 +708,7 @@ async function handleMessage(ws, msg, subscriptions) {
702
708
  return;
703
709
  }
704
710
  personaStore.updatePersona(updatePersonaId, msg.payload?.name || existingPersona.name, msg.payload?.description || existingPersona.description, msg.payload?.systemPrompt || existingPersona.systemPrompt, msg.payload?.toolConfig || existingPersona.toolConfig, msg.payload?.runtime || existingPersona.runtime, msg.payload?.model || existingPersona.model, msg.payload?.maxTurns || existingPersona.maxTurns, msg.payload?.mcpServers || existingPersona.mcpServers);
705
- broadcast({
706
- type: "persona_updated",
707
- payload: { personaId: updatePersonaId },
708
- });
711
+ emit("persona.updated", { personaId: updatePersonaId });
709
712
  break;
710
713
  }
711
714
  case "delete_persona": {
@@ -713,10 +716,7 @@ async function handleMessage(ws, msg, subscriptions) {
713
716
  if (!deletePersonaId)
714
717
  return;
715
718
  personaStore.deletePersona(deletePersonaId);
716
- broadcast({
717
- type: "persona_deleted",
718
- payload: { personaId: deletePersonaId },
719
- });
719
+ emit("persona.deleted", { personaId: deletePersonaId });
720
720
  break;
721
721
  }
722
722
  // ─── Settings ────────────────────────────────────────────
@@ -754,7 +754,7 @@ async function handleMessage(ws, msg, subscriptions) {
754
754
  }
755
755
  }
756
756
  settingsStore.setSetting(key, value);
757
- broadcast({ type: "setting_changed", payload: { key, value } });
757
+ emit("setting.changed", { key, value });
758
758
  break;
759
759
  }
760
760
  // ─── Tasks ─────────────────────────────────────────────
@@ -834,16 +834,7 @@ async function handleMessage(ws, msg, subscriptions) {
834
834
  try {
835
835
  const id = uuid().slice(0, 8);
836
836
  taskStore.createTask(id, projectId, title, msg.payload?.description || "", msg.payload?.dependsOn || [], slugify(project.name), parentTaskId, canDecompose, msg.payload?.defaultPersonaId || "");
837
- const row = taskStore.getTask(id);
838
- broadcast({
839
- type: "task_created",
840
- payload: {
841
- task: row
842
- ? { ...row, dependsOn: safeParseJsonArray(row.dependsOn) }
843
- : null,
844
- requestId,
845
- },
846
- });
837
+ emit("task.created", { taskId: id, projectId, requestId });
847
838
  }
848
839
  catch (error) {
849
840
  const message = error instanceof Error ? error.message : "Failed to create task";
@@ -897,10 +888,7 @@ async function handleMessage(ws, msg, subscriptions) {
897
888
  sendWs(ws, { type: "error", payload: { message: String(err) } });
898
889
  return;
899
890
  }
900
- broadcast({
901
- type: "task_started",
902
- payload: { taskId: updateTaskId, sessionId: lateBindSessionId, projectId: existingTask.projectId },
903
- });
891
+ emit("task.started", { taskId: updateTaskId, sessionId: lateBindSessionId, projectId: existingTask.projectId });
904
892
  break;
905
893
  }
906
894
  // Only allow editing not_started tasks (non-late-bind path)
@@ -929,17 +917,7 @@ async function handleMessage(ws, msg, subscriptions) {
929
917
  ? msg.payload.defaultPersonaId
930
918
  : undefined;
931
919
  taskStore.updateTask(updateTaskId, updatedTitle, updatedDescription, existingTask.status, updatedDependsOn, updatedDefaultPersonaId);
932
- const updatedRow = taskStore.getTask(updateTaskId);
933
- broadcast({
934
- type: "task_updated",
935
- payload: {
936
- taskId: updateTaskId,
937
- projectId: existingTask.projectId,
938
- task: updatedRow
939
- ? { ...updatedRow, dependsOn: safeParseJsonArray(updatedRow.dependsOn) }
940
- : null,
941
- },
942
- });
920
+ emit("task.updated", { taskId: updateTaskId, projectId: existingTask.projectId });
943
921
  break;
944
922
  }
945
923
  case "start_task": {
@@ -999,10 +977,7 @@ async function handleMessage(ws, msg, subscriptions) {
999
977
  },
1000
978
  });
1001
979
  if (task) {
1002
- broadcast({
1003
- type: "task_completed",
1004
- payload: { taskId, projectId: task.projectId },
1005
- });
980
+ emit("task.completed", { taskId, projectId: task.projectId });
1006
981
  }
1007
982
  break;
1008
983
  }
@@ -1055,10 +1030,7 @@ async function handleMessage(ws, msg, subscriptions) {
1055
1030
  projectId: task.projectId,
1056
1031
  taskId: task.id,
1057
1032
  });
1058
- broadcast({
1059
- type: "task_started",
1060
- payload: { taskId: task.id, sessionId: latestSession.id, projectId: task.projectId },
1061
- });
1033
+ emit("task.started", { taskId: task.id, sessionId: latestSession.id, projectId: task.projectId });
1062
1034
  break;
1063
1035
  }
1064
1036
  case "delete_task": {
@@ -1107,7 +1079,7 @@ async function handleMessage(ws, msg, subscriptions) {
1107
1079
  sendWs(ws, { type: "error", payload: { message: `Failed to delete task ${taskId}: no rows affected` } });
1108
1080
  return;
1109
1081
  }
1110
- broadcast({ type: "task_deleted", payload: { taskId, projectId: deletedTask.projectId } });
1082
+ emit("task.deleted", { taskId, projectId: deletedTask.projectId });
1111
1083
  break;
1112
1084
  }
1113
1085
  // ─── Task Sessions ─────────────────────────────────────
@@ -1258,7 +1230,7 @@ async function handleMessage(ws, msg, subscriptions) {
1258
1230
  }
1259
1231
  logger.info({ environmentId, adapterType: env.adapterType }, "Provisioning environment");
1260
1232
  envRegistry.updateEnvironmentStatus(environmentId, "connecting");
1261
- broadcastEnvironments();
1233
+ emit("environment.changed", {});
1262
1234
  // Run provision in background, broadcasting progress to all connected clients
1263
1235
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
1264
1236
  (async () => {
@@ -1267,14 +1239,11 @@ async function handleMessage(ws, msg, subscriptions) {
1267
1239
  const powerlineToken = env.powerlineToken || "";
1268
1240
  for await (const event of reconnectOrProvision(environmentId, adapter, config, powerlineToken, !!env.bootstrapped)) {
1269
1241
  logger.info({ environmentId, stage: event.stage, message: event.message }, "Provision progress");
1270
- broadcast({
1271
- type: "provision_progress",
1272
- payload: {
1273
- environmentId,
1274
- stage: event.stage,
1275
- message: event.message,
1276
- progress: event.progress,
1277
- },
1242
+ emit("environment.provision_progress", {
1243
+ environmentId,
1244
+ stage: event.stage,
1245
+ message: event.message,
1246
+ progress: event.progress,
1278
1247
  });
1279
1248
  }
1280
1249
  logger.info({ environmentId }, "Provision complete, calling adapter.connect");
@@ -1285,31 +1254,25 @@ async function handleMessage(ws, msg, subscriptions) {
1285
1254
  envRegistry.updateEnvironmentStatus(environmentId, "connected");
1286
1255
  envRegistry.markBootstrapped(environmentId);
1287
1256
  logger.info({ environmentId }, "Environment connected");
1288
- broadcast({
1289
- type: "provision_progress",
1290
- payload: {
1291
- environmentId,
1292
- stage: "ready",
1293
- message: "Environment connected",
1294
- progress: 1,
1295
- },
1257
+ emit("environment.provision_progress", {
1258
+ environmentId,
1259
+ stage: "ready",
1260
+ message: "Environment connected",
1261
+ progress: 1,
1296
1262
  });
1297
1263
  }
1298
1264
  catch (err) {
1299
1265
  logger.error({ environmentId, err }, "Provision failed");
1300
1266
  envRegistry.updateEnvironmentStatus(environmentId, "error");
1301
1267
  const errorMessage = err instanceof Error ? err.message : String(err);
1302
- broadcast({
1303
- type: "provision_progress",
1304
- payload: {
1305
- environmentId,
1306
- stage: "error",
1307
- message: `Connection failed: ${errorMessage}`,
1308
- progress: 0,
1309
- },
1268
+ emit("environment.provision_progress", {
1269
+ environmentId,
1270
+ stage: "error",
1271
+ message: `Connection failed: ${errorMessage}`,
1272
+ progress: 0,
1310
1273
  });
1311
1274
  }
1312
- broadcastEnvironments();
1275
+ emit("environment.changed", {});
1313
1276
  })();
1314
1277
  break;
1315
1278
  }
@@ -1338,7 +1301,7 @@ async function handleMessage(ws, msg, subscriptions) {
1338
1301
  adapterManager.removeConnection(environmentId);
1339
1302
  envRegistry.updateEnvironmentStatus(environmentId, "disconnected");
1340
1303
  logger.info({ environmentId }, "Environment stopped");
1341
- broadcastEnvironments();
1304
+ emit("environment.changed", {});
1342
1305
  break;
1343
1306
  }
1344
1307
  case "add_environment": {
@@ -1399,8 +1362,8 @@ async function handleMessage(ws, msg, subscriptions) {
1399
1362
  }
1400
1363
  envRegistry.addEnvironment(id, displayName, adapterType, adapterConfig);
1401
1364
  logger.info({ id, displayName, adapterType }, "Environment added via WebSocket");
1402
- broadcast({ type: "environment_added", payload: { environmentId: id } });
1403
- broadcastEnvironments();
1365
+ emit("environment.added", { environmentId: id });
1366
+ emit("environment.changed", {});
1404
1367
  break;
1405
1368
  }
1406
1369
  case "remove_environment": {
@@ -1435,8 +1398,8 @@ async function handleMessage(ws, msg, subscriptions) {
1435
1398
  sessionStore.deleteByEnvironment(environmentId);
1436
1399
  envRegistry.removeEnvironment(environmentId);
1437
1400
  logger.info({ environmentId }, "Environment removed");
1438
- broadcast({ type: "environment_removed", payload: { environmentId } });
1439
- broadcastEnvironments();
1401
+ emit("environment.removed", { environmentId });
1402
+ emit("environment.changed", {});
1440
1403
  break;
1441
1404
  }
1442
1405
  // ─── Codespaces ─────────────────────────────────────
@@ -1536,7 +1499,7 @@ async function handleMessage(ws, msg, subscriptions) {
1536
1499
  value,
1537
1500
  expiresAt: msg.payload?.expiresAt || "",
1538
1501
  });
1539
- broadcast({ type: "token_changed" });
1502
+ emit("token.changed", {});
1540
1503
  break;
1541
1504
  }
1542
1505
  case "delete_token": {
@@ -1546,7 +1509,7 @@ async function handleMessage(ws, msg, subscriptions) {
1546
1509
  return;
1547
1510
  }
1548
1511
  await tokenBroker.deleteToken(tokenName);
1549
- broadcast({ type: "token_changed" });
1512
+ emit("token.changed", {});
1550
1513
  break;
1551
1514
  }
1552
1515
  case "get_credential_providers": {
@@ -1563,10 +1526,7 @@ async function handleMessage(ws, msg, subscriptions) {
1563
1526
  return;
1564
1527
  }
1565
1528
  credentialProviders.setCredentialProviders(msg.payload);
1566
- broadcast({
1567
- type: "credential_providers",
1568
- payload: credentialProviders.getCredentialProviders(),
1569
- });
1529
+ emit("credential.providers_changed", credentialProviders.getCredentialProviders());
1570
1530
  break;
1571
1531
  }
1572
1532
  }