@agentrific/server 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/cli.js +334 -103
- package/cli.js.map +4 -4
- package/index.js +318 -102
- package/index.js.map +4 -4
- package/package.json +1 -1
package/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.
|
|
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(
|
|
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
|
-
|
|
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
|
|
4132
|
-
import { execFile as
|
|
4133
|
-
import { promisify as
|
|
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
|
|
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" ?
|
|
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 =
|
|
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
|
|
4193
|
-
const project = (await
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4307
|
-
if (activeAgents(await
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
4596
|
+
await Effect17.runPromise(attachments.cleanup());
|
|
4426
4597
|
attachmentCleanup = setInterval(
|
|
4427
4598
|
() => {
|
|
4428
|
-
void
|
|
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
|
|
4440
|
-
const projects = await
|
|
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
|
|
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
|
|
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
|
|
4474
|
-
const project = (await
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4612
|
-
resolveProjectsRoot(parsed.data.projectsRoot).pipe(
|
|
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
|
|
4786
|
+
return Effect17.runPromise(store.setProjectsRoot(result.success));
|
|
4616
4787
|
});
|
|
4617
4788
|
app.get("/api/projects", async (_request, reply) => {
|
|
4618
|
-
const root = await
|
|
4619
|
-
const result = await
|
|
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
|
|
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
|
|
4625
|
-
const result = await
|
|
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
|
|
4635
|
-
const result = await
|
|
4636
|
-
reconnectProject(root, request.params.id, parsed.data.path, store).pipe(
|
|
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", () =>
|
|
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
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
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
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
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
|
|
4693
|
-
attachments.resolve(projectId, ids).pipe(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
4956
|
+
const files = await Effect17.runPromise(
|
|
4745
4957
|
attachments.resolve(prepared.session.projectId, prepared.input.attachmentIds ?? [])
|
|
4746
4958
|
);
|
|
4747
|
-
await
|
|
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
|
|
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
|
|
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
|
|
4809
|
-
const projects = await
|
|
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
|
|
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
|
|
5076
|
+
const files = await Effect17.runPromise(
|
|
4846
5077
|
attachments.resolve(session.projectId, parsed.data.attachmentIds ?? [])
|
|
4847
5078
|
);
|
|
4848
|
-
await
|
|
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
|
|
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
|
|
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:
|
|
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(
|
|
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
|
|
5135
|
+
await Effect17.runPromise(runtime.close());
|
|
4905
5136
|
await agentEffects.dispose();
|
|
4906
5137
|
await attachmentEffects.dispose();
|
|
4907
5138
|
await effects.dispose();
|