@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/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 randomUUID8 } from "node:crypto";
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.3.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 Effect16, ManagedRuntime } from "effect";
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((session) => !session.taskId && !linkedSessionIds.has(session.id)).filter((session) => !projectId || session.projectId === projectId).map(sessionTask);
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 Effect16.runPromise(store.serverIdentity()));
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 Effect16.runPromise(attachments.cleanup());
3828
+ await Effect17.runPromise(attachments.cleanup());
3794
3829
  var attachmentCleanup = setInterval(
3795
3830
  () => {
3796
- void Effect16.runPromise(attachments.cleanup()).catch((error) => app.log.error(error));
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 Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
3808
- const projects = await Effect16.runPromise(discoverProjects(root, store));
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 Effect16.runPromise(attachments.upload(request.params.projectId, file)));
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 Effect16.runPromise(store.getAttachment(request.params.id));
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" ? Effect16.runSync(store.get(event.session.id)) : void 0;
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 = Effect16.runSync(store.append(emittedEvent, notification));
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 Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
3861
- const project = (await Effect16.runPromise(discoverProjects(projectsRoot, store))).find(
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) => Effect16.runSync(store.hasMessage(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 Effect16.runPromise(store.list())) {
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 Effect16.runPromise(store.projectsRoot(defaultProjectsRoot)),
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 Effect16.runPromise(store.projectsRoot(defaultProjectsRoot)),
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 Effect16.runPromise(store.get(request.params.id));
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 Effect16.runPromise(runtime.setModel(session, parsed.data));
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 Effect16.runPromise(
4013
- resolveProjectsRoot(parsed.data.projectsRoot).pipe(Effect16.result)
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 Effect16.runPromise(store.setProjectsRoot(result.success));
4051
+ return Effect17.runPromise(store.setProjectsRoot(result.success));
4017
4052
  });
4018
4053
  app.get("/api/projects", async (_request, reply) => {
4019
- const root = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4020
- const result = await Effect16.runPromise(discoverProjects(root, store).pipe(Effect16.result));
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 Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4026
- const result = await Effect16.runPromise(discoverProjects(root, store).pipe(Effect16.result));
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 Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4036
- const result = await Effect16.runPromise(
4037
- reconnectProject(root, request.params.id, parsed.data.path, store).pipe(Effect16.result)
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", () => Effect16.runPromise(store.list()));
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 Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4055
- const projects = await Effect16.runPromise(
4056
- discoverProjects(projectsRoot, store).pipe(Effect16.result)
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 Effect16.runPromise(
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: randomUUID8(),
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 Effect16.runPromise(store.saveTask(task)));
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 Effect16.runPromise(
4094
- attachments.resolve(projectId, ids).pipe(Effect16.result)
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 Effect16.runPromise(runtime.forget(session.id));
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 Effect16.runPromise(attachments.cleanup());
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 Effect16.runPromise(store.get(request.params.id));
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 Effect16.runPromise(store.get(request.params.id));
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 Effect16.runPromise(
4180
+ const files = await Effect17.runPromise(
4146
4181
  attachments.resolve(prepared.session.projectId, prepared.input.attachmentIds ?? [])
4147
4182
  );
4148
- await Effect16.runPromise(runtime.send(prepared.session, prepared.input.prompt, files));
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 Effect16.runPromise(store.get(request.params.id));
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 Effect16.runPromise(store.history(session.id, query.data.after, query.data.limit));
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 Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4194
- const project = (await Effect16.runPromise(discoverProjects(projectsRoot, store))).find(
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 Effect16.runPromise(
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 Effect16.runPromise(store.getTask(input.taskId)) : void 0;
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: randomUUID8(),
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 Effect16.runPromise(
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 Effect16.runPromise(runtime.send(running, input.prompt, files));
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 Effect16.runPromise(store.get(session.id)), status: 201 };
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 Effect16.runPromise(store.getTask(id));
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 Effect16.runPromise(store.automation());
4319
- if (activeAgents(await Effect16.runPromise(store.list())) >= state.config.parallelism) return;
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 Effect16.runPromise(store.get(task.sessionId)) : void 0;
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 Effect16.runPromise(
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 Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4377
- const projects = await Effect16.runPromise(discoverProjects(root, store));
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 Effect16.runPromise(store.get(request.params.id));
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 Effect16.runPromise(
4467
+ const files = await Effect17.runPromise(
4414
4468
  attachments.resolve(session.projectId, parsed.data.attachmentIds ?? [])
4415
4469
  );
4416
- await Effect16.runPromise(
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 Effect16.runPromise(store.get(id));
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 Effect16.runPromise(store.get(request.params.id));
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 Effect16.runPromise(runtime.stop(session));
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: Effect16.runSync(store.list())
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(Effect16.runSync(store.list())),
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 Effect16.runPromise(runtime.close());
4531
+ await Effect17.runPromise(runtime.close());
4478
4532
  await agentEffects.dispose();
4479
4533
  await attachmentEffects.dispose();
4480
4534
  await effects.dispose();