@agentrific/server 0.3.0 → 0.5.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/CHANGELOG.md +14 -0
- package/cli.js +334 -103
- package/cli.js.map +4 -4
- package/index.js +318 -102
- package/index.js.map +4 -4
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -455,6 +455,7 @@ var RunPreparationSchema = z2.object({
|
|
|
455
455
|
var SessionSchema = z2.object({
|
|
456
456
|
id: z2.string(),
|
|
457
457
|
taskId: z2.string().nullable().optional(),
|
|
458
|
+
kind: z2.enum(["task", "repo_chat"]).optional(),
|
|
458
459
|
projectId: z2.string(),
|
|
459
460
|
title: z2.string(),
|
|
460
461
|
status: SessionStatusSchema,
|
|
@@ -585,11 +586,25 @@ var ConnectionsStatusSchema = z2.object({
|
|
|
585
586
|
githubCli: z2.object({ installed: z2.boolean(), authenticated: z2.boolean() })
|
|
586
587
|
});
|
|
587
588
|
var UpdateSettingsSchema = z2.object({ projectsRoot: z2.string().trim().min(1).max(4096) });
|
|
589
|
+
var RepositoryIdentitySchema = z2.object({
|
|
590
|
+
key: z2.string(),
|
|
591
|
+
display: z2.string()
|
|
592
|
+
});
|
|
593
|
+
var ProjectGitStatusSchema = z2.object({
|
|
594
|
+
observedAt: z2.string().datetime(),
|
|
595
|
+
state: z2.enum(["available", "unavailable"]),
|
|
596
|
+
repository: RepositoryIdentitySchema.nullable(),
|
|
597
|
+
head: z2.string().nullable(),
|
|
598
|
+
branch: z2.string().nullable(),
|
|
599
|
+
trackedChanges: z2.number().int().nonnegative(),
|
|
600
|
+
untrackedChanges: z2.number().int().nonnegative()
|
|
601
|
+
});
|
|
588
602
|
var ProjectSchema = z2.object({
|
|
589
603
|
id: z2.string(),
|
|
590
604
|
name: z2.string(),
|
|
591
605
|
path: z2.string(),
|
|
592
|
-
available: z2.boolean().default(true)
|
|
606
|
+
available: z2.boolean().default(true),
|
|
607
|
+
repository: RepositoryIdentitySchema.nullable().optional()
|
|
593
608
|
});
|
|
594
609
|
var TaskStatusSchema = z2.enum(["backlog", "needs_user_input", "running", "done"]);
|
|
595
610
|
var TaskSchema = z2.object({
|
|
@@ -608,6 +623,7 @@ var TaskSchema = z2.object({
|
|
|
608
623
|
deletedAt: z2.string().datetime().optional()
|
|
609
624
|
});
|
|
610
625
|
var CreateTaskSchema = z2.object({
|
|
626
|
+
clientTaskId: z2.string().min(1).max(128).regex(/^[a-zA-Z0-9_-]+$/).optional(),
|
|
611
627
|
projectId: z2.string().min(1),
|
|
612
628
|
title: z2.string().trim().min(1).max(120),
|
|
613
629
|
prompt: z2.string().trim().max(2e4).default(""),
|
|
@@ -869,9 +885,9 @@ var Attachments = class extends Context.Service()(
|
|
|
869
885
|
};
|
|
870
886
|
|
|
871
887
|
// src/index.ts
|
|
872
|
-
import { randomUUID as
|
|
873
|
-
import { execFile as
|
|
874
|
-
import { promisify as
|
|
888
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
889
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
890
|
+
import { promisify as promisify3 } from "node:util";
|
|
875
891
|
import { realpath as realpath2 } from "node:fs/promises";
|
|
876
892
|
import { homedir as homedir2 } from "node:os";
|
|
877
893
|
import path9 from "node:path";
|
|
@@ -1096,7 +1112,7 @@ async function startControl(directory, security2, status, maintenance2) {
|
|
|
1096
1112
|
// package.json
|
|
1097
1113
|
var package_default = {
|
|
1098
1114
|
name: "@agentrific/server",
|
|
1099
|
-
version: "0.
|
|
1115
|
+
version: "0.5.0",
|
|
1100
1116
|
description: "Self-hosted control plane for long-running coding agents",
|
|
1101
1117
|
keywords: [
|
|
1102
1118
|
"agentrific",
|
|
@@ -1482,7 +1498,7 @@ var UpdateDiscovery = class {
|
|
|
1482
1498
|
};
|
|
1483
1499
|
|
|
1484
1500
|
// src/index.ts
|
|
1485
|
-
import { Effect as
|
|
1501
|
+
import { Effect as Effect17, ManagedRuntime } from "effect";
|
|
1486
1502
|
import { z as z17 } from "zod";
|
|
1487
1503
|
|
|
1488
1504
|
// src/automation.ts
|
|
@@ -1570,7 +1586,9 @@ async function boardTasks(store2, projectId) {
|
|
|
1570
1586
|
status: taskStatusForSession(session.status, session)
|
|
1571
1587
|
};
|
|
1572
1588
|
});
|
|
1573
|
-
const legacyTasks = sessions.filter(
|
|
1589
|
+
const legacyTasks = sessions.filter(
|
|
1590
|
+
(session) => session.kind !== "repo_chat" && !session.taskId && !linkedSessionIds.has(session.id)
|
|
1591
|
+
).filter((session) => !projectId || session.projectId === projectId).map(sessionTask);
|
|
1574
1592
|
return [...linkedTasks, ...legacyTasks].map((task) => TaskSchema.parse(task)).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
1575
1593
|
}
|
|
1576
1594
|
async function findTask(store2, id) {
|
|
@@ -1605,7 +1623,7 @@ function editTask(store2, id, input, resolveAttachments) {
|
|
|
1605
1623
|
});
|
|
1606
1624
|
}
|
|
1607
1625
|
function deleteTask(store2, id, cleanup = async () => {
|
|
1608
|
-
}) {
|
|
1626
|
+
}, unrunUpdatedAt) {
|
|
1609
1627
|
return withTaskLock(id, async () => {
|
|
1610
1628
|
const task = await findTask(store2, id);
|
|
1611
1629
|
const sessions = await Effect6.runPromise(store2.list());
|
|
@@ -1616,6 +1634,8 @@ function deleteTask(store2, id, cleanup = async () => {
|
|
|
1616
1634
|
const associated = sessions.filter(
|
|
1617
1635
|
(session) => session.taskId === task.id || session.id === task.sessionId
|
|
1618
1636
|
);
|
|
1637
|
+
if (unrunUpdatedAt !== void 0 && (task.updatedAt !== unrunUpdatedAt || task.sessionId || associated.length || task.status !== "backlog"))
|
|
1638
|
+
throw new TaskActionError("This task changed or has already run. It cannot be moved.", 409);
|
|
1619
1639
|
for (const session of associated) await cleanup(session);
|
|
1620
1640
|
await Effect6.runPromise(
|
|
1621
1641
|
store2.purgeTask(
|
|
@@ -2024,7 +2044,7 @@ var exec = promisify(execFile);
|
|
|
2024
2044
|
function workflowLog(text) {
|
|
2025
2045
|
return stripVTControlCharacters(text).slice(-32e3);
|
|
2026
2046
|
}
|
|
2027
|
-
|
|
2047
|
+
function workflowEnvironment() {
|
|
2028
2048
|
const env = { ...process.env };
|
|
2029
2049
|
for (const name of [
|
|
2030
2050
|
"GIT_DIR",
|
|
@@ -2044,6 +2064,10 @@ async function workflowCommand(cwd, command, args, signal, managedPreparation =
|
|
|
2044
2064
|
"GIT_REPLACE_REF_BASE"
|
|
2045
2065
|
])
|
|
2046
2066
|
delete env[name];
|
|
2067
|
+
return env;
|
|
2068
|
+
}
|
|
2069
|
+
async function workflowCommand(cwd, command, args, signal, managedPreparation = false) {
|
|
2070
|
+
const env = workflowEnvironment();
|
|
2047
2071
|
if (managedPreparation) env.AGENTRIFIC_MANAGED_PREPARATION = "1";
|
|
2048
2072
|
const result = await exec(command, args, {
|
|
2049
2073
|
cwd,
|
|
@@ -3208,6 +3232,106 @@ function reconnectProject(root, id, input, store2) {
|
|
|
3208
3232
|
}).pipe(projectLock(store2).withPermit);
|
|
3209
3233
|
}
|
|
3210
3234
|
|
|
3235
|
+
// src/project-git.ts
|
|
3236
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
3237
|
+
import { promisify as promisify2 } from "node:util";
|
|
3238
|
+
var execute = promisify2(execFile2);
|
|
3239
|
+
function repositoryIdentity(remote) {
|
|
3240
|
+
const input = remote.trim();
|
|
3241
|
+
if (!input || input.startsWith("/") || input.startsWith(".")) return null;
|
|
3242
|
+
const scp = /^(?:[^@/\s]+@)?([^/:\s]+):([^\s]+)$/.exec(input);
|
|
3243
|
+
let url;
|
|
3244
|
+
try {
|
|
3245
|
+
url = new URL(scp && !input.includes("://") ? `ssh://${scp[1]}/${scp[2]}` : input);
|
|
3246
|
+
} catch {
|
|
3247
|
+
return null;
|
|
3248
|
+
}
|
|
3249
|
+
if (!["https:", "http:", "ssh:", "git:"].includes(url.protocol)) return null;
|
|
3250
|
+
if (url.search || url.hash || url.port) return null;
|
|
3251
|
+
const host2 = url.hostname.toLowerCase();
|
|
3252
|
+
let path10 = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
|
|
3253
|
+
if (!host2 || !path10 || path10.includes("%") || path10.includes("//")) return null;
|
|
3254
|
+
if (["github.com", "bitbucket.org"].includes(host2)) path10 = path10.toLowerCase();
|
|
3255
|
+
const key = `${host2}/${path10}`;
|
|
3256
|
+
return { key, display: key };
|
|
3257
|
+
}
|
|
3258
|
+
async function git(directory, args) {
|
|
3259
|
+
const { stdout } = await execute("git", ["-C", directory, ...args], {
|
|
3260
|
+
timeout: 3e3,
|
|
3261
|
+
maxBuffer: 1024 * 1024,
|
|
3262
|
+
env: { ...workflowEnvironment(), GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" }
|
|
3263
|
+
});
|
|
3264
|
+
return stdout.trim();
|
|
3265
|
+
}
|
|
3266
|
+
async function readRepositoryIdentity(directory) {
|
|
3267
|
+
try {
|
|
3268
|
+
const remotes = (await git(directory, ["remote"])).split("\n").filter(Boolean);
|
|
3269
|
+
const remote = remotes.includes("origin") ? "origin" : remotes.length === 1 ? remotes[0] : null;
|
|
3270
|
+
if (!remote) return null;
|
|
3271
|
+
return repositoryIdentity(await git(directory, ["remote", "get-url", remote]));
|
|
3272
|
+
} catch {
|
|
3273
|
+
return null;
|
|
3274
|
+
}
|
|
3275
|
+
}
|
|
3276
|
+
async function inspect(directory) {
|
|
3277
|
+
const observedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3278
|
+
try {
|
|
3279
|
+
const [repository, head, branch, status] = await Promise.all([
|
|
3280
|
+
readRepositoryIdentity(directory),
|
|
3281
|
+
git(directory, ["rev-parse", "--verify", "HEAD"]),
|
|
3282
|
+
git(directory, ["symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => null),
|
|
3283
|
+
// NUL-separated records avoid ambiguous filenames/newlines. --no-renames
|
|
3284
|
+
// keeps each entry one record, so we only need its two status characters.
|
|
3285
|
+
execute(
|
|
3286
|
+
"git",
|
|
3287
|
+
[
|
|
3288
|
+
"-C",
|
|
3289
|
+
directory,
|
|
3290
|
+
"status",
|
|
3291
|
+
"--porcelain=v1",
|
|
3292
|
+
"-z",
|
|
3293
|
+
"--no-renames",
|
|
3294
|
+
"--untracked-files=normal"
|
|
3295
|
+
],
|
|
3296
|
+
{
|
|
3297
|
+
timeout: 3e3,
|
|
3298
|
+
maxBuffer: 1024 * 1024,
|
|
3299
|
+
env: { ...workflowEnvironment(), GIT_OPTIONAL_LOCKS: "0" }
|
|
3300
|
+
}
|
|
3301
|
+
)
|
|
3302
|
+
]);
|
|
3303
|
+
const records = status.stdout.split("\0").filter(Boolean);
|
|
3304
|
+
return {
|
|
3305
|
+
observedAt,
|
|
3306
|
+
state: "available",
|
|
3307
|
+
repository,
|
|
3308
|
+
head,
|
|
3309
|
+
branch,
|
|
3310
|
+
trackedChanges: records.filter((record) => !record.startsWith("??")).length,
|
|
3311
|
+
untrackedChanges: records.filter((record) => record.startsWith("??")).length
|
|
3312
|
+
};
|
|
3313
|
+
} catch {
|
|
3314
|
+
return {
|
|
3315
|
+
observedAt,
|
|
3316
|
+
state: "unavailable",
|
|
3317
|
+
repository: null,
|
|
3318
|
+
head: null,
|
|
3319
|
+
branch: null,
|
|
3320
|
+
trackedChanges: 0,
|
|
3321
|
+
untrackedChanges: 0
|
|
3322
|
+
};
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
var observations = /* @__PURE__ */ new Map();
|
|
3326
|
+
function projectGitStatus(directory) {
|
|
3327
|
+
const cached = observations.get(directory);
|
|
3328
|
+
if (cached && cached.expires > Date.now()) return cached.value;
|
|
3329
|
+
if (observations.size >= 200) observations.delete(observations.keys().next().value);
|
|
3330
|
+
const value = inspect(directory);
|
|
3331
|
+
observations.set(directory, { expires: Date.now() + 15e3, value });
|
|
3332
|
+
return value;
|
|
3333
|
+
}
|
|
3334
|
+
|
|
3211
3335
|
// src/store.ts
|
|
3212
3336
|
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
3213
3337
|
import path7 from "node:path";
|
|
@@ -3735,6 +3859,38 @@ async function removeRunFiles(dataRoot2, sessionId) {
|
|
|
3735
3859
|
await rm2(path8.join(dataRoot2, "pi", sessionId), { recursive: true, force: true });
|
|
3736
3860
|
}
|
|
3737
3861
|
|
|
3862
|
+
// src/repo-chat.ts
|
|
3863
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
3864
|
+
import { Effect as Effect16 } from "effect";
|
|
3865
|
+
function openRepoChat(store2, projectId, workspacePath, defaultModel, reset = false) {
|
|
3866
|
+
return withTaskLock(`repo-chat:${projectId}`, async () => {
|
|
3867
|
+
const existing = (await Effect16.runPromise(store2.list())).filter((session2) => session2.projectId === projectId && session2.kind === "repo_chat").sort((a, b) => b.createdAt.localeCompare(a.createdAt))[0];
|
|
3868
|
+
if (existing && !reset) return existing;
|
|
3869
|
+
if (existing?.status === "running")
|
|
3870
|
+
throw new Error("Wait for the current reply before resetting chat.");
|
|
3871
|
+
const model = await defaultModel();
|
|
3872
|
+
if (!model) throw new Error("Choose an available default model in Settings before chatting.");
|
|
3873
|
+
const now = new Date(
|
|
3874
|
+
Math.max(Date.now(), existing ? Date.parse(existing.createdAt) + 1 : 0)
|
|
3875
|
+
).toISOString();
|
|
3876
|
+
const session = {
|
|
3877
|
+
id: randomUUID8(),
|
|
3878
|
+
projectId,
|
|
3879
|
+
kind: "repo_chat",
|
|
3880
|
+
title: workspacePath.split("/").at(-1) ?? projectId,
|
|
3881
|
+
status: "waiting_for_input",
|
|
3882
|
+
agent: "pi",
|
|
3883
|
+
model,
|
|
3884
|
+
branch: null,
|
|
3885
|
+
workspacePath,
|
|
3886
|
+
createdAt: now,
|
|
3887
|
+
updatedAt: now
|
|
3888
|
+
};
|
|
3889
|
+
await Effect16.runPromise(store2.append({ type: "session.updated", session }));
|
|
3890
|
+
return session;
|
|
3891
|
+
});
|
|
3892
|
+
}
|
|
3893
|
+
|
|
3738
3894
|
// src/index.ts
|
|
3739
3895
|
var host = process.env.AGENTRIFIC_HOST ?? "127.0.0.1";
|
|
3740
3896
|
var port = Number(process.env.AGENTRIFIC_PORT ?? 4318);
|
|
@@ -3742,7 +3898,7 @@ var defaultProjectsRoot = path9.resolve(process.env.AGENTRIFIC_PROJECTS_DIR ?? "
|
|
|
3742
3898
|
var worktreesRoot = path9.resolve(
|
|
3743
3899
|
process.env.AGENTRIFIC_WORKTREES_DIR ?? path9.join(homedir2(), ".agentrific", "workspaces")
|
|
3744
3900
|
);
|
|
3745
|
-
var execFileAsync =
|
|
3901
|
+
var execFileAsync = promisify3(execFile3);
|
|
3746
3902
|
if (host !== "127.0.0.1" && host !== "::1")
|
|
3747
3903
|
throw new Error("Bind Agentrific to loopback and expose it with Tailscale Serve or cloudflared.");
|
|
3748
3904
|
var app = Fastify({
|
|
@@ -3767,7 +3923,7 @@ var dataRoot = path9.resolve(process.env.AGENTRIFIC_DATA_DIR ?? "./data");
|
|
|
3767
3923
|
var releaseLock = await acquireServerLock(dataRoot);
|
|
3768
3924
|
var effects = ManagedRuntime.make(SessionStore.layer(path9.join(dataRoot, "sessions.sqlite")));
|
|
3769
3925
|
var store = await effects.runPromise(SessionStore);
|
|
3770
|
-
var security = new ServerSecurity(store, await
|
|
3926
|
+
var security = new ServerSecurity(store, await Effect17.runPromise(store.serverIdentity()));
|
|
3771
3927
|
var testPushUrl = process.env.AGENTRIFIC_PUSH_TEST_URL;
|
|
3772
3928
|
if (testPushUrl && (process.env.AGENTRIFIC_TEST_MODE !== "1" || !new URL(testPushUrl).hostname.endsWith(".ts.net") || new URL(testPushUrl).protocol !== "https:"))
|
|
3773
3929
|
throw new Error("Test relay requires explicit test mode and Tailscale HTTPS.");
|
|
@@ -3790,10 +3946,10 @@ var attachmentEffects = ManagedRuntime.make(
|
|
|
3790
3946
|
Attachments.layer(path9.join(dataRoot, "attachments"), store)
|
|
3791
3947
|
);
|
|
3792
3948
|
var attachments = await attachmentEffects.runPromise(Attachments);
|
|
3793
|
-
await
|
|
3949
|
+
await Effect17.runPromise(attachments.cleanup());
|
|
3794
3950
|
var attachmentCleanup = setInterval(
|
|
3795
3951
|
() => {
|
|
3796
|
-
void
|
|
3952
|
+
void Effect17.runPromise(attachments.cleanup()).catch((error) => app.log.error(error));
|
|
3797
3953
|
},
|
|
3798
3954
|
60 * 60 * 1e3
|
|
3799
3955
|
);
|
|
@@ -3804,14 +3960,14 @@ await app.register(multipart, {
|
|
|
3804
3960
|
app.post(
|
|
3805
3961
|
"/api/projects/:projectId/attachments",
|
|
3806
3962
|
async (request, reply) => {
|
|
3807
|
-
const root = await
|
|
3808
|
-
const projects = await
|
|
3963
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
3964
|
+
const projects = await Effect17.runPromise(discoverProjects(root, store));
|
|
3809
3965
|
if (!projects.some((project) => project.id === request.params.projectId && project.available))
|
|
3810
3966
|
return reply.code(404).send({ error: "Project not found." });
|
|
3811
3967
|
try {
|
|
3812
3968
|
const file = await request.file();
|
|
3813
3969
|
if (!file) return reply.code(400).send({ error: "Choose a file to upload." });
|
|
3814
|
-
return reply.code(201).send(await
|
|
3970
|
+
return reply.code(201).send(await Effect17.runPromise(attachments.upload(request.params.projectId, file)));
|
|
3815
3971
|
} catch (error) {
|
|
3816
3972
|
return reply.code(400).send({ error: error instanceof Error ? error.message : "Upload failed." });
|
|
3817
3973
|
}
|
|
@@ -3820,7 +3976,7 @@ app.post(
|
|
|
3820
3976
|
app.get(
|
|
3821
3977
|
"/api/projects/:projectId/attachments/:id/content",
|
|
3822
3978
|
async (request, reply) => {
|
|
3823
|
-
const attachment = await
|
|
3979
|
+
const attachment = await Effect17.runPromise(store.getAttachment(request.params.id));
|
|
3824
3980
|
if (!attachment || attachment.projectId !== request.params.projectId)
|
|
3825
3981
|
return reply.code(404).send({ error: "Attachment not found." });
|
|
3826
3982
|
const preview = request.query.preview === "1" && attachment.kind === "image";
|
|
@@ -3834,7 +3990,7 @@ app.get(
|
|
|
3834
3990
|
}
|
|
3835
3991
|
);
|
|
3836
3992
|
function publish(event, workflowUpdate = false) {
|
|
3837
|
-
const previous = event.type === "session.updated" ?
|
|
3993
|
+
const previous = event.type === "session.updated" ? Effect17.runSync(store.get(event.session.id)) : void 0;
|
|
3838
3994
|
const emittedEvent = event.type === "session.updated" && previous && !workflowUpdate ? {
|
|
3839
3995
|
type: "session.updated",
|
|
3840
3996
|
session: {
|
|
@@ -3844,7 +4000,7 @@ function publish(event, workflowUpdate = false) {
|
|
|
3844
4000
|
}
|
|
3845
4001
|
} : event;
|
|
3846
4002
|
const notification = emittedEvent.type === "session.updated" ? push.pending(emittedEvent.session, previous) : void 0;
|
|
3847
|
-
const record =
|
|
4003
|
+
const record = Effect17.runSync(store.append(emittedEvent, notification));
|
|
3848
4004
|
const message = JSON.stringify({ type: "event", record });
|
|
3849
4005
|
for (const client of security.sockets.keys()) {
|
|
3850
4006
|
if (!(client instanceof WebSocket) || client.readyState !== WebSocket.OPEN) continue;
|
|
@@ -3857,8 +4013,8 @@ var delivery = new DeliveryCoordinator(store, {
|
|
|
3857
4013
|
publish: (event) => publish(event, true),
|
|
3858
4014
|
draft: deliveryWriter(models),
|
|
3859
4015
|
projectPath: async (session) => {
|
|
3860
|
-
const projectsRoot = await
|
|
3861
|
-
const project = (await
|
|
4016
|
+
const projectsRoot = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4017
|
+
const project = (await Effect17.runPromise(discoverProjects(projectsRoot, store))).find(
|
|
3862
4018
|
(item) => item.id === session.projectId && item.available
|
|
3863
4019
|
);
|
|
3864
4020
|
if (!project) throw new Error("Reconnect the project folder before delivering changes.");
|
|
@@ -3872,7 +4028,7 @@ var agentEffects = ManagedRuntime.make(
|
|
|
3872
4028
|
publish,
|
|
3873
4029
|
worktreesRoot,
|
|
3874
4030
|
path9.join(dataRoot, "pi"),
|
|
3875
|
-
(sessionId, messageId) =>
|
|
4031
|
+
(sessionId, messageId) => Effect17.runSync(store.hasMessage(sessionId, messageId)),
|
|
3876
4032
|
(session) => delivery.settled(session.id)
|
|
3877
4033
|
)
|
|
3878
4034
|
);
|
|
@@ -3907,7 +4063,7 @@ var stopUpdateDiscovery = updates.start(
|
|
|
3907
4063
|
() => maintenance.ready(),
|
|
3908
4064
|
(error) => app.log.error(error)
|
|
3909
4065
|
);
|
|
3910
|
-
for (const session of await
|
|
4066
|
+
for (const session of await Effect17.runPromise(store.list())) {
|
|
3911
4067
|
if (session.gitDelivery?.status === "pending") {
|
|
3912
4068
|
publish(
|
|
3913
4069
|
{
|
|
@@ -3951,7 +4107,7 @@ async function githubCliStatus() {
|
|
|
3951
4107
|
}
|
|
3952
4108
|
app.get("/api/connections", async () => githubCliStatus());
|
|
3953
4109
|
app.get("/api/settings", async () => ({
|
|
3954
|
-
projectsRoot: await
|
|
4110
|
+
projectsRoot: await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot)),
|
|
3955
4111
|
defaultModel: await models.defaultIdentity()
|
|
3956
4112
|
}));
|
|
3957
4113
|
app.get("/api/models", async (_request, reply) => {
|
|
@@ -3979,7 +4135,7 @@ app.post("/api/settings/model", async (request, reply) => {
|
|
|
3979
4135
|
try {
|
|
3980
4136
|
const defaultModel = await models.setDefault(parsed.data);
|
|
3981
4137
|
return {
|
|
3982
|
-
projectsRoot: await
|
|
4138
|
+
projectsRoot: await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot)),
|
|
3983
4139
|
defaultModel
|
|
3984
4140
|
};
|
|
3985
4141
|
} catch {
|
|
@@ -3992,13 +4148,13 @@ app.post(
|
|
|
3992
4148
|
"/api/sessions/:id/model",
|
|
3993
4149
|
async (request, reply) => withSessionTaskLock(request.params.id, async () => {
|
|
3994
4150
|
const parsed = ModelReferenceSchema.safeParse(request.body);
|
|
3995
|
-
const session = await
|
|
4151
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
3996
4152
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
3997
4153
|
if (!parsed.success)
|
|
3998
4154
|
return reply.code(400).send({ error: "Choose a model from the server catalogue." });
|
|
3999
4155
|
try {
|
|
4000
4156
|
await models.resolve(parsed.data);
|
|
4001
|
-
return await
|
|
4157
|
+
return await Effect17.runPromise(runtime.setModel(session, parsed.data));
|
|
4002
4158
|
} catch {
|
|
4003
4159
|
return reply.code(409).send({
|
|
4004
4160
|
error: "Could not change the model. Wait for the current reply to finish and choose an available model."
|
|
@@ -4009,21 +4165,38 @@ app.post(
|
|
|
4009
4165
|
app.post("/api/settings", async (request, reply) => {
|
|
4010
4166
|
const parsed = UpdateSettingsSchema.safeParse(request.body);
|
|
4011
4167
|
if (!parsed.success) return reply.code(400).send({ error: "Enter a projects folder." });
|
|
4012
|
-
const result = await
|
|
4013
|
-
resolveProjectsRoot(parsed.data.projectsRoot).pipe(
|
|
4168
|
+
const result = await Effect17.runPromise(
|
|
4169
|
+
resolveProjectsRoot(parsed.data.projectsRoot).pipe(Effect17.result)
|
|
4014
4170
|
);
|
|
4015
4171
|
if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
|
|
4016
|
-
return
|
|
4172
|
+
return Effect17.runPromise(store.setProjectsRoot(result.success));
|
|
4017
4173
|
});
|
|
4018
4174
|
app.get("/api/projects", async (_request, reply) => {
|
|
4019
|
-
const root = await
|
|
4020
|
-
const result = await
|
|
4175
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4176
|
+
const result = await Effect17.runPromise(discoverProjects(root, store).pipe(Effect17.result));
|
|
4021
4177
|
if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
|
|
4022
|
-
return
|
|
4178
|
+
return Effect17.runPromise(
|
|
4179
|
+
Effect17.forEach(
|
|
4180
|
+
result.success,
|
|
4181
|
+
(project) => Effect17.promise(async () => ({
|
|
4182
|
+
...project,
|
|
4183
|
+
repository: project.available ? await readRepositoryIdentity(project.path) : null
|
|
4184
|
+
})),
|
|
4185
|
+
{ concurrency: 4 }
|
|
4186
|
+
)
|
|
4187
|
+
);
|
|
4188
|
+
});
|
|
4189
|
+
app.get("/api/projects/:id/git-status", async (request, reply) => {
|
|
4190
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4191
|
+
const projects = await Effect17.runPromise(discoverProjects(root, store));
|
|
4192
|
+
const project = projects.find((item) => item.id === request.params.id);
|
|
4193
|
+
if (!project?.available)
|
|
4194
|
+
return reply.code(404).send({ error: "Project folder unavailable on this server." });
|
|
4195
|
+
return projectGitStatus(project.path);
|
|
4023
4196
|
});
|
|
4024
4197
|
app.get("/api/projects/:id", async (request, reply) => {
|
|
4025
|
-
const root = await
|
|
4026
|
-
const result = await
|
|
4198
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4199
|
+
const result = await Effect17.runPromise(discoverProjects(root, store).pipe(Effect17.result));
|
|
4027
4200
|
if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
|
|
4028
4201
|
const project = result.success.find((item) => item.id === request.params.id);
|
|
4029
4202
|
return project ?? reply.code(404).send({ error: "Project not found on this server." });
|
|
@@ -4032,50 +4205,62 @@ app.post("/api/projects/:id/reconnect", async (request, reply) => {
|
|
|
4032
4205
|
const parsed = z17.object({ path: z17.string().trim().min(1).max(4096) }).safeParse(request.body);
|
|
4033
4206
|
if (!parsed.success)
|
|
4034
4207
|
return reply.code(400).send({ error: "Enter the project's new folder path." });
|
|
4035
|
-
const root = await
|
|
4036
|
-
const result = await
|
|
4037
|
-
reconnectProject(root, request.params.id, parsed.data.path, store).pipe(
|
|
4208
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4209
|
+
const result = await Effect17.runPromise(
|
|
4210
|
+
reconnectProject(root, request.params.id, parsed.data.path, store).pipe(Effect17.result)
|
|
4038
4211
|
);
|
|
4039
4212
|
return result._tag === "Failure" ? reply.code(400).send({ error: result.failure.message }) : result.success;
|
|
4040
4213
|
});
|
|
4041
|
-
app.get("/api/sessions", () =>
|
|
4214
|
+
app.get("/api/sessions", () => Effect17.runPromise(store.list()));
|
|
4042
4215
|
app.get(
|
|
4043
4216
|
"/api/tasks",
|
|
4044
4217
|
(request) => boardTasks(store, request.query.projectId)
|
|
4045
4218
|
);
|
|
4219
|
+
app.get("/api/task-moves", () => ({ supported: true }));
|
|
4046
4220
|
app.post("/api/tasks", async (request, reply) => {
|
|
4047
4221
|
const parsed = CreateTaskSchema.safeParse(request.body);
|
|
4048
4222
|
if (!parsed.success) return reply.code(400).send({ error: "Enter a task title." });
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4223
|
+
const save = async () => {
|
|
4224
|
+
if (parsed.data.clientTaskId) {
|
|
4225
|
+
const existing = await Effect17.runPromise(store.getTask(parsed.data.clientTaskId));
|
|
4226
|
+
if (existing) {
|
|
4227
|
+
if (existing.deletedAt || existing.projectId !== parsed.data.projectId || existing.title !== parsed.data.title || existing.prompt !== parsed.data.prompt || JSON.stringify(existing.gitFlow) !== JSON.stringify(parsed.data.gitFlow) || JSON.stringify(existing.model) !== JSON.stringify(parsed.data.model))
|
|
4228
|
+
return reply.code(409).send({ error: "This task creation request has already been used." });
|
|
4229
|
+
return reply.code(201).send(existing);
|
|
4230
|
+
}
|
|
4231
|
+
}
|
|
4232
|
+
if (parsed.data.gitFlow?.delivery === "pull_request") {
|
|
4233
|
+
const github = await githubCliStatus();
|
|
4234
|
+
if (!github.githubCli.authenticated)
|
|
4235
|
+
return reply.code(400).send({ error: "GitHub CLI must be authenticated on the server to open a PR." });
|
|
4236
|
+
}
|
|
4237
|
+
const projectsRoot = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4238
|
+
const projects = await Effect17.runPromise(
|
|
4239
|
+
discoverProjects(projectsRoot, store).pipe(Effect17.result)
|
|
4064
4240
|
);
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4241
|
+
if (projects._tag === "Failure" || !projects.success.some((project) => project.id === parsed.data.projectId && project.available))
|
|
4242
|
+
return reply.code(404).send({ error: "Project not found in the server's projects folder." });
|
|
4243
|
+
let files;
|
|
4244
|
+
try {
|
|
4245
|
+
files = await Effect17.runPromise(
|
|
4246
|
+
attachments.resolve(parsed.data.projectId, parsed.data.attachmentIds ?? [])
|
|
4247
|
+
);
|
|
4248
|
+
} catch (error) {
|
|
4249
|
+
return reply.code(400).send({ error: String(error) });
|
|
4250
|
+
}
|
|
4251
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4252
|
+
const task = TaskSchema.parse({
|
|
4253
|
+
id: parsed.data.clientTaskId ?? randomUUID9(),
|
|
4254
|
+
...parsed.data,
|
|
4255
|
+
attachments: files,
|
|
4256
|
+
status: "backlog",
|
|
4257
|
+
sessionId: null,
|
|
4258
|
+
createdAt: now,
|
|
4259
|
+
updatedAt: now
|
|
4260
|
+
});
|
|
4261
|
+
return reply.code(201).send(await Effect17.runPromise(store.saveTask(task)));
|
|
4262
|
+
};
|
|
4263
|
+
return parsed.data.clientTaskId ? withTaskLock(`create-task:${parsed.data.clientTaskId}`, save) : save();
|
|
4079
4264
|
});
|
|
4080
4265
|
app.patch("/api/tasks/:id", async (request, reply) => {
|
|
4081
4266
|
const parsed = UpdateTaskSchema.safeParse(request.body);
|
|
@@ -4090,8 +4275,8 @@ app.patch("/api/tasks/:id", async (request, reply) => {
|
|
|
4090
4275
|
return reply.code(400).send({ error: "GitHub CLI must be authenticated on the server to open a PR." });
|
|
4091
4276
|
}
|
|
4092
4277
|
return await editTask(store, request.params.id, parsed.data, async (projectId, ids) => {
|
|
4093
|
-
const result = await
|
|
4094
|
-
attachments.resolve(projectId, ids).pipe(
|
|
4278
|
+
const result = await Effect17.runPromise(
|
|
4279
|
+
attachments.resolve(projectId, ids).pipe(Effect17.result)
|
|
4095
4280
|
);
|
|
4096
4281
|
if (result._tag === "Failure") throw new TaskActionError(result.failure.message, 400);
|
|
4097
4282
|
return result.success;
|
|
@@ -4102,14 +4287,26 @@ app.patch("/api/tasks/:id", async (request, reply) => {
|
|
|
4102
4287
|
throw error;
|
|
4103
4288
|
}
|
|
4104
4289
|
});
|
|
4290
|
+
app.post("/api/tasks/:id/discard-unrun", async (request, reply) => {
|
|
4291
|
+
const parsed = z17.object({ updatedAt: z17.string().datetime() }).safeParse(request.body);
|
|
4292
|
+
if (!parsed.success) return reply.code(400).send({ error: "The task revision is required." });
|
|
4293
|
+
try {
|
|
4294
|
+
await deleteTask(store, request.params.id, void 0, parsed.data.updatedAt);
|
|
4295
|
+
return reply.code(204).send();
|
|
4296
|
+
} catch (error) {
|
|
4297
|
+
if (error instanceof TaskActionError)
|
|
4298
|
+
return reply.code(error.status).send({ error: error.message });
|
|
4299
|
+
throw error;
|
|
4300
|
+
}
|
|
4301
|
+
});
|
|
4105
4302
|
app.delete("/api/tasks/:id", async (request, reply) => {
|
|
4106
4303
|
try {
|
|
4107
4304
|
await deleteTask(store, request.params.id, async (session) => {
|
|
4108
|
-
await
|
|
4305
|
+
await Effect17.runPromise(runtime.forget(session.id));
|
|
4109
4306
|
await worktreeCleanup.remove(session);
|
|
4110
4307
|
await removeRunFiles(dataRoot, session.id);
|
|
4111
4308
|
});
|
|
4112
|
-
await
|
|
4309
|
+
await Effect17.runPromise(attachments.cleanup());
|
|
4113
4310
|
return reply.code(204).send();
|
|
4114
4311
|
} catch (error) {
|
|
4115
4312
|
if (error instanceof TaskActionError)
|
|
@@ -4118,14 +4315,14 @@ app.delete("/api/tasks/:id", async (request, reply) => {
|
|
|
4118
4315
|
}
|
|
4119
4316
|
});
|
|
4120
4317
|
app.get("/api/sessions/:id", async (request, reply) => {
|
|
4121
|
-
const session = await
|
|
4318
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4122
4319
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4123
4320
|
return session;
|
|
4124
4321
|
});
|
|
4125
4322
|
app.post(
|
|
4126
4323
|
"/api/sessions/:id/delivery",
|
|
4127
4324
|
async (request, reply) => withSessionTaskLock(request.params.id, async () => {
|
|
4128
|
-
const session = await
|
|
4325
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4129
4326
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4130
4327
|
try {
|
|
4131
4328
|
const result = await delivery.retry(session.id);
|
|
@@ -4142,10 +4339,10 @@ app.post(
|
|
|
4142
4339
|
try {
|
|
4143
4340
|
const prepared = await preparation.retry(request.params.id);
|
|
4144
4341
|
preparedSession = prepared.session;
|
|
4145
|
-
const files = await
|
|
4342
|
+
const files = await Effect17.runPromise(
|
|
4146
4343
|
attachments.resolve(prepared.session.projectId, prepared.input.attachmentIds ?? [])
|
|
4147
4344
|
);
|
|
4148
|
-
await
|
|
4345
|
+
await Effect17.runPromise(runtime.send(prepared.session, prepared.input.prompt, files));
|
|
4149
4346
|
return reply.code(202).send(prepared.session);
|
|
4150
4347
|
} catch (error) {
|
|
4151
4348
|
if (preparedSession) {
|
|
@@ -4168,9 +4365,9 @@ var HistoryQuery = z17.object({
|
|
|
4168
4365
|
app.get("/api/sessions/:id/events", async (request, reply) => {
|
|
4169
4366
|
const query = HistoryQuery.safeParse(request.query);
|
|
4170
4367
|
if (!query.success) return reply.code(400).send({ error: "Invalid history cursor or limit." });
|
|
4171
|
-
const session = await
|
|
4368
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4172
4369
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4173
|
-
return
|
|
4370
|
+
return Effect17.runPromise(store.history(session.id, query.data.after, query.data.limit));
|
|
4174
4371
|
});
|
|
4175
4372
|
async function selectSessionModel(input, hasImages) {
|
|
4176
4373
|
try {
|
|
@@ -4190,8 +4387,8 @@ async function selectSessionModel(input, hasImages) {
|
|
|
4190
4387
|
}
|
|
4191
4388
|
}
|
|
4192
4389
|
async function resolveSessionProject(projectId) {
|
|
4193
|
-
const projectsRoot = await
|
|
4194
|
-
const project = (await
|
|
4390
|
+
const projectsRoot = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4391
|
+
const project = (await Effect17.runPromise(discoverProjects(projectsRoot, store))).find(
|
|
4195
4392
|
(item) => item.id === projectId && item.available
|
|
4196
4393
|
);
|
|
4197
4394
|
if (!project)
|
|
@@ -4233,7 +4430,7 @@ async function createAndStartSession(input) {
|
|
|
4233
4430
|
const projectPath = location.projectPath;
|
|
4234
4431
|
let files;
|
|
4235
4432
|
try {
|
|
4236
|
-
files = await
|
|
4433
|
+
files = await Effect17.runPromise(
|
|
4237
4434
|
attachments.resolve(input.projectId, input.attachmentIds ?? [])
|
|
4238
4435
|
);
|
|
4239
4436
|
} catch (error) {
|
|
@@ -4246,11 +4443,11 @@ async function createAndStartSession(input) {
|
|
|
4246
4443
|
if (selection.error !== void 0) return { error: selection.error, status: selection.status };
|
|
4247
4444
|
const selectedModel = selection.model;
|
|
4248
4445
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4249
|
-
const task = input.taskId ? await
|
|
4446
|
+
const task = input.taskId ? await Effect17.runPromise(store.getTask(input.taskId)) : void 0;
|
|
4250
4447
|
if (input.taskId && sessionTaskUnavailable(task, input.projectId))
|
|
4251
4448
|
return { error: "Task not found.", status: 404 };
|
|
4252
4449
|
const session = SessionSchema.parse({
|
|
4253
|
-
id:
|
|
4450
|
+
id: randomUUID9(),
|
|
4254
4451
|
taskId: input.taskId ?? null,
|
|
4255
4452
|
projectId: input.projectId,
|
|
4256
4453
|
title: task?.title ?? input.prompt.slice(0, 80),
|
|
@@ -4276,7 +4473,7 @@ async function createAndStartSession(input) {
|
|
|
4276
4473
|
);
|
|
4277
4474
|
} catch (error) {
|
|
4278
4475
|
if (task)
|
|
4279
|
-
await
|
|
4476
|
+
await Effect17.runPromise(
|
|
4280
4477
|
store.saveTask({ ...task, sessionId: session.id, updatedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
4281
4478
|
);
|
|
4282
4479
|
return {
|
|
@@ -4285,7 +4482,7 @@ async function createAndStartSession(input) {
|
|
|
4285
4482
|
};
|
|
4286
4483
|
}
|
|
4287
4484
|
try {
|
|
4288
|
-
await
|
|
4485
|
+
await Effect17.runPromise(runtime.send(running, input.prompt, files));
|
|
4289
4486
|
} catch (error) {
|
|
4290
4487
|
publish({
|
|
4291
4488
|
type: "session.updated",
|
|
@@ -4294,8 +4491,27 @@ async function createAndStartSession(input) {
|
|
|
4294
4491
|
publish({ type: "error", sessionId: session.id, message: String(error) });
|
|
4295
4492
|
return { error: "Could not start the agent session.", status: 500 };
|
|
4296
4493
|
}
|
|
4297
|
-
return { session: await
|
|
4494
|
+
return { session: await Effect17.runPromise(store.get(session.id)), status: 201 };
|
|
4298
4495
|
}
|
|
4496
|
+
app.post("/api/projects/:id/chat", async (request, reply) => {
|
|
4497
|
+
maintenance.assertReady();
|
|
4498
|
+
const location = await resolveSessionProject(request.params.id);
|
|
4499
|
+
if (location.error !== void 0)
|
|
4500
|
+
return reply.code(location.status).send({ error: location.error });
|
|
4501
|
+
const parsed = z17.object({ reset: z17.boolean().optional() }).safeParse(request.body ?? {});
|
|
4502
|
+
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
4503
|
+
try {
|
|
4504
|
+
return await openRepoChat(
|
|
4505
|
+
store,
|
|
4506
|
+
request.params.id,
|
|
4507
|
+
location.projectPath,
|
|
4508
|
+
() => models.defaultIdentity(),
|
|
4509
|
+
parsed.data.reset
|
|
4510
|
+
);
|
|
4511
|
+
} catch (error) {
|
|
4512
|
+
return reply.code(409).send({ error: String(error) });
|
|
4513
|
+
}
|
|
4514
|
+
});
|
|
4299
4515
|
app.post("/api/sessions", async (request, reply) => {
|
|
4300
4516
|
const parsed = CreateSessionSchema.safeParse(request.body);
|
|
4301
4517
|
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
@@ -4312,11 +4528,11 @@ function startTask(id, instructionsOverride, automated = false) {
|
|
|
4312
4528
|
"agent-dispatch",
|
|
4313
4529
|
() => withTaskLock(id, async () => {
|
|
4314
4530
|
maintenance.assertReady();
|
|
4315
|
-
const task = await
|
|
4531
|
+
const task = await Effect17.runPromise(store.getTask(id));
|
|
4316
4532
|
if (!task || task.deletedAt) throw new TaskActionError("Task not found.", 404);
|
|
4317
4533
|
if (automated) {
|
|
4318
|
-
const state = await
|
|
4319
|
-
if (activeAgents(await
|
|
4534
|
+
const state = await Effect17.runPromise(store.automation());
|
|
4535
|
+
if (activeAgents(await Effect17.runPromise(store.list())) >= state.config.parallelism) return;
|
|
4320
4536
|
const backlog = await boardTasks(store);
|
|
4321
4537
|
if (!backlog.some((item) => item.id === id && item.status === "backlog")) return;
|
|
4322
4538
|
}
|
|
@@ -4324,7 +4540,7 @@ function startTask(id, instructionsOverride, automated = false) {
|
|
|
4324
4540
|
const prompt = instructions ? `${task.title}
|
|
4325
4541
|
|
|
4326
4542
|
${instructions}` : task.title;
|
|
4327
|
-
const linkedSession = task.sessionId ? await
|
|
4543
|
+
const linkedSession = task.sessionId ? await Effect17.runPromise(store.get(task.sessionId)) : void 0;
|
|
4328
4544
|
if (linkedSession && (["starting", "running", "queued"].includes(linkedSession.status) || linkedSession.gitDelivery?.status === "pending"))
|
|
4329
4545
|
throw new TaskActionError("This task already has a running agent.", 409);
|
|
4330
4546
|
const result = await createAndStartSession({
|
|
@@ -4338,7 +4554,7 @@ ${instructions}` : task.title;
|
|
|
4338
4554
|
});
|
|
4339
4555
|
if (!result.session)
|
|
4340
4556
|
throw new TaskActionError(result.error ?? "Could not start task.", result.status);
|
|
4341
|
-
const updatedTask = await
|
|
4557
|
+
const updatedTask = await Effect17.runPromise(
|
|
4342
4558
|
store.saveTask({
|
|
4343
4559
|
...task,
|
|
4344
4560
|
status: taskStatusForSession(result.session.status, result.session),
|
|
@@ -4373,8 +4589,8 @@ app.patch("/api/automation", async (request, reply) => {
|
|
|
4373
4589
|
const parsed = AutomationConfigSchema.safeParse(request.body);
|
|
4374
4590
|
if (!parsed.success)
|
|
4375
4591
|
return reply.code(400).send({ error: parsed.error.issues.map((issue) => issue.message).join(" ") });
|
|
4376
|
-
const root = await
|
|
4377
|
-
const projects = await
|
|
4592
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4593
|
+
const projects = await Effect17.runPromise(discoverProjects(root, store));
|
|
4378
4594
|
if (parsed.data.projectIds.some(
|
|
4379
4595
|
(id) => !projects.some((project) => project.id === id && project.available)
|
|
4380
4596
|
))
|
|
@@ -4402,7 +4618,7 @@ automationTimer.unref();
|
|
|
4402
4618
|
app.post(
|
|
4403
4619
|
"/api/sessions/:id/messages",
|
|
4404
4620
|
async (request, reply) => withSessionTaskLock(request.params.id, async () => {
|
|
4405
|
-
const session = await
|
|
4621
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4406
4622
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4407
4623
|
const parsed = SendMessageSchema.safeParse(request.body);
|
|
4408
4624
|
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
@@ -4410,10 +4626,10 @@ app.post(
|
|
|
4410
4626
|
await delivery.assertCanWork(session.id);
|
|
4411
4627
|
if (session.preparation && session.preparation.status !== "ready")
|
|
4412
4628
|
throw new Error("Retry workspace preparation before sending a message.");
|
|
4413
|
-
const files = await
|
|
4629
|
+
const files = await Effect17.runPromise(
|
|
4414
4630
|
attachments.resolve(session.projectId, parsed.data.attachmentIds ?? [])
|
|
4415
4631
|
);
|
|
4416
|
-
await
|
|
4632
|
+
await Effect17.runPromise(
|
|
4417
4633
|
runtime.send(
|
|
4418
4634
|
session,
|
|
4419
4635
|
parsed.data.message,
|
|
@@ -4429,22 +4645,22 @@ app.post(
|
|
|
4429
4645
|
})
|
|
4430
4646
|
);
|
|
4431
4647
|
async function withSessionTaskLock(id, operation) {
|
|
4432
|
-
const session = await
|
|
4648
|
+
const session = await Effect17.runPromise(store.get(id));
|
|
4433
4649
|
const lockId = session ? await sessionTaskLockId(store, session) : `session-${id}`;
|
|
4434
4650
|
return withTaskLock(lockId, operation);
|
|
4435
4651
|
}
|
|
4436
4652
|
app.post(
|
|
4437
4653
|
"/api/sessions/:id/stop",
|
|
4438
4654
|
async (request, reply) => withSessionTaskLock(request.params.id, async () => {
|
|
4439
|
-
const session = await
|
|
4655
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4440
4656
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4441
|
-
await
|
|
4657
|
+
await Effect17.runPromise(runtime.stop(session));
|
|
4442
4658
|
return reply.code(202).send({ accepted: true });
|
|
4443
4659
|
})
|
|
4444
4660
|
);
|
|
4445
4661
|
attachEventServer(app, security, allowedOrigins, () => ({
|
|
4446
4662
|
type: "snapshot",
|
|
4447
|
-
sessions:
|
|
4663
|
+
sessions: Effect17.runSync(store.list())
|
|
4448
4664
|
}));
|
|
4449
4665
|
var closeControl = await startControl(
|
|
4450
4666
|
dataRoot,
|
|
@@ -4456,7 +4672,7 @@ var closeControl = await startControl(
|
|
|
4456
4672
|
connectedDevices: new Set(security.sockets.values()).size,
|
|
4457
4673
|
connectedSockets: security.sockets.size,
|
|
4458
4674
|
memory: process.memoryUsage().rss,
|
|
4459
|
-
activeRuns: activeAgents(
|
|
4675
|
+
activeRuns: activeAgents(Effect17.runSync(store.list())),
|
|
4460
4676
|
maintenance: maintenance.status()
|
|
4461
4677
|
}),
|
|
4462
4678
|
maintenance
|
|
@@ -4474,7 +4690,7 @@ app.addHook("onClose", async () => {
|
|
|
4474
4690
|
clearInterval(automationTimer);
|
|
4475
4691
|
await withTaskLock("automation", async () => {
|
|
4476
4692
|
});
|
|
4477
|
-
await
|
|
4693
|
+
await Effect17.runPromise(runtime.close());
|
|
4478
4694
|
await agentEffects.dispose();
|
|
4479
4695
|
await attachmentEffects.dispose();
|
|
4480
4696
|
await effects.dispose();
|