@agentrific/server 0.3.0 → 0.4.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 +6 -0
- package/cli.js +133 -72
- package/cli.js.map +4 -4
- package/index.js +126 -72
- 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,
|
|
@@ -869,7 +870,7 @@ var Attachments = class extends Context.Service()(
|
|
|
869
870
|
};
|
|
870
871
|
|
|
871
872
|
// src/index.ts
|
|
872
|
-
import { randomUUID as
|
|
873
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
873
874
|
import { execFile as execFile2 } from "node:child_process";
|
|
874
875
|
import { promisify as promisify2 } from "node:util";
|
|
875
876
|
import { realpath as realpath2 } from "node:fs/promises";
|
|
@@ -1096,7 +1097,7 @@ async function startControl(directory, security2, status, maintenance2) {
|
|
|
1096
1097
|
// package.json
|
|
1097
1098
|
var package_default = {
|
|
1098
1099
|
name: "@agentrific/server",
|
|
1099
|
-
version: "0.
|
|
1100
|
+
version: "0.4.0",
|
|
1100
1101
|
description: "Self-hosted control plane for long-running coding agents",
|
|
1101
1102
|
keywords: [
|
|
1102
1103
|
"agentrific",
|
|
@@ -1482,7 +1483,7 @@ var UpdateDiscovery = class {
|
|
|
1482
1483
|
};
|
|
1483
1484
|
|
|
1484
1485
|
// src/index.ts
|
|
1485
|
-
import { Effect as
|
|
1486
|
+
import { Effect as Effect17, ManagedRuntime } from "effect";
|
|
1486
1487
|
import { z as z17 } from "zod";
|
|
1487
1488
|
|
|
1488
1489
|
// src/automation.ts
|
|
@@ -1570,7 +1571,9 @@ async function boardTasks(store2, projectId) {
|
|
|
1570
1571
|
status: taskStatusForSession(session.status, session)
|
|
1571
1572
|
};
|
|
1572
1573
|
});
|
|
1573
|
-
const legacyTasks = sessions.filter(
|
|
1574
|
+
const legacyTasks = sessions.filter(
|
|
1575
|
+
(session) => session.kind !== "repo_chat" && !session.taskId && !linkedSessionIds.has(session.id)
|
|
1576
|
+
).filter((session) => !projectId || session.projectId === projectId).map(sessionTask);
|
|
1574
1577
|
return [...linkedTasks, ...legacyTasks].map((task) => TaskSchema.parse(task)).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
1575
1578
|
}
|
|
1576
1579
|
async function findTask(store2, id) {
|
|
@@ -3735,6 +3738,38 @@ async function removeRunFiles(dataRoot2, sessionId) {
|
|
|
3735
3738
|
await rm2(path8.join(dataRoot2, "pi", sessionId), { recursive: true, force: true });
|
|
3736
3739
|
}
|
|
3737
3740
|
|
|
3741
|
+
// src/repo-chat.ts
|
|
3742
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
3743
|
+
import { Effect as Effect16 } from "effect";
|
|
3744
|
+
function openRepoChat(store2, projectId, workspacePath, defaultModel, reset = false) {
|
|
3745
|
+
return withTaskLock(`repo-chat:${projectId}`, async () => {
|
|
3746
|
+
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];
|
|
3747
|
+
if (existing && !reset) return existing;
|
|
3748
|
+
if (existing?.status === "running")
|
|
3749
|
+
throw new Error("Wait for the current reply before resetting chat.");
|
|
3750
|
+
const model = await defaultModel();
|
|
3751
|
+
if (!model) throw new Error("Choose an available default model in Settings before chatting.");
|
|
3752
|
+
const now = new Date(
|
|
3753
|
+
Math.max(Date.now(), existing ? Date.parse(existing.createdAt) + 1 : 0)
|
|
3754
|
+
).toISOString();
|
|
3755
|
+
const session = {
|
|
3756
|
+
id: randomUUID8(),
|
|
3757
|
+
projectId,
|
|
3758
|
+
kind: "repo_chat",
|
|
3759
|
+
title: workspacePath.split("/").at(-1) ?? projectId,
|
|
3760
|
+
status: "waiting_for_input",
|
|
3761
|
+
agent: "pi",
|
|
3762
|
+
model,
|
|
3763
|
+
branch: null,
|
|
3764
|
+
workspacePath,
|
|
3765
|
+
createdAt: now,
|
|
3766
|
+
updatedAt: now
|
|
3767
|
+
};
|
|
3768
|
+
await Effect16.runPromise(store2.append({ type: "session.updated", session }));
|
|
3769
|
+
return session;
|
|
3770
|
+
});
|
|
3771
|
+
}
|
|
3772
|
+
|
|
3738
3773
|
// src/index.ts
|
|
3739
3774
|
var host = process.env.AGENTRIFIC_HOST ?? "127.0.0.1";
|
|
3740
3775
|
var port = Number(process.env.AGENTRIFIC_PORT ?? 4318);
|
|
@@ -3767,7 +3802,7 @@ var dataRoot = path9.resolve(process.env.AGENTRIFIC_DATA_DIR ?? "./data");
|
|
|
3767
3802
|
var releaseLock = await acquireServerLock(dataRoot);
|
|
3768
3803
|
var effects = ManagedRuntime.make(SessionStore.layer(path9.join(dataRoot, "sessions.sqlite")));
|
|
3769
3804
|
var store = await effects.runPromise(SessionStore);
|
|
3770
|
-
var security = new ServerSecurity(store, await
|
|
3805
|
+
var security = new ServerSecurity(store, await Effect17.runPromise(store.serverIdentity()));
|
|
3771
3806
|
var testPushUrl = process.env.AGENTRIFIC_PUSH_TEST_URL;
|
|
3772
3807
|
if (testPushUrl && (process.env.AGENTRIFIC_TEST_MODE !== "1" || !new URL(testPushUrl).hostname.endsWith(".ts.net") || new URL(testPushUrl).protocol !== "https:"))
|
|
3773
3808
|
throw new Error("Test relay requires explicit test mode and Tailscale HTTPS.");
|
|
@@ -3790,10 +3825,10 @@ var attachmentEffects = ManagedRuntime.make(
|
|
|
3790
3825
|
Attachments.layer(path9.join(dataRoot, "attachments"), store)
|
|
3791
3826
|
);
|
|
3792
3827
|
var attachments = await attachmentEffects.runPromise(Attachments);
|
|
3793
|
-
await
|
|
3828
|
+
await Effect17.runPromise(attachments.cleanup());
|
|
3794
3829
|
var attachmentCleanup = setInterval(
|
|
3795
3830
|
() => {
|
|
3796
|
-
void
|
|
3831
|
+
void Effect17.runPromise(attachments.cleanup()).catch((error) => app.log.error(error));
|
|
3797
3832
|
},
|
|
3798
3833
|
60 * 60 * 1e3
|
|
3799
3834
|
);
|
|
@@ -3804,14 +3839,14 @@ await app.register(multipart, {
|
|
|
3804
3839
|
app.post(
|
|
3805
3840
|
"/api/projects/:projectId/attachments",
|
|
3806
3841
|
async (request, reply) => {
|
|
3807
|
-
const root = await
|
|
3808
|
-
const projects = await
|
|
3842
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
3843
|
+
const projects = await Effect17.runPromise(discoverProjects(root, store));
|
|
3809
3844
|
if (!projects.some((project) => project.id === request.params.projectId && project.available))
|
|
3810
3845
|
return reply.code(404).send({ error: "Project not found." });
|
|
3811
3846
|
try {
|
|
3812
3847
|
const file = await request.file();
|
|
3813
3848
|
if (!file) return reply.code(400).send({ error: "Choose a file to upload." });
|
|
3814
|
-
return reply.code(201).send(await
|
|
3849
|
+
return reply.code(201).send(await Effect17.runPromise(attachments.upload(request.params.projectId, file)));
|
|
3815
3850
|
} catch (error) {
|
|
3816
3851
|
return reply.code(400).send({ error: error instanceof Error ? error.message : "Upload failed." });
|
|
3817
3852
|
}
|
|
@@ -3820,7 +3855,7 @@ app.post(
|
|
|
3820
3855
|
app.get(
|
|
3821
3856
|
"/api/projects/:projectId/attachments/:id/content",
|
|
3822
3857
|
async (request, reply) => {
|
|
3823
|
-
const attachment = await
|
|
3858
|
+
const attachment = await Effect17.runPromise(store.getAttachment(request.params.id));
|
|
3824
3859
|
if (!attachment || attachment.projectId !== request.params.projectId)
|
|
3825
3860
|
return reply.code(404).send({ error: "Attachment not found." });
|
|
3826
3861
|
const preview = request.query.preview === "1" && attachment.kind === "image";
|
|
@@ -3834,7 +3869,7 @@ app.get(
|
|
|
3834
3869
|
}
|
|
3835
3870
|
);
|
|
3836
3871
|
function publish(event, workflowUpdate = false) {
|
|
3837
|
-
const previous = event.type === "session.updated" ?
|
|
3872
|
+
const previous = event.type === "session.updated" ? Effect17.runSync(store.get(event.session.id)) : void 0;
|
|
3838
3873
|
const emittedEvent = event.type === "session.updated" && previous && !workflowUpdate ? {
|
|
3839
3874
|
type: "session.updated",
|
|
3840
3875
|
session: {
|
|
@@ -3844,7 +3879,7 @@ function publish(event, workflowUpdate = false) {
|
|
|
3844
3879
|
}
|
|
3845
3880
|
} : event;
|
|
3846
3881
|
const notification = emittedEvent.type === "session.updated" ? push.pending(emittedEvent.session, previous) : void 0;
|
|
3847
|
-
const record =
|
|
3882
|
+
const record = Effect17.runSync(store.append(emittedEvent, notification));
|
|
3848
3883
|
const message = JSON.stringify({ type: "event", record });
|
|
3849
3884
|
for (const client of security.sockets.keys()) {
|
|
3850
3885
|
if (!(client instanceof WebSocket) || client.readyState !== WebSocket.OPEN) continue;
|
|
@@ -3857,8 +3892,8 @@ var delivery = new DeliveryCoordinator(store, {
|
|
|
3857
3892
|
publish: (event) => publish(event, true),
|
|
3858
3893
|
draft: deliveryWriter(models),
|
|
3859
3894
|
projectPath: async (session) => {
|
|
3860
|
-
const projectsRoot = await
|
|
3861
|
-
const project = (await
|
|
3895
|
+
const projectsRoot = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
3896
|
+
const project = (await Effect17.runPromise(discoverProjects(projectsRoot, store))).find(
|
|
3862
3897
|
(item) => item.id === session.projectId && item.available
|
|
3863
3898
|
);
|
|
3864
3899
|
if (!project) throw new Error("Reconnect the project folder before delivering changes.");
|
|
@@ -3872,7 +3907,7 @@ var agentEffects = ManagedRuntime.make(
|
|
|
3872
3907
|
publish,
|
|
3873
3908
|
worktreesRoot,
|
|
3874
3909
|
path9.join(dataRoot, "pi"),
|
|
3875
|
-
(sessionId, messageId) =>
|
|
3910
|
+
(sessionId, messageId) => Effect17.runSync(store.hasMessage(sessionId, messageId)),
|
|
3876
3911
|
(session) => delivery.settled(session.id)
|
|
3877
3912
|
)
|
|
3878
3913
|
);
|
|
@@ -3907,7 +3942,7 @@ var stopUpdateDiscovery = updates.start(
|
|
|
3907
3942
|
() => maintenance.ready(),
|
|
3908
3943
|
(error) => app.log.error(error)
|
|
3909
3944
|
);
|
|
3910
|
-
for (const session of await
|
|
3945
|
+
for (const session of await Effect17.runPromise(store.list())) {
|
|
3911
3946
|
if (session.gitDelivery?.status === "pending") {
|
|
3912
3947
|
publish(
|
|
3913
3948
|
{
|
|
@@ -3951,7 +3986,7 @@ async function githubCliStatus() {
|
|
|
3951
3986
|
}
|
|
3952
3987
|
app.get("/api/connections", async () => githubCliStatus());
|
|
3953
3988
|
app.get("/api/settings", async () => ({
|
|
3954
|
-
projectsRoot: await
|
|
3989
|
+
projectsRoot: await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot)),
|
|
3955
3990
|
defaultModel: await models.defaultIdentity()
|
|
3956
3991
|
}));
|
|
3957
3992
|
app.get("/api/models", async (_request, reply) => {
|
|
@@ -3979,7 +4014,7 @@ app.post("/api/settings/model", async (request, reply) => {
|
|
|
3979
4014
|
try {
|
|
3980
4015
|
const defaultModel = await models.setDefault(parsed.data);
|
|
3981
4016
|
return {
|
|
3982
|
-
projectsRoot: await
|
|
4017
|
+
projectsRoot: await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot)),
|
|
3983
4018
|
defaultModel
|
|
3984
4019
|
};
|
|
3985
4020
|
} catch {
|
|
@@ -3992,13 +4027,13 @@ app.post(
|
|
|
3992
4027
|
"/api/sessions/:id/model",
|
|
3993
4028
|
async (request, reply) => withSessionTaskLock(request.params.id, async () => {
|
|
3994
4029
|
const parsed = ModelReferenceSchema.safeParse(request.body);
|
|
3995
|
-
const session = await
|
|
4030
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
3996
4031
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
3997
4032
|
if (!parsed.success)
|
|
3998
4033
|
return reply.code(400).send({ error: "Choose a model from the server catalogue." });
|
|
3999
4034
|
try {
|
|
4000
4035
|
await models.resolve(parsed.data);
|
|
4001
|
-
return await
|
|
4036
|
+
return await Effect17.runPromise(runtime.setModel(session, parsed.data));
|
|
4002
4037
|
} catch {
|
|
4003
4038
|
return reply.code(409).send({
|
|
4004
4039
|
error: "Could not change the model. Wait for the current reply to finish and choose an available model."
|
|
@@ -4009,21 +4044,21 @@ app.post(
|
|
|
4009
4044
|
app.post("/api/settings", async (request, reply) => {
|
|
4010
4045
|
const parsed = UpdateSettingsSchema.safeParse(request.body);
|
|
4011
4046
|
if (!parsed.success) return reply.code(400).send({ error: "Enter a projects folder." });
|
|
4012
|
-
const result = await
|
|
4013
|
-
resolveProjectsRoot(parsed.data.projectsRoot).pipe(
|
|
4047
|
+
const result = await Effect17.runPromise(
|
|
4048
|
+
resolveProjectsRoot(parsed.data.projectsRoot).pipe(Effect17.result)
|
|
4014
4049
|
);
|
|
4015
4050
|
if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
|
|
4016
|
-
return
|
|
4051
|
+
return Effect17.runPromise(store.setProjectsRoot(result.success));
|
|
4017
4052
|
});
|
|
4018
4053
|
app.get("/api/projects", async (_request, reply) => {
|
|
4019
|
-
const root = await
|
|
4020
|
-
const result = await
|
|
4054
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4055
|
+
const result = await Effect17.runPromise(discoverProjects(root, store).pipe(Effect17.result));
|
|
4021
4056
|
if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
|
|
4022
4057
|
return result.success;
|
|
4023
4058
|
});
|
|
4024
4059
|
app.get("/api/projects/:id", async (request, reply) => {
|
|
4025
|
-
const root = await
|
|
4026
|
-
const result = await
|
|
4060
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4061
|
+
const result = await Effect17.runPromise(discoverProjects(root, store).pipe(Effect17.result));
|
|
4027
4062
|
if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
|
|
4028
4063
|
const project = result.success.find((item) => item.id === request.params.id);
|
|
4029
4064
|
return project ?? reply.code(404).send({ error: "Project not found on this server." });
|
|
@@ -4032,13 +4067,13 @@ app.post("/api/projects/:id/reconnect", async (request, reply) => {
|
|
|
4032
4067
|
const parsed = z17.object({ path: z17.string().trim().min(1).max(4096) }).safeParse(request.body);
|
|
4033
4068
|
if (!parsed.success)
|
|
4034
4069
|
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(
|
|
4070
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4071
|
+
const result = await Effect17.runPromise(
|
|
4072
|
+
reconnectProject(root, request.params.id, parsed.data.path, store).pipe(Effect17.result)
|
|
4038
4073
|
);
|
|
4039
4074
|
return result._tag === "Failure" ? reply.code(400).send({ error: result.failure.message }) : result.success;
|
|
4040
4075
|
});
|
|
4041
|
-
app.get("/api/sessions", () =>
|
|
4076
|
+
app.get("/api/sessions", () => Effect17.runPromise(store.list()));
|
|
4042
4077
|
app.get(
|
|
4043
4078
|
"/api/tasks",
|
|
4044
4079
|
(request) => boardTasks(store, request.query.projectId)
|
|
@@ -4051,15 +4086,15 @@ app.post("/api/tasks", async (request, reply) => {
|
|
|
4051
4086
|
if (!github.githubCli.authenticated)
|
|
4052
4087
|
return reply.code(400).send({ error: "GitHub CLI must be authenticated on the server to open a PR." });
|
|
4053
4088
|
}
|
|
4054
|
-
const projectsRoot = await
|
|
4055
|
-
const projects = await
|
|
4056
|
-
discoverProjects(projectsRoot, store).pipe(
|
|
4089
|
+
const projectsRoot = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4090
|
+
const projects = await Effect17.runPromise(
|
|
4091
|
+
discoverProjects(projectsRoot, store).pipe(Effect17.result)
|
|
4057
4092
|
);
|
|
4058
4093
|
if (projects._tag === "Failure" || !projects.success.some((project) => project.id === parsed.data.projectId && project.available))
|
|
4059
4094
|
return reply.code(404).send({ error: "Project not found in the server's projects folder." });
|
|
4060
4095
|
let files;
|
|
4061
4096
|
try {
|
|
4062
|
-
files = await
|
|
4097
|
+
files = await Effect17.runPromise(
|
|
4063
4098
|
attachments.resolve(parsed.data.projectId, parsed.data.attachmentIds ?? [])
|
|
4064
4099
|
);
|
|
4065
4100
|
} catch (error) {
|
|
@@ -4067,7 +4102,7 @@ app.post("/api/tasks", async (request, reply) => {
|
|
|
4067
4102
|
}
|
|
4068
4103
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4069
4104
|
const task = TaskSchema.parse({
|
|
4070
|
-
id:
|
|
4105
|
+
id: randomUUID9(),
|
|
4071
4106
|
...parsed.data,
|
|
4072
4107
|
attachments: files,
|
|
4073
4108
|
status: "backlog",
|
|
@@ -4075,7 +4110,7 @@ app.post("/api/tasks", async (request, reply) => {
|
|
|
4075
4110
|
createdAt: now,
|
|
4076
4111
|
updatedAt: now
|
|
4077
4112
|
});
|
|
4078
|
-
return reply.code(201).send(await
|
|
4113
|
+
return reply.code(201).send(await Effect17.runPromise(store.saveTask(task)));
|
|
4079
4114
|
});
|
|
4080
4115
|
app.patch("/api/tasks/:id", async (request, reply) => {
|
|
4081
4116
|
const parsed = UpdateTaskSchema.safeParse(request.body);
|
|
@@ -4090,8 +4125,8 @@ app.patch("/api/tasks/:id", async (request, reply) => {
|
|
|
4090
4125
|
return reply.code(400).send({ error: "GitHub CLI must be authenticated on the server to open a PR." });
|
|
4091
4126
|
}
|
|
4092
4127
|
return await editTask(store, request.params.id, parsed.data, async (projectId, ids) => {
|
|
4093
|
-
const result = await
|
|
4094
|
-
attachments.resolve(projectId, ids).pipe(
|
|
4128
|
+
const result = await Effect17.runPromise(
|
|
4129
|
+
attachments.resolve(projectId, ids).pipe(Effect17.result)
|
|
4095
4130
|
);
|
|
4096
4131
|
if (result._tag === "Failure") throw new TaskActionError(result.failure.message, 400);
|
|
4097
4132
|
return result.success;
|
|
@@ -4105,11 +4140,11 @@ app.patch("/api/tasks/:id", async (request, reply) => {
|
|
|
4105
4140
|
app.delete("/api/tasks/:id", async (request, reply) => {
|
|
4106
4141
|
try {
|
|
4107
4142
|
await deleteTask(store, request.params.id, async (session) => {
|
|
4108
|
-
await
|
|
4143
|
+
await Effect17.runPromise(runtime.forget(session.id));
|
|
4109
4144
|
await worktreeCleanup.remove(session);
|
|
4110
4145
|
await removeRunFiles(dataRoot, session.id);
|
|
4111
4146
|
});
|
|
4112
|
-
await
|
|
4147
|
+
await Effect17.runPromise(attachments.cleanup());
|
|
4113
4148
|
return reply.code(204).send();
|
|
4114
4149
|
} catch (error) {
|
|
4115
4150
|
if (error instanceof TaskActionError)
|
|
@@ -4118,14 +4153,14 @@ app.delete("/api/tasks/:id", async (request, reply) => {
|
|
|
4118
4153
|
}
|
|
4119
4154
|
});
|
|
4120
4155
|
app.get("/api/sessions/:id", async (request, reply) => {
|
|
4121
|
-
const session = await
|
|
4156
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4122
4157
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4123
4158
|
return session;
|
|
4124
4159
|
});
|
|
4125
4160
|
app.post(
|
|
4126
4161
|
"/api/sessions/:id/delivery",
|
|
4127
4162
|
async (request, reply) => withSessionTaskLock(request.params.id, async () => {
|
|
4128
|
-
const session = await
|
|
4163
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4129
4164
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4130
4165
|
try {
|
|
4131
4166
|
const result = await delivery.retry(session.id);
|
|
@@ -4142,10 +4177,10 @@ app.post(
|
|
|
4142
4177
|
try {
|
|
4143
4178
|
const prepared = await preparation.retry(request.params.id);
|
|
4144
4179
|
preparedSession = prepared.session;
|
|
4145
|
-
const files = await
|
|
4180
|
+
const files = await Effect17.runPromise(
|
|
4146
4181
|
attachments.resolve(prepared.session.projectId, prepared.input.attachmentIds ?? [])
|
|
4147
4182
|
);
|
|
4148
|
-
await
|
|
4183
|
+
await Effect17.runPromise(runtime.send(prepared.session, prepared.input.prompt, files));
|
|
4149
4184
|
return reply.code(202).send(prepared.session);
|
|
4150
4185
|
} catch (error) {
|
|
4151
4186
|
if (preparedSession) {
|
|
@@ -4168,9 +4203,9 @@ var HistoryQuery = z17.object({
|
|
|
4168
4203
|
app.get("/api/sessions/:id/events", async (request, reply) => {
|
|
4169
4204
|
const query = HistoryQuery.safeParse(request.query);
|
|
4170
4205
|
if (!query.success) return reply.code(400).send({ error: "Invalid history cursor or limit." });
|
|
4171
|
-
const session = await
|
|
4206
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4172
4207
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4173
|
-
return
|
|
4208
|
+
return Effect17.runPromise(store.history(session.id, query.data.after, query.data.limit));
|
|
4174
4209
|
});
|
|
4175
4210
|
async function selectSessionModel(input, hasImages) {
|
|
4176
4211
|
try {
|
|
@@ -4190,8 +4225,8 @@ async function selectSessionModel(input, hasImages) {
|
|
|
4190
4225
|
}
|
|
4191
4226
|
}
|
|
4192
4227
|
async function resolveSessionProject(projectId) {
|
|
4193
|
-
const projectsRoot = await
|
|
4194
|
-
const project = (await
|
|
4228
|
+
const projectsRoot = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4229
|
+
const project = (await Effect17.runPromise(discoverProjects(projectsRoot, store))).find(
|
|
4195
4230
|
(item) => item.id === projectId && item.available
|
|
4196
4231
|
);
|
|
4197
4232
|
if (!project)
|
|
@@ -4233,7 +4268,7 @@ async function createAndStartSession(input) {
|
|
|
4233
4268
|
const projectPath = location.projectPath;
|
|
4234
4269
|
let files;
|
|
4235
4270
|
try {
|
|
4236
|
-
files = await
|
|
4271
|
+
files = await Effect17.runPromise(
|
|
4237
4272
|
attachments.resolve(input.projectId, input.attachmentIds ?? [])
|
|
4238
4273
|
);
|
|
4239
4274
|
} catch (error) {
|
|
@@ -4246,11 +4281,11 @@ async function createAndStartSession(input) {
|
|
|
4246
4281
|
if (selection.error !== void 0) return { error: selection.error, status: selection.status };
|
|
4247
4282
|
const selectedModel = selection.model;
|
|
4248
4283
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4249
|
-
const task = input.taskId ? await
|
|
4284
|
+
const task = input.taskId ? await Effect17.runPromise(store.getTask(input.taskId)) : void 0;
|
|
4250
4285
|
if (input.taskId && sessionTaskUnavailable(task, input.projectId))
|
|
4251
4286
|
return { error: "Task not found.", status: 404 };
|
|
4252
4287
|
const session = SessionSchema.parse({
|
|
4253
|
-
id:
|
|
4288
|
+
id: randomUUID9(),
|
|
4254
4289
|
taskId: input.taskId ?? null,
|
|
4255
4290
|
projectId: input.projectId,
|
|
4256
4291
|
title: task?.title ?? input.prompt.slice(0, 80),
|
|
@@ -4276,7 +4311,7 @@ async function createAndStartSession(input) {
|
|
|
4276
4311
|
);
|
|
4277
4312
|
} catch (error) {
|
|
4278
4313
|
if (task)
|
|
4279
|
-
await
|
|
4314
|
+
await Effect17.runPromise(
|
|
4280
4315
|
store.saveTask({ ...task, sessionId: session.id, updatedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
4281
4316
|
);
|
|
4282
4317
|
return {
|
|
@@ -4285,7 +4320,7 @@ async function createAndStartSession(input) {
|
|
|
4285
4320
|
};
|
|
4286
4321
|
}
|
|
4287
4322
|
try {
|
|
4288
|
-
await
|
|
4323
|
+
await Effect17.runPromise(runtime.send(running, input.prompt, files));
|
|
4289
4324
|
} catch (error) {
|
|
4290
4325
|
publish({
|
|
4291
4326
|
type: "session.updated",
|
|
@@ -4294,8 +4329,27 @@ async function createAndStartSession(input) {
|
|
|
4294
4329
|
publish({ type: "error", sessionId: session.id, message: String(error) });
|
|
4295
4330
|
return { error: "Could not start the agent session.", status: 500 };
|
|
4296
4331
|
}
|
|
4297
|
-
return { session: await
|
|
4332
|
+
return { session: await Effect17.runPromise(store.get(session.id)), status: 201 };
|
|
4298
4333
|
}
|
|
4334
|
+
app.post("/api/projects/:id/chat", async (request, reply) => {
|
|
4335
|
+
maintenance.assertReady();
|
|
4336
|
+
const location = await resolveSessionProject(request.params.id);
|
|
4337
|
+
if (location.error !== void 0)
|
|
4338
|
+
return reply.code(location.status).send({ error: location.error });
|
|
4339
|
+
const parsed = z17.object({ reset: z17.boolean().optional() }).safeParse(request.body ?? {});
|
|
4340
|
+
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
4341
|
+
try {
|
|
4342
|
+
return await openRepoChat(
|
|
4343
|
+
store,
|
|
4344
|
+
request.params.id,
|
|
4345
|
+
location.projectPath,
|
|
4346
|
+
() => models.defaultIdentity(),
|
|
4347
|
+
parsed.data.reset
|
|
4348
|
+
);
|
|
4349
|
+
} catch (error) {
|
|
4350
|
+
return reply.code(409).send({ error: String(error) });
|
|
4351
|
+
}
|
|
4352
|
+
});
|
|
4299
4353
|
app.post("/api/sessions", async (request, reply) => {
|
|
4300
4354
|
const parsed = CreateSessionSchema.safeParse(request.body);
|
|
4301
4355
|
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
@@ -4312,11 +4366,11 @@ function startTask(id, instructionsOverride, automated = false) {
|
|
|
4312
4366
|
"agent-dispatch",
|
|
4313
4367
|
() => withTaskLock(id, async () => {
|
|
4314
4368
|
maintenance.assertReady();
|
|
4315
|
-
const task = await
|
|
4369
|
+
const task = await Effect17.runPromise(store.getTask(id));
|
|
4316
4370
|
if (!task || task.deletedAt) throw new TaskActionError("Task not found.", 404);
|
|
4317
4371
|
if (automated) {
|
|
4318
|
-
const state = await
|
|
4319
|
-
if (activeAgents(await
|
|
4372
|
+
const state = await Effect17.runPromise(store.automation());
|
|
4373
|
+
if (activeAgents(await Effect17.runPromise(store.list())) >= state.config.parallelism) return;
|
|
4320
4374
|
const backlog = await boardTasks(store);
|
|
4321
4375
|
if (!backlog.some((item) => item.id === id && item.status === "backlog")) return;
|
|
4322
4376
|
}
|
|
@@ -4324,7 +4378,7 @@ function startTask(id, instructionsOverride, automated = false) {
|
|
|
4324
4378
|
const prompt = instructions ? `${task.title}
|
|
4325
4379
|
|
|
4326
4380
|
${instructions}` : task.title;
|
|
4327
|
-
const linkedSession = task.sessionId ? await
|
|
4381
|
+
const linkedSession = task.sessionId ? await Effect17.runPromise(store.get(task.sessionId)) : void 0;
|
|
4328
4382
|
if (linkedSession && (["starting", "running", "queued"].includes(linkedSession.status) || linkedSession.gitDelivery?.status === "pending"))
|
|
4329
4383
|
throw new TaskActionError("This task already has a running agent.", 409);
|
|
4330
4384
|
const result = await createAndStartSession({
|
|
@@ -4338,7 +4392,7 @@ ${instructions}` : task.title;
|
|
|
4338
4392
|
});
|
|
4339
4393
|
if (!result.session)
|
|
4340
4394
|
throw new TaskActionError(result.error ?? "Could not start task.", result.status);
|
|
4341
|
-
const updatedTask = await
|
|
4395
|
+
const updatedTask = await Effect17.runPromise(
|
|
4342
4396
|
store.saveTask({
|
|
4343
4397
|
...task,
|
|
4344
4398
|
status: taskStatusForSession(result.session.status, result.session),
|
|
@@ -4373,8 +4427,8 @@ app.patch("/api/automation", async (request, reply) => {
|
|
|
4373
4427
|
const parsed = AutomationConfigSchema.safeParse(request.body);
|
|
4374
4428
|
if (!parsed.success)
|
|
4375
4429
|
return reply.code(400).send({ error: parsed.error.issues.map((issue) => issue.message).join(" ") });
|
|
4376
|
-
const root = await
|
|
4377
|
-
const projects = await
|
|
4430
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4431
|
+
const projects = await Effect17.runPromise(discoverProjects(root, store));
|
|
4378
4432
|
if (parsed.data.projectIds.some(
|
|
4379
4433
|
(id) => !projects.some((project) => project.id === id && project.available)
|
|
4380
4434
|
))
|
|
@@ -4402,7 +4456,7 @@ automationTimer.unref();
|
|
|
4402
4456
|
app.post(
|
|
4403
4457
|
"/api/sessions/:id/messages",
|
|
4404
4458
|
async (request, reply) => withSessionTaskLock(request.params.id, async () => {
|
|
4405
|
-
const session = await
|
|
4459
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4406
4460
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4407
4461
|
const parsed = SendMessageSchema.safeParse(request.body);
|
|
4408
4462
|
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
@@ -4410,10 +4464,10 @@ app.post(
|
|
|
4410
4464
|
await delivery.assertCanWork(session.id);
|
|
4411
4465
|
if (session.preparation && session.preparation.status !== "ready")
|
|
4412
4466
|
throw new Error("Retry workspace preparation before sending a message.");
|
|
4413
|
-
const files = await
|
|
4467
|
+
const files = await Effect17.runPromise(
|
|
4414
4468
|
attachments.resolve(session.projectId, parsed.data.attachmentIds ?? [])
|
|
4415
4469
|
);
|
|
4416
|
-
await
|
|
4470
|
+
await Effect17.runPromise(
|
|
4417
4471
|
runtime.send(
|
|
4418
4472
|
session,
|
|
4419
4473
|
parsed.data.message,
|
|
@@ -4429,22 +4483,22 @@ app.post(
|
|
|
4429
4483
|
})
|
|
4430
4484
|
);
|
|
4431
4485
|
async function withSessionTaskLock(id, operation) {
|
|
4432
|
-
const session = await
|
|
4486
|
+
const session = await Effect17.runPromise(store.get(id));
|
|
4433
4487
|
const lockId = session ? await sessionTaskLockId(store, session) : `session-${id}`;
|
|
4434
4488
|
return withTaskLock(lockId, operation);
|
|
4435
4489
|
}
|
|
4436
4490
|
app.post(
|
|
4437
4491
|
"/api/sessions/:id/stop",
|
|
4438
4492
|
async (request, reply) => withSessionTaskLock(request.params.id, async () => {
|
|
4439
|
-
const session = await
|
|
4493
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4440
4494
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4441
|
-
await
|
|
4495
|
+
await Effect17.runPromise(runtime.stop(session));
|
|
4442
4496
|
return reply.code(202).send({ accepted: true });
|
|
4443
4497
|
})
|
|
4444
4498
|
);
|
|
4445
4499
|
attachEventServer(app, security, allowedOrigins, () => ({
|
|
4446
4500
|
type: "snapshot",
|
|
4447
|
-
sessions:
|
|
4501
|
+
sessions: Effect17.runSync(store.list())
|
|
4448
4502
|
}));
|
|
4449
4503
|
var closeControl = await startControl(
|
|
4450
4504
|
dataRoot,
|
|
@@ -4456,7 +4510,7 @@ var closeControl = await startControl(
|
|
|
4456
4510
|
connectedDevices: new Set(security.sockets.values()).size,
|
|
4457
4511
|
connectedSockets: security.sockets.size,
|
|
4458
4512
|
memory: process.memoryUsage().rss,
|
|
4459
|
-
activeRuns: activeAgents(
|
|
4513
|
+
activeRuns: activeAgents(Effect17.runSync(store.list())),
|
|
4460
4514
|
maintenance: maintenance.status()
|
|
4461
4515
|
}),
|
|
4462
4516
|
maintenance
|
|
@@ -4474,7 +4528,7 @@ app.addHook("onClose", async () => {
|
|
|
4474
4528
|
clearInterval(automationTimer);
|
|
4475
4529
|
await withTaskLock("automation", async () => {
|
|
4476
4530
|
});
|
|
4477
|
-
await
|
|
4531
|
+
await Effect17.runPromise(runtime.close());
|
|
4478
4532
|
await agentEffects.dispose();
|
|
4479
4533
|
await attachmentEffects.dispose();
|
|
4480
4534
|
await effects.dispose();
|