@grackle-ai/server 0.45.1 → 0.47.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.
- 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.js +70 -70
- 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 +76 -76
- 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,
|
|
@@ -243,10 +243,10 @@ async function startTaskSession(ws, task, options) {
|
|
|
243
243
|
maxTurns,
|
|
244
244
|
branch: freshTask.branch,
|
|
245
245
|
worktreeBasePath: freshTask.branch && useWorktrees
|
|
246
|
-
? (
|
|
246
|
+
? (workspace?.worktreeBasePath || process.env.GRACKLE_WORKTREE_BASE || "/workspace")
|
|
247
247
|
: "",
|
|
248
248
|
systemContext,
|
|
249
|
-
|
|
249
|
+
workspaceId: freshTask.workspaceId ?? undefined,
|
|
250
250
|
taskId: freshTask.id,
|
|
251
251
|
mcpServersJson,
|
|
252
252
|
mcpUrl,
|
|
@@ -255,7 +255,7 @@ async function startTaskSession(ws, task, options) {
|
|
|
255
255
|
processEventStream(conn.client.spawn(powerlineReq), {
|
|
256
256
|
sessionId,
|
|
257
257
|
logPath,
|
|
258
|
-
|
|
258
|
+
workspaceId: freshTask.workspaceId ?? undefined,
|
|
259
259
|
taskId: freshTask.id,
|
|
260
260
|
});
|
|
261
261
|
return undefined;
|
|
@@ -530,7 +530,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
530
530
|
if (session.taskId) {
|
|
531
531
|
const task = taskStore.getTask(session.taskId);
|
|
532
532
|
if (task) {
|
|
533
|
-
emit("task.updated", { taskId: task.id,
|
|
533
|
+
emit("task.updated", { taskId: task.id, workspaceId: task.workspaceId || "" });
|
|
534
534
|
}
|
|
535
535
|
}
|
|
536
536
|
break;
|
|
@@ -563,7 +563,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
563
563
|
// Mark task complete (same as "complete_task" handler)
|
|
564
564
|
taskStore.markTaskComplete(taskId, TASK_STATUS.COMPLETE);
|
|
565
565
|
const stoppedTask = taskStore.getTask(taskId);
|
|
566
|
-
const unblocked = stoppedTask?.
|
|
566
|
+
const unblocked = stoppedTask?.workspaceId ? taskStore.checkAndUnblock(stoppedTask.workspaceId) : [];
|
|
567
567
|
sendWs(ws, {
|
|
568
568
|
type: "task_completed",
|
|
569
569
|
payload: {
|
|
@@ -572,7 +572,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
572
572
|
},
|
|
573
573
|
});
|
|
574
574
|
if (stoppedTask) {
|
|
575
|
-
emit("task.completed", { taskId,
|
|
575
|
+
emit("task.completed", { taskId, workspaceId: stoppedTask.workspaceId || "" });
|
|
576
576
|
}
|
|
577
577
|
break;
|
|
578
578
|
}
|
|
@@ -592,13 +592,13 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
592
592
|
}
|
|
593
593
|
break;
|
|
594
594
|
}
|
|
595
|
-
// ───
|
|
596
|
-
case "
|
|
597
|
-
const rows =
|
|
595
|
+
// ─── Workspaces ────────────────────────────────────────
|
|
596
|
+
case "list_workspaces": {
|
|
597
|
+
const rows = workspaceStore.listWorkspaces();
|
|
598
598
|
sendWs(ws, {
|
|
599
|
-
type: "
|
|
599
|
+
type: "workspaces",
|
|
600
600
|
payload: {
|
|
601
|
-
|
|
601
|
+
workspaces: rows.map((r) => ({
|
|
602
602
|
id: r.id,
|
|
603
603
|
name: r.name,
|
|
604
604
|
description: r.description,
|
|
@@ -615,47 +615,47 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
615
615
|
});
|
|
616
616
|
break;
|
|
617
617
|
}
|
|
618
|
-
case "
|
|
618
|
+
case "create_workspace": {
|
|
619
619
|
const name = msg.payload?.name;
|
|
620
620
|
if (!name) {
|
|
621
621
|
sendWs(ws, { type: "error", payload: { message: "name required" } });
|
|
622
622
|
return;
|
|
623
623
|
}
|
|
624
|
-
const
|
|
625
|
-
let id =
|
|
626
|
-
for (let attempt = 0; attempt < 10 &&
|
|
627
|
-
id = `${
|
|
624
|
+
const baseWorkspaceId = slugify(name) || uuid().slice(0, 8);
|
|
625
|
+
let id = baseWorkspaceId;
|
|
626
|
+
for (let attempt = 0; attempt < 10 && workspaceStore.getWorkspace(id); attempt++) {
|
|
627
|
+
id = `${baseWorkspaceId}-${uuid().slice(0, 4)}`;
|
|
628
628
|
}
|
|
629
|
-
if (
|
|
629
|
+
if (workspaceStore.getWorkspace(id)) {
|
|
630
630
|
id = uuid();
|
|
631
631
|
}
|
|
632
632
|
// useWorktrees defaults to true when not specified
|
|
633
633
|
const createUseWorktrees = msg.payload?.useWorktrees ?? true;
|
|
634
|
-
|
|
635
|
-
emit("
|
|
634
|
+
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 || "");
|
|
635
|
+
emit("workspace.created", { workspaceId: id });
|
|
636
636
|
break;
|
|
637
637
|
}
|
|
638
|
-
case "
|
|
639
|
-
const
|
|
640
|
-
if (
|
|
641
|
-
|
|
642
|
-
emit("
|
|
638
|
+
case "archive_workspace": {
|
|
639
|
+
const workspaceId = msg.payload?.workspaceId;
|
|
640
|
+
if (workspaceId)
|
|
641
|
+
workspaceStore.archiveWorkspace(workspaceId);
|
|
642
|
+
emit("workspace.archived", { workspaceId });
|
|
643
643
|
break;
|
|
644
644
|
}
|
|
645
|
-
case "
|
|
646
|
-
const
|
|
647
|
-
if (!
|
|
648
|
-
sendWs(ws, { type: "error", payload: { message: "
|
|
645
|
+
case "update_workspace": {
|
|
646
|
+
const workspaceId = msg.payload?.workspaceId;
|
|
647
|
+
if (!workspaceId) {
|
|
648
|
+
sendWs(ws, { type: "error", payload: { message: "workspaceId required" } });
|
|
649
649
|
return;
|
|
650
650
|
}
|
|
651
|
-
const existing =
|
|
651
|
+
const existing = workspaceStore.getWorkspace(workspaceId);
|
|
652
652
|
if (!existing) {
|
|
653
|
-
sendWs(ws, { type: "error", payload: { message: `
|
|
653
|
+
sendWs(ws, { type: "error", payload: { message: `Workspace not found: ${workspaceId}` } });
|
|
654
654
|
return;
|
|
655
655
|
}
|
|
656
656
|
const nameVal = typeof msg.payload?.name === "string" ? msg.payload.name : undefined;
|
|
657
657
|
if (nameVal?.trim() === "") {
|
|
658
|
-
sendWs(ws, { type: "error", payload: { message: "
|
|
658
|
+
sendWs(ws, { type: "error", payload: { message: "Workspace name cannot be empty" } });
|
|
659
659
|
return;
|
|
660
660
|
}
|
|
661
661
|
const descVal = typeof msg.payload?.description === "string" ? msg.payload.description : undefined;
|
|
@@ -668,7 +668,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
668
668
|
const worktreesVal = typeof msg.payload?.useWorktrees === "boolean" ? msg.payload.useWorktrees : undefined;
|
|
669
669
|
const worktreeBasePathVal = typeof msg.payload?.worktreeBasePath === "string" ? msg.payload.worktreeBasePath : undefined;
|
|
670
670
|
const defaultPersonaIdVal = typeof msg.payload?.defaultPersonaId === "string" ? msg.payload.defaultPersonaId : undefined;
|
|
671
|
-
|
|
671
|
+
workspaceStore.updateWorkspace(workspaceId, {
|
|
672
672
|
name: nameVal !== undefined ? nameVal.trim() : undefined,
|
|
673
673
|
description: descVal,
|
|
674
674
|
repoUrl: repoVal,
|
|
@@ -677,7 +677,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
677
677
|
worktreeBasePath: worktreeBasePathVal,
|
|
678
678
|
defaultPersonaId: defaultPersonaIdVal,
|
|
679
679
|
});
|
|
680
|
-
emit("
|
|
680
|
+
emit("workspace.updated", { workspaceId });
|
|
681
681
|
break;
|
|
682
682
|
}
|
|
683
683
|
// ─── Personas ──────────────────────────────────────────
|
|
@@ -823,8 +823,8 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
823
823
|
}
|
|
824
824
|
// ─── Tasks ─────────────────────────────────────────────
|
|
825
825
|
case "list_tasks": {
|
|
826
|
-
const
|
|
827
|
-
const rows = taskStore.listTasks(
|
|
826
|
+
const workspaceId = msg.payload?.workspaceId || undefined;
|
|
827
|
+
const rows = taskStore.listTasks(workspaceId, {
|
|
828
828
|
search: msg.payload?.search || undefined,
|
|
829
829
|
status: msg.payload?.status || undefined,
|
|
830
830
|
});
|
|
@@ -841,13 +841,13 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
841
841
|
sendWs(ws, {
|
|
842
842
|
type: "tasks",
|
|
843
843
|
payload: {
|
|
844
|
-
|
|
844
|
+
workspaceId,
|
|
845
845
|
tasks: rows.map((r) => {
|
|
846
846
|
const taskSessions = sessionsByTask.get(r.id) ?? [];
|
|
847
847
|
const computed = computeTaskStatus(r.status, taskSessions);
|
|
848
848
|
return {
|
|
849
849
|
id: r.id,
|
|
850
|
-
|
|
850
|
+
workspaceId: r.workspaceId ?? undefined,
|
|
851
851
|
title: r.title,
|
|
852
852
|
description: r.description,
|
|
853
853
|
status: computed.status,
|
|
@@ -868,7 +868,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
868
868
|
break;
|
|
869
869
|
}
|
|
870
870
|
case "create_task": {
|
|
871
|
-
const
|
|
871
|
+
const workspaceId = msg.payload?.workspaceId || undefined;
|
|
872
872
|
const title = msg.payload?.title;
|
|
873
873
|
const requestId = typeof msg.payload?.requestId === "string"
|
|
874
874
|
? msg.payload.requestId
|
|
@@ -880,13 +880,13 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
880
880
|
});
|
|
881
881
|
return;
|
|
882
882
|
}
|
|
883
|
-
let
|
|
884
|
-
if (
|
|
885
|
-
|
|
886
|
-
if (!
|
|
883
|
+
let workspace;
|
|
884
|
+
if (workspaceId) {
|
|
885
|
+
workspace = workspaceStore.getWorkspace(workspaceId);
|
|
886
|
+
if (!workspace) {
|
|
887
887
|
sendWs(ws, {
|
|
888
888
|
type: "create_task_error",
|
|
889
|
-
payload: { message: `
|
|
889
|
+
payload: { message: `Workspace not found: ${workspaceId}`, requestId },
|
|
890
890
|
});
|
|
891
891
|
return;
|
|
892
892
|
}
|
|
@@ -898,8 +898,8 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
898
898
|
const canDecompose = typeof rawCanDecompose === "boolean" ? rawCanDecompose : false;
|
|
899
899
|
try {
|
|
900
900
|
const id = uuid().slice(0, 8);
|
|
901
|
-
taskStore.createTask(id,
|
|
902
|
-
emit("task.created", { taskId: id,
|
|
901
|
+
taskStore.createTask(id, workspaceId, title, msg.payload?.description || "", msg.payload?.dependsOn || [], workspace ? slugify(workspace.name) : "", parentTaskId, canDecompose, msg.payload?.defaultPersonaId || "");
|
|
902
|
+
emit("task.created", { taskId: id, workspaceId, requestId });
|
|
903
903
|
}
|
|
904
904
|
catch (error) {
|
|
905
905
|
const message = error instanceof Error ? error.message : "Failed to create task";
|
|
@@ -947,13 +947,13 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
947
947
|
}
|
|
948
948
|
sessionStore.setSessionTask(lateBindSessionId, updateTaskId);
|
|
949
949
|
try {
|
|
950
|
-
processorRegistry.lateBind(lateBindSessionId, updateTaskId, existingTask.
|
|
950
|
+
processorRegistry.lateBind(lateBindSessionId, updateTaskId, existingTask.workspaceId || undefined);
|
|
951
951
|
}
|
|
952
952
|
catch (err) {
|
|
953
953
|
sendWs(ws, { type: "error", payload: { message: String(err) } });
|
|
954
954
|
return;
|
|
955
955
|
}
|
|
956
|
-
emit("task.started", { taskId: updateTaskId, sessionId: lateBindSessionId,
|
|
956
|
+
emit("task.started", { taskId: updateTaskId, sessionId: lateBindSessionId, workspaceId: existingTask.workspaceId || "" });
|
|
957
957
|
break;
|
|
958
958
|
}
|
|
959
959
|
// Only allow editing not_started tasks (non-late-bind path)
|
|
@@ -982,7 +982,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
982
982
|
? msg.payload.defaultPersonaId
|
|
983
983
|
: undefined;
|
|
984
984
|
taskStore.updateTask(updateTaskId, updatedTitle, updatedDescription, existingTask.status, updatedDependsOn, updatedDefaultPersonaId);
|
|
985
|
-
emit("task.updated", { taskId: updateTaskId,
|
|
985
|
+
emit("task.updated", { taskId: updateTaskId, workspaceId: existingTask.workspaceId || "" });
|
|
986
986
|
break;
|
|
987
987
|
}
|
|
988
988
|
case "start_task": {
|
|
@@ -1033,7 +1033,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1033
1033
|
return;
|
|
1034
1034
|
taskStore.markTaskComplete(taskId, TASK_STATUS.COMPLETE);
|
|
1035
1035
|
const task = taskStore.getTask(taskId);
|
|
1036
|
-
const unblocked = task?.
|
|
1036
|
+
const unblocked = task?.workspaceId ? taskStore.checkAndUnblock(task.workspaceId) : [];
|
|
1037
1037
|
sendWs(ws, {
|
|
1038
1038
|
type: "task_completed",
|
|
1039
1039
|
payload: {
|
|
@@ -1042,7 +1042,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1042
1042
|
},
|
|
1043
1043
|
});
|
|
1044
1044
|
if (task) {
|
|
1045
|
-
emit("task.completed", { taskId,
|
|
1045
|
+
emit("task.completed", { taskId, workspaceId: task.workspaceId || "" });
|
|
1046
1046
|
}
|
|
1047
1047
|
break;
|
|
1048
1048
|
}
|
|
@@ -1092,10 +1092,10 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1092
1092
|
processEventStream(conn.client.resume(powerlineReq), {
|
|
1093
1093
|
sessionId: latestSession.id,
|
|
1094
1094
|
logPath,
|
|
1095
|
-
|
|
1095
|
+
workspaceId: task.workspaceId ?? undefined,
|
|
1096
1096
|
taskId: task.id,
|
|
1097
1097
|
});
|
|
1098
|
-
emit("task.started", { taskId: task.id, sessionId: latestSession.id,
|
|
1098
|
+
emit("task.started", { taskId: task.id, sessionId: latestSession.id, workspaceId: task.workspaceId || "" });
|
|
1099
1099
|
break;
|
|
1100
1100
|
}
|
|
1101
1101
|
case "delete_task": {
|
|
@@ -1144,7 +1144,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1144
1144
|
sendWs(ws, { type: "error", payload: { message: `Failed to delete task ${taskId}: no rows affected` } });
|
|
1145
1145
|
return;
|
|
1146
1146
|
}
|
|
1147
|
-
emit("task.deleted", { taskId,
|
|
1147
|
+
emit("task.deleted", { taskId, workspaceId: deletedTask.workspaceId || "" });
|
|
1148
1148
|
break;
|
|
1149
1149
|
}
|
|
1150
1150
|
// ─── Task Sessions ─────────────────────────────────────
|
|
@@ -1175,17 +1175,17 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1175
1175
|
}
|
|
1176
1176
|
// ─── Findings ──────────────────────────────────────────
|
|
1177
1177
|
case "list_findings": {
|
|
1178
|
-
const
|
|
1179
|
-
if (!
|
|
1178
|
+
const workspaceId = msg.payload?.workspaceId;
|
|
1179
|
+
if (!workspaceId)
|
|
1180
1180
|
return;
|
|
1181
|
-
const rows = findingStore.queryFindings(
|
|
1181
|
+
const rows = findingStore.queryFindings(workspaceId, msg.payload?.categories || undefined, msg.payload?.tags || undefined, msg.payload?.limit || undefined);
|
|
1182
1182
|
sendWs(ws, {
|
|
1183
1183
|
type: "findings",
|
|
1184
1184
|
payload: {
|
|
1185
|
-
|
|
1185
|
+
workspaceId,
|
|
1186
1186
|
findings: rows.map((r) => ({
|
|
1187
1187
|
id: r.id,
|
|
1188
|
-
|
|
1188
|
+
workspaceId: r.workspaceId,
|
|
1189
1189
|
taskId: r.taskId,
|
|
1190
1190
|
sessionId: r.sessionId,
|
|
1191
1191
|
category: r.category,
|
|
@@ -1199,18 +1199,18 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1199
1199
|
break;
|
|
1200
1200
|
}
|
|
1201
1201
|
case "post_finding": {
|
|
1202
|
-
const
|
|
1202
|
+
const workspaceId = msg.payload?.workspaceId;
|
|
1203
1203
|
const title = msg.payload?.title;
|
|
1204
|
-
if (!
|
|
1204
|
+
if (!workspaceId || !title) {
|
|
1205
1205
|
sendWs(ws, {
|
|
1206
1206
|
type: "error",
|
|
1207
|
-
payload: { message: "
|
|
1207
|
+
payload: { message: "workspaceId and title required" },
|
|
1208
1208
|
});
|
|
1209
1209
|
return;
|
|
1210
1210
|
}
|
|
1211
1211
|
const id = uuid().slice(0, 8);
|
|
1212
|
-
findingStore.postFinding(id,
|
|
1213
|
-
sendWs(ws, { type: "finding_posted", payload: { id,
|
|
1212
|
+
findingStore.postFinding(id, workspaceId, msg.payload?.taskId || "", msg.payload?.sessionId || "", msg.payload?.category || "general", title, msg.payload?.content || "", msg.payload?.tags || []);
|
|
1213
|
+
sendWs(ws, { type: "finding_posted", payload: { id, workspaceId } });
|
|
1214
1214
|
break;
|
|
1215
1215
|
}
|
|
1216
1216
|
// ─── Diff ──────────────────────────────────────────────
|
|
@@ -1226,8 +1226,8 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1226
1226
|
});
|
|
1227
1227
|
return;
|
|
1228
1228
|
}
|
|
1229
|
-
const environmentId = task.
|
|
1230
|
-
?
|
|
1229
|
+
const environmentId = task.workspaceId
|
|
1230
|
+
? workspaceStore.getWorkspace(task.workspaceId)?.defaultEnvironmentId
|
|
1231
1231
|
: undefined;
|
|
1232
1232
|
if (!environmentId) {
|
|
1233
1233
|
sendWs(ws, {
|