@grackle-ai/server 0.24.1 → 0.26.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.
@@ -5,7 +5,7 @@ import { MAX_TASK_DEPTH } from "@grackle-ai/common";
5
5
  import { safeParseJsonArray } from "./json-helpers.js";
6
6
  import { slugify } from "./utils/slugify.js";
7
7
  /** Insert a new task with auto-generated branch name and sort order. */
8
- export function createTask(id, projectId, title, description, environmentId, dependsOn, projectSlug, parentTaskId = "", canDecompose, personaId = "") {
8
+ export function createTask(id, projectId, title, description, dependsOn, projectSlug, parentTaskId = "", canDecompose) {
9
9
  let depth = 0;
10
10
  let branch;
11
11
  if (parentTaskId) {
@@ -41,13 +41,11 @@ export function createTask(id, projectId, title, description, environmentId, dep
41
41
  title,
42
42
  description,
43
43
  branch,
44
- environmentId,
45
44
  dependsOn: depsJson,
46
45
  sortOrder,
47
46
  parentTaskId,
48
47
  depth,
49
48
  canDecompose: resolvedCanDecompose,
50
- personaId,
51
49
  })
52
50
  .run();
53
51
  }
@@ -65,16 +63,14 @@ export function listTasks(projectId) {
65
63
  .all();
66
64
  }
67
65
  /** Update multiple task fields at once. */
68
- export function updateTask(id, title, description, status, environmentId, dependsOn, reviewNotes, personaId) {
66
+ export function updateTask(id, title, description, status, dependsOn, reviewNotes) {
69
67
  db.update(tasks)
70
68
  .set({
71
69
  title,
72
70
  description,
73
71
  status,
74
- environmentId,
75
72
  dependsOn: JSON.stringify(dependsOn),
76
73
  reviewNotes,
77
- personaId,
78
74
  updatedAt: sql `datetime('now')`,
79
75
  })
80
76
  .where(eq(tasks.id, id))
@@ -100,28 +96,10 @@ export function updateTaskStatus(id, status) {
100
96
  .where(eq(tasks.id, id))
101
97
  .run();
102
98
  }
103
- /** Set the session ID for a task. */
104
- export function setTaskSession(id, sessionId) {
105
- db.update(tasks)
106
- .set({
107
- sessionId,
108
- updatedAt: sql `datetime('now')`,
109
- })
110
- .where(eq(tasks.id, id))
111
- .run();
112
- }
113
- /** Mark a task as in_progress with a started_at timestamp. */
114
- export function markTaskStarted(id) {
115
- db.update(tasks)
116
- .set({
117
- status: "in_progress",
118
- startedAt: sql `datetime('now')`,
119
- updatedAt: sql `datetime('now')`,
120
- })
121
- .where(eq(tasks.id, id))
122
- .run();
123
- }
124
- /** Mark a task as completed (review, done, or failed) with a completed_at timestamp. */
99
+ /**
100
+ * Mark a task as completed with a completed_at timestamp.
101
+ * Used only for human-authoritative status transitions (approve → "done").
102
+ */
125
103
  export function markTaskCompleted(id, status) {
126
104
  db.update(tasks)
127
105
  .set({
@@ -1 +1 @@
1
- {"version":3,"file":"task-store.js","sourceRoot":"","sources":["../src/task-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"task-store.js","sourceRoot":"","sources":["../src/task-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,KAAK,EAAgB,MAAM,aAAa,CAAC;AAClD,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAI7C,wEAAwE;AACxE,MAAM,UAAU,UAAU,CACxB,EAAU,EACV,SAAiB,EACjB,KAAa,EACb,WAAmB,EACnB,SAAmB,EACnB,WAAmB,EACnB,eAAuB,EAAE,EACzB,YAAsB;IAEtB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,IAAI,MAAc,CAAC;IAEnB,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;QACrC,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,IAAI,KAAK,CAAC,0BAA0B,YAAY,EAAE,CAAC,CAAC;QAC5D,CAAC;QACD,IAAI,CAAC,MAAM,CAAC,YAAY,EAAE,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,gBAAgB,MAAM,CAAC,KAAK,MAAM,YAAY,sCAAsC,CACrF,CAAC;QACJ,CAAC;QACD,KAAK,GAAG,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC;QACzB,IAAI,KAAK,GAAG,cAAc,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,sCAAsC,cAAc,EAAE,CAAC,CAAC;QAC1E,CAAC;QACD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;IAChD,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,GAAG,WAAW,IAAI,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;IAC9C,CAAC;IAED,sEAAsE;IACtE,MAAM,oBAAoB,GAAG,YAAY,IAAI,CAAC,YAAY,CAAC;IAE3D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,EAAE;SACd,MAAM,CAAC,EAAE,QAAQ,EAAE,GAAG,CAAQ,iBAAiB,EAAE,CAAC;SAClD,IAAI,CAAC,KAAK,CAAC;SACX,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;SACrC,GAAG,EAAE,CAAC;IACT,MAAM,SAAS,GAAG,CAAC,MAAM,EAAE,QAAQ,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IAC/C,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;SACb,MAAM,CAAC;QACN,EAAE;QACF,SAAS;QACT,KAAK;QACL,WAAW;QACX,MAAM;QACN,SAAS,EAAE,QAAQ;QACnB,SAAS;QACT,YAAY;QACZ,KAAK;QACL,YAAY,EAAE,oBAAoB;KACnC,CAAC;SACD,GAAG,EAAE,CAAC;AACX,CAAC;AAED,oCAAoC;AACpC,MAAM,UAAU,OAAO,CAAC,EAAU;IAChC,OAAO,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;AAC/D,CAAC;AAED,6EAA6E;AAC7E,MAAM,UAAU,SAAS,CAAC,SAAiB;IACzC,OAAO,EAAE;SACN,MAAM,EAAE;SACR,IAAI,CAAC,KAAK,CAAC;SACX,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;SACrC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;SACnD,GAAG,EAAE,CAAC;AACX,CAAC;AAED,2CAA2C;AAC3C,MAAM,UAAU,UAAU,CACxB,EAAU,EACV,KAAa,EACb,WAAmB,EACnB,MAAc,EACd,SAAmB,EACnB,WAAmB;IAEnB,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;SACb,GAAG,CAAC;QACH,KAAK;QACL,WAAW;QACX,MAAM;QACN,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;QACpC,WAAW;QACX,SAAS,EAAE,GAAG,CAAA,iBAAiB;KAChC,CAAC;SACD,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;SACvB,GAAG,EAAE,CAAC;AACX,CAAC;AAED,iDAAiD;AACjD,MAAM,UAAU,gBAAgB,CAAC,EAAU,EAAE,SAAmB;IAC9D,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;SACb,GAAG,CAAC;QACH,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC;QACpC,SAAS,EAAE,GAAG,CAAA,iBAAiB;KAChC,CAAC;SACD,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;SACvB,GAAG,EAAE,CAAC;AACX,CAAC;AAED,mCAAmC;AACnC,MAAM,UAAU,gBAAgB,CAAC,EAAU,EAAE,MAAkB;IAC7D,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;SACb,GAAG,CAAC;QACH,MAAM;QACN,SAAS,EAAE,GAAG,CAAA,iBAAiB;KAChC,CAAC;SACD,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;SACvB,GAAG,EAAE,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,EAAU,EACV,MAAoC;IAEpC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC;SACb,GAAG,CAAC;QACH,MAAM;QACN,WAAW,EAAE,GAAG,CAAA,iBAAiB;QACjC,SAAS,EAAE,GAAG,CAAA,iBAAiB;KAChC,CAAC;SACD,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;SACvB,GAAG,EAAE,CAAC;AACX,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,UAAU,CAAC,EAAU;IACnC,MAAM,MAAM,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;IAC9D,OAAO,MAAM,CAAC,OAAO,CAAC;AACxB,CAAC;AAED,iEAAiE;AACjE,MAAM,UAAU,iBAAiB,CAAC,SAAiB;IACjD,MAAM,GAAG,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,OAAO,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QACzB,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC9B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAChD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACtB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;YAC1B,MAAM,GAAG,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,KAAK,CAAC,CAAC;YAC5C,OAAO,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;QAChC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,OAAO,iBAAiB,CAAC,SAAS,CAAC,CAAC;AACtC,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,kBAAkB,CAAC,MAAc;IAC/C,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAChD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE;QAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;QAC3B,OAAO,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,wDAAwD;AAExD,sGAAsG;AACtG,MAAM,UAAU,gBAAgB,CAAC,IAAe;IAC9C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAoB,CAAC;IACxC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,GAAG,CAAC,YAAY,EAAE,CAAC;YACrB,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;YAC3C,IAAI,QAAQ,EAAE,CAAC;gBACb,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACxB,CAAC;iBAAM,CAAC;gBACN,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;YACtC,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,4DAA4D;AAC5D,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,OAAO,EAAE;SACN,MAAM,EAAE;SACR,IAAI,CAAC,KAAK,CAAC;SACX,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;SACrC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;SACnD,GAAG,EAAE,CAAC;AACX,CAAC;AAED,2HAA2H;AAC3H,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7B,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,OAAO,GAAG,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAC1C,MAAM,WAAW,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAkB,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAExE,MAAM,MAAM,GAAc,EAAE,CAAC;IAC7B,MAAM,KAAK,GAAa,CAAC,MAAM,CAAC,CAAC;IACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,WAAW,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC5C,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,SAAS;QACX,CAAC;QACD,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;YAC/B,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YACnC,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACnB,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACvB,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,mEAAmE;AACnE,MAAM,UAAU,YAAY,CAAC,MAAc;IACzC,MAAM,SAAS,GAAc,EAAE,CAAC;IAChC,IAAI,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC9B,OAAO,OAAO,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;QACvC,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;QAC7C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM;QACR,CAAC;QACD,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAC1B,OAAO,GAAG,MAAM,CAAC;IACnB,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,kDAAkD;AAClD,MAAM,UAAU,oBAAoB,CAAC,MAAc;IACjD,MAAM,QAAQ,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC;IACrC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,KAAK,MAAM,KAAK,IAAI,QAAQ,EAAE,CAAC;QAC7B,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IACzD,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"ws-bridge.d.ts","sourceRoot":"","sources":["../src/ws-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAa,MAAM,IAAI,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AAiDtD,wGAAwG;AACxG,wBAAgB,cAAc,CAC5B,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GACvC,eAAe,CAuCjB"}
1
+ {"version":3,"file":"ws-bridge.d.ts","sourceRoot":"","sources":["../src/ws-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAa,MAAM,IAAI,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAC;AAmDtD,wGAAwG;AACxG,wBAAgB,cAAc,CAC5B,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,GACvC,eAAe,CAuCjB"}
package/dist/ws-bridge.js CHANGED
@@ -21,8 +21,10 @@ import { logger } from "./logger.js";
21
21
  import { buildTaskSystemContext } from "./utils/system-context.js";
22
22
  import { slugify } from "./utils/slugify.js";
23
23
  import { processEventStream } from "./event-processor.js";
24
+ import * as processorRegistry from "./processor-registry.js";
24
25
  import { broadcast, setWssInstance } from "./ws-broadcast.js";
25
26
  import { buildMcpServersJson } from "./grpc-service.js";
27
+ import { computeTaskStatus } from "./compute-task-status.js";
26
28
  import { exec } from "./utils/exec.js";
27
29
  const GH_CODESPACE_LIST_TIMEOUT_MS = 30_000;
28
30
  const GH_CODESPACE_CREATE_TIMEOUT_MS = 300_000;
@@ -190,7 +192,7 @@ async function startTaskSession(ws, task, options) {
190
192
  logger.warn({ taskId: task.id }, "startTaskSession failed: project not found");
191
193
  return `Project not found: ${task.projectId}`;
192
194
  }
193
- const environmentId = task.environmentId || project.defaultEnvironmentId;
195
+ const environmentId = options?.environmentId || project.defaultEnvironmentId;
194
196
  const env = envRegistry.getEnvironment(environmentId);
195
197
  if (!env) {
196
198
  logger.warn({ taskId: task.id, environmentId }, "startTaskSession failed: environment not found");
@@ -203,7 +205,7 @@ async function startTaskSession(ws, task, options) {
203
205
  return undefined;
204
206
  }
205
207
  // Resolve persona
206
- const resolvedPersonaId = options?.personaId || task.personaId;
208
+ const resolvedPersonaId = options?.personaId || "";
207
209
  const persona = resolvedPersonaId
208
210
  ? personaStore.getPersona(resolvedPersonaId)
209
211
  : undefined;
@@ -226,9 +228,7 @@ async function startTaskSession(ws, task, options) {
226
228
  if (persona) {
227
229
  systemContext = persona.systemPrompt + "\n\n" + systemContext;
228
230
  }
229
- sessionStore.createSession(sessionId, environmentId, runtime, freshTask.title, model, logPath, freshTask.id);
230
- taskStore.setTaskSession(freshTask.id, sessionId);
231
- taskStore.markTaskStarted(freshTask.id);
231
+ sessionStore.createSession(sessionId, environmentId, runtime, freshTask.title, model, logPath, freshTask.id, resolvedPersonaId);
232
232
  broadcast({
233
233
  type: "task_started",
234
234
  payload: {
@@ -274,22 +274,6 @@ async function startTaskSession(ws, task, options) {
274
274
  logPath,
275
275
  projectId: freshTask.projectId,
276
276
  taskId: freshTask.id,
277
- onComplete: () => {
278
- const t = taskStore.getTask(freshTask.id);
279
- if (t && (t.status === "in_progress" || t.status === "waiting_input")) {
280
- const sess = sessionStore.getSession(sessionId);
281
- if (sess?.status === "completed") {
282
- taskStore.markTaskCompleted(freshTask.id, "review");
283
- }
284
- else if (sess?.status === "failed") {
285
- taskStore.markTaskCompleted(freshTask.id, "failed");
286
- }
287
- broadcast({
288
- type: "task_updated",
289
- payload: { taskId: freshTask.id, projectId: freshTask.projectId },
290
- });
291
- }
292
- },
293
277
  });
294
278
  return undefined;
295
279
  }
@@ -546,11 +530,7 @@ async function handleMessage(ws, msg, subscriptions) {
546
530
  await conn.client.kill(create(powerline.SessionIdSchema, { id: sessionId }));
547
531
  }
548
532
  catch (err) {
549
- sendWs(ws, {
550
- type: "error",
551
- payload: { message: `Kill failed: ${err}` },
552
- });
553
- return;
533
+ logger.warn({ sessionId, err }, "PowerLine kill failed — marking session killed anyway");
554
534
  }
555
535
  }
556
536
  sessionStore.updateSession(sessionId, "killed");
@@ -561,6 +541,13 @@ async function handleMessage(ws, msg, subscriptions) {
561
541
  content: "killed",
562
542
  raw: "",
563
543
  }));
544
+ // Broadcast task_updated so frontend re-fetches computed status
545
+ if (session.taskId) {
546
+ const task = taskStore.getTask(session.taskId);
547
+ if (task) {
548
+ broadcast({ type: "task_updated", payload: { taskId: task.id, projectId: task.projectId } });
549
+ }
550
+ }
564
551
  break;
565
552
  }
566
553
  // ─── Projects ──────────────────────────────────────────
@@ -749,29 +736,40 @@ async function handleMessage(ws, msg, subscriptions) {
749
736
  return;
750
737
  const rows = taskStore.listTasks(projectId);
751
738
  const childIdsMap = taskStore.buildChildIdsMap(rows);
739
+ // Batch-fetch sessions for all tasks and group by taskId
740
+ const taskIds = rows.map((r) => r.id);
741
+ const allSessions = sessionStore.listSessionsByTaskIds(taskIds);
742
+ const sessionsByTask = new Map();
743
+ for (const s of allSessions) {
744
+ const arr = sessionsByTask.get(s.taskId) ?? [];
745
+ arr.push(s);
746
+ sessionsByTask.set(s.taskId, arr);
747
+ }
752
748
  sendWs(ws, {
753
749
  type: "tasks",
754
750
  payload: {
755
751
  projectId,
756
- tasks: rows.map((r) => ({
757
- id: r.id,
758
- projectId: r.projectId,
759
- title: r.title,
760
- description: r.description,
761
- status: r.status,
762
- branch: r.branch,
763
- environmentId: r.environmentId,
764
- sessionId: r.sessionId,
765
- dependsOn: safeParseJsonArray(r.dependsOn),
766
- reviewNotes: r.reviewNotes,
767
- sortOrder: r.sortOrder,
768
- createdAt: r.createdAt,
769
- parentTaskId: r.parentTaskId,
770
- depth: r.depth,
771
- childTaskIds: childIdsMap.get(r.id) ?? [],
772
- canDecompose: r.canDecompose,
773
- personaId: r.personaId,
774
- })),
752
+ tasks: rows.map((r) => {
753
+ const taskSessions = sessionsByTask.get(r.id) ?? [];
754
+ const computed = computeTaskStatus(r.status, taskSessions);
755
+ return {
756
+ id: r.id,
757
+ projectId: r.projectId,
758
+ title: r.title,
759
+ description: r.description,
760
+ status: computed.status,
761
+ branch: r.branch,
762
+ latestSessionId: computed.latestSessionId,
763
+ dependsOn: safeParseJsonArray(r.dependsOn),
764
+ reviewNotes: r.reviewNotes,
765
+ sortOrder: r.sortOrder,
766
+ createdAt: r.createdAt,
767
+ parentTaskId: r.parentTaskId,
768
+ depth: r.depth,
769
+ childTaskIds: childIdsMap.get(r.id) ?? [],
770
+ canDecompose: r.canDecompose,
771
+ };
772
+ }),
775
773
  },
776
774
  });
777
775
  break;
@@ -797,19 +795,8 @@ async function handleMessage(ws, msg, subscriptions) {
797
795
  const parentTaskId = msg.payload?.parentTaskId || "";
798
796
  const rawCanDecompose = msg.payload?.canDecompose;
799
797
  const canDecompose = typeof rawCanDecompose === "boolean" ? rawCanDecompose : undefined;
800
- // Resolve environment: explicit > parent task's env > project default
801
- let resolvedEnvId = msg.payload?.environmentId || "";
802
- if (!resolvedEnvId && parentTaskId) {
803
- const parentTask = taskStore.getTask(parentTaskId);
804
- if (parentTask?.environmentId) {
805
- resolvedEnvId = parentTask.environmentId;
806
- }
807
- }
808
- if (!resolvedEnvId) {
809
- resolvedEnvId = project.defaultEnvironmentId;
810
- }
811
798
  const id = uuid().slice(0, 8);
812
- taskStore.createTask(id, projectId, title, msg.payload?.description || "", resolvedEnvId, msg.payload?.dependsOn || [], slugify(project.name), parentTaskId, canDecompose, msg.payload?.personaId || "");
799
+ taskStore.createTask(id, projectId, title, msg.payload?.description || "", msg.payload?.dependsOn || [], slugify(project.name), parentTaskId, canDecompose);
813
800
  const row = taskStore.getTask(id);
814
801
  broadcast({
815
802
  type: "task_created",
@@ -832,7 +819,45 @@ async function handleMessage(ws, msg, subscriptions) {
832
819
  sendWs(ws, { type: "error", payload: { message: `Task not found: ${updateTaskId}` } });
833
820
  return;
834
821
  }
835
- // Only allow editing pending/assigned tasks
822
+ // Late-bind: associate a running session with this task
823
+ const lateBindSessionId = typeof msg.payload?.sessionId === "string" ? msg.payload.sessionId : "";
824
+ if (lateBindSessionId) {
825
+ const session = sessionStore.getSession(lateBindSessionId);
826
+ if (!session) {
827
+ sendWs(ws, { type: "error", payload: { message: `Session not found: ${lateBindSessionId}` } });
828
+ return;
829
+ }
830
+ const terminalStatuses = ["completed", "failed", "killed"];
831
+ if (terminalStatuses.includes(session.status)) {
832
+ sendWs(ws, {
833
+ type: "error",
834
+ payload: { message: `Cannot bind terminal session ${lateBindSessionId} (status: ${session.status})` },
835
+ });
836
+ return;
837
+ }
838
+ // Verify the processor exists before mutating DB state to avoid partial updates
839
+ if (!processorRegistry.get(lateBindSessionId)) {
840
+ sendWs(ws, {
841
+ type: "error",
842
+ payload: { message: `No active event processor for session ${lateBindSessionId}` },
843
+ });
844
+ return;
845
+ }
846
+ sessionStore.setSessionTask(lateBindSessionId, updateTaskId);
847
+ try {
848
+ processorRegistry.lateBind(lateBindSessionId, updateTaskId, existingTask.projectId);
849
+ }
850
+ catch (err) {
851
+ sendWs(ws, { type: "error", payload: { message: String(err) } });
852
+ return;
853
+ }
854
+ broadcast({
855
+ type: "task_started",
856
+ payload: { taskId: updateTaskId, sessionId: lateBindSessionId, projectId: existingTask.projectId },
857
+ });
858
+ break;
859
+ }
860
+ // Only allow editing pending/assigned tasks (non-late-bind path)
836
861
  if (!["pending", "assigned"].includes(existingTask.status)) {
837
862
  sendWs(ws, {
838
863
  type: "error",
@@ -854,13 +879,7 @@ async function handleMessage(ws, msg, subscriptions) {
854
879
  .filter((d) => d !== updateTaskId)),
855
880
  ]
856
881
  : safeParseJsonArray(existingTask.dependsOn);
857
- const updatedEnvironmentId = typeof msg.payload?.environmentId === "string"
858
- ? msg.payload.environmentId
859
- : existingTask.environmentId;
860
- const updatedPersonaId = typeof msg.payload?.personaId === "string"
861
- ? msg.payload.personaId
862
- : existingTask.personaId;
863
- taskStore.updateTask(updateTaskId, updatedTitle, updatedDescription, existingTask.status, updatedEnvironmentId, updatedDependsOn, existingTask.reviewNotes, updatedPersonaId);
882
+ taskStore.updateTask(updateTaskId, updatedTitle, updatedDescription, existingTask.status, updatedDependsOn, existingTask.reviewNotes);
864
883
  const updatedRow = taskStore.getTask(updateTaskId);
865
884
  broadcast({
866
885
  type: "task_updated",
@@ -886,14 +905,18 @@ async function handleMessage(ws, msg, subscriptions) {
886
905
  });
887
906
  return;
888
907
  }
889
- if (!["pending", "assigned", "failed"].includes(task.status)) {
890
- sendWs(ws, {
891
- type: "error",
892
- payload: {
893
- message: `Task cannot be started (status: ${task.status})`,
894
- },
895
- });
896
- return;
908
+ {
909
+ const taskSessions = sessionStore.listSessionsForTask(taskId);
910
+ const { status: effectiveStatus } = computeTaskStatus(task.status, taskSessions);
911
+ if (!["pending", "assigned", "failed"].includes(effectiveStatus)) {
912
+ sendWs(ws, {
913
+ type: "error",
914
+ payload: {
915
+ message: `Task cannot be started (status: ${effectiveStatus})`,
916
+ },
917
+ });
918
+ return;
919
+ }
897
920
  }
898
921
  if (!taskStore.areDependenciesMet(taskId)) {
899
922
  sendWs(ws, {
@@ -905,7 +928,8 @@ async function handleMessage(ws, msg, subscriptions) {
905
928
  const startError = await startTaskSession(ws, task, {
906
929
  runtime: msg.payload?.runtime,
907
930
  model: msg.payload?.model,
908
- personaId: msg.payload?.personaId || task.personaId || undefined,
931
+ personaId: msg.payload?.personaId || undefined,
932
+ environmentId: msg.payload?.environmentId || undefined,
909
933
  });
910
934
  if (startError) {
911
935
  sendWs(ws, { type: "error", payload: { message: startError } });
@@ -936,22 +960,27 @@ async function handleMessage(ws, msg, subscriptions) {
936
960
  const task = taskStore.getTask(taskId);
937
961
  if (!task)
938
962
  return;
939
- if (task.status !== "review") {
963
+ // Use computed status (derived from session history) since the stored
964
+ // status is never explicitly set to "review".
965
+ const taskSessions = sessionStore.listSessionsByTaskIds([taskId]);
966
+ const { status: effectiveStatus } = computeTaskStatus(task.status, taskSessions);
967
+ if (effectiveStatus !== "review") {
940
968
  sendWs(ws, {
941
969
  type: "error",
942
970
  payload: {
943
- message: `Task cannot be rejected (status: ${task.status})`,
971
+ message: `Task cannot be rejected (status: ${effectiveStatus})`,
944
972
  },
945
973
  });
946
974
  return;
947
975
  }
948
976
  // Preserve runtime/model from the previous session so the retry
949
977
  // doesn't unexpectedly switch runtimes/models.
950
- const previousSession = task.sessionId
951
- ? sessionStore.getSession(task.sessionId)
952
- : undefined;
953
- // Store review notes and set status to assigned
954
- taskStore.updateTask(task.id, task.title, task.description, "assigned", task.environmentId, safeParseJsonArray(task.dependsOn), reviewNotes, task.personaId);
978
+ const previousSession = sessionStore.getLatestSessionForTask(task.id);
979
+ // Store review notes and reset status to "pending" so computeTaskStatus
980
+ // can derive the effective status from the new retry session. We use
981
+ // "pending" rather than "assigned" because rejection + retry is an
982
+ // automated flow "assigned" implies deliberate human assignment.
983
+ taskStore.updateTask(task.id, task.title, task.description, "pending", safeParseJsonArray(task.dependsOn), reviewNotes);
955
984
  broadcast({
956
985
  type: "task_rejected",
957
986
  payload: { taskId, projectId: task.projectId },
@@ -962,9 +991,17 @@ async function handleMessage(ws, msg, subscriptions) {
962
991
  const retryError = await startTaskSession(ws, freshTask, {
963
992
  runtime: previousSession?.runtime,
964
993
  model: previousSession?.model,
994
+ environmentId: previousSession?.environmentId,
995
+ personaId: previousSession?.personaId,
965
996
  });
966
997
  if (retryError) {
967
- logger.warn({ taskId, error: retryError }, "Auto-retry after rejection failed task remains assigned");
998
+ // Retry failed set status to "assigned" so the user can retry manually.
999
+ logger.warn({ taskId, error: retryError }, "Auto-retry after rejection failed — task set to assigned");
1000
+ taskStore.updateTask(freshTask.id, freshTask.title, freshTask.description, "assigned", safeParseJsonArray(freshTask.dependsOn), freshTask.reviewNotes);
1001
+ broadcast({
1002
+ type: "task_updated",
1003
+ payload: { taskId, projectId: freshTask.projectId },
1004
+ });
968
1005
  }
969
1006
  }
970
1007
  break;
@@ -988,28 +1025,26 @@ async function handleMessage(ws, msg, subscriptions) {
988
1025
  });
989
1026
  return;
990
1027
  }
991
- // Kill active session before deleting the task
992
- if (deletedTask.sessionId) {
993
- const activeSession = sessionStore.getSession(deletedTask.sessionId);
994
- if (activeSession && (activeSession.status === "running" || activeSession.status === "waiting_input")) {
995
- const conn = adapterManager.getConnection(activeSession.environmentId);
996
- if (conn) {
997
- try {
998
- await conn.client.kill(create(powerline.SessionIdSchema, { id: deletedTask.sessionId }));
999
- }
1000
- catch (err) {
1001
- logger.warn({ taskId, sessionId: deletedTask.sessionId, err }, "Failed to kill session during task deletion");
1002
- }
1028
+ // Kill all active sessions before deleting the task
1029
+ const activeSessions = sessionStore.getActiveSessionsForTask(taskId);
1030
+ for (const activeSession of activeSessions) {
1031
+ const conn = adapterManager.getConnection(activeSession.environmentId);
1032
+ if (conn) {
1033
+ try {
1034
+ await conn.client.kill(create(powerline.SessionIdSchema, { id: activeSession.id }));
1035
+ }
1036
+ catch (err) {
1037
+ logger.warn({ taskId, sessionId: activeSession.id, err }, "Failed to kill session during task deletion");
1003
1038
  }
1004
- sessionStore.updateSession(deletedTask.sessionId, "killed");
1005
- streamHub.publish(create(grackle.SessionEventSchema, {
1006
- sessionId: deletedTask.sessionId,
1007
- type: grackle.EventType.STATUS,
1008
- timestamp: new Date().toISOString(),
1009
- content: "killed",
1010
- raw: "",
1011
- }));
1012
1039
  }
1040
+ sessionStore.updateSession(activeSession.id, "killed");
1041
+ streamHub.publish(create(grackle.SessionEventSchema, {
1042
+ sessionId: activeSession.id,
1043
+ type: grackle.EventType.STATUS,
1044
+ timestamp: new Date().toISOString(),
1045
+ content: "killed",
1046
+ raw: "",
1047
+ }));
1013
1048
  }
1014
1049
  const changes = taskStore.deleteTask(taskId);
1015
1050
  if (changes === 0) {
@@ -1098,8 +1133,7 @@ async function handleMessage(ws, msg, subscriptions) {
1098
1133
  });
1099
1134
  return;
1100
1135
  }
1101
- const environmentId = task.environmentId ||
1102
- projectStore.getProject(task.projectId)?.defaultEnvironmentId;
1136
+ const environmentId = projectStore.getProject(task.projectId)?.defaultEnvironmentId;
1103
1137
  if (!environmentId) {
1104
1138
  sendWs(ws, {
1105
1139
  type: "task_diff",