@agentrific/server 0.2.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 +14 -0
- package/cli.js +161 -75
- package/cli.js.map +4 -4
- package/index.js +154 -75
- 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,
|
|
@@ -465,6 +466,11 @@ var SessionSchema = z2.object({
|
|
|
465
466
|
gitFlow: GitFlowSchema.optional(),
|
|
466
467
|
gitDelivery: GitDeliverySchema.optional(),
|
|
467
468
|
preparation: RunPreparationSchema.optional(),
|
|
469
|
+
usage: z2.object({
|
|
470
|
+
tokens: z2.number().nonnegative(),
|
|
471
|
+
turns: z2.number().int().nonnegative(),
|
|
472
|
+
tools: z2.number().int().nonnegative()
|
|
473
|
+
}).optional(),
|
|
468
474
|
createdAt: z2.string().datetime(),
|
|
469
475
|
updatedAt: z2.string().datetime()
|
|
470
476
|
});
|
|
@@ -528,6 +534,7 @@ var SessionEventSchema = z2.discriminatedUnion("type", [
|
|
|
528
534
|
}),
|
|
529
535
|
z2.object({
|
|
530
536
|
type: z2.literal("message.completed"),
|
|
537
|
+
tokens: z2.number().nonnegative().optional(),
|
|
531
538
|
model: ModelIdentitySchema.optional(),
|
|
532
539
|
sessionId: z2.string(),
|
|
533
540
|
messageId: z2.string().optional(),
|
|
@@ -863,7 +870,7 @@ var Attachments = class extends Context.Service()(
|
|
|
863
870
|
};
|
|
864
871
|
|
|
865
872
|
// src/index.ts
|
|
866
|
-
import { randomUUID as
|
|
873
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
867
874
|
import { execFile as execFile2 } from "node:child_process";
|
|
868
875
|
import { promisify as promisify2 } from "node:util";
|
|
869
876
|
import { realpath as realpath2 } from "node:fs/promises";
|
|
@@ -1090,7 +1097,7 @@ async function startControl(directory, security2, status, maintenance2) {
|
|
|
1090
1097
|
// package.json
|
|
1091
1098
|
var package_default = {
|
|
1092
1099
|
name: "@agentrific/server",
|
|
1093
|
-
version: "0.
|
|
1100
|
+
version: "0.4.0",
|
|
1094
1101
|
description: "Self-hosted control plane for long-running coding agents",
|
|
1095
1102
|
keywords: [
|
|
1096
1103
|
"agentrific",
|
|
@@ -1476,7 +1483,7 @@ var UpdateDiscovery = class {
|
|
|
1476
1483
|
};
|
|
1477
1484
|
|
|
1478
1485
|
// src/index.ts
|
|
1479
|
-
import { Effect as
|
|
1486
|
+
import { Effect as Effect17, ManagedRuntime } from "effect";
|
|
1480
1487
|
import { z as z17 } from "zod";
|
|
1481
1488
|
|
|
1482
1489
|
// src/automation.ts
|
|
@@ -1564,7 +1571,9 @@ async function boardTasks(store2, projectId) {
|
|
|
1564
1571
|
status: taskStatusForSession(session.status, session)
|
|
1565
1572
|
};
|
|
1566
1573
|
});
|
|
1567
|
-
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);
|
|
1568
1577
|
return [...linkedTasks, ...legacyTasks].map((task) => TaskSchema.parse(task)).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
1569
1578
|
}
|
|
1570
1579
|
async function findTask(store2, id) {
|
|
@@ -1783,13 +1792,29 @@ var AgentController = class {
|
|
|
1783
1792
|
const entry = {
|
|
1784
1793
|
session,
|
|
1785
1794
|
agent,
|
|
1786
|
-
unsubscribe:
|
|
1795
|
+
unsubscribe: () => {
|
|
1796
|
+
},
|
|
1787
1797
|
running: false,
|
|
1788
1798
|
stopping: false,
|
|
1789
1799
|
pending: []
|
|
1790
1800
|
};
|
|
1801
|
+
entry.unsubscribe = agent.subscribe((event) => {
|
|
1802
|
+
this.emit(event);
|
|
1803
|
+
if (event.type !== "message.completed" && event.type !== "tool.started") return;
|
|
1804
|
+
const usage = entry.session.usage ?? { tokens: 0, turns: 0, tools: 0 };
|
|
1805
|
+
entry.session = {
|
|
1806
|
+
...entry.session,
|
|
1807
|
+
usage: {
|
|
1808
|
+
tokens: usage.tokens + (event.type === "message.completed" ? event.tokens ?? 0 : 0),
|
|
1809
|
+
turns: usage.turns + (event.type === "message.completed" ? 1 : 0),
|
|
1810
|
+
tools: usage.tools + (event.type === "tool.started" ? 1 : 0)
|
|
1811
|
+
},
|
|
1812
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1813
|
+
};
|
|
1814
|
+
this.emit({ type: "session.updated", session: entry.session });
|
|
1815
|
+
});
|
|
1791
1816
|
if (agent.model) {
|
|
1792
|
-
entry.session = { ...session, model: agent.model };
|
|
1817
|
+
entry.session = { ...entry.session, model: agent.model };
|
|
1793
1818
|
this.emit({ type: "session.updated", session: entry.session });
|
|
1794
1819
|
}
|
|
1795
1820
|
this.active.set(session.id, entry);
|
|
@@ -2981,6 +3006,7 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
2981
3006
|
sessionId,
|
|
2982
3007
|
messageId: assistantMessageId,
|
|
2983
3008
|
text,
|
|
3009
|
+
tokens: event.message.usage.totalTokens,
|
|
2984
3010
|
model: models2.identity(event.message.provider, event.message.model)
|
|
2985
3011
|
});
|
|
2986
3012
|
assistantMessageId = void 0;
|
|
@@ -3004,7 +3030,8 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
3004
3030
|
sessionId,
|
|
3005
3031
|
toolCallId: event.toolCallId,
|
|
3006
3032
|
tool: event.toolName,
|
|
3007
|
-
|
|
3033
|
+
// Preserve complete Bash arguments so multiline commands can be inspected.
|
|
3034
|
+
input: event.toolName === "bash" ? JSON.stringify(event.args) : boundedJson(event.args, 1200),
|
|
3008
3035
|
filePath: filePaths.get(event.toolCallId)
|
|
3009
3036
|
});
|
|
3010
3037
|
return true;
|
|
@@ -3711,6 +3738,38 @@ async function removeRunFiles(dataRoot2, sessionId) {
|
|
|
3711
3738
|
await rm2(path8.join(dataRoot2, "pi", sessionId), { recursive: true, force: true });
|
|
3712
3739
|
}
|
|
3713
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
|
+
|
|
3714
3773
|
// src/index.ts
|
|
3715
3774
|
var host = process.env.AGENTRIFIC_HOST ?? "127.0.0.1";
|
|
3716
3775
|
var port = Number(process.env.AGENTRIFIC_PORT ?? 4318);
|
|
@@ -3743,7 +3802,7 @@ var dataRoot = path9.resolve(process.env.AGENTRIFIC_DATA_DIR ?? "./data");
|
|
|
3743
3802
|
var releaseLock = await acquireServerLock(dataRoot);
|
|
3744
3803
|
var effects = ManagedRuntime.make(SessionStore.layer(path9.join(dataRoot, "sessions.sqlite")));
|
|
3745
3804
|
var store = await effects.runPromise(SessionStore);
|
|
3746
|
-
var security = new ServerSecurity(store, await
|
|
3805
|
+
var security = new ServerSecurity(store, await Effect17.runPromise(store.serverIdentity()));
|
|
3747
3806
|
var testPushUrl = process.env.AGENTRIFIC_PUSH_TEST_URL;
|
|
3748
3807
|
if (testPushUrl && (process.env.AGENTRIFIC_TEST_MODE !== "1" || !new URL(testPushUrl).hostname.endsWith(".ts.net") || new URL(testPushUrl).protocol !== "https:"))
|
|
3749
3808
|
throw new Error("Test relay requires explicit test mode and Tailscale HTTPS.");
|
|
@@ -3766,10 +3825,10 @@ var attachmentEffects = ManagedRuntime.make(
|
|
|
3766
3825
|
Attachments.layer(path9.join(dataRoot, "attachments"), store)
|
|
3767
3826
|
);
|
|
3768
3827
|
var attachments = await attachmentEffects.runPromise(Attachments);
|
|
3769
|
-
await
|
|
3828
|
+
await Effect17.runPromise(attachments.cleanup());
|
|
3770
3829
|
var attachmentCleanup = setInterval(
|
|
3771
3830
|
() => {
|
|
3772
|
-
void
|
|
3831
|
+
void Effect17.runPromise(attachments.cleanup()).catch((error) => app.log.error(error));
|
|
3773
3832
|
},
|
|
3774
3833
|
60 * 60 * 1e3
|
|
3775
3834
|
);
|
|
@@ -3780,14 +3839,14 @@ await app.register(multipart, {
|
|
|
3780
3839
|
app.post(
|
|
3781
3840
|
"/api/projects/:projectId/attachments",
|
|
3782
3841
|
async (request, reply) => {
|
|
3783
|
-
const root = await
|
|
3784
|
-
const projects = await
|
|
3842
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
3843
|
+
const projects = await Effect17.runPromise(discoverProjects(root, store));
|
|
3785
3844
|
if (!projects.some((project) => project.id === request.params.projectId && project.available))
|
|
3786
3845
|
return reply.code(404).send({ error: "Project not found." });
|
|
3787
3846
|
try {
|
|
3788
3847
|
const file = await request.file();
|
|
3789
3848
|
if (!file) return reply.code(400).send({ error: "Choose a file to upload." });
|
|
3790
|
-
return reply.code(201).send(await
|
|
3849
|
+
return reply.code(201).send(await Effect17.runPromise(attachments.upload(request.params.projectId, file)));
|
|
3791
3850
|
} catch (error) {
|
|
3792
3851
|
return reply.code(400).send({ error: error instanceof Error ? error.message : "Upload failed." });
|
|
3793
3852
|
}
|
|
@@ -3796,7 +3855,7 @@ app.post(
|
|
|
3796
3855
|
app.get(
|
|
3797
3856
|
"/api/projects/:projectId/attachments/:id/content",
|
|
3798
3857
|
async (request, reply) => {
|
|
3799
|
-
const attachment = await
|
|
3858
|
+
const attachment = await Effect17.runPromise(store.getAttachment(request.params.id));
|
|
3800
3859
|
if (!attachment || attachment.projectId !== request.params.projectId)
|
|
3801
3860
|
return reply.code(404).send({ error: "Attachment not found." });
|
|
3802
3861
|
const preview = request.query.preview === "1" && attachment.kind === "image";
|
|
@@ -3810,7 +3869,7 @@ app.get(
|
|
|
3810
3869
|
}
|
|
3811
3870
|
);
|
|
3812
3871
|
function publish(event, workflowUpdate = false) {
|
|
3813
|
-
const previous = event.type === "session.updated" ?
|
|
3872
|
+
const previous = event.type === "session.updated" ? Effect17.runSync(store.get(event.session.id)) : void 0;
|
|
3814
3873
|
const emittedEvent = event.type === "session.updated" && previous && !workflowUpdate ? {
|
|
3815
3874
|
type: "session.updated",
|
|
3816
3875
|
session: {
|
|
@@ -3820,7 +3879,7 @@ function publish(event, workflowUpdate = false) {
|
|
|
3820
3879
|
}
|
|
3821
3880
|
} : event;
|
|
3822
3881
|
const notification = emittedEvent.type === "session.updated" ? push.pending(emittedEvent.session, previous) : void 0;
|
|
3823
|
-
const record =
|
|
3882
|
+
const record = Effect17.runSync(store.append(emittedEvent, notification));
|
|
3824
3883
|
const message = JSON.stringify({ type: "event", record });
|
|
3825
3884
|
for (const client of security.sockets.keys()) {
|
|
3826
3885
|
if (!(client instanceof WebSocket) || client.readyState !== WebSocket.OPEN) continue;
|
|
@@ -3833,8 +3892,8 @@ var delivery = new DeliveryCoordinator(store, {
|
|
|
3833
3892
|
publish: (event) => publish(event, true),
|
|
3834
3893
|
draft: deliveryWriter(models),
|
|
3835
3894
|
projectPath: async (session) => {
|
|
3836
|
-
const projectsRoot = await
|
|
3837
|
-
const project = (await
|
|
3895
|
+
const projectsRoot = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
3896
|
+
const project = (await Effect17.runPromise(discoverProjects(projectsRoot, store))).find(
|
|
3838
3897
|
(item) => item.id === session.projectId && item.available
|
|
3839
3898
|
);
|
|
3840
3899
|
if (!project) throw new Error("Reconnect the project folder before delivering changes.");
|
|
@@ -3848,7 +3907,7 @@ var agentEffects = ManagedRuntime.make(
|
|
|
3848
3907
|
publish,
|
|
3849
3908
|
worktreesRoot,
|
|
3850
3909
|
path9.join(dataRoot, "pi"),
|
|
3851
|
-
(sessionId, messageId) =>
|
|
3910
|
+
(sessionId, messageId) => Effect17.runSync(store.hasMessage(sessionId, messageId)),
|
|
3852
3911
|
(session) => delivery.settled(session.id)
|
|
3853
3912
|
)
|
|
3854
3913
|
);
|
|
@@ -3883,7 +3942,7 @@ var stopUpdateDiscovery = updates.start(
|
|
|
3883
3942
|
() => maintenance.ready(),
|
|
3884
3943
|
(error) => app.log.error(error)
|
|
3885
3944
|
);
|
|
3886
|
-
for (const session of await
|
|
3945
|
+
for (const session of await Effect17.runPromise(store.list())) {
|
|
3887
3946
|
if (session.gitDelivery?.status === "pending") {
|
|
3888
3947
|
publish(
|
|
3889
3948
|
{
|
|
@@ -3927,7 +3986,7 @@ async function githubCliStatus() {
|
|
|
3927
3986
|
}
|
|
3928
3987
|
app.get("/api/connections", async () => githubCliStatus());
|
|
3929
3988
|
app.get("/api/settings", async () => ({
|
|
3930
|
-
projectsRoot: await
|
|
3989
|
+
projectsRoot: await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot)),
|
|
3931
3990
|
defaultModel: await models.defaultIdentity()
|
|
3932
3991
|
}));
|
|
3933
3992
|
app.get("/api/models", async (_request, reply) => {
|
|
@@ -3955,7 +4014,7 @@ app.post("/api/settings/model", async (request, reply) => {
|
|
|
3955
4014
|
try {
|
|
3956
4015
|
const defaultModel = await models.setDefault(parsed.data);
|
|
3957
4016
|
return {
|
|
3958
|
-
projectsRoot: await
|
|
4017
|
+
projectsRoot: await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot)),
|
|
3959
4018
|
defaultModel
|
|
3960
4019
|
};
|
|
3961
4020
|
} catch {
|
|
@@ -3968,13 +4027,13 @@ app.post(
|
|
|
3968
4027
|
"/api/sessions/:id/model",
|
|
3969
4028
|
async (request, reply) => withSessionTaskLock(request.params.id, async () => {
|
|
3970
4029
|
const parsed = ModelReferenceSchema.safeParse(request.body);
|
|
3971
|
-
const session = await
|
|
4030
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
3972
4031
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
3973
4032
|
if (!parsed.success)
|
|
3974
4033
|
return reply.code(400).send({ error: "Choose a model from the server catalogue." });
|
|
3975
4034
|
try {
|
|
3976
4035
|
await models.resolve(parsed.data);
|
|
3977
|
-
return await
|
|
4036
|
+
return await Effect17.runPromise(runtime.setModel(session, parsed.data));
|
|
3978
4037
|
} catch {
|
|
3979
4038
|
return reply.code(409).send({
|
|
3980
4039
|
error: "Could not change the model. Wait for the current reply to finish and choose an available model."
|
|
@@ -3985,21 +4044,21 @@ app.post(
|
|
|
3985
4044
|
app.post("/api/settings", async (request, reply) => {
|
|
3986
4045
|
const parsed = UpdateSettingsSchema.safeParse(request.body);
|
|
3987
4046
|
if (!parsed.success) return reply.code(400).send({ error: "Enter a projects folder." });
|
|
3988
|
-
const result = await
|
|
3989
|
-
resolveProjectsRoot(parsed.data.projectsRoot).pipe(
|
|
4047
|
+
const result = await Effect17.runPromise(
|
|
4048
|
+
resolveProjectsRoot(parsed.data.projectsRoot).pipe(Effect17.result)
|
|
3990
4049
|
);
|
|
3991
4050
|
if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
|
|
3992
|
-
return
|
|
4051
|
+
return Effect17.runPromise(store.setProjectsRoot(result.success));
|
|
3993
4052
|
});
|
|
3994
4053
|
app.get("/api/projects", async (_request, reply) => {
|
|
3995
|
-
const root = await
|
|
3996
|
-
const result = await
|
|
4054
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4055
|
+
const result = await Effect17.runPromise(discoverProjects(root, store).pipe(Effect17.result));
|
|
3997
4056
|
if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
|
|
3998
4057
|
return result.success;
|
|
3999
4058
|
});
|
|
4000
4059
|
app.get("/api/projects/:id", async (request, reply) => {
|
|
4001
|
-
const root = await
|
|
4002
|
-
const result = await
|
|
4060
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4061
|
+
const result = await Effect17.runPromise(discoverProjects(root, store).pipe(Effect17.result));
|
|
4003
4062
|
if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
|
|
4004
4063
|
const project = result.success.find((item) => item.id === request.params.id);
|
|
4005
4064
|
return project ?? reply.code(404).send({ error: "Project not found on this server." });
|
|
@@ -4008,13 +4067,13 @@ app.post("/api/projects/:id/reconnect", async (request, reply) => {
|
|
|
4008
4067
|
const parsed = z17.object({ path: z17.string().trim().min(1).max(4096) }).safeParse(request.body);
|
|
4009
4068
|
if (!parsed.success)
|
|
4010
4069
|
return reply.code(400).send({ error: "Enter the project's new folder path." });
|
|
4011
|
-
const root = await
|
|
4012
|
-
const result = await
|
|
4013
|
-
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)
|
|
4014
4073
|
);
|
|
4015
4074
|
return result._tag === "Failure" ? reply.code(400).send({ error: result.failure.message }) : result.success;
|
|
4016
4075
|
});
|
|
4017
|
-
app.get("/api/sessions", () =>
|
|
4076
|
+
app.get("/api/sessions", () => Effect17.runPromise(store.list()));
|
|
4018
4077
|
app.get(
|
|
4019
4078
|
"/api/tasks",
|
|
4020
4079
|
(request) => boardTasks(store, request.query.projectId)
|
|
@@ -4027,15 +4086,15 @@ app.post("/api/tasks", async (request, reply) => {
|
|
|
4027
4086
|
if (!github.githubCli.authenticated)
|
|
4028
4087
|
return reply.code(400).send({ error: "GitHub CLI must be authenticated on the server to open a PR." });
|
|
4029
4088
|
}
|
|
4030
|
-
const projectsRoot = await
|
|
4031
|
-
const projects = await
|
|
4032
|
-
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)
|
|
4033
4092
|
);
|
|
4034
4093
|
if (projects._tag === "Failure" || !projects.success.some((project) => project.id === parsed.data.projectId && project.available))
|
|
4035
4094
|
return reply.code(404).send({ error: "Project not found in the server's projects folder." });
|
|
4036
4095
|
let files;
|
|
4037
4096
|
try {
|
|
4038
|
-
files = await
|
|
4097
|
+
files = await Effect17.runPromise(
|
|
4039
4098
|
attachments.resolve(parsed.data.projectId, parsed.data.attachmentIds ?? [])
|
|
4040
4099
|
);
|
|
4041
4100
|
} catch (error) {
|
|
@@ -4043,7 +4102,7 @@ app.post("/api/tasks", async (request, reply) => {
|
|
|
4043
4102
|
}
|
|
4044
4103
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4045
4104
|
const task = TaskSchema.parse({
|
|
4046
|
-
id:
|
|
4105
|
+
id: randomUUID9(),
|
|
4047
4106
|
...parsed.data,
|
|
4048
4107
|
attachments: files,
|
|
4049
4108
|
status: "backlog",
|
|
@@ -4051,7 +4110,7 @@ app.post("/api/tasks", async (request, reply) => {
|
|
|
4051
4110
|
createdAt: now,
|
|
4052
4111
|
updatedAt: now
|
|
4053
4112
|
});
|
|
4054
|
-
return reply.code(201).send(await
|
|
4113
|
+
return reply.code(201).send(await Effect17.runPromise(store.saveTask(task)));
|
|
4055
4114
|
});
|
|
4056
4115
|
app.patch("/api/tasks/:id", async (request, reply) => {
|
|
4057
4116
|
const parsed = UpdateTaskSchema.safeParse(request.body);
|
|
@@ -4066,8 +4125,8 @@ app.patch("/api/tasks/:id", async (request, reply) => {
|
|
|
4066
4125
|
return reply.code(400).send({ error: "GitHub CLI must be authenticated on the server to open a PR." });
|
|
4067
4126
|
}
|
|
4068
4127
|
return await editTask(store, request.params.id, parsed.data, async (projectId, ids) => {
|
|
4069
|
-
const result = await
|
|
4070
|
-
attachments.resolve(projectId, ids).pipe(
|
|
4128
|
+
const result = await Effect17.runPromise(
|
|
4129
|
+
attachments.resolve(projectId, ids).pipe(Effect17.result)
|
|
4071
4130
|
);
|
|
4072
4131
|
if (result._tag === "Failure") throw new TaskActionError(result.failure.message, 400);
|
|
4073
4132
|
return result.success;
|
|
@@ -4081,11 +4140,11 @@ app.patch("/api/tasks/:id", async (request, reply) => {
|
|
|
4081
4140
|
app.delete("/api/tasks/:id", async (request, reply) => {
|
|
4082
4141
|
try {
|
|
4083
4142
|
await deleteTask(store, request.params.id, async (session) => {
|
|
4084
|
-
await
|
|
4143
|
+
await Effect17.runPromise(runtime.forget(session.id));
|
|
4085
4144
|
await worktreeCleanup.remove(session);
|
|
4086
4145
|
await removeRunFiles(dataRoot, session.id);
|
|
4087
4146
|
});
|
|
4088
|
-
await
|
|
4147
|
+
await Effect17.runPromise(attachments.cleanup());
|
|
4089
4148
|
return reply.code(204).send();
|
|
4090
4149
|
} catch (error) {
|
|
4091
4150
|
if (error instanceof TaskActionError)
|
|
@@ -4094,14 +4153,14 @@ app.delete("/api/tasks/:id", async (request, reply) => {
|
|
|
4094
4153
|
}
|
|
4095
4154
|
});
|
|
4096
4155
|
app.get("/api/sessions/:id", async (request, reply) => {
|
|
4097
|
-
const session = await
|
|
4156
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4098
4157
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4099
4158
|
return session;
|
|
4100
4159
|
});
|
|
4101
4160
|
app.post(
|
|
4102
4161
|
"/api/sessions/:id/delivery",
|
|
4103
4162
|
async (request, reply) => withSessionTaskLock(request.params.id, async () => {
|
|
4104
|
-
const session = await
|
|
4163
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4105
4164
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4106
4165
|
try {
|
|
4107
4166
|
const result = await delivery.retry(session.id);
|
|
@@ -4118,10 +4177,10 @@ app.post(
|
|
|
4118
4177
|
try {
|
|
4119
4178
|
const prepared = await preparation.retry(request.params.id);
|
|
4120
4179
|
preparedSession = prepared.session;
|
|
4121
|
-
const files = await
|
|
4180
|
+
const files = await Effect17.runPromise(
|
|
4122
4181
|
attachments.resolve(prepared.session.projectId, prepared.input.attachmentIds ?? [])
|
|
4123
4182
|
);
|
|
4124
|
-
await
|
|
4183
|
+
await Effect17.runPromise(runtime.send(prepared.session, prepared.input.prompt, files));
|
|
4125
4184
|
return reply.code(202).send(prepared.session);
|
|
4126
4185
|
} catch (error) {
|
|
4127
4186
|
if (preparedSession) {
|
|
@@ -4144,9 +4203,9 @@ var HistoryQuery = z17.object({
|
|
|
4144
4203
|
app.get("/api/sessions/:id/events", async (request, reply) => {
|
|
4145
4204
|
const query = HistoryQuery.safeParse(request.query);
|
|
4146
4205
|
if (!query.success) return reply.code(400).send({ error: "Invalid history cursor or limit." });
|
|
4147
|
-
const session = await
|
|
4206
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4148
4207
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4149
|
-
return
|
|
4208
|
+
return Effect17.runPromise(store.history(session.id, query.data.after, query.data.limit));
|
|
4150
4209
|
});
|
|
4151
4210
|
async function selectSessionModel(input, hasImages) {
|
|
4152
4211
|
try {
|
|
@@ -4166,8 +4225,8 @@ async function selectSessionModel(input, hasImages) {
|
|
|
4166
4225
|
}
|
|
4167
4226
|
}
|
|
4168
4227
|
async function resolveSessionProject(projectId) {
|
|
4169
|
-
const projectsRoot = await
|
|
4170
|
-
const project = (await
|
|
4228
|
+
const projectsRoot = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4229
|
+
const project = (await Effect17.runPromise(discoverProjects(projectsRoot, store))).find(
|
|
4171
4230
|
(item) => item.id === projectId && item.available
|
|
4172
4231
|
);
|
|
4173
4232
|
if (!project)
|
|
@@ -4209,7 +4268,7 @@ async function createAndStartSession(input) {
|
|
|
4209
4268
|
const projectPath = location.projectPath;
|
|
4210
4269
|
let files;
|
|
4211
4270
|
try {
|
|
4212
|
-
files = await
|
|
4271
|
+
files = await Effect17.runPromise(
|
|
4213
4272
|
attachments.resolve(input.projectId, input.attachmentIds ?? [])
|
|
4214
4273
|
);
|
|
4215
4274
|
} catch (error) {
|
|
@@ -4222,17 +4281,18 @@ async function createAndStartSession(input) {
|
|
|
4222
4281
|
if (selection.error !== void 0) return { error: selection.error, status: selection.status };
|
|
4223
4282
|
const selectedModel = selection.model;
|
|
4224
4283
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4225
|
-
const task = input.taskId ? await
|
|
4284
|
+
const task = input.taskId ? await Effect17.runPromise(store.getTask(input.taskId)) : void 0;
|
|
4226
4285
|
if (input.taskId && sessionTaskUnavailable(task, input.projectId))
|
|
4227
4286
|
return { error: "Task not found.", status: 404 };
|
|
4228
4287
|
const session = SessionSchema.parse({
|
|
4229
|
-
id:
|
|
4288
|
+
id: randomUUID9(),
|
|
4230
4289
|
taskId: input.taskId ?? null,
|
|
4231
4290
|
projectId: input.projectId,
|
|
4232
4291
|
title: task?.title ?? input.prompt.slice(0, 80),
|
|
4233
4292
|
status: "starting",
|
|
4234
4293
|
agent: input.agent,
|
|
4235
4294
|
model: selectedModel,
|
|
4295
|
+
usage: { tokens: 0, turns: 0, tools: 0 },
|
|
4236
4296
|
gitFlow: input.gitFlow,
|
|
4237
4297
|
branch: null,
|
|
4238
4298
|
createdAt: now,
|
|
@@ -4251,7 +4311,7 @@ async function createAndStartSession(input) {
|
|
|
4251
4311
|
);
|
|
4252
4312
|
} catch (error) {
|
|
4253
4313
|
if (task)
|
|
4254
|
-
await
|
|
4314
|
+
await Effect17.runPromise(
|
|
4255
4315
|
store.saveTask({ ...task, sessionId: session.id, updatedAt: (/* @__PURE__ */ new Date()).toISOString() })
|
|
4256
4316
|
);
|
|
4257
4317
|
return {
|
|
@@ -4260,7 +4320,7 @@ async function createAndStartSession(input) {
|
|
|
4260
4320
|
};
|
|
4261
4321
|
}
|
|
4262
4322
|
try {
|
|
4263
|
-
await
|
|
4323
|
+
await Effect17.runPromise(runtime.send(running, input.prompt, files));
|
|
4264
4324
|
} catch (error) {
|
|
4265
4325
|
publish({
|
|
4266
4326
|
type: "session.updated",
|
|
@@ -4269,8 +4329,27 @@ async function createAndStartSession(input) {
|
|
|
4269
4329
|
publish({ type: "error", sessionId: session.id, message: String(error) });
|
|
4270
4330
|
return { error: "Could not start the agent session.", status: 500 };
|
|
4271
4331
|
}
|
|
4272
|
-
return { session: await
|
|
4332
|
+
return { session: await Effect17.runPromise(store.get(session.id)), status: 201 };
|
|
4273
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
|
+
});
|
|
4274
4353
|
app.post("/api/sessions", async (request, reply) => {
|
|
4275
4354
|
const parsed = CreateSessionSchema.safeParse(request.body);
|
|
4276
4355
|
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
@@ -4287,11 +4366,11 @@ function startTask(id, instructionsOverride, automated = false) {
|
|
|
4287
4366
|
"agent-dispatch",
|
|
4288
4367
|
() => withTaskLock(id, async () => {
|
|
4289
4368
|
maintenance.assertReady();
|
|
4290
|
-
const task = await
|
|
4369
|
+
const task = await Effect17.runPromise(store.getTask(id));
|
|
4291
4370
|
if (!task || task.deletedAt) throw new TaskActionError("Task not found.", 404);
|
|
4292
4371
|
if (automated) {
|
|
4293
|
-
const state = await
|
|
4294
|
-
if (activeAgents(await
|
|
4372
|
+
const state = await Effect17.runPromise(store.automation());
|
|
4373
|
+
if (activeAgents(await Effect17.runPromise(store.list())) >= state.config.parallelism) return;
|
|
4295
4374
|
const backlog = await boardTasks(store);
|
|
4296
4375
|
if (!backlog.some((item) => item.id === id && item.status === "backlog")) return;
|
|
4297
4376
|
}
|
|
@@ -4299,7 +4378,7 @@ function startTask(id, instructionsOverride, automated = false) {
|
|
|
4299
4378
|
const prompt = instructions ? `${task.title}
|
|
4300
4379
|
|
|
4301
4380
|
${instructions}` : task.title;
|
|
4302
|
-
const linkedSession = task.sessionId ? await
|
|
4381
|
+
const linkedSession = task.sessionId ? await Effect17.runPromise(store.get(task.sessionId)) : void 0;
|
|
4303
4382
|
if (linkedSession && (["starting", "running", "queued"].includes(linkedSession.status) || linkedSession.gitDelivery?.status === "pending"))
|
|
4304
4383
|
throw new TaskActionError("This task already has a running agent.", 409);
|
|
4305
4384
|
const result = await createAndStartSession({
|
|
@@ -4313,7 +4392,7 @@ ${instructions}` : task.title;
|
|
|
4313
4392
|
});
|
|
4314
4393
|
if (!result.session)
|
|
4315
4394
|
throw new TaskActionError(result.error ?? "Could not start task.", result.status);
|
|
4316
|
-
const updatedTask = await
|
|
4395
|
+
const updatedTask = await Effect17.runPromise(
|
|
4317
4396
|
store.saveTask({
|
|
4318
4397
|
...task,
|
|
4319
4398
|
status: taskStatusForSession(result.session.status, result.session),
|
|
@@ -4348,8 +4427,8 @@ app.patch("/api/automation", async (request, reply) => {
|
|
|
4348
4427
|
const parsed = AutomationConfigSchema.safeParse(request.body);
|
|
4349
4428
|
if (!parsed.success)
|
|
4350
4429
|
return reply.code(400).send({ error: parsed.error.issues.map((issue) => issue.message).join(" ") });
|
|
4351
|
-
const root = await
|
|
4352
|
-
const projects = await
|
|
4430
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4431
|
+
const projects = await Effect17.runPromise(discoverProjects(root, store));
|
|
4353
4432
|
if (parsed.data.projectIds.some(
|
|
4354
4433
|
(id) => !projects.some((project) => project.id === id && project.available)
|
|
4355
4434
|
))
|
|
@@ -4377,7 +4456,7 @@ automationTimer.unref();
|
|
|
4377
4456
|
app.post(
|
|
4378
4457
|
"/api/sessions/:id/messages",
|
|
4379
4458
|
async (request, reply) => withSessionTaskLock(request.params.id, async () => {
|
|
4380
|
-
const session = await
|
|
4459
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4381
4460
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4382
4461
|
const parsed = SendMessageSchema.safeParse(request.body);
|
|
4383
4462
|
if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
|
|
@@ -4385,10 +4464,10 @@ app.post(
|
|
|
4385
4464
|
await delivery.assertCanWork(session.id);
|
|
4386
4465
|
if (session.preparation && session.preparation.status !== "ready")
|
|
4387
4466
|
throw new Error("Retry workspace preparation before sending a message.");
|
|
4388
|
-
const files = await
|
|
4467
|
+
const files = await Effect17.runPromise(
|
|
4389
4468
|
attachments.resolve(session.projectId, parsed.data.attachmentIds ?? [])
|
|
4390
4469
|
);
|
|
4391
|
-
await
|
|
4470
|
+
await Effect17.runPromise(
|
|
4392
4471
|
runtime.send(
|
|
4393
4472
|
session,
|
|
4394
4473
|
parsed.data.message,
|
|
@@ -4404,22 +4483,22 @@ app.post(
|
|
|
4404
4483
|
})
|
|
4405
4484
|
);
|
|
4406
4485
|
async function withSessionTaskLock(id, operation) {
|
|
4407
|
-
const session = await
|
|
4486
|
+
const session = await Effect17.runPromise(store.get(id));
|
|
4408
4487
|
const lockId = session ? await sessionTaskLockId(store, session) : `session-${id}`;
|
|
4409
4488
|
return withTaskLock(lockId, operation);
|
|
4410
4489
|
}
|
|
4411
4490
|
app.post(
|
|
4412
4491
|
"/api/sessions/:id/stop",
|
|
4413
4492
|
async (request, reply) => withSessionTaskLock(request.params.id, async () => {
|
|
4414
|
-
const session = await
|
|
4493
|
+
const session = await Effect17.runPromise(store.get(request.params.id));
|
|
4415
4494
|
if (!session) return reply.code(404).send({ error: "Session not found." });
|
|
4416
|
-
await
|
|
4495
|
+
await Effect17.runPromise(runtime.stop(session));
|
|
4417
4496
|
return reply.code(202).send({ accepted: true });
|
|
4418
4497
|
})
|
|
4419
4498
|
);
|
|
4420
4499
|
attachEventServer(app, security, allowedOrigins, () => ({
|
|
4421
4500
|
type: "snapshot",
|
|
4422
|
-
sessions:
|
|
4501
|
+
sessions: Effect17.runSync(store.list())
|
|
4423
4502
|
}));
|
|
4424
4503
|
var closeControl = await startControl(
|
|
4425
4504
|
dataRoot,
|
|
@@ -4431,7 +4510,7 @@ var closeControl = await startControl(
|
|
|
4431
4510
|
connectedDevices: new Set(security.sockets.values()).size,
|
|
4432
4511
|
connectedSockets: security.sockets.size,
|
|
4433
4512
|
memory: process.memoryUsage().rss,
|
|
4434
|
-
activeRuns: activeAgents(
|
|
4513
|
+
activeRuns: activeAgents(Effect17.runSync(store.list())),
|
|
4435
4514
|
maintenance: maintenance.status()
|
|
4436
4515
|
}),
|
|
4437
4516
|
maintenance
|
|
@@ -4449,7 +4528,7 @@ app.addHook("onClose", async () => {
|
|
|
4449
4528
|
clearInterval(automationTimer);
|
|
4450
4529
|
await withTaskLock("automation", async () => {
|
|
4451
4530
|
});
|
|
4452
|
-
await
|
|
4531
|
+
await Effect17.runPromise(runtime.close());
|
|
4453
4532
|
await agentEffects.dispose();
|
|
4454
4533
|
await attachmentEffects.dispose();
|
|
4455
4534
|
await effects.dispose();
|