@grackle-ai/server 0.39.1 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +62 -0
- package/dist/db.js.map +1 -1
- package/dist/env-registry.d.ts +1 -1
- package/dist/env-registry.d.ts.map +1 -1
- package/dist/env-registry.js +1 -2
- package/dist/env-registry.js.map +1 -1
- package/dist/event-bus.d.ts +37 -0
- package/dist/event-bus.d.ts.map +1 -0
- package/dist/event-bus.js +65 -0
- package/dist/event-bus.js.map +1 -0
- package/dist/event-processor.d.ts.map +1 -1
- package/dist/event-processor.js +14 -11
- package/dist/event-processor.js.map +1 -1
- package/dist/event-store.d.ts +9 -0
- package/dist/event-store.d.ts.map +1 -0
- package/dist/event-store.js +16 -0
- package/dist/event-store.js.map +1 -0
- package/dist/github-import.js +3 -5
- package/dist/github-import.js.map +1 -1
- package/dist/grpc-service.d.ts.map +1 -1
- package/dist/grpc-service.js +106 -129
- package/dist/grpc-service.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -7
- package/dist/index.js.map +1 -1
- package/dist/project-store.d.ts +3 -1
- package/dist/project-store.d.ts.map +1 -1
- package/dist/project-store.js +5 -1
- package/dist/project-store.js.map +1 -1
- package/dist/reanimate-agent.d.ts +12 -0
- package/dist/reanimate-agent.d.ts.map +1 -0
- package/dist/reanimate-agent.js +78 -0
- package/dist/reanimate-agent.js.map +1 -0
- package/dist/resolve-persona.d.ts +29 -0
- package/dist/resolve-persona.d.ts.map +1 -0
- package/dist/resolve-persona.js +40 -0
- package/dist/resolve-persona.js.map +1 -0
- package/dist/schema.d.ts +123 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +9 -0
- package/dist/schema.js.map +1 -1
- package/dist/session-store.d.ts +6 -1
- package/dist/session-store.d.ts.map +1 -1
- package/dist/session-store.js +23 -5
- package/dist/session-store.js.map +1 -1
- package/dist/settings-store.d.ts +14 -0
- package/dist/settings-store.d.ts.map +1 -0
- package/dist/settings-store.js +29 -0
- package/dist/settings-store.js.map +1 -0
- package/dist/task-store.d.ts +2 -2
- package/dist/task-store.d.ts.map +1 -1
- package/dist/task-store.js +10 -5
- package/dist/task-store.js.map +1 -1
- package/dist/ws-bridge.d.ts.map +1 -1
- package/dist/ws-bridge.js +163 -164
- package/dist/ws-bridge.js.map +1 -1
- package/dist/ws-broadcast.d.ts +5 -0
- package/dist/ws-broadcast.d.ts.map +1 -1
- package/dist/ws-broadcast.js +24 -1
- package/dist/ws-broadcast.js.map +1 -1
- package/package.json +7 -6
package/dist/ws-bridge.js
CHANGED
|
@@ -14,7 +14,10 @@ import * as findingStore from "./finding-store.js";
|
|
|
14
14
|
import * as personaStore from "./persona-store.js";
|
|
15
15
|
import { v4 as uuid } from "uuid";
|
|
16
16
|
import { join } from "node:path";
|
|
17
|
-
import { LOGS_DIR,
|
|
17
|
+
import { LOGS_DIR, SESSION_STATUS, TASK_STATUS, eventTypeToString, } from "@grackle-ai/common";
|
|
18
|
+
import { resolvePersona } from "./resolve-persona.js";
|
|
19
|
+
import * as settingsStore from "./settings-store.js";
|
|
20
|
+
import { isAllowedSettingKey } from "./settings-store.js";
|
|
18
21
|
import { grackleHome } from "./paths.js";
|
|
19
22
|
import * as logWriter from "./log-writer.js";
|
|
20
23
|
import { safeParseJsonArray } from "./json-helpers.js";
|
|
@@ -23,8 +26,11 @@ import { buildTaskSystemContext } from "./utils/system-context.js";
|
|
|
23
26
|
import { slugify } from "./utils/slugify.js";
|
|
24
27
|
import { processEventStream } from "./event-processor.js";
|
|
25
28
|
import * as processorRegistry from "./processor-registry.js";
|
|
26
|
-
import {
|
|
29
|
+
import { setWssInstance, envRowToWs } from "./ws-broadcast.js";
|
|
30
|
+
import { emit } from "./event-bus.js";
|
|
27
31
|
import { buildMcpServersJson } from "./grpc-service.js";
|
|
32
|
+
import { reanimateAgent } from "./reanimate-agent.js";
|
|
33
|
+
import { ConnectError } from "@connectrpc/connect";
|
|
28
34
|
import { computeTaskStatus } from "./compute-task-status.js";
|
|
29
35
|
import { exec } from "./utils/exec.js";
|
|
30
36
|
import { formatGhError } from "./utils/format-gh-error.js";
|
|
@@ -108,20 +114,17 @@ async function autoProvisionEnvironment(ws, environmentId, env, logContext) {
|
|
|
108
114
|
}
|
|
109
115
|
logger.info({ environmentId, ...logContext }, "Auto-provisioning environment");
|
|
110
116
|
envRegistry.updateEnvironmentStatus(environmentId, "connecting");
|
|
111
|
-
|
|
117
|
+
emit("environment.changed", {});
|
|
112
118
|
try {
|
|
113
119
|
const config = safeParseAdapterConfig(env.adapterConfig, environmentId);
|
|
114
120
|
const powerlineToken = env.powerlineToken || "";
|
|
115
121
|
for await (const provEvent of reconnectOrProvision(environmentId, adapter, config, powerlineToken, !!env.bootstrapped)) {
|
|
116
122
|
logger.info({ environmentId, stage: provEvent.stage, ...logContext }, "Auto-provision progress");
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
message: provEvent.message,
|
|
123
|
-
progress: provEvent.progress,
|
|
124
|
-
},
|
|
123
|
+
emit("environment.provision_progress", {
|
|
124
|
+
environmentId,
|
|
125
|
+
stage: provEvent.stage,
|
|
126
|
+
message: provEvent.message,
|
|
127
|
+
progress: provEvent.progress,
|
|
125
128
|
});
|
|
126
129
|
}
|
|
127
130
|
conn = await adapter.connect(environmentId, config, powerlineToken);
|
|
@@ -130,32 +133,26 @@ async function autoProvisionEnvironment(ws, environmentId, env, logContext) {
|
|
|
130
133
|
await tokenBroker.pushToEnv(environmentId);
|
|
131
134
|
envRegistry.updateEnvironmentStatus(environmentId, "connected");
|
|
132
135
|
envRegistry.markBootstrapped(environmentId);
|
|
133
|
-
|
|
136
|
+
emit("environment.changed", {});
|
|
134
137
|
logger.info({ environmentId, ...logContext }, "Auto-provision complete");
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
message: "Environment connected",
|
|
141
|
-
progress: 1,
|
|
142
|
-
},
|
|
138
|
+
emit("environment.provision_progress", {
|
|
139
|
+
environmentId,
|
|
140
|
+
stage: "ready",
|
|
141
|
+
message: "Environment connected",
|
|
142
|
+
progress: 1,
|
|
143
143
|
});
|
|
144
144
|
return conn;
|
|
145
145
|
}
|
|
146
146
|
catch (err) {
|
|
147
147
|
logger.error({ environmentId, ...logContext, err }, "Auto-provision failed");
|
|
148
148
|
envRegistry.updateEnvironmentStatus(environmentId, "error");
|
|
149
|
-
|
|
149
|
+
emit("environment.changed", {});
|
|
150
150
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
message: `Auto-provision failed: ${errorMessage}`,
|
|
157
|
-
progress: 0,
|
|
158
|
-
},
|
|
151
|
+
emit("environment.provision_progress", {
|
|
152
|
+
environmentId,
|
|
153
|
+
stage: "error",
|
|
154
|
+
message: `Auto-provision failed: ${errorMessage}`,
|
|
155
|
+
progress: 0,
|
|
159
156
|
});
|
|
160
157
|
sendWs(ws, {
|
|
161
158
|
type: "error",
|
|
@@ -192,51 +189,43 @@ async function startTaskSession(ws, task, options) {
|
|
|
192
189
|
if (!conn) {
|
|
193
190
|
return undefined;
|
|
194
191
|
}
|
|
195
|
-
// Resolve persona
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return
|
|
192
|
+
// Resolve persona via cascade (request → task → project → app default)
|
|
193
|
+
let resolved;
|
|
194
|
+
try {
|
|
195
|
+
resolved = resolvePersona(options?.personaId || "", task.defaultPersonaId, project.defaultPersonaId);
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
return err.message;
|
|
202
199
|
}
|
|
203
200
|
const sessionId = uuid();
|
|
204
|
-
const runtime
|
|
205
|
-
const model = options?.model || persona?.model ||
|
|
206
|
-
process.env.GRACKLE_DEFAULT_MODEL || DEFAULT_MODEL;
|
|
207
|
-
const maxTurns = persona?.maxTurns || 0;
|
|
201
|
+
const { runtime, model, maxTurns, systemPrompt, persona: resolvedPersonaRow } = resolved;
|
|
208
202
|
const logPath = join(grackleHome, LOGS_DIR, sessionId);
|
|
209
203
|
const freshTask = taskStore.getTask(task.id) || task;
|
|
210
204
|
let systemContext = buildTaskSystemContext(freshTask.title, freshTask.description, options?.notes || "", freshTask.canDecompose);
|
|
211
|
-
if (
|
|
212
|
-
systemContext =
|
|
205
|
+
if (systemPrompt) {
|
|
206
|
+
systemContext = systemPrompt + "\n\n" + systemContext;
|
|
213
207
|
}
|
|
214
|
-
sessionStore.createSession(sessionId, environmentId, runtime, freshTask.title, model, logPath, freshTask.id,
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
sessionId,
|
|
220
|
-
projectId: freshTask.projectId,
|
|
221
|
-
},
|
|
208
|
+
sessionStore.createSession(sessionId, environmentId, runtime, freshTask.title, model, logPath, freshTask.id, resolved.personaId);
|
|
209
|
+
emit("task.started", {
|
|
210
|
+
taskId: freshTask.id,
|
|
211
|
+
sessionId,
|
|
212
|
+
projectId: freshTask.projectId,
|
|
222
213
|
});
|
|
223
214
|
// Re-push stored tokens + provider credentials (scoped to runtime) so they're fresh for this session.
|
|
224
215
|
// For local envs, skip file tokens — the PowerLine is on the same machine.
|
|
225
216
|
await tokenBroker.refreshTokensForTask(environmentId, runtime, env.adapterType === "local" ? { excludeFileTokens: true } : undefined);
|
|
226
217
|
let mcpServersJson = "";
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
mcpServersJson = buildMcpServersJson(mcpServers);
|
|
234
|
-
}
|
|
218
|
+
try {
|
|
219
|
+
const parsed = JSON.parse(resolvedPersonaRow.mcpServers || "[]");
|
|
220
|
+
if (Array.isArray(parsed)) {
|
|
221
|
+
const mcpServers = parsed;
|
|
222
|
+
if (mcpServers.length > 0) {
|
|
223
|
+
mcpServersJson = buildMcpServersJson(mcpServers);
|
|
235
224
|
}
|
|
236
225
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
226
|
+
}
|
|
227
|
+
catch {
|
|
228
|
+
logger.warn("Failed to parse persona.mcpServers JSON; ignoring");
|
|
240
229
|
}
|
|
241
230
|
const powerlineReq = create(powerline.SpawnRequestSchema, {
|
|
242
231
|
sessionId,
|
|
@@ -371,8 +360,6 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
371
360
|
case "spawn": {
|
|
372
361
|
const environmentId = msg.payload?.environmentId;
|
|
373
362
|
const prompt = msg.payload?.prompt;
|
|
374
|
-
const model = msg.payload?.model || undefined;
|
|
375
|
-
const runtime = msg.payload?.runtime || undefined;
|
|
376
363
|
const branch = msg.payload?.branch || "";
|
|
377
364
|
const systemContext = msg.payload?.systemContext || "";
|
|
378
365
|
const spawnPersonaId = msg.payload?.personaId || "";
|
|
@@ -383,10 +370,13 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
383
370
|
});
|
|
384
371
|
return;
|
|
385
372
|
}
|
|
386
|
-
// Resolve persona
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
373
|
+
// Resolve persona via cascade (request → app default)
|
|
374
|
+
let resolved;
|
|
375
|
+
try {
|
|
376
|
+
resolved = resolvePersona(spawnPersonaId);
|
|
377
|
+
}
|
|
378
|
+
catch (err) {
|
|
379
|
+
sendWs(ws, { type: "error", payload: { message: err.message } });
|
|
390
380
|
return;
|
|
391
381
|
}
|
|
392
382
|
const env = envRegistry.getEnvironment(environmentId);
|
|
@@ -403,13 +393,11 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
403
393
|
return;
|
|
404
394
|
}
|
|
405
395
|
const sessionId = uuid();
|
|
406
|
-
const sessionRuntime
|
|
407
|
-
const sessionModel = model || spawnPersona?.model || process.env.GRACKLE_DEFAULT_MODEL || DEFAULT_MODEL;
|
|
408
|
-
const maxTurns = spawnPersona?.maxTurns || 0;
|
|
396
|
+
const { runtime: sessionRuntime, model: sessionModel, maxTurns, systemPrompt: spawnSystemPrompt } = resolved;
|
|
409
397
|
const logPath = join(grackleHome, LOGS_DIR, sessionId);
|
|
410
398
|
let finalSystemContext = systemContext;
|
|
411
|
-
if (
|
|
412
|
-
finalSystemContext =
|
|
399
|
+
if (spawnSystemPrompt) {
|
|
400
|
+
finalSystemContext = spawnSystemPrompt + (systemContext ? "\n\n" + systemContext : "");
|
|
413
401
|
}
|
|
414
402
|
sessionStore.createSession(sessionId, environmentId, sessionRuntime, prompt, sessionModel, logPath);
|
|
415
403
|
sendWs(ws, { type: "spawned", payload: { sessionId } });
|
|
@@ -532,11 +520,27 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
532
520
|
if (session.taskId) {
|
|
533
521
|
const task = taskStore.getTask(session.taskId);
|
|
534
522
|
if (task) {
|
|
535
|
-
|
|
523
|
+
emit("task.updated", { taskId: task.id, projectId: task.projectId });
|
|
536
524
|
}
|
|
537
525
|
}
|
|
538
526
|
break;
|
|
539
527
|
}
|
|
528
|
+
case "resume_agent": {
|
|
529
|
+
const resumeSessionId = msg.payload?.sessionId;
|
|
530
|
+
if (!resumeSessionId) {
|
|
531
|
+
sendWs(ws, { type: "error", payload: { message: "sessionId required" } });
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
try {
|
|
535
|
+
reanimateAgent(resumeSessionId);
|
|
536
|
+
sendWs(ws, { type: "agent_resumed", payload: { sessionId: resumeSessionId } });
|
|
537
|
+
}
|
|
538
|
+
catch (err) {
|
|
539
|
+
const message = err instanceof ConnectError ? err.message : String(err);
|
|
540
|
+
sendWs(ws, { type: "error", payload: { message } });
|
|
541
|
+
}
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
540
544
|
// ─── Projects ──────────────────────────────────────────
|
|
541
545
|
case "list_projects": {
|
|
542
546
|
const rows = projectStore.listProjects();
|
|
@@ -549,6 +553,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
549
553
|
description: r.description,
|
|
550
554
|
repoUrl: r.repoUrl,
|
|
551
555
|
defaultEnvironmentId: r.defaultEnvironmentId,
|
|
556
|
+
defaultPersonaId: r.defaultPersonaId,
|
|
552
557
|
status: r.status,
|
|
553
558
|
useWorktrees: r.useWorktrees,
|
|
554
559
|
worktreeBasePath: r.worktreeBasePath,
|
|
@@ -575,16 +580,15 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
575
580
|
}
|
|
576
581
|
// useWorktrees defaults to true when not specified
|
|
577
582
|
const createUseWorktrees = msg.payload?.useWorktrees ?? true;
|
|
578
|
-
projectStore.createProject(id, name, msg.payload?.description || "", msg.payload?.repoUrl || "", msg.payload?.defaultEnvironmentId || "", createUseWorktrees, typeof msg.payload?.worktreeBasePath === "string" ? msg.payload.worktreeBasePath.trim() : "");
|
|
579
|
-
|
|
580
|
-
broadcast({ type: "project_created", payload: { project: row } });
|
|
583
|
+
projectStore.createProject(id, name, msg.payload?.description || "", msg.payload?.repoUrl || "", msg.payload?.defaultEnvironmentId || "", createUseWorktrees, typeof msg.payload?.worktreeBasePath === "string" ? msg.payload.worktreeBasePath.trim() : "", msg.payload?.defaultPersonaId || "");
|
|
584
|
+
emit("project.created", { projectId: id });
|
|
581
585
|
break;
|
|
582
586
|
}
|
|
583
587
|
case "archive_project": {
|
|
584
588
|
const projectId = msg.payload?.projectId;
|
|
585
589
|
if (projectId)
|
|
586
590
|
projectStore.archiveProject(projectId);
|
|
587
|
-
|
|
591
|
+
emit("project.archived", { projectId });
|
|
588
592
|
break;
|
|
589
593
|
}
|
|
590
594
|
case "update_project": {
|
|
@@ -612,6 +616,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
612
616
|
}
|
|
613
617
|
const worktreesVal = typeof msg.payload?.useWorktrees === "boolean" ? msg.payload.useWorktrees : undefined;
|
|
614
618
|
const worktreeBasePathVal = typeof msg.payload?.worktreeBasePath === "string" ? msg.payload.worktreeBasePath : undefined;
|
|
619
|
+
const defaultPersonaIdVal = typeof msg.payload?.defaultPersonaId === "string" ? msg.payload.defaultPersonaId : undefined;
|
|
615
620
|
projectStore.updateProject(projectId, {
|
|
616
621
|
name: nameVal !== undefined ? nameVal.trim() : undefined,
|
|
617
622
|
description: descVal,
|
|
@@ -619,8 +624,9 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
619
624
|
defaultEnvironmentId: envVal,
|
|
620
625
|
useWorktrees: worktreesVal,
|
|
621
626
|
worktreeBasePath: worktreeBasePathVal,
|
|
627
|
+
defaultPersonaId: defaultPersonaIdVal,
|
|
622
628
|
});
|
|
623
|
-
|
|
629
|
+
emit("project.updated", { projectId });
|
|
624
630
|
break;
|
|
625
631
|
}
|
|
626
632
|
// ─── Personas ──────────────────────────────────────────
|
|
@@ -666,7 +672,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
666
672
|
personaId = `${slugify(personaName) || "persona"}-${uuid().slice(0, 4)}`;
|
|
667
673
|
}
|
|
668
674
|
personaStore.createPersona(personaId, personaName, msg.payload?.description || "", personaSystemPrompt, msg.payload?.toolConfig || "{}", msg.payload?.runtime || "", msg.payload?.model || "", msg.payload?.maxTurns || 0, msg.payload?.mcpServers || "[]");
|
|
669
|
-
|
|
675
|
+
emit("persona.created", { personaId });
|
|
670
676
|
break;
|
|
671
677
|
}
|
|
672
678
|
case "get_persona": {
|
|
@@ -702,10 +708,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
702
708
|
return;
|
|
703
709
|
}
|
|
704
710
|
personaStore.updatePersona(updatePersonaId, msg.payload?.name || existingPersona.name, msg.payload?.description || existingPersona.description, msg.payload?.systemPrompt || existingPersona.systemPrompt, msg.payload?.toolConfig || existingPersona.toolConfig, msg.payload?.runtime || existingPersona.runtime, msg.payload?.model || existingPersona.model, msg.payload?.maxTurns || existingPersona.maxTurns, msg.payload?.mcpServers || existingPersona.mcpServers);
|
|
705
|
-
|
|
706
|
-
type: "persona_updated",
|
|
707
|
-
payload: { personaId: updatePersonaId },
|
|
708
|
-
});
|
|
711
|
+
emit("persona.updated", { personaId: updatePersonaId });
|
|
709
712
|
break;
|
|
710
713
|
}
|
|
711
714
|
case "delete_persona": {
|
|
@@ -713,10 +716,45 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
713
716
|
if (!deletePersonaId)
|
|
714
717
|
return;
|
|
715
718
|
personaStore.deletePersona(deletePersonaId);
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
719
|
+
emit("persona.deleted", { personaId: deletePersonaId });
|
|
720
|
+
break;
|
|
721
|
+
}
|
|
722
|
+
// ─── Settings ────────────────────────────────────────────
|
|
723
|
+
case "get_setting": {
|
|
724
|
+
const key = msg.payload?.key;
|
|
725
|
+
if (typeof key !== "string" || !key)
|
|
726
|
+
return;
|
|
727
|
+
if (!isAllowedSettingKey(key)) {
|
|
728
|
+
sendWs(ws, { type: "error", payload: { message: `Setting key not allowed: ${key}` } });
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
const value = settingsStore.getSetting(key) ?? "";
|
|
732
|
+
sendWs(ws, { type: "setting", payload: { key, value } });
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
735
|
+
case "set_setting": {
|
|
736
|
+
const key = msg.payload?.key;
|
|
737
|
+
const value = msg.payload?.value || "";
|
|
738
|
+
if (typeof key !== "string" || !key)
|
|
739
|
+
return;
|
|
740
|
+
if (!isAllowedSettingKey(key)) {
|
|
741
|
+
sendWs(ws, { type: "error", payload: { message: `Setting key not allowed: ${key}` } });
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
// Validate persona exists and has required fields when setting default_persona_id
|
|
745
|
+
if (key === "default_persona_id" && value) {
|
|
746
|
+
const persona = personaStore.getPersona(value);
|
|
747
|
+
if (!persona) {
|
|
748
|
+
sendWs(ws, { type: "error", payload: { message: `Persona not found: ${value}` } });
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
if (!persona.runtime || !persona.model) {
|
|
752
|
+
sendWs(ws, { type: "error", payload: { message: `Persona "${persona.name}" must have runtime and model configured` } });
|
|
753
|
+
return;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
settingsStore.setSetting(key, value);
|
|
757
|
+
emit("setting.changed", { key, value });
|
|
720
758
|
break;
|
|
721
759
|
}
|
|
722
760
|
// ─── Tasks ─────────────────────────────────────────────
|
|
@@ -760,6 +798,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
760
798
|
depth: r.depth,
|
|
761
799
|
childTaskIds: childIdsMap.get(r.id) ?? [],
|
|
762
800
|
canDecompose: r.canDecompose,
|
|
801
|
+
defaultPersonaId: r.defaultPersonaId,
|
|
763
802
|
};
|
|
764
803
|
}),
|
|
765
804
|
},
|
|
@@ -794,17 +833,8 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
794
833
|
const canDecompose = typeof rawCanDecompose === "boolean" ? rawCanDecompose : false;
|
|
795
834
|
try {
|
|
796
835
|
const id = uuid().slice(0, 8);
|
|
797
|
-
taskStore.createTask(id, projectId, title, msg.payload?.description || "", msg.payload?.dependsOn || [], slugify(project.name), parentTaskId, canDecompose);
|
|
798
|
-
|
|
799
|
-
broadcast({
|
|
800
|
-
type: "task_created",
|
|
801
|
-
payload: {
|
|
802
|
-
task: row
|
|
803
|
-
? { ...row, dependsOn: safeParseJsonArray(row.dependsOn) }
|
|
804
|
-
: null,
|
|
805
|
-
requestId,
|
|
806
|
-
},
|
|
807
|
-
});
|
|
836
|
+
taskStore.createTask(id, projectId, title, msg.payload?.description || "", msg.payload?.dependsOn || [], slugify(project.name), parentTaskId, canDecompose, msg.payload?.defaultPersonaId || "");
|
|
837
|
+
emit("task.created", { taskId: id, projectId, requestId });
|
|
808
838
|
}
|
|
809
839
|
catch (error) {
|
|
810
840
|
const message = error instanceof Error ? error.message : "Failed to create task";
|
|
@@ -858,10 +888,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
858
888
|
sendWs(ws, { type: "error", payload: { message: String(err) } });
|
|
859
889
|
return;
|
|
860
890
|
}
|
|
861
|
-
|
|
862
|
-
type: "task_started",
|
|
863
|
-
payload: { taskId: updateTaskId, sessionId: lateBindSessionId, projectId: existingTask.projectId },
|
|
864
|
-
});
|
|
891
|
+
emit("task.started", { taskId: updateTaskId, sessionId: lateBindSessionId, projectId: existingTask.projectId });
|
|
865
892
|
break;
|
|
866
893
|
}
|
|
867
894
|
// Only allow editing not_started tasks (non-late-bind path)
|
|
@@ -886,18 +913,11 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
886
913
|
.filter((d) => d !== updateTaskId)),
|
|
887
914
|
]
|
|
888
915
|
: safeParseJsonArray(existingTask.dependsOn);
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
taskId: updateTaskId,
|
|
895
|
-
projectId: existingTask.projectId,
|
|
896
|
-
task: updatedRow
|
|
897
|
-
? { ...updatedRow, dependsOn: safeParseJsonArray(updatedRow.dependsOn) }
|
|
898
|
-
: null,
|
|
899
|
-
},
|
|
900
|
-
});
|
|
916
|
+
const updatedDefaultPersonaId = typeof msg.payload?.defaultPersonaId === "string"
|
|
917
|
+
? msg.payload.defaultPersonaId
|
|
918
|
+
: undefined;
|
|
919
|
+
taskStore.updateTask(updateTaskId, updatedTitle, updatedDescription, existingTask.status, updatedDependsOn, updatedDefaultPersonaId);
|
|
920
|
+
emit("task.updated", { taskId: updateTaskId, projectId: existingTask.projectId });
|
|
901
921
|
break;
|
|
902
922
|
}
|
|
903
923
|
case "start_task": {
|
|
@@ -933,8 +953,6 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
933
953
|
return;
|
|
934
954
|
}
|
|
935
955
|
const startError = await startTaskSession(ws, task, {
|
|
936
|
-
runtime: msg.payload?.runtime,
|
|
937
|
-
model: msg.payload?.model,
|
|
938
956
|
personaId: msg.payload?.personaId || undefined,
|
|
939
957
|
environmentId: msg.payload?.environmentId || undefined,
|
|
940
958
|
notes: msg.payload?.notes || undefined,
|
|
@@ -959,10 +977,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
959
977
|
},
|
|
960
978
|
});
|
|
961
979
|
if (task) {
|
|
962
|
-
|
|
963
|
-
type: "task_completed",
|
|
964
|
-
payload: { taskId, projectId: task.projectId },
|
|
965
|
-
});
|
|
980
|
+
emit("task.completed", { taskId, projectId: task.projectId });
|
|
966
981
|
}
|
|
967
982
|
break;
|
|
968
983
|
}
|
|
@@ -1015,10 +1030,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1015
1030
|
projectId: task.projectId,
|
|
1016
1031
|
taskId: task.id,
|
|
1017
1032
|
});
|
|
1018
|
-
|
|
1019
|
-
type: "task_started",
|
|
1020
|
-
payload: { taskId: task.id, sessionId: latestSession.id, projectId: task.projectId },
|
|
1021
|
-
});
|
|
1033
|
+
emit("task.started", { taskId: task.id, sessionId: latestSession.id, projectId: task.projectId });
|
|
1022
1034
|
break;
|
|
1023
1035
|
}
|
|
1024
1036
|
case "delete_task": {
|
|
@@ -1067,7 +1079,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1067
1079
|
sendWs(ws, { type: "error", payload: { message: `Failed to delete task ${taskId}: no rows affected` } });
|
|
1068
1080
|
return;
|
|
1069
1081
|
}
|
|
1070
|
-
|
|
1082
|
+
emit("task.deleted", { taskId, projectId: deletedTask.projectId });
|
|
1071
1083
|
break;
|
|
1072
1084
|
}
|
|
1073
1085
|
// ─── Task Sessions ─────────────────────────────────────
|
|
@@ -1218,7 +1230,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1218
1230
|
}
|
|
1219
1231
|
logger.info({ environmentId, adapterType: env.adapterType }, "Provisioning environment");
|
|
1220
1232
|
envRegistry.updateEnvironmentStatus(environmentId, "connecting");
|
|
1221
|
-
|
|
1233
|
+
emit("environment.changed", {});
|
|
1222
1234
|
// Run provision in background, broadcasting progress to all connected clients
|
|
1223
1235
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
|
1224
1236
|
(async () => {
|
|
@@ -1227,14 +1239,11 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1227
1239
|
const powerlineToken = env.powerlineToken || "";
|
|
1228
1240
|
for await (const event of reconnectOrProvision(environmentId, adapter, config, powerlineToken, !!env.bootstrapped)) {
|
|
1229
1241
|
logger.info({ environmentId, stage: event.stage, message: event.message }, "Provision progress");
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
message: event.message,
|
|
1236
|
-
progress: event.progress,
|
|
1237
|
-
},
|
|
1242
|
+
emit("environment.provision_progress", {
|
|
1243
|
+
environmentId,
|
|
1244
|
+
stage: event.stage,
|
|
1245
|
+
message: event.message,
|
|
1246
|
+
progress: event.progress,
|
|
1238
1247
|
});
|
|
1239
1248
|
}
|
|
1240
1249
|
logger.info({ environmentId }, "Provision complete, calling adapter.connect");
|
|
@@ -1245,31 +1254,25 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1245
1254
|
envRegistry.updateEnvironmentStatus(environmentId, "connected");
|
|
1246
1255
|
envRegistry.markBootstrapped(environmentId);
|
|
1247
1256
|
logger.info({ environmentId }, "Environment connected");
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
message: "Environment connected",
|
|
1254
|
-
progress: 1,
|
|
1255
|
-
},
|
|
1257
|
+
emit("environment.provision_progress", {
|
|
1258
|
+
environmentId,
|
|
1259
|
+
stage: "ready",
|
|
1260
|
+
message: "Environment connected",
|
|
1261
|
+
progress: 1,
|
|
1256
1262
|
});
|
|
1257
1263
|
}
|
|
1258
1264
|
catch (err) {
|
|
1259
1265
|
logger.error({ environmentId, err }, "Provision failed");
|
|
1260
1266
|
envRegistry.updateEnvironmentStatus(environmentId, "error");
|
|
1261
1267
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
message: `Connection failed: ${errorMessage}`,
|
|
1268
|
-
progress: 0,
|
|
1269
|
-
},
|
|
1268
|
+
emit("environment.provision_progress", {
|
|
1269
|
+
environmentId,
|
|
1270
|
+
stage: "error",
|
|
1271
|
+
message: `Connection failed: ${errorMessage}`,
|
|
1272
|
+
progress: 0,
|
|
1270
1273
|
});
|
|
1271
1274
|
}
|
|
1272
|
-
|
|
1275
|
+
emit("environment.changed", {});
|
|
1273
1276
|
})();
|
|
1274
1277
|
break;
|
|
1275
1278
|
}
|
|
@@ -1298,7 +1301,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1298
1301
|
adapterManager.removeConnection(environmentId);
|
|
1299
1302
|
envRegistry.updateEnvironmentStatus(environmentId, "disconnected");
|
|
1300
1303
|
logger.info({ environmentId }, "Environment stopped");
|
|
1301
|
-
|
|
1304
|
+
emit("environment.changed", {});
|
|
1302
1305
|
break;
|
|
1303
1306
|
}
|
|
1304
1307
|
case "add_environment": {
|
|
@@ -1357,11 +1360,10 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1357
1360
|
});
|
|
1358
1361
|
return;
|
|
1359
1362
|
}
|
|
1360
|
-
|
|
1361
|
-
envRegistry.addEnvironment(id, displayName, adapterType, adapterConfig, defaultRuntime);
|
|
1363
|
+
envRegistry.addEnvironment(id, displayName, adapterType, adapterConfig);
|
|
1362
1364
|
logger.info({ id, displayName, adapterType }, "Environment added via WebSocket");
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
+
emit("environment.added", { environmentId: id });
|
|
1366
|
+
emit("environment.changed", {});
|
|
1365
1367
|
break;
|
|
1366
1368
|
}
|
|
1367
1369
|
case "remove_environment": {
|
|
@@ -1396,8 +1398,8 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1396
1398
|
sessionStore.deleteByEnvironment(environmentId);
|
|
1397
1399
|
envRegistry.removeEnvironment(environmentId);
|
|
1398
1400
|
logger.info({ environmentId }, "Environment removed");
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
+
emit("environment.removed", { environmentId });
|
|
1402
|
+
emit("environment.changed", {});
|
|
1401
1403
|
break;
|
|
1402
1404
|
}
|
|
1403
1405
|
// ─── Codespaces ─────────────────────────────────────
|
|
@@ -1497,7 +1499,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1497
1499
|
value,
|
|
1498
1500
|
expiresAt: msg.payload?.expiresAt || "",
|
|
1499
1501
|
});
|
|
1500
|
-
|
|
1502
|
+
emit("token.changed", {});
|
|
1501
1503
|
break;
|
|
1502
1504
|
}
|
|
1503
1505
|
case "delete_token": {
|
|
@@ -1507,7 +1509,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1507
1509
|
return;
|
|
1508
1510
|
}
|
|
1509
1511
|
await tokenBroker.deleteToken(tokenName);
|
|
1510
|
-
|
|
1512
|
+
emit("token.changed", {});
|
|
1511
1513
|
break;
|
|
1512
1514
|
}
|
|
1513
1515
|
case "get_credential_providers": {
|
|
@@ -1524,10 +1526,7 @@ async function handleMessage(ws, msg, subscriptions) {
|
|
|
1524
1526
|
return;
|
|
1525
1527
|
}
|
|
1526
1528
|
credentialProviders.setCredentialProviders(msg.payload);
|
|
1527
|
-
|
|
1528
|
-
type: "credential_providers",
|
|
1529
|
-
payload: credentialProviders.getCredentialProviders(),
|
|
1530
|
-
});
|
|
1529
|
+
emit("credential.providers_changed", credentialProviders.getCredentialProviders());
|
|
1531
1530
|
break;
|
|
1532
1531
|
}
|
|
1533
1532
|
}
|