@grackle-ai/server 0.46.0 → 0.47.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +49 -21
- package/dist/db.js.map +1 -1
- package/dist/event-bus.d.ts +1 -1
- package/dist/event-bus.d.ts.map +1 -1
- package/dist/event-processor.d.ts +1 -1
- package/dist/event-processor.d.ts.map +1 -1
- package/dist/event-processor.js +16 -16
- package/dist/event-processor.js.map +1 -1
- package/dist/finding-store.d.ts +5 -5
- package/dist/finding-store.d.ts.map +1 -1
- package/dist/finding-store.js +10 -10
- package/dist/finding-store.js.map +1 -1
- package/dist/github-import.d.ts +2 -2
- package/dist/github-import.d.ts.map +1 -1
- package/dist/github-import.js +15 -15
- package/dist/github-import.js.map +1 -1
- package/dist/grpc-service.d.ts.map +1 -1
- package/dist/grpc-service.js +72 -74
- package/dist/grpc-service.js.map +1 -1
- package/dist/processor-registry.d.ts +3 -3
- package/dist/processor-registry.d.ts.map +1 -1
- package/dist/processor-registry.js +4 -4
- package/dist/processor-registry.js.map +1 -1
- package/dist/reanimate-agent.js +3 -3
- package/dist/reanimate-agent.js.map +1 -1
- package/dist/resolve-persona.d.ts +2 -2
- package/dist/resolve-persona.d.ts.map +1 -1
- package/dist/resolve-persona.js +4 -4
- package/dist/resolve-persona.js.map +1 -1
- package/dist/schema.d.ts +21 -21
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +6 -6
- package/dist/schema.js.map +1 -1
- package/dist/task-store.d.ts +6 -6
- package/dist/task-store.d.ts.map +1 -1
- package/dist/task-store.js +15 -15
- package/dist/task-store.js.map +1 -1
- package/dist/utils/slugify.d.ts +1 -1
- package/dist/utils/slugify.js +1 -1
- package/dist/utils/system-context.js +4 -4
- package/dist/utils/system-context.js.map +1 -1
- package/dist/workspace-store.d.ts +26 -0
- package/dist/workspace-store.d.ts.map +1 -0
- package/dist/{project-store.js → workspace-store.js} +21 -21
- package/dist/workspace-store.js.map +1 -0
- package/dist/ws-bridge.js +78 -77
- package/dist/ws-bridge.js.map +1 -1
- package/package.json +6 -6
- package/dist/project-store.d.ts +0 -26
- package/dist/project-store.d.ts.map +0 -1
- package/dist/project-store.js.map +0 -1
|
@@ -6,13 +6,13 @@ export function buildTaskSystemContext(title, description, notes, canDecompose)
|
|
|
6
6
|
notes ? `## Notes (from previous attempt or user feedback)\n${notes}` : "",
|
|
7
7
|
`## Grackle Tools (MCP)`,
|
|
8
8
|
`You have a "grackle" MCP server with tools for coordinating with other agents:`,
|
|
9
|
-
`- **mcp__grackle__finding_post**: Share discoveries (architecture decisions, bugs, patterns) with other agents working on this
|
|
10
|
-
`- **mcp__grackle__finding_list**: Query findings posted by other agents. Parameters:
|
|
9
|
+
`- **mcp__grackle__finding_post**: Share discoveries (architecture decisions, bugs, patterns) with other agents working on this workspace. Parameters: workspaceId (string — injected automatically), title (string, required), content (string, optional), category (optional: architecture|api|bug|decision|dependency|pattern|general), tags (optional: string[]).`,
|
|
10
|
+
`- **mcp__grackle__finding_list**: Query findings posted by other agents. Parameters: workspaceId (string — injected automatically), category (optional), tag (optional), limit (optional). Findings from previous tasks are also in your system context above.`,
|
|
11
11
|
];
|
|
12
12
|
if (canDecompose) {
|
|
13
|
-
sections.push(`- **mcp__grackle__task_create**: Create a new task in the
|
|
13
|
+
sections.push(`- **mcp__grackle__task_create**: Create a new task in the workspace. Use this when work is too large or complex for you to complete alone. Parameters: workspaceId (string — injected automatically), title (string, required), description (string, optional — be specific about what to do and what "done" looks like).`);
|
|
14
14
|
}
|
|
15
|
-
sections.push(`## Completion Checklist`, `When you have finished implementing the task, you MUST complete ALL steps below in order. Do NOT stop early or go to "waiting for input" until every step is done.`, ``, `### Phase 1: Implement & Test`, `1. **Implement** the task requirements.`, `2. **Write tests**: Write unit tests, integration tests, or E2E specs as appropriate. Every implementation MUST include tests unless the change is purely cosmetic or untestable (state why if skipping).`, `3. **Build**: Run the
|
|
15
|
+
sections.push(`## Completion Checklist`, `When you have finished implementing the task, you MUST complete ALL steps below in order. Do NOT stop early or go to "waiting for input" until every step is done.`, ``, `### Phase 1: Implement & Test`, `1. **Implement** the task requirements.`, `2. **Write tests**: Write unit tests, integration tests, or E2E specs as appropriate. Every implementation MUST include tests unless the change is purely cosmetic or untestable (state why if skipping).`, `3. **Build**: Run the repository's build command and fix any errors.`, `4. **Run tests**: Run relevant tests and ensure they pass.`, `5. **Manual test**: If the change affects UI, visually verify. If it affects CLI or API, run the commands manually. State explicitly if skipping and why.`, ``, `### Phase 2: Create PR`, `6. **Sync with main**: Fetch and merge the main branch. If merge conflicts arise, resolve them, stage, and commit the merge. NEVER rebase.`, `7. **Rebuild after merge**: If the merge brought in new commits, rebuild to catch integration conflicts.`, `8. **Commit**: Stage your changed files and create a descriptive git commit. Use a conventional commit message (e.g., \`fix: ...\`, \`feat: ...\`).`, `9. **Push**: Push your branch to the remote.`, `10. **Create PR**: Create a pull request that links back to the issue (e.g., "Closes #ISSUE").`, ``, `### Phase 3: PR Readiness (you MUST complete this — do NOT skip)`, `After creating the PR, you must ensure it is ready to merge.`, ``, `11. **Check for merge conflicts**: Verify the PR has no merge conflicts. If it does, fetch and merge the main branch, resolve conflicts, rebuild, commit, and push.`, `12. **Wait for CI**: Wait for all CI checks to complete. If any check fails, read the logs, fix the issue, commit, push, and repeat.`, `13. **Address code review comments**: Check for automated code review comments. For each unresolved comment: read the suggestion, fix the code or dismiss with an explanation, reply to the comment, and resolve the thread. After fixing, commit, push, and check again. Repeat until all review threads are resolved.`, `14. **Post finding**: Use mcp__grackle__post_finding to summarize what you did and any key decisions.`, ``, `IMPORTANT: The PR is the deliverable, but a PR with failing CI or unresolved review comments is NOT done. You MUST complete Phase 3. Do NOT go to "waiting for input" until CI is green AND all review threads are resolved.`);
|
|
16
16
|
return sections.filter(Boolean).join("\n\n");
|
|
17
17
|
}
|
|
18
18
|
//# sourceMappingURL=system-context.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"system-context.js","sourceRoot":"","sources":["../../src/utils/system-context.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,MAAM,UAAU,sBAAsB,CAAC,KAAa,EAAE,WAAmB,EAAE,KAAa,EAAE,YAAsB;IAC9G,MAAM,QAAQ,GAAa;QACzB,YAAY,KAAK,EAAE;QACnB,WAAW;QACX,KAAK,CAAC,CAAC,CAAC,sDAAsD,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE;QAC1E,wBAAwB;QACxB,gFAAgF;QAChF,
|
|
1
|
+
{"version":3,"file":"system-context.js","sourceRoot":"","sources":["../../src/utils/system-context.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,MAAM,UAAU,sBAAsB,CAAC,KAAa,EAAE,WAAmB,EAAE,KAAa,EAAE,YAAsB;IAC9G,MAAM,QAAQ,GAAa;QACzB,YAAY,KAAK,EAAE;QACnB,WAAW;QACX,KAAK,CAAC,CAAC,CAAC,sDAAsD,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE;QAC1E,wBAAwB;QACxB,gFAAgF;QAChF,sWAAsW;QACtW,gQAAgQ;KACjQ,CAAC;IAEF,IAAI,YAAY,EAAE,CAAC;QACjB,QAAQ,CAAC,IAAI,CACX,2TAA2T,CAC5T,CAAC;IACJ,CAAC;IAED,QAAQ,CAAC,IAAI,CACX,yBAAyB,EACzB,oKAAoK,EACpK,EAAE,EACF,+BAA+B,EAC/B,yCAAyC,EACzC,2MAA2M,EAC3M,sEAAsE,EACtE,4DAA4D,EAC5D,2JAA2J,EAC3J,EAAE,EACF,wBAAwB,EACxB,4IAA4I,EAC5I,0GAA0G,EAC1G,qJAAqJ,EACrJ,8CAA8C,EAC9C,gGAAgG,EAChG,EAAE,EACF,kEAAkE,EAClE,8DAA8D,EAC9D,EAAE,EACF,qKAAqK,EACrK,sIAAsI,EACtI,yTAAyT,EACzT,uGAAuG,EACvG,EAAE,EACF,8NAA8N,CAC/N,CAAC;IAEF,OAAO,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC/C,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type WorkspaceRow } from "./schema.js";
|
|
2
|
+
export type { WorkspaceRow };
|
|
3
|
+
/** Insert a new workspace record. */
|
|
4
|
+
export declare function createWorkspace(id: string, name: string, description: string, repoUrl: string, defaultEnvironmentId: string, useWorktrees?: boolean, worktreeBasePath?: string, defaultPersonaId?: string): void;
|
|
5
|
+
/** Retrieve a single workspace by ID. */
|
|
6
|
+
export declare function getWorkspace(id: string): WorkspaceRow | undefined;
|
|
7
|
+
/** Return all active workspaces, newest first. */
|
|
8
|
+
export declare function listWorkspaces(): WorkspaceRow[];
|
|
9
|
+
/** Mark a workspace as archived. */
|
|
10
|
+
export declare function archiveWorkspace(id: string): void;
|
|
11
|
+
/** Partial-update fields for a workspace. Undefined means "no change"; empty string means "clear". */
|
|
12
|
+
export interface UpdateWorkspaceFields {
|
|
13
|
+
name?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
repoUrl?: string;
|
|
16
|
+
defaultEnvironmentId?: string;
|
|
17
|
+
/** When false, agents work directly in the main checkout instead of creating a worktree. */
|
|
18
|
+
useWorktrees?: boolean;
|
|
19
|
+
/** Custom base path for worktrees (e.g. /workspaces/my-repo). Empty means use default. */
|
|
20
|
+
worktreeBasePath?: string;
|
|
21
|
+
/** Default persona for tasks in this workspace. */
|
|
22
|
+
defaultPersonaId?: string;
|
|
23
|
+
}
|
|
24
|
+
/** Update one or more fields on an existing workspace. Returns the updated row, or undefined if not found. */
|
|
25
|
+
export declare function updateWorkspace(id: string, fields: UpdateWorkspaceFields): WorkspaceRow | undefined;
|
|
26
|
+
//# sourceMappingURL=workspace-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-store.d.ts","sourceRoot":"","sources":["../src/workspace-store.ts"],"names":[],"mappings":"AACA,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AAG5D,YAAY,EAAE,YAAY,EAAE,CAAC;AAE7B,qCAAqC;AACrC,wBAAgB,eAAe,CAC7B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,MAAM,EACf,oBAAoB,EAAE,MAAM,EAC5B,YAAY,GAAE,OAAc,EAC5B,gBAAgB,GAAE,MAAW,EAC7B,gBAAgB,GAAE,MAAW,GAC5B,IAAI,CAWN;AAED,yCAAyC;AACzC,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS,CAEjE;AAED,kDAAkD;AAClD,wBAAgB,cAAc,IAAI,YAAY,EAAE,CAK/C;AAED,oCAAoC;AACpC,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAKjD;AAED,sGAAsG;AACtG,MAAM,WAAW,qBAAqB;IACpC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,4FAA4F;IAC5F,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0FAA0F;IAC1F,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,8GAA8G;AAC9G,wBAAgB,eAAe,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,qBAAqB,GAAG,YAAY,GAAG,SAAS,CAyBnG"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import db from "./db.js";
|
|
2
|
-
import {
|
|
2
|
+
import { workspaces } from "./schema.js";
|
|
3
3
|
import { eq, desc, sql } from "drizzle-orm";
|
|
4
|
-
/** Insert a new
|
|
5
|
-
export function
|
|
6
|
-
db.insert(
|
|
4
|
+
/** Insert a new workspace record. */
|
|
5
|
+
export function createWorkspace(id, name, description, repoUrl, defaultEnvironmentId, useWorktrees = true, worktreeBasePath = "", defaultPersonaId = "") {
|
|
6
|
+
db.insert(workspaces).values({
|
|
7
7
|
id,
|
|
8
8
|
name,
|
|
9
9
|
description,
|
|
@@ -14,26 +14,26 @@ export function createProject(id, name, description, repoUrl, defaultEnvironment
|
|
|
14
14
|
defaultPersonaId,
|
|
15
15
|
}).run();
|
|
16
16
|
}
|
|
17
|
-
/** Retrieve a single
|
|
18
|
-
export function
|
|
19
|
-
return db.select().from(
|
|
17
|
+
/** Retrieve a single workspace by ID. */
|
|
18
|
+
export function getWorkspace(id) {
|
|
19
|
+
return db.select().from(workspaces).where(eq(workspaces.id, id)).get();
|
|
20
20
|
}
|
|
21
|
-
/** Return all active
|
|
22
|
-
export function
|
|
23
|
-
return db.select().from(
|
|
24
|
-
.where(eq(
|
|
25
|
-
.orderBy(desc(
|
|
21
|
+
/** Return all active workspaces, newest first. */
|
|
22
|
+
export function listWorkspaces() {
|
|
23
|
+
return db.select().from(workspaces)
|
|
24
|
+
.where(eq(workspaces.status, "active"))
|
|
25
|
+
.orderBy(desc(workspaces.createdAt))
|
|
26
26
|
.all();
|
|
27
27
|
}
|
|
28
|
-
/** Mark a
|
|
29
|
-
export function
|
|
30
|
-
db.update(
|
|
28
|
+
/** Mark a workspace as archived. */
|
|
29
|
+
export function archiveWorkspace(id) {
|
|
30
|
+
db.update(workspaces)
|
|
31
31
|
.set({ status: "archived", updatedAt: sql `datetime('now')` })
|
|
32
|
-
.where(eq(
|
|
32
|
+
.where(eq(workspaces.id, id))
|
|
33
33
|
.run();
|
|
34
34
|
}
|
|
35
|
-
/** Update one or more fields on an existing
|
|
36
|
-
export function
|
|
35
|
+
/** Update one or more fields on an existing workspace. Returns the updated row, or undefined if not found. */
|
|
36
|
+
export function updateWorkspace(id, fields) {
|
|
37
37
|
const sets = { updatedAt: sql `datetime('now')` };
|
|
38
38
|
if (fields.name !== undefined) {
|
|
39
39
|
sets.name = fields.name;
|
|
@@ -56,7 +56,7 @@ export function updateProject(id, fields) {
|
|
|
56
56
|
if (fields.defaultPersonaId !== undefined) {
|
|
57
57
|
sets.defaultPersonaId = fields.defaultPersonaId;
|
|
58
58
|
}
|
|
59
|
-
db.update(
|
|
60
|
-
return
|
|
59
|
+
db.update(workspaces).set(sets).where(eq(workspaces.id, id)).run();
|
|
60
|
+
return getWorkspace(id);
|
|
61
61
|
}
|
|
62
|
-
//# sourceMappingURL=
|
|
62
|
+
//# sourceMappingURL=workspace-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workspace-store.js","sourceRoot":"","sources":["../src/workspace-store.ts"],"names":[],"mappings":"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"}
|
package/dist/ws-bridge.js
CHANGED
|
@@ -8,7 +8,7 @@ import { reconnectOrProvision, } from "@grackle-ai/adapter-sdk";
|
|
|
8
8
|
import * as streamHub from "./stream-hub.js";
|
|
9
9
|
import * as tokenBroker from "./token-broker.js";
|
|
10
10
|
import * as credentialProviders from "./credential-providers.js";
|
|
11
|
-
import * as
|
|
11
|
+
import * as workspaceStore from "./workspace-store.js";
|
|
12
12
|
import * as taskStore from "./task-store.js";
|
|
13
13
|
import * as findingStore from "./finding-store.js";
|
|
14
14
|
import * as personaStore from "./persona-store.js";
|
|
@@ -174,12 +174,12 @@ async function autoProvisionEnvironment(ws, environmentId, env, logContext) {
|
|
|
174
174
|
* string for failures that need the caller to surface to the client.
|
|
175
175
|
*/
|
|
176
176
|
async function startTaskSession(ws, task, options) {
|
|
177
|
-
const
|
|
178
|
-
if (task.
|
|
179
|
-
logger.warn({ taskId: task.id }, "startTaskSession failed:
|
|
180
|
-
return `
|
|
177
|
+
const workspace = task.workspaceId ? workspaceStore.getWorkspace(task.workspaceId) : undefined;
|
|
178
|
+
if (task.workspaceId && !workspace) {
|
|
179
|
+
logger.warn({ taskId: task.id }, "startTaskSession failed: workspace not found");
|
|
180
|
+
return `Workspace not found: ${task.workspaceId}`;
|
|
181
181
|
}
|
|
182
|
-
const environmentId = options?.environmentId ||
|
|
182
|
+
const environmentId = options?.environmentId || workspace?.defaultEnvironmentId || "";
|
|
183
183
|
const env = envRegistry.getEnvironment(environmentId);
|
|
184
184
|
if (!env) {
|
|
185
185
|
logger.warn({ taskId: task.id, environmentId }, "startTaskSession failed: environment not found");
|
|
@@ -191,10 +191,10 @@ async function startTaskSession(ws, task, options) {
|
|
|
191
191
|
if (!conn) {
|
|
192
192
|
return undefined;
|
|
193
193
|
}
|
|
194
|
-
// Resolve persona via cascade (request → task →
|
|
194
|
+
// Resolve persona via cascade (request → task → workspace → app default)
|
|
195
195
|
let resolved;
|
|
196
196
|
try {
|
|
197
|
-
resolved = resolvePersona(options?.personaId || "", task.defaultPersonaId,
|
|
197
|
+
resolved = resolvePersona(options?.personaId || "", task.defaultPersonaId, workspace?.defaultPersonaId || "");
|
|
198
198
|
}
|
|
199
199
|
catch (err) {
|
|
200
200
|
return err.message;
|
|
@@ -211,7 +211,7 @@ async function startTaskSession(ws, task, options) {
|
|
|
211
211
|
emit("task.started", {
|
|
212
212
|
taskId: freshTask.id,
|
|
213
213
|
sessionId,
|
|
214
|
-
|
|
214
|
+
workspaceId: freshTask.workspaceId || "",
|
|
215
215
|
});
|
|
216
216
|
// Re-push stored tokens + provider credentials (scoped to runtime) so they're fresh for this session.
|
|
217
217
|
// For local envs, skip file tokens — the PowerLine is on the same machine.
|
|
@@ -233,8 +233,8 @@ async function startTaskSession(ws, task, options) {
|
|
|
233
233
|
const mcpPort = parseInt(process.env.GRACKLE_MCP_PORT || String(DEFAULT_MCP_PORT), 10);
|
|
234
234
|
const mcpDialHost = toDialableHost(process.env.GRACKLE_HOST || "127.0.0.1");
|
|
235
235
|
const mcpUrl = `http://${mcpDialHost}:${mcpPort}/mcp`;
|
|
236
|
-
const mcpToken = createScopedToken({ sub: freshTask.id, pid: freshTask.
|
|
237
|
-
const useWorktrees =
|
|
236
|
+
const mcpToken = createScopedToken({ sub: freshTask.id, pid: freshTask.workspaceId || "", per: resolved.personaId, sid: sessionId }, loadOrCreateApiKey());
|
|
237
|
+
const useWorktrees = workspace?.useWorktrees ?? false;
|
|
238
238
|
const powerlineReq = create(powerline.SpawnRequestSchema, {
|
|
239
239
|
sessionId,
|
|
240
240
|
runtime,
|
|
@@ -242,11 +242,12 @@ async function startTaskSession(ws, task, options) {
|
|
|
242
242
|
model,
|
|
243
243
|
maxTurns,
|
|
244
244
|
branch: freshTask.branch,
|
|
245
|
-
worktreeBasePath: freshTask.branch
|
|
246
|
-
? (
|
|
245
|
+
worktreeBasePath: freshTask.branch
|
|
246
|
+
? (workspace?.worktreeBasePath || process.env.GRACKLE_WORKTREE_BASE || "/workspace")
|
|
247
247
|
: "",
|
|
248
|
+
useWorktrees,
|
|
248
249
|
systemContext,
|
|
249
|
-
|
|
250
|
+
workspaceId: freshTask.workspaceId ?? undefined,
|
|
250
251
|
taskId: freshTask.id,
|
|
251
252
|
mcpServersJson,
|
|
252
253
|
mcpUrl,
|
|
@@ -255,7 +256,7 @@ async function startTaskSession(ws, task, options) {
|
|
|
255
256
|
processEventStream(conn.client.spawn(powerlineReq), {
|
|
256
257
|
sessionId,
|
|
257
258
|
logPath,
|
|
258
|
-
|
|
259
|
+
workspaceId: freshTask.workspaceId ?? undefined,
|
|
259
260
|
taskId: freshTask.id,
|
|
260
261
|
});
|
|
261
262
|
return undefined;
|
|
@@ -530,7 +531,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
530
531
|
if (session.taskId) {
|
|
531
532
|
const task = taskStore.getTask(session.taskId);
|
|
532
533
|
if (task) {
|
|
533
|
-
emit("task.updated", { taskId: task.id,
|
|
534
|
+
emit("task.updated", { taskId: task.id, workspaceId: task.workspaceId || "" });
|
|
534
535
|
}
|
|
535
536
|
}
|
|
536
537
|
break;
|
|
@@ -563,7 +564,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
563
564
|
// Mark task complete (same as "complete_task" handler)
|
|
564
565
|
taskStore.markTaskComplete(taskId, TASK_STATUS.COMPLETE);
|
|
565
566
|
const stoppedTask = taskStore.getTask(taskId);
|
|
566
|
-
const unblocked = stoppedTask?.
|
|
567
|
+
const unblocked = stoppedTask?.workspaceId ? taskStore.checkAndUnblock(stoppedTask.workspaceId) : [];
|
|
567
568
|
sendWs(ws, {
|
|
568
569
|
type: "task_completed",
|
|
569
570
|
payload: {
|
|
@@ -572,7 +573,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
572
573
|
},
|
|
573
574
|
});
|
|
574
575
|
if (stoppedTask) {
|
|
575
|
-
emit("task.completed", { taskId,
|
|
576
|
+
emit("task.completed", { taskId, workspaceId: stoppedTask.workspaceId || "" });
|
|
576
577
|
}
|
|
577
578
|
break;
|
|
578
579
|
}
|
|
@@ -592,13 +593,13 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
592
593
|
}
|
|
593
594
|
break;
|
|
594
595
|
}
|
|
595
|
-
// ───
|
|
596
|
-
case "
|
|
597
|
-
const rows =
|
|
596
|
+
// ─── Workspaces ────────────────────────────────────────
|
|
597
|
+
case "list_workspaces": {
|
|
598
|
+
const rows = workspaceStore.listWorkspaces();
|
|
598
599
|
sendWs(ws, {
|
|
599
|
-
type: "
|
|
600
|
+
type: "workspaces",
|
|
600
601
|
payload: {
|
|
601
|
-
|
|
602
|
+
workspaces: rows.map((r) => ({
|
|
602
603
|
id: r.id,
|
|
603
604
|
name: r.name,
|
|
604
605
|
description: r.description,
|
|
@@ -615,47 +616,47 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
615
616
|
});
|
|
616
617
|
break;
|
|
617
618
|
}
|
|
618
|
-
case "
|
|
619
|
+
case "create_workspace": {
|
|
619
620
|
const name = msg.payload?.name;
|
|
620
621
|
if (!name) {
|
|
621
622
|
sendWs(ws, { type: "error", payload: { message: "name required" } });
|
|
622
623
|
return;
|
|
623
624
|
}
|
|
624
|
-
const
|
|
625
|
-
let id =
|
|
626
|
-
for (let attempt = 0; attempt < 10 &&
|
|
627
|
-
id = `${
|
|
625
|
+
const baseWorkspaceId = slugify(name) || uuid().slice(0, 8);
|
|
626
|
+
let id = baseWorkspaceId;
|
|
627
|
+
for (let attempt = 0; attempt < 10 && workspaceStore.getWorkspace(id); attempt++) {
|
|
628
|
+
id = `${baseWorkspaceId}-${uuid().slice(0, 4)}`;
|
|
628
629
|
}
|
|
629
|
-
if (
|
|
630
|
+
if (workspaceStore.getWorkspace(id)) {
|
|
630
631
|
id = uuid();
|
|
631
632
|
}
|
|
632
633
|
// useWorktrees defaults to true when not specified
|
|
633
634
|
const createUseWorktrees = msg.payload?.useWorktrees ?? true;
|
|
634
|
-
|
|
635
|
-
emit("
|
|
635
|
+
workspaceStore.createWorkspace(id, name, msg.payload?.description || "", msg.payload?.repoUrl || "", msg.payload?.defaultEnvironmentId || "", createUseWorktrees, typeof msg.payload?.worktreeBasePath === "string" ? msg.payload.worktreeBasePath.trim() : "", msg.payload?.defaultPersonaId || "");
|
|
636
|
+
emit("workspace.created", { workspaceId: id });
|
|
636
637
|
break;
|
|
637
638
|
}
|
|
638
|
-
case "
|
|
639
|
-
const
|
|
640
|
-
if (
|
|
641
|
-
|
|
642
|
-
emit("
|
|
639
|
+
case "archive_workspace": {
|
|
640
|
+
const workspaceId = msg.payload?.workspaceId;
|
|
641
|
+
if (workspaceId)
|
|
642
|
+
workspaceStore.archiveWorkspace(workspaceId);
|
|
643
|
+
emit("workspace.archived", { workspaceId });
|
|
643
644
|
break;
|
|
644
645
|
}
|
|
645
|
-
case "
|
|
646
|
-
const
|
|
647
|
-
if (!
|
|
648
|
-
sendWs(ws, { type: "error", payload: { message: "
|
|
646
|
+
case "update_workspace": {
|
|
647
|
+
const workspaceId = msg.payload?.workspaceId;
|
|
648
|
+
if (!workspaceId) {
|
|
649
|
+
sendWs(ws, { type: "error", payload: { message: "workspaceId required" } });
|
|
649
650
|
return;
|
|
650
651
|
}
|
|
651
|
-
const existing =
|
|
652
|
+
const existing = workspaceStore.getWorkspace(workspaceId);
|
|
652
653
|
if (!existing) {
|
|
653
|
-
sendWs(ws, { type: "error", payload: { message: `
|
|
654
|
+
sendWs(ws, { type: "error", payload: { message: `Workspace not found: ${workspaceId}` } });
|
|
654
655
|
return;
|
|
655
656
|
}
|
|
656
657
|
const nameVal = typeof msg.payload?.name === "string" ? msg.payload.name : undefined;
|
|
657
658
|
if (nameVal?.trim() === "") {
|
|
658
|
-
sendWs(ws, { type: "error", payload: { message: "
|
|
659
|
+
sendWs(ws, { type: "error", payload: { message: "Workspace name cannot be empty" } });
|
|
659
660
|
return;
|
|
660
661
|
}
|
|
661
662
|
const descVal = typeof msg.payload?.description === "string" ? msg.payload.description : undefined;
|
|
@@ -668,7 +669,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
668
669
|
const worktreesVal = typeof msg.payload?.useWorktrees === "boolean" ? msg.payload.useWorktrees : undefined;
|
|
669
670
|
const worktreeBasePathVal = typeof msg.payload?.worktreeBasePath === "string" ? msg.payload.worktreeBasePath : undefined;
|
|
670
671
|
const defaultPersonaIdVal = typeof msg.payload?.defaultPersonaId === "string" ? msg.payload.defaultPersonaId : undefined;
|
|
671
|
-
|
|
672
|
+
workspaceStore.updateWorkspace(workspaceId, {
|
|
672
673
|
name: nameVal !== undefined ? nameVal.trim() : undefined,
|
|
673
674
|
description: descVal,
|
|
674
675
|
repoUrl: repoVal,
|
|
@@ -677,7 +678,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
677
678
|
worktreeBasePath: worktreeBasePathVal,
|
|
678
679
|
defaultPersonaId: defaultPersonaIdVal,
|
|
679
680
|
});
|
|
680
|
-
emit("
|
|
681
|
+
emit("workspace.updated", { workspaceId });
|
|
681
682
|
break;
|
|
682
683
|
}
|
|
683
684
|
// ─── Personas ──────────────────────────────────────────
|
|
@@ -823,8 +824,8 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
823
824
|
}
|
|
824
825
|
// ─── Tasks ─────────────────────────────────────────────
|
|
825
826
|
case "list_tasks": {
|
|
826
|
-
const
|
|
827
|
-
const rows = taskStore.listTasks(
|
|
827
|
+
const workspaceId = msg.payload?.workspaceId || undefined;
|
|
828
|
+
const rows = taskStore.listTasks(workspaceId, {
|
|
828
829
|
search: msg.payload?.search || undefined,
|
|
829
830
|
status: msg.payload?.status || undefined,
|
|
830
831
|
});
|
|
@@ -841,13 +842,13 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
841
842
|
sendWs(ws, {
|
|
842
843
|
type: "tasks",
|
|
843
844
|
payload: {
|
|
844
|
-
|
|
845
|
+
workspaceId,
|
|
845
846
|
tasks: rows.map((r) => {
|
|
846
847
|
const taskSessions = sessionsByTask.get(r.id) ?? [];
|
|
847
848
|
const computed = computeTaskStatus(r.status, taskSessions);
|
|
848
849
|
return {
|
|
849
850
|
id: r.id,
|
|
850
|
-
|
|
851
|
+
workspaceId: r.workspaceId ?? undefined,
|
|
851
852
|
title: r.title,
|
|
852
853
|
description: r.description,
|
|
853
854
|
status: computed.status,
|
|
@@ -868,7 +869,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
868
869
|
break;
|
|
869
870
|
}
|
|
870
871
|
case "create_task": {
|
|
871
|
-
const
|
|
872
|
+
const workspaceId = msg.payload?.workspaceId || undefined;
|
|
872
873
|
const title = msg.payload?.title;
|
|
873
874
|
const requestId = typeof msg.payload?.requestId === "string"
|
|
874
875
|
? msg.payload.requestId
|
|
@@ -880,13 +881,13 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
880
881
|
});
|
|
881
882
|
return;
|
|
882
883
|
}
|
|
883
|
-
let
|
|
884
|
-
if (
|
|
885
|
-
|
|
886
|
-
if (!
|
|
884
|
+
let workspace;
|
|
885
|
+
if (workspaceId) {
|
|
886
|
+
workspace = workspaceStore.getWorkspace(workspaceId);
|
|
887
|
+
if (!workspace) {
|
|
887
888
|
sendWs(ws, {
|
|
888
889
|
type: "create_task_error",
|
|
889
|
-
payload: { message: `
|
|
890
|
+
payload: { message: `Workspace not found: ${workspaceId}`, requestId },
|
|
890
891
|
});
|
|
891
892
|
return;
|
|
892
893
|
}
|
|
@@ -898,8 +899,8 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
898
899
|
const canDecompose = typeof rawCanDecompose === "boolean" ? rawCanDecompose : false;
|
|
899
900
|
try {
|
|
900
901
|
const id = uuid().slice(0, 8);
|
|
901
|
-
taskStore.createTask(id,
|
|
902
|
-
emit("task.created", { taskId: id,
|
|
902
|
+
taskStore.createTask(id, workspaceId, title, msg.payload?.description || "", msg.payload?.dependsOn || [], workspace ? slugify(workspace.name) : "", parentTaskId, canDecompose, msg.payload?.defaultPersonaId || "");
|
|
903
|
+
emit("task.created", { taskId: id, workspaceId, requestId });
|
|
903
904
|
}
|
|
904
905
|
catch (error) {
|
|
905
906
|
const message = error instanceof Error ? error.message : "Failed to create task";
|
|
@@ -947,13 +948,13 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
947
948
|
}
|
|
948
949
|
sessionStore.setSessionTask(lateBindSessionId, updateTaskId);
|
|
949
950
|
try {
|
|
950
|
-
processorRegistry.lateBind(lateBindSessionId, updateTaskId, existingTask.
|
|
951
|
+
processorRegistry.lateBind(lateBindSessionId, updateTaskId, existingTask.workspaceId || undefined);
|
|
951
952
|
}
|
|
952
953
|
catch (err) {
|
|
953
954
|
sendWs(ws, { type: "error", payload: { message: String(err) } });
|
|
954
955
|
return;
|
|
955
956
|
}
|
|
956
|
-
emit("task.started", { taskId: updateTaskId, sessionId: lateBindSessionId,
|
|
957
|
+
emit("task.started", { taskId: updateTaskId, sessionId: lateBindSessionId, workspaceId: existingTask.workspaceId || "" });
|
|
957
958
|
break;
|
|
958
959
|
}
|
|
959
960
|
// Only allow editing not_started tasks (non-late-bind path)
|
|
@@ -982,7 +983,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
982
983
|
? msg.payload.defaultPersonaId
|
|
983
984
|
: undefined;
|
|
984
985
|
taskStore.updateTask(updateTaskId, updatedTitle, updatedDescription, existingTask.status, updatedDependsOn, updatedDefaultPersonaId);
|
|
985
|
-
emit("task.updated", { taskId: updateTaskId,
|
|
986
|
+
emit("task.updated", { taskId: updateTaskId, workspaceId: existingTask.workspaceId || "" });
|
|
986
987
|
break;
|
|
987
988
|
}
|
|
988
989
|
case "start_task": {
|
|
@@ -1033,7 +1034,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1033
1034
|
return;
|
|
1034
1035
|
taskStore.markTaskComplete(taskId, TASK_STATUS.COMPLETE);
|
|
1035
1036
|
const task = taskStore.getTask(taskId);
|
|
1036
|
-
const unblocked = task?.
|
|
1037
|
+
const unblocked = task?.workspaceId ? taskStore.checkAndUnblock(task.workspaceId) : [];
|
|
1037
1038
|
sendWs(ws, {
|
|
1038
1039
|
type: "task_completed",
|
|
1039
1040
|
payload: {
|
|
@@ -1042,7 +1043,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1042
1043
|
},
|
|
1043
1044
|
});
|
|
1044
1045
|
if (task) {
|
|
1045
|
-
emit("task.completed", { taskId,
|
|
1046
|
+
emit("task.completed", { taskId, workspaceId: task.workspaceId || "" });
|
|
1046
1047
|
}
|
|
1047
1048
|
break;
|
|
1048
1049
|
}
|
|
@@ -1092,10 +1093,10 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1092
1093
|
processEventStream(conn.client.resume(powerlineReq), {
|
|
1093
1094
|
sessionId: latestSession.id,
|
|
1094
1095
|
logPath,
|
|
1095
|
-
|
|
1096
|
+
workspaceId: task.workspaceId ?? undefined,
|
|
1096
1097
|
taskId: task.id,
|
|
1097
1098
|
});
|
|
1098
|
-
emit("task.started", { taskId: task.id, sessionId: latestSession.id,
|
|
1099
|
+
emit("task.started", { taskId: task.id, sessionId: latestSession.id, workspaceId: task.workspaceId || "" });
|
|
1099
1100
|
break;
|
|
1100
1101
|
}
|
|
1101
1102
|
case "delete_task": {
|
|
@@ -1144,7 +1145,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1144
1145
|
sendWs(ws, { type: "error", payload: { message: `Failed to delete task ${taskId}: no rows affected` } });
|
|
1145
1146
|
return;
|
|
1146
1147
|
}
|
|
1147
|
-
emit("task.deleted", { taskId,
|
|
1148
|
+
emit("task.deleted", { taskId, workspaceId: deletedTask.workspaceId || "" });
|
|
1148
1149
|
break;
|
|
1149
1150
|
}
|
|
1150
1151
|
// ─── Task Sessions ─────────────────────────────────────
|
|
@@ -1175,17 +1176,17 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1175
1176
|
}
|
|
1176
1177
|
// ─── Findings ──────────────────────────────────────────
|
|
1177
1178
|
case "list_findings": {
|
|
1178
|
-
const
|
|
1179
|
-
if (!
|
|
1179
|
+
const workspaceId = msg.payload?.workspaceId;
|
|
1180
|
+
if (!workspaceId)
|
|
1180
1181
|
return;
|
|
1181
|
-
const rows = findingStore.queryFindings(
|
|
1182
|
+
const rows = findingStore.queryFindings(workspaceId, msg.payload?.categories || undefined, msg.payload?.tags || undefined, msg.payload?.limit || undefined);
|
|
1182
1183
|
sendWs(ws, {
|
|
1183
1184
|
type: "findings",
|
|
1184
1185
|
payload: {
|
|
1185
|
-
|
|
1186
|
+
workspaceId,
|
|
1186
1187
|
findings: rows.map((r) => ({
|
|
1187
1188
|
id: r.id,
|
|
1188
|
-
|
|
1189
|
+
workspaceId: r.workspaceId,
|
|
1189
1190
|
taskId: r.taskId,
|
|
1190
1191
|
sessionId: r.sessionId,
|
|
1191
1192
|
category: r.category,
|
|
@@ -1199,18 +1200,18 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1199
1200
|
break;
|
|
1200
1201
|
}
|
|
1201
1202
|
case "post_finding": {
|
|
1202
|
-
const
|
|
1203
|
+
const workspaceId = msg.payload?.workspaceId;
|
|
1203
1204
|
const title = msg.payload?.title;
|
|
1204
|
-
if (!
|
|
1205
|
+
if (!workspaceId || !title) {
|
|
1205
1206
|
sendWs(ws, {
|
|
1206
1207
|
type: "error",
|
|
1207
|
-
payload: { message: "
|
|
1208
|
+
payload: { message: "workspaceId and title required" },
|
|
1208
1209
|
});
|
|
1209
1210
|
return;
|
|
1210
1211
|
}
|
|
1211
1212
|
const id = uuid().slice(0, 8);
|
|
1212
|
-
findingStore.postFinding(id,
|
|
1213
|
-
sendWs(ws, { type: "finding_posted", payload: { id,
|
|
1213
|
+
findingStore.postFinding(id, workspaceId, msg.payload?.taskId || "", msg.payload?.sessionId || "", msg.payload?.category || "general", title, msg.payload?.content || "", msg.payload?.tags || []);
|
|
1214
|
+
sendWs(ws, { type: "finding_posted", payload: { id, workspaceId } });
|
|
1214
1215
|
break;
|
|
1215
1216
|
}
|
|
1216
1217
|
// ─── Diff ──────────────────────────────────────────────
|
|
@@ -1226,8 +1227,8 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1226
1227
|
});
|
|
1227
1228
|
return;
|
|
1228
1229
|
}
|
|
1229
|
-
const environmentId = task.
|
|
1230
|
-
?
|
|
1230
|
+
const environmentId = task.workspaceId
|
|
1231
|
+
? workspaceStore.getWorkspace(task.workspaceId)?.defaultEnvironmentId
|
|
1231
1232
|
: undefined;
|
|
1232
1233
|
if (!environmentId) {
|
|
1233
1234
|
sendWs(ws, {
|