@agentrific/server 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cli.js CHANGED
@@ -30,7 +30,7 @@ var init_package = __esm({
30
30
  "package.json"() {
31
31
  package_default = {
32
32
  name: "@agentrific/server",
33
- version: "0.3.0",
33
+ version: "0.5.0",
34
34
  description: "Self-hosted control plane for long-running coding agents",
35
35
  keywords: [
36
36
  "agentrific",
@@ -246,7 +246,7 @@ function createPairingLink(payload) {
246
246
  link.searchParams.set("data", JSON.stringify(PairingPayloadSchema.parse(payload)));
247
247
  return link.toString();
248
248
  }
249
- var cronBounds, AutomationConfigSchema, AutomationStateSchema, SessionStatusSchema, AgentKindSchema, ModelReferenceSchema, ModelIdentitySchema, ModelOptionSchema, ModelCatalogSchema, GitFlowSchema, GitDeliverySchema, DeliveryRequestSchema, RunPreparationSchema, SessionSchema, MAX_ATTACHMENTS, MAX_ATTACHMENT_BYTES, AttachmentSchema, AttachmentIdsSchema, CreateSessionSchema, SendMessageSchema, FileChangeSchema, SessionEventSchema, RecordedEventSchema, EventHistorySchema, ServerEventSchema, ServerSettingsSchema, ConnectionsStatusSchema, UpdateSettingsSchema, ProjectSchema, TaskStatusSchema, TaskSchema, CreateTaskSchema, UpdateTaskSchema, PROTOCOL_VERSION, ServerUpdateStatusSchema, ServerHealthSchema, PairingPayloadSchema, PairingInvitationSchema, PairingRequestSchema, DeviceSchema, PairingResultSchema, HandshakeSchema, HandshakeAckSchema;
249
+ var cronBounds, AutomationConfigSchema, AutomationStateSchema, SessionStatusSchema, AgentKindSchema, ModelReferenceSchema, ModelIdentitySchema, ModelOptionSchema, ModelCatalogSchema, GitFlowSchema, GitDeliverySchema, DeliveryRequestSchema, RunPreparationSchema, SessionSchema, MAX_ATTACHMENTS, MAX_ATTACHMENT_BYTES, AttachmentSchema, AttachmentIdsSchema, CreateSessionSchema, SendMessageSchema, FileChangeSchema, SessionEventSchema, RecordedEventSchema, EventHistorySchema, ServerEventSchema, ServerSettingsSchema, ConnectionsStatusSchema, UpdateSettingsSchema, RepositoryIdentitySchema, ProjectGitStatusSchema, ProjectSchema, TaskStatusSchema, TaskSchema, CreateTaskSchema, UpdateTaskSchema, PROTOCOL_VERSION, ServerUpdateStatusSchema, ServerHealthSchema, PairingPayloadSchema, PairingInvitationSchema, PairingRequestSchema, DeviceSchema, PairingResultSchema, HandshakeSchema, HandshakeAckSchema;
250
250
  var init_src = __esm({
251
251
  "../../packages/contracts/src/index.ts"() {
252
252
  cronBounds = [
@@ -345,6 +345,7 @@ var init_src = __esm({
345
345
  SessionSchema = z2.object({
346
346
  id: z2.string(),
347
347
  taskId: z2.string().nullable().optional(),
348
+ kind: z2.enum(["task", "repo_chat"]).optional(),
348
349
  projectId: z2.string(),
349
350
  title: z2.string(),
350
351
  status: SessionStatusSchema,
@@ -475,11 +476,25 @@ var init_src = __esm({
475
476
  githubCli: z2.object({ installed: z2.boolean(), authenticated: z2.boolean() })
476
477
  });
477
478
  UpdateSettingsSchema = z2.object({ projectsRoot: z2.string().trim().min(1).max(4096) });
479
+ RepositoryIdentitySchema = z2.object({
480
+ key: z2.string(),
481
+ display: z2.string()
482
+ });
483
+ ProjectGitStatusSchema = z2.object({
484
+ observedAt: z2.string().datetime(),
485
+ state: z2.enum(["available", "unavailable"]),
486
+ repository: RepositoryIdentitySchema.nullable(),
487
+ head: z2.string().nullable(),
488
+ branch: z2.string().nullable(),
489
+ trackedChanges: z2.number().int().nonnegative(),
490
+ untrackedChanges: z2.number().int().nonnegative()
491
+ });
478
492
  ProjectSchema = z2.object({
479
493
  id: z2.string(),
480
494
  name: z2.string(),
481
495
  path: z2.string(),
482
- available: z2.boolean().default(true)
496
+ available: z2.boolean().default(true),
497
+ repository: RepositoryIdentitySchema.nullable().optional()
483
498
  });
484
499
  TaskStatusSchema = z2.enum(["backlog", "needs_user_input", "running", "done"]);
485
500
  TaskSchema = z2.object({
@@ -498,6 +513,7 @@ var init_src = __esm({
498
513
  deletedAt: z2.string().datetime().optional()
499
514
  });
500
515
  CreateTaskSchema = z2.object({
516
+ clientTaskId: z2.string().min(1).max(128).regex(/^[a-zA-Z0-9_-]+$/).optional(),
501
517
  projectId: z2.string().min(1),
502
518
  title: z2.string().trim().min(1).max(120),
503
519
  prompt: z2.string().trim().max(2e4).default(""),
@@ -1833,7 +1849,9 @@ async function boardTasks(store2, projectId) {
1833
1849
  status: taskStatusForSession(session.status, session)
1834
1850
  };
1835
1851
  });
1836
- const legacyTasks = sessions.filter((session) => !session.taskId && !linkedSessionIds.has(session.id)).filter((session) => !projectId || session.projectId === projectId).map(sessionTask);
1852
+ const legacyTasks = sessions.filter(
1853
+ (session) => session.kind !== "repo_chat" && !session.taskId && !linkedSessionIds.has(session.id)
1854
+ ).filter((session) => !projectId || session.projectId === projectId).map(sessionTask);
1837
1855
  return [...linkedTasks, ...legacyTasks].map((task) => TaskSchema.parse(task)).sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
1838
1856
  }
1839
1857
  async function findTask(store2, id) {
@@ -1868,7 +1886,7 @@ function editTask(store2, id, input, resolveAttachments) {
1868
1886
  });
1869
1887
  }
1870
1888
  function deleteTask(store2, id, cleanup = async () => {
1871
- }) {
1889
+ }, unrunUpdatedAt) {
1872
1890
  return withTaskLock(id, async () => {
1873
1891
  const task = await findTask(store2, id);
1874
1892
  const sessions = await Effect6.runPromise(store2.list());
@@ -1879,6 +1897,8 @@ function deleteTask(store2, id, cleanup = async () => {
1879
1897
  const associated = sessions.filter(
1880
1898
  (session) => session.taskId === task.id || session.id === task.sessionId
1881
1899
  );
1900
+ if (unrunUpdatedAt !== void 0 && (task.updatedAt !== unrunUpdatedAt || task.sessionId || associated.length || task.status !== "backlog"))
1901
+ throw new TaskActionError("This task changed or has already run. It cannot be moved.", 409);
1882
1902
  for (const session of associated) await cleanup(session);
1883
1903
  await Effect6.runPromise(
1884
1904
  store2.purgeTask(
@@ -2317,7 +2337,7 @@ import { promisify as promisify2, stripVTControlCharacters } from "node:util";
2317
2337
  function workflowLog(text) {
2318
2338
  return stripVTControlCharacters(text).slice(-32e3);
2319
2339
  }
2320
- async function workflowCommand(cwd, command, args, signal, managedPreparation = false) {
2340
+ function workflowEnvironment() {
2321
2341
  const env = { ...process.env };
2322
2342
  for (const name of [
2323
2343
  "GIT_DIR",
@@ -2337,6 +2357,10 @@ async function workflowCommand(cwd, command, args, signal, managedPreparation =
2337
2357
  "GIT_REPLACE_REF_BASE"
2338
2358
  ])
2339
2359
  delete env[name];
2360
+ return env;
2361
+ }
2362
+ async function workflowCommand(cwd, command, args, signal, managedPreparation = false) {
2363
+ const env = workflowEnvironment();
2340
2364
  if (managedPreparation) env.AGENTRIFIC_MANAGED_PREPARATION = "1";
2341
2365
  const result = await exec(command, args, {
2342
2366
  cwd,
@@ -3570,6 +3594,113 @@ var init_projects = __esm({
3570
3594
  }
3571
3595
  });
3572
3596
 
3597
+ // src/project-git.ts
3598
+ import { execFile as execFile3 } from "node:child_process";
3599
+ import { promisify as promisify3 } from "node:util";
3600
+ function repositoryIdentity(remote) {
3601
+ const input = remote.trim();
3602
+ if (!input || input.startsWith("/") || input.startsWith(".")) return null;
3603
+ const scp = /^(?:[^@/\s]+@)?([^/:\s]+):([^\s]+)$/.exec(input);
3604
+ let url;
3605
+ try {
3606
+ url = new URL(scp && !input.includes("://") ? `ssh://${scp[1]}/${scp[2]}` : input);
3607
+ } catch {
3608
+ return null;
3609
+ }
3610
+ if (!["https:", "http:", "ssh:", "git:"].includes(url.protocol)) return null;
3611
+ if (url.search || url.hash || url.port) return null;
3612
+ const host2 = url.hostname.toLowerCase();
3613
+ let path13 = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
3614
+ if (!host2 || !path13 || path13.includes("%") || path13.includes("//")) return null;
3615
+ if (["github.com", "bitbucket.org"].includes(host2)) path13 = path13.toLowerCase();
3616
+ const key = `${host2}/${path13}`;
3617
+ return { key, display: key };
3618
+ }
3619
+ async function git(directory, args) {
3620
+ const { stdout } = await execute3("git", ["-C", directory, ...args], {
3621
+ timeout: 3e3,
3622
+ maxBuffer: 1024 * 1024,
3623
+ env: { ...workflowEnvironment(), GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" }
3624
+ });
3625
+ return stdout.trim();
3626
+ }
3627
+ async function readRepositoryIdentity(directory) {
3628
+ try {
3629
+ const remotes = (await git(directory, ["remote"])).split("\n").filter(Boolean);
3630
+ const remote = remotes.includes("origin") ? "origin" : remotes.length === 1 ? remotes[0] : null;
3631
+ if (!remote) return null;
3632
+ return repositoryIdentity(await git(directory, ["remote", "get-url", remote]));
3633
+ } catch {
3634
+ return null;
3635
+ }
3636
+ }
3637
+ async function inspect(directory) {
3638
+ const observedAt = (/* @__PURE__ */ new Date()).toISOString();
3639
+ try {
3640
+ const [repository, head, branch, status] = await Promise.all([
3641
+ readRepositoryIdentity(directory),
3642
+ git(directory, ["rev-parse", "--verify", "HEAD"]),
3643
+ git(directory, ["symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => null),
3644
+ // NUL-separated records avoid ambiguous filenames/newlines. --no-renames
3645
+ // keeps each entry one record, so we only need its two status characters.
3646
+ execute3(
3647
+ "git",
3648
+ [
3649
+ "-C",
3650
+ directory,
3651
+ "status",
3652
+ "--porcelain=v1",
3653
+ "-z",
3654
+ "--no-renames",
3655
+ "--untracked-files=normal"
3656
+ ],
3657
+ {
3658
+ timeout: 3e3,
3659
+ maxBuffer: 1024 * 1024,
3660
+ env: { ...workflowEnvironment(), GIT_OPTIONAL_LOCKS: "0" }
3661
+ }
3662
+ )
3663
+ ]);
3664
+ const records = status.stdout.split("\0").filter(Boolean);
3665
+ return {
3666
+ observedAt,
3667
+ state: "available",
3668
+ repository,
3669
+ head,
3670
+ branch,
3671
+ trackedChanges: records.filter((record) => !record.startsWith("??")).length,
3672
+ untrackedChanges: records.filter((record) => record.startsWith("??")).length
3673
+ };
3674
+ } catch {
3675
+ return {
3676
+ observedAt,
3677
+ state: "unavailable",
3678
+ repository: null,
3679
+ head: null,
3680
+ branch: null,
3681
+ trackedChanges: 0,
3682
+ untrackedChanges: 0
3683
+ };
3684
+ }
3685
+ }
3686
+ function projectGitStatus(directory) {
3687
+ const cached = observations.get(directory);
3688
+ if (cached && cached.expires > Date.now()) return cached.value;
3689
+ if (observations.size >= 200) observations.delete(observations.keys().next().value);
3690
+ const value = inspect(directory);
3691
+ observations.set(directory, { expires: Date.now() + 15e3, value });
3692
+ return value;
3693
+ }
3694
+ var execute3, observations;
3695
+ var init_project_git = __esm({
3696
+ "src/project-git.ts"() {
3697
+ "use strict";
3698
+ init_workflow_command();
3699
+ execute3 = promisify3(execFile3);
3700
+ observations = /* @__PURE__ */ new Map();
3701
+ }
3702
+ });
3703
+
3573
3704
  // src/push-schema.ts
3574
3705
  import { z as z16 } from "zod";
3575
3706
  var PushBindingSchema, PushStateSchema, PendingPushSchema;
@@ -4121,6 +4252,44 @@ var init_worktree_cleanup = __esm({
4121
4252
  }
4122
4253
  });
4123
4254
 
4255
+ // src/repo-chat.ts
4256
+ import { randomUUID as randomUUID8 } from "node:crypto";
4257
+ import { Effect as Effect16 } from "effect";
4258
+ function openRepoChat(store2, projectId, workspacePath, defaultModel, reset = false) {
4259
+ return withTaskLock(`repo-chat:${projectId}`, async () => {
4260
+ 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];
4261
+ if (existing && !reset) return existing;
4262
+ if (existing?.status === "running")
4263
+ throw new Error("Wait for the current reply before resetting chat.");
4264
+ const model = await defaultModel();
4265
+ if (!model) throw new Error("Choose an available default model in Settings before chatting.");
4266
+ const now = new Date(
4267
+ Math.max(Date.now(), existing ? Date.parse(existing.createdAt) + 1 : 0)
4268
+ ).toISOString();
4269
+ const session = {
4270
+ id: randomUUID8(),
4271
+ projectId,
4272
+ kind: "repo_chat",
4273
+ title: workspacePath.split("/").at(-1) ?? projectId,
4274
+ status: "waiting_for_input",
4275
+ agent: "pi",
4276
+ model,
4277
+ branch: null,
4278
+ workspacePath,
4279
+ createdAt: now,
4280
+ updatedAt: now
4281
+ };
4282
+ await Effect16.runPromise(store2.append({ type: "session.updated", session }));
4283
+ return session;
4284
+ });
4285
+ }
4286
+ var init_repo_chat = __esm({
4287
+ "src/repo-chat.ts"() {
4288
+ "use strict";
4289
+ init_tasks();
4290
+ }
4291
+ });
4292
+
4124
4293
  // src/index.ts
4125
4294
  var index_exports = {};
4126
4295
  import Fastify from "fastify";
@@ -4128,18 +4297,18 @@ import pretty from "pino-pretty";
4128
4297
  import { styleText as styleText4 } from "node:util";
4129
4298
  import cors from "@fastify/cors";
4130
4299
  import multipart from "@fastify/multipart";
4131
- import { randomUUID as randomUUID8 } from "node:crypto";
4132
- import { execFile as execFile3 } from "node:child_process";
4133
- import { promisify as promisify3 } from "node:util";
4300
+ import { randomUUID as randomUUID9 } from "node:crypto";
4301
+ import { execFile as execFile4 } from "node:child_process";
4302
+ import { promisify as promisify4 } from "node:util";
4134
4303
  import { realpath as realpath2 } from "node:fs/promises";
4135
4304
  import { homedir as homedir4 } from "node:os";
4136
4305
  import path11 from "node:path";
4137
4306
  import { WebSocket } from "ws";
4138
- import { Effect as Effect16, ManagedRuntime } from "effect";
4307
+ import { Effect as Effect17, ManagedRuntime } from "effect";
4139
4308
  import { z as z18 } from "zod";
4140
4309
  import { ModelRuntime } from "@earendil-works/pi-coding-agent";
4141
4310
  function publish(event, workflowUpdate = false) {
4142
- const previous = event.type === "session.updated" ? Effect16.runSync(store.get(event.session.id)) : void 0;
4311
+ const previous = event.type === "session.updated" ? Effect17.runSync(store.get(event.session.id)) : void 0;
4143
4312
  const emittedEvent = event.type === "session.updated" && previous && !workflowUpdate ? {
4144
4313
  type: "session.updated",
4145
4314
  session: {
@@ -4149,7 +4318,7 @@ function publish(event, workflowUpdate = false) {
4149
4318
  }
4150
4319
  } : event;
4151
4320
  const notification = emittedEvent.type === "session.updated" ? push.pending(emittedEvent.session, previous) : void 0;
4152
- const record = Effect16.runSync(store.append(emittedEvent, notification));
4321
+ const record = Effect17.runSync(store.append(emittedEvent, notification));
4153
4322
  const message = JSON.stringify({ type: "event", record });
4154
4323
  for (const client of security.sockets.keys()) {
4155
4324
  if (!(client instanceof WebSocket) || client.readyState !== WebSocket.OPEN) continue;
@@ -4189,8 +4358,8 @@ async function selectSessionModel(input, hasImages) {
4189
4358
  }
4190
4359
  }
4191
4360
  async function resolveSessionProject(projectId) {
4192
- const projectsRoot = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4193
- const project = (await Effect16.runPromise(discoverProjects(projectsRoot, store))).find(
4361
+ const projectsRoot = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
4362
+ const project = (await Effect17.runPromise(discoverProjects(projectsRoot, store))).find(
4194
4363
  (item) => item.id === projectId && item.available
4195
4364
  );
4196
4365
  if (!project)
@@ -4232,7 +4401,7 @@ async function createAndStartSession(input) {
4232
4401
  const projectPath = location.projectPath;
4233
4402
  let files;
4234
4403
  try {
4235
- files = await Effect16.runPromise(
4404
+ files = await Effect17.runPromise(
4236
4405
  attachments.resolve(input.projectId, input.attachmentIds ?? [])
4237
4406
  );
4238
4407
  } catch (error) {
@@ -4245,11 +4414,11 @@ async function createAndStartSession(input) {
4245
4414
  if (selection.error !== void 0) return { error: selection.error, status: selection.status };
4246
4415
  const selectedModel = selection.model;
4247
4416
  const now = (/* @__PURE__ */ new Date()).toISOString();
4248
- const task = input.taskId ? await Effect16.runPromise(store.getTask(input.taskId)) : void 0;
4417
+ const task = input.taskId ? await Effect17.runPromise(store.getTask(input.taskId)) : void 0;
4249
4418
  if (input.taskId && sessionTaskUnavailable(task, input.projectId))
4250
4419
  return { error: "Task not found.", status: 404 };
4251
4420
  const session = SessionSchema.parse({
4252
- id: randomUUID8(),
4421
+ id: randomUUID9(),
4253
4422
  taskId: input.taskId ?? null,
4254
4423
  projectId: input.projectId,
4255
4424
  title: task?.title ?? input.prompt.slice(0, 80),
@@ -4275,7 +4444,7 @@ async function createAndStartSession(input) {
4275
4444
  );
4276
4445
  } catch (error) {
4277
4446
  if (task)
4278
- await Effect16.runPromise(
4447
+ await Effect17.runPromise(
4279
4448
  store.saveTask({ ...task, sessionId: session.id, updatedAt: (/* @__PURE__ */ new Date()).toISOString() })
4280
4449
  );
4281
4450
  return {
@@ -4284,7 +4453,7 @@ async function createAndStartSession(input) {
4284
4453
  };
4285
4454
  }
4286
4455
  try {
4287
- await Effect16.runPromise(runtime.send(running, input.prompt, files));
4456
+ await Effect17.runPromise(runtime.send(running, input.prompt, files));
4288
4457
  } catch (error) {
4289
4458
  publish({
4290
4459
  type: "session.updated",
@@ -4293,18 +4462,18 @@ async function createAndStartSession(input) {
4293
4462
  publish({ type: "error", sessionId: session.id, message: String(error) });
4294
4463
  return { error: "Could not start the agent session.", status: 500 };
4295
4464
  }
4296
- return { session: await Effect16.runPromise(store.get(session.id)), status: 201 };
4465
+ return { session: await Effect17.runPromise(store.get(session.id)), status: 201 };
4297
4466
  }
4298
4467
  function startTask(id, instructionsOverride, automated = false) {
4299
4468
  return withTaskLock(
4300
4469
  "agent-dispatch",
4301
4470
  () => withTaskLock(id, async () => {
4302
4471
  maintenance.assertReady();
4303
- const task = await Effect16.runPromise(store.getTask(id));
4472
+ const task = await Effect17.runPromise(store.getTask(id));
4304
4473
  if (!task || task.deletedAt) throw new TaskActionError("Task not found.", 404);
4305
4474
  if (automated) {
4306
- const state = await Effect16.runPromise(store.automation());
4307
- if (activeAgents(await Effect16.runPromise(store.list())) >= state.config.parallelism) return;
4475
+ const state = await Effect17.runPromise(store.automation());
4476
+ if (activeAgents(await Effect17.runPromise(store.list())) >= state.config.parallelism) return;
4308
4477
  const backlog = await boardTasks(store);
4309
4478
  if (!backlog.some((item) => item.id === id && item.status === "backlog")) return;
4310
4479
  }
@@ -4312,7 +4481,7 @@ function startTask(id, instructionsOverride, automated = false) {
4312
4481
  const prompt = instructions ? `${task.title}
4313
4482
 
4314
4483
  ${instructions}` : task.title;
4315
- const linkedSession = task.sessionId ? await Effect16.runPromise(store.get(task.sessionId)) : void 0;
4484
+ const linkedSession = task.sessionId ? await Effect17.runPromise(store.get(task.sessionId)) : void 0;
4316
4485
  if (linkedSession && (["starting", "running", "queued"].includes(linkedSession.status) || linkedSession.gitDelivery?.status === "pending"))
4317
4486
  throw new TaskActionError("This task already has a running agent.", 409);
4318
4487
  const result = await createAndStartSession({
@@ -4326,7 +4495,7 @@ ${instructions}` : task.title;
4326
4495
  });
4327
4496
  if (!result.session)
4328
4497
  throw new TaskActionError(result.error ?? "Could not start task.", result.status);
4329
- const updatedTask = await Effect16.runPromise(
4498
+ const updatedTask = await Effect17.runPromise(
4330
4499
  store.saveTask({
4331
4500
  ...task,
4332
4501
  status: taskStatusForSession(result.session.status, result.session),
@@ -4339,7 +4508,7 @@ ${instructions}` : task.title;
4339
4508
  );
4340
4509
  }
4341
4510
  async function withSessionTaskLock(id, operation) {
4342
- const session = await Effect16.runPromise(store.get(id));
4511
+ const session = await Effect17.runPromise(store.get(id));
4343
4512
  const lockId = session ? await sessionTaskLockId(store, session) : `session-${id}`;
4344
4513
  return withTaskLock(lockId, operation);
4345
4514
  }
@@ -4365,16 +4534,18 @@ var init_index = __esm({
4365
4534
  init_models();
4366
4535
  init_pi();
4367
4536
  init_projects();
4537
+ init_project_git();
4368
4538
  init_store();
4369
4539
  init_tasks();
4370
4540
  init_worktree_cleanup();
4541
+ init_repo_chat();
4371
4542
  host = process.env.AGENTRIFIC_HOST ?? "127.0.0.1";
4372
4543
  port = Number(process.env.AGENTRIFIC_PORT ?? 4318);
4373
4544
  defaultProjectsRoot = path11.resolve(process.env.AGENTRIFIC_PROJECTS_DIR ?? "./projects");
4374
4545
  worktreesRoot = path11.resolve(
4375
4546
  process.env.AGENTRIFIC_WORKTREES_DIR ?? path11.join(homedir4(), ".agentrific", "workspaces")
4376
4547
  );
4377
- execFileAsync = promisify3(execFile3);
4548
+ execFileAsync = promisify4(execFile4);
4378
4549
  if (host !== "127.0.0.1" && host !== "::1")
4379
4550
  throw new Error("Bind Agentrific to loopback and expose it with Tailscale Serve or cloudflared.");
4380
4551
  app = Fastify({
@@ -4399,7 +4570,7 @@ var init_index = __esm({
4399
4570
  releaseLock = await acquireServerLock(dataRoot);
4400
4571
  effects = ManagedRuntime.make(SessionStore.layer(path11.join(dataRoot, "sessions.sqlite")));
4401
4572
  store = await effects.runPromise(SessionStore);
4402
- security = new ServerSecurity(store, await Effect16.runPromise(store.serverIdentity()));
4573
+ security = new ServerSecurity(store, await Effect17.runPromise(store.serverIdentity()));
4403
4574
  testPushUrl = process.env.AGENTRIFIC_PUSH_TEST_URL;
4404
4575
  if (testPushUrl && (process.env.AGENTRIFIC_TEST_MODE !== "1" || !new URL(testPushUrl).hostname.endsWith(".ts.net") || new URL(testPushUrl).protocol !== "https:"))
4405
4576
  throw new Error("Test relay requires explicit test mode and Tailscale HTTPS.");
@@ -4422,10 +4593,10 @@ var init_index = __esm({
4422
4593
  Attachments.layer(path11.join(dataRoot, "attachments"), store)
4423
4594
  );
4424
4595
  attachments = await attachmentEffects.runPromise(Attachments);
4425
- await Effect16.runPromise(attachments.cleanup());
4596
+ await Effect17.runPromise(attachments.cleanup());
4426
4597
  attachmentCleanup = setInterval(
4427
4598
  () => {
4428
- void Effect16.runPromise(attachments.cleanup()).catch((error) => app.log.error(error));
4599
+ void Effect17.runPromise(attachments.cleanup()).catch((error) => app.log.error(error));
4429
4600
  },
4430
4601
  60 * 60 * 1e3
4431
4602
  );
@@ -4436,14 +4607,14 @@ var init_index = __esm({
4436
4607
  app.post(
4437
4608
  "/api/projects/:projectId/attachments",
4438
4609
  async (request, reply) => {
4439
- const root = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4440
- const projects = await Effect16.runPromise(discoverProjects(root, store));
4610
+ const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
4611
+ const projects = await Effect17.runPromise(discoverProjects(root, store));
4441
4612
  if (!projects.some((project) => project.id === request.params.projectId && project.available))
4442
4613
  return reply.code(404).send({ error: "Project not found." });
4443
4614
  try {
4444
4615
  const file = await request.file();
4445
4616
  if (!file) return reply.code(400).send({ error: "Choose a file to upload." });
4446
- return reply.code(201).send(await Effect16.runPromise(attachments.upload(request.params.projectId, file)));
4617
+ return reply.code(201).send(await Effect17.runPromise(attachments.upload(request.params.projectId, file)));
4447
4618
  } catch (error) {
4448
4619
  return reply.code(400).send({ error: error instanceof Error ? error.message : "Upload failed." });
4449
4620
  }
@@ -4452,7 +4623,7 @@ var init_index = __esm({
4452
4623
  app.get(
4453
4624
  "/api/projects/:projectId/attachments/:id/content",
4454
4625
  async (request, reply) => {
4455
- const attachment = await Effect16.runPromise(store.getAttachment(request.params.id));
4626
+ const attachment = await Effect17.runPromise(store.getAttachment(request.params.id));
4456
4627
  if (!attachment || attachment.projectId !== request.params.projectId)
4457
4628
  return reply.code(404).send({ error: "Attachment not found." });
4458
4629
  const preview = request.query.preview === "1" && attachment.kind === "image";
@@ -4470,8 +4641,8 @@ var init_index = __esm({
4470
4641
  publish: (event) => publish(event, true),
4471
4642
  draft: deliveryWriter(models),
4472
4643
  projectPath: async (session) => {
4473
- const projectsRoot = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4474
- const project = (await Effect16.runPromise(discoverProjects(projectsRoot, store))).find(
4644
+ const projectsRoot = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
4645
+ const project = (await Effect17.runPromise(discoverProjects(projectsRoot, store))).find(
4475
4646
  (item) => item.id === session.projectId && item.available
4476
4647
  );
4477
4648
  if (!project) throw new Error("Reconnect the project folder before delivering changes.");
@@ -4485,7 +4656,7 @@ var init_index = __esm({
4485
4656
  publish,
4486
4657
  worktreesRoot,
4487
4658
  path11.join(dataRoot, "pi"),
4488
- (sessionId, messageId) => Effect16.runSync(store.hasMessage(sessionId, messageId)),
4659
+ (sessionId, messageId) => Effect17.runSync(store.hasMessage(sessionId, messageId)),
4489
4660
  (session) => delivery.settled(session.id)
4490
4661
  )
4491
4662
  );
@@ -4520,7 +4691,7 @@ var init_index = __esm({
4520
4691
  () => maintenance.ready(),
4521
4692
  (error) => app.log.error(error)
4522
4693
  );
4523
- for (const session of await Effect16.runPromise(store.list())) {
4694
+ for (const session of await Effect17.runPromise(store.list())) {
4524
4695
  if (session.gitDelivery?.status === "pending") {
4525
4696
  publish(
4526
4697
  {
@@ -4550,7 +4721,7 @@ var init_index = __esm({
4550
4721
  await delivery.recover();
4551
4722
  app.get("/api/connections", async () => githubCliStatus());
4552
4723
  app.get("/api/settings", async () => ({
4553
- projectsRoot: await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot)),
4724
+ projectsRoot: await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot)),
4554
4725
  defaultModel: await models.defaultIdentity()
4555
4726
  }));
4556
4727
  app.get("/api/models", async (_request, reply) => {
@@ -4578,7 +4749,7 @@ var init_index = __esm({
4578
4749
  try {
4579
4750
  const defaultModel = await models.setDefault(parsed.data);
4580
4751
  return {
4581
- projectsRoot: await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot)),
4752
+ projectsRoot: await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot)),
4582
4753
  defaultModel
4583
4754
  };
4584
4755
  } catch {
@@ -4591,13 +4762,13 @@ var init_index = __esm({
4591
4762
  "/api/sessions/:id/model",
4592
4763
  async (request, reply) => withSessionTaskLock(request.params.id, async () => {
4593
4764
  const parsed = ModelReferenceSchema.safeParse(request.body);
4594
- const session = await Effect16.runPromise(store.get(request.params.id));
4765
+ const session = await Effect17.runPromise(store.get(request.params.id));
4595
4766
  if (!session) return reply.code(404).send({ error: "Session not found." });
4596
4767
  if (!parsed.success)
4597
4768
  return reply.code(400).send({ error: "Choose a model from the server catalogue." });
4598
4769
  try {
4599
4770
  await models.resolve(parsed.data);
4600
- return await Effect16.runPromise(runtime.setModel(session, parsed.data));
4771
+ return await Effect17.runPromise(runtime.setModel(session, parsed.data));
4601
4772
  } catch {
4602
4773
  return reply.code(409).send({
4603
4774
  error: "Could not change the model. Wait for the current reply to finish and choose an available model."
@@ -4608,21 +4779,38 @@ var init_index = __esm({
4608
4779
  app.post("/api/settings", async (request, reply) => {
4609
4780
  const parsed = UpdateSettingsSchema.safeParse(request.body);
4610
4781
  if (!parsed.success) return reply.code(400).send({ error: "Enter a projects folder." });
4611
- const result = await Effect16.runPromise(
4612
- resolveProjectsRoot(parsed.data.projectsRoot).pipe(Effect16.result)
4782
+ const result = await Effect17.runPromise(
4783
+ resolveProjectsRoot(parsed.data.projectsRoot).pipe(Effect17.result)
4613
4784
  );
4614
4785
  if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
4615
- return Effect16.runPromise(store.setProjectsRoot(result.success));
4786
+ return Effect17.runPromise(store.setProjectsRoot(result.success));
4616
4787
  });
4617
4788
  app.get("/api/projects", async (_request, reply) => {
4618
- const root = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4619
- const result = await Effect16.runPromise(discoverProjects(root, store).pipe(Effect16.result));
4789
+ const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
4790
+ const result = await Effect17.runPromise(discoverProjects(root, store).pipe(Effect17.result));
4620
4791
  if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
4621
- return result.success;
4792
+ return Effect17.runPromise(
4793
+ Effect17.forEach(
4794
+ result.success,
4795
+ (project) => Effect17.promise(async () => ({
4796
+ ...project,
4797
+ repository: project.available ? await readRepositoryIdentity(project.path) : null
4798
+ })),
4799
+ { concurrency: 4 }
4800
+ )
4801
+ );
4802
+ });
4803
+ app.get("/api/projects/:id/git-status", async (request, reply) => {
4804
+ const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
4805
+ const projects = await Effect17.runPromise(discoverProjects(root, store));
4806
+ const project = projects.find((item) => item.id === request.params.id);
4807
+ if (!project?.available)
4808
+ return reply.code(404).send({ error: "Project folder unavailable on this server." });
4809
+ return projectGitStatus(project.path);
4622
4810
  });
4623
4811
  app.get("/api/projects/:id", async (request, reply) => {
4624
- const root = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4625
- const result = await Effect16.runPromise(discoverProjects(root, store).pipe(Effect16.result));
4812
+ const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
4813
+ const result = await Effect17.runPromise(discoverProjects(root, store).pipe(Effect17.result));
4626
4814
  if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
4627
4815
  const project = result.success.find((item) => item.id === request.params.id);
4628
4816
  return project ?? reply.code(404).send({ error: "Project not found on this server." });
@@ -4631,50 +4819,62 @@ var init_index = __esm({
4631
4819
  const parsed = z18.object({ path: z18.string().trim().min(1).max(4096) }).safeParse(request.body);
4632
4820
  if (!parsed.success)
4633
4821
  return reply.code(400).send({ error: "Enter the project's new folder path." });
4634
- const root = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4635
- const result = await Effect16.runPromise(
4636
- reconnectProject(root, request.params.id, parsed.data.path, store).pipe(Effect16.result)
4822
+ const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
4823
+ const result = await Effect17.runPromise(
4824
+ reconnectProject(root, request.params.id, parsed.data.path, store).pipe(Effect17.result)
4637
4825
  );
4638
4826
  return result._tag === "Failure" ? reply.code(400).send({ error: result.failure.message }) : result.success;
4639
4827
  });
4640
- app.get("/api/sessions", () => Effect16.runPromise(store.list()));
4828
+ app.get("/api/sessions", () => Effect17.runPromise(store.list()));
4641
4829
  app.get(
4642
4830
  "/api/tasks",
4643
4831
  (request) => boardTasks(store, request.query.projectId)
4644
4832
  );
4833
+ app.get("/api/task-moves", () => ({ supported: true }));
4645
4834
  app.post("/api/tasks", async (request, reply) => {
4646
4835
  const parsed = CreateTaskSchema.safeParse(request.body);
4647
4836
  if (!parsed.success) return reply.code(400).send({ error: "Enter a task title." });
4648
- if (parsed.data.gitFlow?.delivery === "pull_request") {
4649
- const github = await githubCliStatus();
4650
- if (!github.githubCli.authenticated)
4651
- return reply.code(400).send({ error: "GitHub CLI must be authenticated on the server to open a PR." });
4652
- }
4653
- const projectsRoot = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4654
- const projects = await Effect16.runPromise(
4655
- discoverProjects(projectsRoot, store).pipe(Effect16.result)
4656
- );
4657
- if (projects._tag === "Failure" || !projects.success.some((project) => project.id === parsed.data.projectId && project.available))
4658
- return reply.code(404).send({ error: "Project not found in the server's projects folder." });
4659
- let files;
4660
- try {
4661
- files = await Effect16.runPromise(
4662
- attachments.resolve(parsed.data.projectId, parsed.data.attachmentIds ?? [])
4837
+ const save = async () => {
4838
+ if (parsed.data.clientTaskId) {
4839
+ const existing = await Effect17.runPromise(store.getTask(parsed.data.clientTaskId));
4840
+ if (existing) {
4841
+ if (existing.deletedAt || existing.projectId !== parsed.data.projectId || existing.title !== parsed.data.title || existing.prompt !== parsed.data.prompt || JSON.stringify(existing.gitFlow) !== JSON.stringify(parsed.data.gitFlow) || JSON.stringify(existing.model) !== JSON.stringify(parsed.data.model))
4842
+ return reply.code(409).send({ error: "This task creation request has already been used." });
4843
+ return reply.code(201).send(existing);
4844
+ }
4845
+ }
4846
+ if (parsed.data.gitFlow?.delivery === "pull_request") {
4847
+ const github = await githubCliStatus();
4848
+ if (!github.githubCli.authenticated)
4849
+ return reply.code(400).send({ error: "GitHub CLI must be authenticated on the server to open a PR." });
4850
+ }
4851
+ const projectsRoot = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
4852
+ const projects = await Effect17.runPromise(
4853
+ discoverProjects(projectsRoot, store).pipe(Effect17.result)
4663
4854
  );
4664
- } catch (error) {
4665
- return reply.code(400).send({ error: String(error) });
4666
- }
4667
- const now = (/* @__PURE__ */ new Date()).toISOString();
4668
- const task = TaskSchema.parse({
4669
- id: randomUUID8(),
4670
- ...parsed.data,
4671
- attachments: files,
4672
- status: "backlog",
4673
- sessionId: null,
4674
- createdAt: now,
4675
- updatedAt: now
4676
- });
4677
- return reply.code(201).send(await Effect16.runPromise(store.saveTask(task)));
4855
+ if (projects._tag === "Failure" || !projects.success.some((project) => project.id === parsed.data.projectId && project.available))
4856
+ return reply.code(404).send({ error: "Project not found in the server's projects folder." });
4857
+ let files;
4858
+ try {
4859
+ files = await Effect17.runPromise(
4860
+ attachments.resolve(parsed.data.projectId, parsed.data.attachmentIds ?? [])
4861
+ );
4862
+ } catch (error) {
4863
+ return reply.code(400).send({ error: String(error) });
4864
+ }
4865
+ const now = (/* @__PURE__ */ new Date()).toISOString();
4866
+ const task = TaskSchema.parse({
4867
+ id: parsed.data.clientTaskId ?? randomUUID9(),
4868
+ ...parsed.data,
4869
+ attachments: files,
4870
+ status: "backlog",
4871
+ sessionId: null,
4872
+ createdAt: now,
4873
+ updatedAt: now
4874
+ });
4875
+ return reply.code(201).send(await Effect17.runPromise(store.saveTask(task)));
4876
+ };
4877
+ return parsed.data.clientTaskId ? withTaskLock(`create-task:${parsed.data.clientTaskId}`, save) : save();
4678
4878
  });
4679
4879
  app.patch("/api/tasks/:id", async (request, reply) => {
4680
4880
  const parsed = UpdateTaskSchema.safeParse(request.body);
@@ -4689,8 +4889,8 @@ var init_index = __esm({
4689
4889
  return reply.code(400).send({ error: "GitHub CLI must be authenticated on the server to open a PR." });
4690
4890
  }
4691
4891
  return await editTask(store, request.params.id, parsed.data, async (projectId, ids) => {
4692
- const result = await Effect16.runPromise(
4693
- attachments.resolve(projectId, ids).pipe(Effect16.result)
4892
+ const result = await Effect17.runPromise(
4893
+ attachments.resolve(projectId, ids).pipe(Effect17.result)
4694
4894
  );
4695
4895
  if (result._tag === "Failure") throw new TaskActionError(result.failure.message, 400);
4696
4896
  return result.success;
@@ -4701,14 +4901,26 @@ var init_index = __esm({
4701
4901
  throw error;
4702
4902
  }
4703
4903
  });
4904
+ app.post("/api/tasks/:id/discard-unrun", async (request, reply) => {
4905
+ const parsed = z18.object({ updatedAt: z18.string().datetime() }).safeParse(request.body);
4906
+ if (!parsed.success) return reply.code(400).send({ error: "The task revision is required." });
4907
+ try {
4908
+ await deleteTask(store, request.params.id, void 0, parsed.data.updatedAt);
4909
+ return reply.code(204).send();
4910
+ } catch (error) {
4911
+ if (error instanceof TaskActionError)
4912
+ return reply.code(error.status).send({ error: error.message });
4913
+ throw error;
4914
+ }
4915
+ });
4704
4916
  app.delete("/api/tasks/:id", async (request, reply) => {
4705
4917
  try {
4706
4918
  await deleteTask(store, request.params.id, async (session) => {
4707
- await Effect16.runPromise(runtime.forget(session.id));
4919
+ await Effect17.runPromise(runtime.forget(session.id));
4708
4920
  await worktreeCleanup.remove(session);
4709
4921
  await removeRunFiles(dataRoot, session.id);
4710
4922
  });
4711
- await Effect16.runPromise(attachments.cleanup());
4923
+ await Effect17.runPromise(attachments.cleanup());
4712
4924
  return reply.code(204).send();
4713
4925
  } catch (error) {
4714
4926
  if (error instanceof TaskActionError)
@@ -4717,14 +4929,14 @@ var init_index = __esm({
4717
4929
  }
4718
4930
  });
4719
4931
  app.get("/api/sessions/:id", async (request, reply) => {
4720
- const session = await Effect16.runPromise(store.get(request.params.id));
4932
+ const session = await Effect17.runPromise(store.get(request.params.id));
4721
4933
  if (!session) return reply.code(404).send({ error: "Session not found." });
4722
4934
  return session;
4723
4935
  });
4724
4936
  app.post(
4725
4937
  "/api/sessions/:id/delivery",
4726
4938
  async (request, reply) => withSessionTaskLock(request.params.id, async () => {
4727
- const session = await Effect16.runPromise(store.get(request.params.id));
4939
+ const session = await Effect17.runPromise(store.get(request.params.id));
4728
4940
  if (!session) return reply.code(404).send({ error: "Session not found." });
4729
4941
  try {
4730
4942
  const result = await delivery.retry(session.id);
@@ -4741,10 +4953,10 @@ var init_index = __esm({
4741
4953
  try {
4742
4954
  const prepared = await preparation.retry(request.params.id);
4743
4955
  preparedSession = prepared.session;
4744
- const files = await Effect16.runPromise(
4956
+ const files = await Effect17.runPromise(
4745
4957
  attachments.resolve(prepared.session.projectId, prepared.input.attachmentIds ?? [])
4746
4958
  );
4747
- await Effect16.runPromise(runtime.send(prepared.session, prepared.input.prompt, files));
4959
+ await Effect17.runPromise(runtime.send(prepared.session, prepared.input.prompt, files));
4748
4960
  return reply.code(202).send(prepared.session);
4749
4961
  } catch (error) {
4750
4962
  if (preparedSession) {
@@ -4767,9 +4979,28 @@ var init_index = __esm({
4767
4979
  app.get("/api/sessions/:id/events", async (request, reply) => {
4768
4980
  const query = HistoryQuery.safeParse(request.query);
4769
4981
  if (!query.success) return reply.code(400).send({ error: "Invalid history cursor or limit." });
4770
- const session = await Effect16.runPromise(store.get(request.params.id));
4982
+ const session = await Effect17.runPromise(store.get(request.params.id));
4771
4983
  if (!session) return reply.code(404).send({ error: "Session not found." });
4772
- return Effect16.runPromise(store.history(session.id, query.data.after, query.data.limit));
4984
+ return Effect17.runPromise(store.history(session.id, query.data.after, query.data.limit));
4985
+ });
4986
+ app.post("/api/projects/:id/chat", async (request, reply) => {
4987
+ maintenance.assertReady();
4988
+ const location = await resolveSessionProject(request.params.id);
4989
+ if (location.error !== void 0)
4990
+ return reply.code(location.status).send({ error: location.error });
4991
+ const parsed = z18.object({ reset: z18.boolean().optional() }).safeParse(request.body ?? {});
4992
+ if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
4993
+ try {
4994
+ return await openRepoChat(
4995
+ store,
4996
+ request.params.id,
4997
+ location.projectPath,
4998
+ () => models.defaultIdentity(),
4999
+ parsed.data.reset
5000
+ );
5001
+ } catch (error) {
5002
+ return reply.code(409).send({ error: String(error) });
5003
+ }
4773
5004
  });
4774
5005
  app.post("/api/sessions", async (request, reply) => {
4775
5006
  const parsed = CreateSessionSchema.safeParse(request.body);
@@ -4805,8 +5036,8 @@ var init_index = __esm({
4805
5036
  const parsed = AutomationConfigSchema.safeParse(request.body);
4806
5037
  if (!parsed.success)
4807
5038
  return reply.code(400).send({ error: parsed.error.issues.map((issue) => issue.message).join(" ") });
4808
- const root = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
4809
- const projects = await Effect16.runPromise(discoverProjects(root, store));
5039
+ const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
5040
+ const projects = await Effect17.runPromise(discoverProjects(root, store));
4810
5041
  if (parsed.data.projectIds.some(
4811
5042
  (id) => !projects.some((project) => project.id === id && project.available)
4812
5043
  ))
@@ -4834,7 +5065,7 @@ var init_index = __esm({
4834
5065
  app.post(
4835
5066
  "/api/sessions/:id/messages",
4836
5067
  async (request, reply) => withSessionTaskLock(request.params.id, async () => {
4837
- const session = await Effect16.runPromise(store.get(request.params.id));
5068
+ const session = await Effect17.runPromise(store.get(request.params.id));
4838
5069
  if (!session) return reply.code(404).send({ error: "Session not found." });
4839
5070
  const parsed = SendMessageSchema.safeParse(request.body);
4840
5071
  if (!parsed.success) return reply.code(400).send({ error: parsed.error.flatten() });
@@ -4842,10 +5073,10 @@ var init_index = __esm({
4842
5073
  await delivery.assertCanWork(session.id);
4843
5074
  if (session.preparation && session.preparation.status !== "ready")
4844
5075
  throw new Error("Retry workspace preparation before sending a message.");
4845
- const files = await Effect16.runPromise(
5076
+ const files = await Effect17.runPromise(
4846
5077
  attachments.resolve(session.projectId, parsed.data.attachmentIds ?? [])
4847
5078
  );
4848
- await Effect16.runPromise(
5079
+ await Effect17.runPromise(
4849
5080
  runtime.send(
4850
5081
  session,
4851
5082
  parsed.data.message,
@@ -4863,15 +5094,15 @@ var init_index = __esm({
4863
5094
  app.post(
4864
5095
  "/api/sessions/:id/stop",
4865
5096
  async (request, reply) => withSessionTaskLock(request.params.id, async () => {
4866
- const session = await Effect16.runPromise(store.get(request.params.id));
5097
+ const session = await Effect17.runPromise(store.get(request.params.id));
4867
5098
  if (!session) return reply.code(404).send({ error: "Session not found." });
4868
- await Effect16.runPromise(runtime.stop(session));
5099
+ await Effect17.runPromise(runtime.stop(session));
4869
5100
  return reply.code(202).send({ accepted: true });
4870
5101
  })
4871
5102
  );
4872
5103
  attachEventServer(app, security, allowedOrigins, () => ({
4873
5104
  type: "snapshot",
4874
- sessions: Effect16.runSync(store.list())
5105
+ sessions: Effect17.runSync(store.list())
4875
5106
  }));
4876
5107
  closeControl = await startControl(
4877
5108
  dataRoot,
@@ -4883,7 +5114,7 @@ var init_index = __esm({
4883
5114
  connectedDevices: new Set(security.sockets.values()).size,
4884
5115
  connectedSockets: security.sockets.size,
4885
5116
  memory: process.memoryUsage().rss,
4886
- activeRuns: activeAgents(Effect16.runSync(store.list())),
5117
+ activeRuns: activeAgents(Effect17.runSync(store.list())),
4887
5118
  maintenance: maintenance.status()
4888
5119
  }),
4889
5120
  maintenance
@@ -4901,7 +5132,7 @@ var init_index = __esm({
4901
5132
  clearInterval(automationTimer);
4902
5133
  await withTaskLock("automation", async () => {
4903
5134
  });
4904
- await Effect16.runPromise(runtime.close());
5135
+ await Effect17.runPromise(runtime.close());
4905
5136
  await agentEffects.dispose();
4906
5137
  await attachmentEffects.dispose();
4907
5138
  await effects.dispose();