@agentrific/server 0.4.0 → 0.5.1
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 +222 -41
- package/cli.js.map +4 -4
- package/index.js +229 -59
- package/index.js.map +4 -4
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @agentrific/server
|
|
2
2
|
|
|
3
|
+
## 0.5.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- Add a separate sandbox push service and require explicit test mode for sandbox grants so TestFlight purchases cannot unlock production notifications.
|
|
8
|
+
|
|
9
|
+
## 0.5.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- fbb6281: Group matching repositories across connected servers while keeping each task and chat associated with its server. Show repository identity and Git status, support explicit checkout association for older servers, and choose a task server directly in the native composer. Allow never-run backlog tasks to move safely between compatible servers.
|
|
14
|
+
|
|
15
|
+
Preserve native navigation and floating glass tabs, center and truncate conversation titles, show compact status dots, and improve navigation performance. Fix native task editor character loss, server picker alignment, and tool-call grid sizing.
|
|
16
|
+
|
|
3
17
|
## 0.4.0
|
|
4
18
|
|
|
5
19
|
### Minor Changes
|
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.1",
|
|
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 = [
|
|
@@ -476,11 +476,25 @@ var init_src = __esm({
|
|
|
476
476
|
githubCli: z2.object({ installed: z2.boolean(), authenticated: z2.boolean() })
|
|
477
477
|
});
|
|
478
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
|
+
});
|
|
479
492
|
ProjectSchema = z2.object({
|
|
480
493
|
id: z2.string(),
|
|
481
494
|
name: z2.string(),
|
|
482
495
|
path: z2.string(),
|
|
483
|
-
available: z2.boolean().default(true)
|
|
496
|
+
available: z2.boolean().default(true),
|
|
497
|
+
repository: RepositoryIdentitySchema.nullable().optional()
|
|
484
498
|
});
|
|
485
499
|
TaskStatusSchema = z2.enum(["backlog", "needs_user_input", "running", "done"]);
|
|
486
500
|
TaskSchema = z2.object({
|
|
@@ -499,6 +513,7 @@ var init_src = __esm({
|
|
|
499
513
|
deletedAt: z2.string().datetime().optional()
|
|
500
514
|
});
|
|
501
515
|
CreateTaskSchema = z2.object({
|
|
516
|
+
clientTaskId: z2.string().min(1).max(128).regex(/^[a-zA-Z0-9_-]+$/).optional(),
|
|
502
517
|
projectId: z2.string().min(1),
|
|
503
518
|
title: z2.string().trim().min(1).max(120),
|
|
504
519
|
prompt: z2.string().trim().max(2e4).default(""),
|
|
@@ -958,11 +973,12 @@ function pushSigningText(installationId, operation, request) {
|
|
|
958
973
|
request.payload
|
|
959
974
|
].join("\n");
|
|
960
975
|
}
|
|
961
|
-
var PUSH_API_URL, PushKindSchema, PushPreferencesSchema, PushPublicKeySchema, PushInstallationSchema, PushRegistrationSchema, PushEnrollmentSchema, PushGrantSchema, PushPullRequestUrlSchema, PushEventSchema, PushSignedRequestSchema, PushEntitlementSchema, PushServerStatusSchema;
|
|
976
|
+
var PUSH_API_URL, PUSH_SANDBOX_API_URL, PushKindSchema, PushPreferencesSchema, PushPublicKeySchema, PushInstallationSchema, PushRegistrationSchema, PushEnrollmentSchema, PushGrantSchema, PushPullRequestUrlSchema, PushEventSchema, PushSignedRequestSchema, PushEntitlementSchema, PushServerStatusSchema;
|
|
962
977
|
var init_push = __esm({
|
|
963
978
|
"../../packages/contracts/src/push.ts"() {
|
|
964
979
|
"use strict";
|
|
965
980
|
PUSH_API_URL = "https://api.agentrific.com";
|
|
981
|
+
PUSH_SANDBOX_API_URL = "https://sandbox-api.agentrific.com";
|
|
966
982
|
PushKindSchema = z6.enum([
|
|
967
983
|
"started",
|
|
968
984
|
"completed",
|
|
@@ -1871,7 +1887,7 @@ function editTask(store2, id, input, resolveAttachments) {
|
|
|
1871
1887
|
});
|
|
1872
1888
|
}
|
|
1873
1889
|
function deleteTask(store2, id, cleanup = async () => {
|
|
1874
|
-
}) {
|
|
1890
|
+
}, unrunUpdatedAt) {
|
|
1875
1891
|
return withTaskLock(id, async () => {
|
|
1876
1892
|
const task = await findTask(store2, id);
|
|
1877
1893
|
const sessions = await Effect6.runPromise(store2.list());
|
|
@@ -1882,6 +1898,8 @@ function deleteTask(store2, id, cleanup = async () => {
|
|
|
1882
1898
|
const associated = sessions.filter(
|
|
1883
1899
|
(session) => session.taskId === task.id || session.id === task.sessionId
|
|
1884
1900
|
);
|
|
1901
|
+
if (unrunUpdatedAt !== void 0 && (task.updatedAt !== unrunUpdatedAt || task.sessionId || associated.length || task.status !== "backlog"))
|
|
1902
|
+
throw new TaskActionError("This task changed or has already run. It cannot be moved.", 409);
|
|
1885
1903
|
for (const session of associated) await cleanup(session);
|
|
1886
1904
|
await Effect6.runPromise(
|
|
1887
1905
|
store2.purgeTask(
|
|
@@ -2320,7 +2338,7 @@ import { promisify as promisify2, stripVTControlCharacters } from "node:util";
|
|
|
2320
2338
|
function workflowLog(text) {
|
|
2321
2339
|
return stripVTControlCharacters(text).slice(-32e3);
|
|
2322
2340
|
}
|
|
2323
|
-
|
|
2341
|
+
function workflowEnvironment() {
|
|
2324
2342
|
const env = { ...process.env };
|
|
2325
2343
|
for (const name of [
|
|
2326
2344
|
"GIT_DIR",
|
|
@@ -2340,6 +2358,10 @@ async function workflowCommand(cwd, command, args, signal, managedPreparation =
|
|
|
2340
2358
|
"GIT_REPLACE_REF_BASE"
|
|
2341
2359
|
])
|
|
2342
2360
|
delete env[name];
|
|
2361
|
+
return env;
|
|
2362
|
+
}
|
|
2363
|
+
async function workflowCommand(cwd, command, args, signal, managedPreparation = false) {
|
|
2364
|
+
const env = workflowEnvironment();
|
|
2343
2365
|
if (managedPreparation) env.AGENTRIFIC_MANAGED_PREPARATION = "1";
|
|
2344
2366
|
const result = await exec(command, args, {
|
|
2345
2367
|
cwd,
|
|
@@ -3573,6 +3595,113 @@ var init_projects = __esm({
|
|
|
3573
3595
|
}
|
|
3574
3596
|
});
|
|
3575
3597
|
|
|
3598
|
+
// src/project-git.ts
|
|
3599
|
+
import { execFile as execFile3 } from "node:child_process";
|
|
3600
|
+
import { promisify as promisify3 } from "node:util";
|
|
3601
|
+
function repositoryIdentity(remote) {
|
|
3602
|
+
const input = remote.trim();
|
|
3603
|
+
if (!input || input.startsWith("/") || input.startsWith(".")) return null;
|
|
3604
|
+
const scp = /^(?:[^@/\s]+@)?([^/:\s]+):([^\s]+)$/.exec(input);
|
|
3605
|
+
let url;
|
|
3606
|
+
try {
|
|
3607
|
+
url = new URL(scp && !input.includes("://") ? `ssh://${scp[1]}/${scp[2]}` : input);
|
|
3608
|
+
} catch {
|
|
3609
|
+
return null;
|
|
3610
|
+
}
|
|
3611
|
+
if (!["https:", "http:", "ssh:", "git:"].includes(url.protocol)) return null;
|
|
3612
|
+
if (url.search || url.hash || url.port) return null;
|
|
3613
|
+
const host2 = url.hostname.toLowerCase();
|
|
3614
|
+
let path13 = url.pathname.replace(/^\/+|\/+$/g, "").replace(/\.git$/, "");
|
|
3615
|
+
if (!host2 || !path13 || path13.includes("%") || path13.includes("//")) return null;
|
|
3616
|
+
if (["github.com", "bitbucket.org"].includes(host2)) path13 = path13.toLowerCase();
|
|
3617
|
+
const key = `${host2}/${path13}`;
|
|
3618
|
+
return { key, display: key };
|
|
3619
|
+
}
|
|
3620
|
+
async function git(directory, args) {
|
|
3621
|
+
const { stdout } = await execute3("git", ["-C", directory, ...args], {
|
|
3622
|
+
timeout: 3e3,
|
|
3623
|
+
maxBuffer: 1024 * 1024,
|
|
3624
|
+
env: { ...workflowEnvironment(), GIT_OPTIONAL_LOCKS: "0", GIT_TERMINAL_PROMPT: "0" }
|
|
3625
|
+
});
|
|
3626
|
+
return stdout.trim();
|
|
3627
|
+
}
|
|
3628
|
+
async function readRepositoryIdentity(directory) {
|
|
3629
|
+
try {
|
|
3630
|
+
const remotes = (await git(directory, ["remote"])).split("\n").filter(Boolean);
|
|
3631
|
+
const remote = remotes.includes("origin") ? "origin" : remotes.length === 1 ? remotes[0] : null;
|
|
3632
|
+
if (!remote) return null;
|
|
3633
|
+
return repositoryIdentity(await git(directory, ["remote", "get-url", remote]));
|
|
3634
|
+
} catch {
|
|
3635
|
+
return null;
|
|
3636
|
+
}
|
|
3637
|
+
}
|
|
3638
|
+
async function inspect(directory) {
|
|
3639
|
+
const observedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3640
|
+
try {
|
|
3641
|
+
const [repository, head, branch, status] = await Promise.all([
|
|
3642
|
+
readRepositoryIdentity(directory),
|
|
3643
|
+
git(directory, ["rev-parse", "--verify", "HEAD"]),
|
|
3644
|
+
git(directory, ["symbolic-ref", "--quiet", "--short", "HEAD"]).catch(() => null),
|
|
3645
|
+
// NUL-separated records avoid ambiguous filenames/newlines. --no-renames
|
|
3646
|
+
// keeps each entry one record, so we only need its two status characters.
|
|
3647
|
+
execute3(
|
|
3648
|
+
"git",
|
|
3649
|
+
[
|
|
3650
|
+
"-C",
|
|
3651
|
+
directory,
|
|
3652
|
+
"status",
|
|
3653
|
+
"--porcelain=v1",
|
|
3654
|
+
"-z",
|
|
3655
|
+
"--no-renames",
|
|
3656
|
+
"--untracked-files=normal"
|
|
3657
|
+
],
|
|
3658
|
+
{
|
|
3659
|
+
timeout: 3e3,
|
|
3660
|
+
maxBuffer: 1024 * 1024,
|
|
3661
|
+
env: { ...workflowEnvironment(), GIT_OPTIONAL_LOCKS: "0" }
|
|
3662
|
+
}
|
|
3663
|
+
)
|
|
3664
|
+
]);
|
|
3665
|
+
const records = status.stdout.split("\0").filter(Boolean);
|
|
3666
|
+
return {
|
|
3667
|
+
observedAt,
|
|
3668
|
+
state: "available",
|
|
3669
|
+
repository,
|
|
3670
|
+
head,
|
|
3671
|
+
branch,
|
|
3672
|
+
trackedChanges: records.filter((record) => !record.startsWith("??")).length,
|
|
3673
|
+
untrackedChanges: records.filter((record) => record.startsWith("??")).length
|
|
3674
|
+
};
|
|
3675
|
+
} catch {
|
|
3676
|
+
return {
|
|
3677
|
+
observedAt,
|
|
3678
|
+
state: "unavailable",
|
|
3679
|
+
repository: null,
|
|
3680
|
+
head: null,
|
|
3681
|
+
branch: null,
|
|
3682
|
+
trackedChanges: 0,
|
|
3683
|
+
untrackedChanges: 0
|
|
3684
|
+
};
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
function projectGitStatus(directory) {
|
|
3688
|
+
const cached = observations.get(directory);
|
|
3689
|
+
if (cached && cached.expires > Date.now()) return cached.value;
|
|
3690
|
+
if (observations.size >= 200) observations.delete(observations.keys().next().value);
|
|
3691
|
+
const value = inspect(directory);
|
|
3692
|
+
observations.set(directory, { expires: Date.now() + 15e3, value });
|
|
3693
|
+
return value;
|
|
3694
|
+
}
|
|
3695
|
+
var execute3, observations;
|
|
3696
|
+
var init_project_git = __esm({
|
|
3697
|
+
"src/project-git.ts"() {
|
|
3698
|
+
"use strict";
|
|
3699
|
+
init_workflow_command();
|
|
3700
|
+
execute3 = promisify3(execFile3);
|
|
3701
|
+
observations = /* @__PURE__ */ new Map();
|
|
3702
|
+
}
|
|
3703
|
+
});
|
|
3704
|
+
|
|
3576
3705
|
// src/push-schema.ts
|
|
3577
3706
|
import { z as z16 } from "zod";
|
|
3578
3707
|
var PushBindingSchema, PushStateSchema, PendingPushSchema;
|
|
@@ -4170,8 +4299,8 @@ import { styleText as styleText4 } from "node:util";
|
|
|
4170
4299
|
import cors from "@fastify/cors";
|
|
4171
4300
|
import multipart from "@fastify/multipart";
|
|
4172
4301
|
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
4173
|
-
import { execFile as
|
|
4174
|
-
import { promisify as
|
|
4302
|
+
import { execFile as execFile4 } from "node:child_process";
|
|
4303
|
+
import { promisify as promisify4 } from "node:util";
|
|
4175
4304
|
import { realpath as realpath2 } from "node:fs/promises";
|
|
4176
4305
|
import { homedir as homedir4 } from "node:os";
|
|
4177
4306
|
import path11 from "node:path";
|
|
@@ -4384,10 +4513,11 @@ async function withSessionTaskLock(id, operation) {
|
|
|
4384
4513
|
const lockId = session ? await sessionTaskLockId(store, session) : `session-${id}`;
|
|
4385
4514
|
return withTaskLock(lockId, operation);
|
|
4386
4515
|
}
|
|
4387
|
-
var host, port, defaultProjectsRoot, worktreesRoot, execFileAsync, app, dataRoot, releaseLock, effects, store, security, testPushUrl, push, allowedOrigins, attachmentEffects, attachments, attachmentCleanup, models, delivery, preparation, agentEffects, runtime, worktreeCleanup, maxWorktreeAgeDays, pruneWorktrees, worktreeCleanupTimer, updates, maintenance, stopUpdateDiscovery, HistoryQuery, StartTaskBody, automation, automationTimer, closeControl;
|
|
4516
|
+
var host, port, defaultProjectsRoot, worktreesRoot, execFileAsync, app, dataRoot, releaseLock, effects, store, security, sandboxPushUrl, testPushUrl, push, allowedOrigins, attachmentEffects, attachments, attachmentCleanup, models, delivery, preparation, agentEffects, runtime, worktreeCleanup, maxWorktreeAgeDays, pruneWorktrees, worktreeCleanupTimer, updates, maintenance, stopUpdateDiscovery, HistoryQuery, StartTaskBody, automation, automationTimer, closeControl;
|
|
4388
4517
|
var init_index = __esm({
|
|
4389
4518
|
async "src/index.ts"() {
|
|
4390
4519
|
"use strict";
|
|
4520
|
+
init_push();
|
|
4391
4521
|
init_warnings();
|
|
4392
4522
|
init_push2();
|
|
4393
4523
|
init_attachments();
|
|
@@ -4406,6 +4536,7 @@ var init_index = __esm({
|
|
|
4406
4536
|
init_models();
|
|
4407
4537
|
init_pi();
|
|
4408
4538
|
init_projects();
|
|
4539
|
+
init_project_git();
|
|
4409
4540
|
init_store();
|
|
4410
4541
|
init_tasks();
|
|
4411
4542
|
init_worktree_cleanup();
|
|
@@ -4416,7 +4547,7 @@ var init_index = __esm({
|
|
|
4416
4547
|
worktreesRoot = path11.resolve(
|
|
4417
4548
|
process.env.AGENTRIFIC_WORKTREES_DIR ?? path11.join(homedir4(), ".agentrific", "workspaces")
|
|
4418
4549
|
);
|
|
4419
|
-
execFileAsync =
|
|
4550
|
+
execFileAsync = promisify4(execFile4);
|
|
4420
4551
|
if (host !== "127.0.0.1" && host !== "::1")
|
|
4421
4552
|
throw new Error("Bind Agentrific to loopback and expose it with Tailscale Serve or cloudflared.");
|
|
4422
4553
|
app = Fastify({
|
|
@@ -4442,10 +4573,19 @@ var init_index = __esm({
|
|
|
4442
4573
|
effects = ManagedRuntime.make(SessionStore.layer(path11.join(dataRoot, "sessions.sqlite")));
|
|
4443
4574
|
store = await effects.runPromise(SessionStore);
|
|
4444
4575
|
security = new ServerSecurity(store, await Effect17.runPromise(store.serverIdentity()));
|
|
4576
|
+
sandboxPushUrl = process.env.AGENTRIFIC_PUSH_SANDBOX === "1" ? PUSH_SANDBOX_API_URL : void 0;
|
|
4577
|
+
if (sandboxPushUrl && process.env.AGENTRIFIC_TEST_MODE !== "1")
|
|
4578
|
+
throw new Error("Sandbox push requires explicit test mode.");
|
|
4445
4579
|
testPushUrl = process.env.AGENTRIFIC_PUSH_TEST_URL;
|
|
4446
4580
|
if (testPushUrl && (process.env.AGENTRIFIC_TEST_MODE !== "1" || !new URL(testPushUrl).hostname.endsWith(".ts.net") || new URL(testPushUrl).protocol !== "https:"))
|
|
4447
4581
|
throw new Error("Test relay requires explicit test mode and Tailscale HTTPS.");
|
|
4448
|
-
push = new ServerPush(
|
|
4582
|
+
push = new ServerPush(
|
|
4583
|
+
store,
|
|
4584
|
+
security.serverId,
|
|
4585
|
+
dataRoot,
|
|
4586
|
+
fetch,
|
|
4587
|
+
testPushUrl ?? sandboxPushUrl
|
|
4588
|
+
);
|
|
4449
4589
|
allowedOrigins = [
|
|
4450
4590
|
.../* @__PURE__ */ new Set([
|
|
4451
4591
|
"agentrific-desktop://app",
|
|
@@ -4660,7 +4800,24 @@ var init_index = __esm({
|
|
|
4660
4800
|
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4661
4801
|
const result = await Effect17.runPromise(discoverProjects(root, store).pipe(Effect17.result));
|
|
4662
4802
|
if (result._tag === "Failure") return reply.code(400).send({ error: result.failure.message });
|
|
4663
|
-
return
|
|
4803
|
+
return Effect17.runPromise(
|
|
4804
|
+
Effect17.forEach(
|
|
4805
|
+
result.success,
|
|
4806
|
+
(project) => Effect17.promise(async () => ({
|
|
4807
|
+
...project,
|
|
4808
|
+
repository: project.available ? await readRepositoryIdentity(project.path) : null
|
|
4809
|
+
})),
|
|
4810
|
+
{ concurrency: 4 }
|
|
4811
|
+
)
|
|
4812
|
+
);
|
|
4813
|
+
});
|
|
4814
|
+
app.get("/api/projects/:id/git-status", async (request, reply) => {
|
|
4815
|
+
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4816
|
+
const projects = await Effect17.runPromise(discoverProjects(root, store));
|
|
4817
|
+
const project = projects.find((item) => item.id === request.params.id);
|
|
4818
|
+
if (!project?.available)
|
|
4819
|
+
return reply.code(404).send({ error: "Project folder unavailable on this server." });
|
|
4820
|
+
return projectGitStatus(project.path);
|
|
4664
4821
|
});
|
|
4665
4822
|
app.get("/api/projects/:id", async (request, reply) => {
|
|
4666
4823
|
const root = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
@@ -4684,39 +4841,51 @@ var init_index = __esm({
|
|
|
4684
4841
|
"/api/tasks",
|
|
4685
4842
|
(request) => boardTasks(store, request.query.projectId)
|
|
4686
4843
|
);
|
|
4844
|
+
app.get("/api/task-moves", () => ({ supported: true }));
|
|
4687
4845
|
app.post("/api/tasks", async (request, reply) => {
|
|
4688
4846
|
const parsed = CreateTaskSchema.safeParse(request.body);
|
|
4689
4847
|
if (!parsed.success) return reply.code(400).send({ error: "Enter a task title." });
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4848
|
+
const save = async () => {
|
|
4849
|
+
if (parsed.data.clientTaskId) {
|
|
4850
|
+
const existing = await Effect17.runPromise(store.getTask(parsed.data.clientTaskId));
|
|
4851
|
+
if (existing) {
|
|
4852
|
+
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))
|
|
4853
|
+
return reply.code(409).send({ error: "This task creation request has already been used." });
|
|
4854
|
+
return reply.code(201).send(existing);
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4857
|
+
if (parsed.data.gitFlow?.delivery === "pull_request") {
|
|
4858
|
+
const github = await githubCliStatus();
|
|
4859
|
+
if (!github.githubCli.authenticated)
|
|
4860
|
+
return reply.code(400).send({ error: "GitHub CLI must be authenticated on the server to open a PR." });
|
|
4861
|
+
}
|
|
4862
|
+
const projectsRoot = await Effect17.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
4863
|
+
const projects = await Effect17.runPromise(
|
|
4864
|
+
discoverProjects(projectsRoot, store).pipe(Effect17.result)
|
|
4705
4865
|
);
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4866
|
+
if (projects._tag === "Failure" || !projects.success.some((project) => project.id === parsed.data.projectId && project.available))
|
|
4867
|
+
return reply.code(404).send({ error: "Project not found in the server's projects folder." });
|
|
4868
|
+
let files;
|
|
4869
|
+
try {
|
|
4870
|
+
files = await Effect17.runPromise(
|
|
4871
|
+
attachments.resolve(parsed.data.projectId, parsed.data.attachmentIds ?? [])
|
|
4872
|
+
);
|
|
4873
|
+
} catch (error) {
|
|
4874
|
+
return reply.code(400).send({ error: String(error) });
|
|
4875
|
+
}
|
|
4876
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4877
|
+
const task = TaskSchema.parse({
|
|
4878
|
+
id: parsed.data.clientTaskId ?? randomUUID9(),
|
|
4879
|
+
...parsed.data,
|
|
4880
|
+
attachments: files,
|
|
4881
|
+
status: "backlog",
|
|
4882
|
+
sessionId: null,
|
|
4883
|
+
createdAt: now,
|
|
4884
|
+
updatedAt: now
|
|
4885
|
+
});
|
|
4886
|
+
return reply.code(201).send(await Effect17.runPromise(store.saveTask(task)));
|
|
4887
|
+
};
|
|
4888
|
+
return parsed.data.clientTaskId ? withTaskLock(`create-task:${parsed.data.clientTaskId}`, save) : save();
|
|
4720
4889
|
});
|
|
4721
4890
|
app.patch("/api/tasks/:id", async (request, reply) => {
|
|
4722
4891
|
const parsed = UpdateTaskSchema.safeParse(request.body);
|
|
@@ -4743,6 +4912,18 @@ var init_index = __esm({
|
|
|
4743
4912
|
throw error;
|
|
4744
4913
|
}
|
|
4745
4914
|
});
|
|
4915
|
+
app.post("/api/tasks/:id/discard-unrun", async (request, reply) => {
|
|
4916
|
+
const parsed = z18.object({ updatedAt: z18.string().datetime() }).safeParse(request.body);
|
|
4917
|
+
if (!parsed.success) return reply.code(400).send({ error: "The task revision is required." });
|
|
4918
|
+
try {
|
|
4919
|
+
await deleteTask(store, request.params.id, void 0, parsed.data.updatedAt);
|
|
4920
|
+
return reply.code(204).send();
|
|
4921
|
+
} catch (error) {
|
|
4922
|
+
if (error instanceof TaskActionError)
|
|
4923
|
+
return reply.code(error.status).send({ error: error.message });
|
|
4924
|
+
throw error;
|
|
4925
|
+
}
|
|
4926
|
+
});
|
|
4746
4927
|
app.delete("/api/tasks/:id", async (request, reply) => {
|
|
4747
4928
|
try {
|
|
4748
4929
|
await deleteTask(store, request.params.id, async (session) => {
|