@agentrific/server 0.1.6 → 0.3.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 +208 -69
- package/cli.js.map +4 -4
- package/index.js +183 -52
- package/index.js.map +4 -4
- package/package.json +2 -1
package/index.js
CHANGED
|
@@ -465,6 +465,11 @@ var SessionSchema = z2.object({
|
|
|
465
465
|
gitFlow: GitFlowSchema.optional(),
|
|
466
466
|
gitDelivery: GitDeliverySchema.optional(),
|
|
467
467
|
preparation: RunPreparationSchema.optional(),
|
|
468
|
+
usage: z2.object({
|
|
469
|
+
tokens: z2.number().nonnegative(),
|
|
470
|
+
turns: z2.number().int().nonnegative(),
|
|
471
|
+
tools: z2.number().int().nonnegative()
|
|
472
|
+
}).optional(),
|
|
468
473
|
createdAt: z2.string().datetime(),
|
|
469
474
|
updatedAt: z2.string().datetime()
|
|
470
475
|
});
|
|
@@ -492,11 +497,18 @@ var CreateSessionSchema = z2.object({
|
|
|
492
497
|
var SendMessageSchema = z2.object({
|
|
493
498
|
message: z2.string().trim().max(2e4),
|
|
494
499
|
attachmentIds: AttachmentIdsSchema.optional(),
|
|
495
|
-
clientMessageId: z2.string().min(1).max(120).optional()
|
|
500
|
+
clientMessageId: z2.string().min(1).max(120).optional(),
|
|
501
|
+
mode: z2.enum(["followUp", "steer"]).optional()
|
|
496
502
|
}).refine(
|
|
497
503
|
(input) => input.message.length > 0 || (input.attachmentIds?.length ?? 0) > 0,
|
|
498
504
|
"Write a message or attach a file."
|
|
499
505
|
);
|
|
506
|
+
var FileChangeSchema = z2.object({
|
|
507
|
+
path: z2.string(),
|
|
508
|
+
patch: z2.string().optional(),
|
|
509
|
+
content: z2.string().optional(),
|
|
510
|
+
omitted: z2.boolean().optional()
|
|
511
|
+
});
|
|
500
512
|
var SessionEventSchema = z2.discriminatedUnion("type", [
|
|
501
513
|
z2.object({ type: z2.literal("session.updated"), session: SessionSchema }),
|
|
502
514
|
z2.object({
|
|
@@ -521,6 +533,7 @@ var SessionEventSchema = z2.discriminatedUnion("type", [
|
|
|
521
533
|
}),
|
|
522
534
|
z2.object({
|
|
523
535
|
type: z2.literal("message.completed"),
|
|
536
|
+
tokens: z2.number().nonnegative().optional(),
|
|
524
537
|
model: ModelIdentitySchema.optional(),
|
|
525
538
|
sessionId: z2.string(),
|
|
526
539
|
messageId: z2.string().optional(),
|
|
@@ -531,7 +544,8 @@ var SessionEventSchema = z2.discriminatedUnion("type", [
|
|
|
531
544
|
sessionId: z2.string(),
|
|
532
545
|
toolCallId: z2.string().optional(),
|
|
533
546
|
tool: z2.string(),
|
|
534
|
-
input: z2.string()
|
|
547
|
+
input: z2.string(),
|
|
548
|
+
filePath: z2.string().optional()
|
|
535
549
|
}),
|
|
536
550
|
z2.object({
|
|
537
551
|
type: z2.literal("tool.completed"),
|
|
@@ -539,6 +553,7 @@ var SessionEventSchema = z2.discriminatedUnion("type", [
|
|
|
539
553
|
toolCallId: z2.string().optional(),
|
|
540
554
|
tool: z2.string(),
|
|
541
555
|
output: z2.string(),
|
|
556
|
+
fileChange: FileChangeSchema.optional(),
|
|
542
557
|
isError: z2.boolean().default(false)
|
|
543
558
|
}),
|
|
544
559
|
z2.object({
|
|
@@ -1081,7 +1096,7 @@ async function startControl(directory, security2, status, maintenance2) {
|
|
|
1081
1096
|
// package.json
|
|
1082
1097
|
var package_default = {
|
|
1083
1098
|
name: "@agentrific/server",
|
|
1084
|
-
version: "0.
|
|
1099
|
+
version: "0.3.0",
|
|
1085
1100
|
description: "Self-hosted control plane for long-running coding agents",
|
|
1086
1101
|
keywords: [
|
|
1087
1102
|
"agentrific",
|
|
@@ -1118,6 +1133,7 @@ var package_default = {
|
|
|
1118
1133
|
"@fastify/cors": "^11.3.0",
|
|
1119
1134
|
"@fastify/multipart": "^10.1.2",
|
|
1120
1135
|
"@inquirer/select": "^5.2.5",
|
|
1136
|
+
diff: "^9.0.0",
|
|
1121
1137
|
effect: "4.0.0-rc.117",
|
|
1122
1138
|
fastify: "^5.6.2",
|
|
1123
1139
|
"pino-pretty": "^13.1.3",
|
|
@@ -1467,7 +1483,7 @@ var UpdateDiscovery = class {
|
|
|
1467
1483
|
|
|
1468
1484
|
// src/index.ts
|
|
1469
1485
|
import { Effect as Effect16, ManagedRuntime } from "effect";
|
|
1470
|
-
import { z as
|
|
1486
|
+
import { z as z17 } from "zod";
|
|
1471
1487
|
|
|
1472
1488
|
// src/automation.ts
|
|
1473
1489
|
import { Effect as Effect7 } from "effect";
|
|
@@ -1773,13 +1789,29 @@ var AgentController = class {
|
|
|
1773
1789
|
const entry = {
|
|
1774
1790
|
session,
|
|
1775
1791
|
agent,
|
|
1776
|
-
unsubscribe:
|
|
1792
|
+
unsubscribe: () => {
|
|
1793
|
+
},
|
|
1777
1794
|
running: false,
|
|
1778
1795
|
stopping: false,
|
|
1779
1796
|
pending: []
|
|
1780
1797
|
};
|
|
1798
|
+
entry.unsubscribe = agent.subscribe((event) => {
|
|
1799
|
+
this.emit(event);
|
|
1800
|
+
if (event.type !== "message.completed" && event.type !== "tool.started") return;
|
|
1801
|
+
const usage = entry.session.usage ?? { tokens: 0, turns: 0, tools: 0 };
|
|
1802
|
+
entry.session = {
|
|
1803
|
+
...entry.session,
|
|
1804
|
+
usage: {
|
|
1805
|
+
tokens: usage.tokens + (event.type === "message.completed" ? event.tokens ?? 0 : 0),
|
|
1806
|
+
turns: usage.turns + (event.type === "message.completed" ? 1 : 0),
|
|
1807
|
+
tools: usage.tools + (event.type === "tool.started" ? 1 : 0)
|
|
1808
|
+
},
|
|
1809
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1810
|
+
};
|
|
1811
|
+
this.emit({ type: "session.updated", session: entry.session });
|
|
1812
|
+
});
|
|
1781
1813
|
if (agent.model) {
|
|
1782
|
-
entry.session = { ...session, model: agent.model };
|
|
1814
|
+
entry.session = { ...entry.session, model: agent.model };
|
|
1783
1815
|
this.emit({ type: "session.updated", session: entry.session });
|
|
1784
1816
|
}
|
|
1785
1817
|
this.active.set(session.id, entry);
|
|
@@ -1829,7 +1861,7 @@ var AgentController = class {
|
|
|
1829
1861
|
Effect8.ensuring(Effect8.sync(() => this.working.delete(entry.session.id)))
|
|
1830
1862
|
);
|
|
1831
1863
|
}
|
|
1832
|
-
send(session, message, attachments2 = [], messageId = randomUUID4()) {
|
|
1864
|
+
send(session, message, attachments2 = [], messageId = randomUUID4(), mode = "followUp") {
|
|
1833
1865
|
return this.lock(session.id).withPermit(
|
|
1834
1866
|
Effect8.gen({ self: this }, function* () {
|
|
1835
1867
|
if (this.closed)
|
|
@@ -1839,6 +1871,16 @@ var AgentController = class {
|
|
|
1839
1871
|
if (this.hasMessage(session.id, messageId)) return;
|
|
1840
1872
|
const entry = yield* this.open(session);
|
|
1841
1873
|
yield* this.operation(async () => entry.agent.validateAttachments?.(attachments2));
|
|
1874
|
+
const steering = entry.running && mode === "steer";
|
|
1875
|
+
if (steering) {
|
|
1876
|
+
if (!entry.agent.steer)
|
|
1877
|
+
return yield* Effect8.fail(
|
|
1878
|
+
new AgentError({ message: "This agent does not support steering." })
|
|
1879
|
+
);
|
|
1880
|
+
yield* this.operation(
|
|
1881
|
+
() => entry.agent.steer(gitFlowPrompt(entry.session, message), attachments2)
|
|
1882
|
+
);
|
|
1883
|
+
}
|
|
1842
1884
|
this.emit({
|
|
1843
1885
|
type: "message.user",
|
|
1844
1886
|
sessionId: session.id,
|
|
@@ -1846,6 +1888,7 @@ var AgentController = class {
|
|
|
1846
1888
|
text: message,
|
|
1847
1889
|
attachments: attachments2
|
|
1848
1890
|
});
|
|
1891
|
+
if (steering) return;
|
|
1849
1892
|
if (entry.running) {
|
|
1850
1893
|
entry.pending.push({ text: message, attachments: attachments2 });
|
|
1851
1894
|
return;
|
|
@@ -2749,7 +2792,7 @@ import {
|
|
|
2749
2792
|
createAgentSession as createAgentSession2,
|
|
2750
2793
|
SessionManager as SessionManager2
|
|
2751
2794
|
} from "@earendil-works/pi-coding-agent";
|
|
2752
|
-
import { z as
|
|
2795
|
+
import { z as z13 } from "zod";
|
|
2753
2796
|
|
|
2754
2797
|
// src/delivery-tool.ts
|
|
2755
2798
|
import { defineTool } from "@earendil-works/pi-coding-agent";
|
|
@@ -2783,9 +2826,70 @@ function deliveryTool(sessionId, delivery2) {
|
|
|
2783
2826
|
});
|
|
2784
2827
|
}
|
|
2785
2828
|
|
|
2829
|
+
// src/file-change.ts
|
|
2830
|
+
import { createWriteToolDefinition, defineTool as defineTool2 } from "@earendil-works/pi-coding-agent";
|
|
2831
|
+
import { createTwoFilesPatch } from "diff";
|
|
2832
|
+
import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile2 } from "node:fs/promises";
|
|
2833
|
+
import { z as z12 } from "zod";
|
|
2834
|
+
var previewLimit = 256e3;
|
|
2835
|
+
function boundFileChange(change) {
|
|
2836
|
+
if ((change.patch?.length ?? 0) + (change.content?.length ?? 0) > previewLimit)
|
|
2837
|
+
return { path: change.path, omitted: true };
|
|
2838
|
+
return change;
|
|
2839
|
+
}
|
|
2840
|
+
var EditResultSchema = z12.object({
|
|
2841
|
+
details: z12.object({ patch: z12.string().optional(), fileChange: FileChangeSchema.optional() }).optional()
|
|
2842
|
+
});
|
|
2843
|
+
function completedFileChange(result, path10) {
|
|
2844
|
+
const parsed = EditResultSchema.safeParse(result);
|
|
2845
|
+
if (!parsed.success) return;
|
|
2846
|
+
const details = parsed.data.details;
|
|
2847
|
+
if (details?.fileChange) return boundFileChange(details.fileChange);
|
|
2848
|
+
if (path10 && details?.patch) return boundFileChange({ path: path10, patch: details.patch });
|
|
2849
|
+
}
|
|
2850
|
+
function fileWriteTool(cwd) {
|
|
2851
|
+
const base = createWriteToolDefinition(cwd);
|
|
2852
|
+
return defineTool2({
|
|
2853
|
+
name: base.name,
|
|
2854
|
+
label: base.label,
|
|
2855
|
+
description: base.description,
|
|
2856
|
+
promptSnippet: base.promptSnippet,
|
|
2857
|
+
promptGuidelines: base.promptGuidelines,
|
|
2858
|
+
parameters: base.parameters,
|
|
2859
|
+
constrainedSampling: base.constrainedSampling,
|
|
2860
|
+
async execute(toolCallId, args, signal, _onUpdate, ctx) {
|
|
2861
|
+
let fileChange;
|
|
2862
|
+
const tool = createWriteToolDefinition(cwd, {
|
|
2863
|
+
operations: {
|
|
2864
|
+
mkdir: async (dir) => {
|
|
2865
|
+
await mkdir4(dir, { recursive: true });
|
|
2866
|
+
},
|
|
2867
|
+
// Pi invokes this inside its per-file mutation queue, so the snapshot
|
|
2868
|
+
// and write cannot race another built-in edit/write on the same file.
|
|
2869
|
+
writeFile: async (path10, content) => {
|
|
2870
|
+
const previous = await readFile4(path10, "utf8").catch((error) => {
|
|
2871
|
+
if (error.code === "ENOENT") return void 0;
|
|
2872
|
+
throw error;
|
|
2873
|
+
});
|
|
2874
|
+
await writeFile2(path10, content, "utf8");
|
|
2875
|
+
fileChange = boundFileChange(
|
|
2876
|
+
previous === void 0 ? { path: args.path, content } : {
|
|
2877
|
+
path: args.path,
|
|
2878
|
+
patch: createTwoFilesPatch(args.path, args.path, previous, content)
|
|
2879
|
+
}
|
|
2880
|
+
);
|
|
2881
|
+
}
|
|
2882
|
+
}
|
|
2883
|
+
});
|
|
2884
|
+
const result = await tool.execute(toolCallId, args, signal, void 0, ctx);
|
|
2885
|
+
return { ...result, details: { fileChange } };
|
|
2886
|
+
}
|
|
2887
|
+
});
|
|
2888
|
+
}
|
|
2889
|
+
|
|
2786
2890
|
// src/pi.ts
|
|
2787
|
-
var ToolResultSchema =
|
|
2788
|
-
content:
|
|
2891
|
+
var ToolResultSchema = z13.object({
|
|
2892
|
+
content: z13.array(z13.object({ type: z13.string(), text: z13.string().optional() })).optional()
|
|
2789
2893
|
}).passthrough();
|
|
2790
2894
|
var boundedJson = (value, limit) => {
|
|
2791
2895
|
let text;
|
|
@@ -2810,7 +2914,10 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
2810
2914
|
model,
|
|
2811
2915
|
modelRuntime: models2.runtime,
|
|
2812
2916
|
sessionManager: SessionManager2.continueRecent(cwd, sessionDirectory),
|
|
2813
|
-
customTools:
|
|
2917
|
+
customTools: [
|
|
2918
|
+
fileWriteTool(cwd),
|
|
2919
|
+
...run?.gitFlow && delivery2 ? [deliveryTool(sessionId, delivery2)] : []
|
|
2920
|
+
]
|
|
2814
2921
|
});
|
|
2815
2922
|
return {
|
|
2816
2923
|
get model() {
|
|
@@ -2834,6 +2941,12 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
2834
2941
|
throw new Error(last.errorMessage ?? "The model request failed");
|
|
2835
2942
|
}
|
|
2836
2943
|
},
|
|
2944
|
+
async steer(message, attachments2 = []) {
|
|
2945
|
+
const context = await Effect12.runPromise(files.prepare(attachments2));
|
|
2946
|
+
if (!session.isStreaming)
|
|
2947
|
+
throw new Error("The response has finished. Send this message as a follow-up instead.");
|
|
2948
|
+
await session.steer(message + context.context, context.images);
|
|
2949
|
+
},
|
|
2837
2950
|
async abort() {
|
|
2838
2951
|
session.clearQueue();
|
|
2839
2952
|
await session.abort();
|
|
@@ -2890,6 +3003,7 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
2890
3003
|
sessionId,
|
|
2891
3004
|
messageId: assistantMessageId,
|
|
2892
3005
|
text,
|
|
3006
|
+
tokens: event.message.usage.totalTokens,
|
|
2893
3007
|
model: models2.identity(event.message.provider, event.message.model)
|
|
2894
3008
|
});
|
|
2895
3009
|
assistantMessageId = void 0;
|
|
@@ -2898,16 +3012,24 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
2898
3012
|
}
|
|
2899
3013
|
return false;
|
|
2900
3014
|
}
|
|
3015
|
+
const filePaths = /* @__PURE__ */ new Map();
|
|
3016
|
+
const FileArgsSchema = z13.object({ path: z13.string() });
|
|
2901
3017
|
function handleTool(event) {
|
|
2902
3018
|
if (event.type === "tool_execution_start") {
|
|
2903
3019
|
flushText();
|
|
2904
3020
|
setActivity("thinking");
|
|
3021
|
+
if (event.toolName === "edit" || event.toolName === "write") {
|
|
3022
|
+
const args = FileArgsSchema.safeParse(event.args);
|
|
3023
|
+
if (args.success) filePaths.set(event.toolCallId, args.data.path);
|
|
3024
|
+
}
|
|
2905
3025
|
emit({
|
|
2906
3026
|
type: "tool.started",
|
|
2907
3027
|
sessionId,
|
|
2908
3028
|
toolCallId: event.toolCallId,
|
|
2909
3029
|
tool: event.toolName,
|
|
2910
|
-
|
|
3030
|
+
// Preserve complete Bash arguments so multiline commands can be inspected.
|
|
3031
|
+
input: event.toolName === "bash" ? JSON.stringify(event.args) : boundedJson(event.args, 1200),
|
|
3032
|
+
filePath: filePaths.get(event.toolCallId)
|
|
2911
3033
|
});
|
|
2912
3034
|
return true;
|
|
2913
3035
|
}
|
|
@@ -2919,8 +3041,10 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
2919
3041
|
toolCallId: event.toolCallId,
|
|
2920
3042
|
tool: event.toolName,
|
|
2921
3043
|
output: toolOutput(event.result),
|
|
3044
|
+
fileChange: event.isError ? void 0 : completedFileChange(event.result, filePaths.get(event.toolCallId)),
|
|
2922
3045
|
isError: event.isError
|
|
2923
3046
|
});
|
|
3047
|
+
filePaths.delete(event.toolCallId);
|
|
2924
3048
|
setActivity("thinking");
|
|
2925
3049
|
return true;
|
|
2926
3050
|
}
|
|
@@ -2949,9 +3073,9 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
2949
3073
|
}
|
|
2950
3074
|
|
|
2951
3075
|
// src/projects.ts
|
|
2952
|
-
import { access, readdir as readdir2, realpath, stat as stat2, readFile as
|
|
3076
|
+
import { access, readdir as readdir2, realpath, stat as stat2, readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
|
|
2953
3077
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
2954
|
-
import { z as
|
|
3078
|
+
import { z as z14 } from "zod";
|
|
2955
3079
|
import { constants } from "node:fs";
|
|
2956
3080
|
import { homedir } from "node:os";
|
|
2957
3081
|
import path6 from "node:path";
|
|
@@ -2983,12 +3107,12 @@ function projectLock(store2) {
|
|
|
2983
3107
|
}
|
|
2984
3108
|
return lock;
|
|
2985
3109
|
}
|
|
2986
|
-
var ProjectId =
|
|
3110
|
+
var ProjectId = z14.string().trim().min(1).max(255);
|
|
2987
3111
|
async function gitDirectory(directory) {
|
|
2988
3112
|
const location = path6.join(directory, ".git");
|
|
2989
3113
|
const info = await stat2(location);
|
|
2990
3114
|
if (info.isDirectory()) return location;
|
|
2991
|
-
const contents = await
|
|
3115
|
+
const contents = await readFile5(location, "utf8");
|
|
2992
3116
|
const match = /^gitdir: (.+)\s*$/m.exec(contents);
|
|
2993
3117
|
if (!match) throw new Error("Invalid Git worktree metadata.");
|
|
2994
3118
|
const resolved = await realpath(path6.resolve(directory, match[1].trim()));
|
|
@@ -2998,21 +3122,21 @@ async function gitDirectory(directory) {
|
|
|
2998
3122
|
async function identity(directory, preferred) {
|
|
2999
3123
|
const filename = path6.join(await gitDirectory(directory), "agentrific-project-id");
|
|
3000
3124
|
try {
|
|
3001
|
-
return ProjectId.parse(await
|
|
3125
|
+
return ProjectId.parse(await readFile5(filename, "utf8"));
|
|
3002
3126
|
} catch (error) {
|
|
3003
|
-
if (!
|
|
3127
|
+
if (!z14.object({ code: z14.literal("ENOENT") }).safeParse(error).success) throw error;
|
|
3004
3128
|
const legacyFilename = path6.join(await gitDirectory(directory), "chatplans-project-id");
|
|
3005
|
-
const legacy = await
|
|
3006
|
-
if (!
|
|
3129
|
+
const legacy = await readFile5(legacyFilename, "utf8").catch((cause) => {
|
|
3130
|
+
if (!z14.object({ code: z14.literal("ENOENT") }).safeParse(cause).success) throw cause;
|
|
3007
3131
|
return null;
|
|
3008
3132
|
});
|
|
3009
3133
|
const id = legacy === null ? preferred ?? randomUUID6() : ProjectId.parse(legacy);
|
|
3010
3134
|
try {
|
|
3011
|
-
await
|
|
3135
|
+
await writeFile3(filename, id + "\n", { flag: "wx" });
|
|
3012
3136
|
return id;
|
|
3013
3137
|
} catch (writeError) {
|
|
3014
|
-
if (!
|
|
3015
|
-
return ProjectId.parse(await
|
|
3138
|
+
if (!z14.object({ code: z14.literal("EEXIST") }).safeParse(writeError).success) throw writeError;
|
|
3139
|
+
return ProjectId.parse(await readFile5(filename, "utf8"));
|
|
3016
3140
|
}
|
|
3017
3141
|
}
|
|
3018
3142
|
}
|
|
@@ -3032,7 +3156,7 @@ function scanProjects(root, store2) {
|
|
|
3032
3156
|
let existing = registered.find((project2) => project2.id === id);
|
|
3033
3157
|
if (found.some((project2) => project2.id === id) || existing && existing.path !== directory && await stat2(path6.join(existing.path, ".git")).catch(() => null)) {
|
|
3034
3158
|
id = randomUUID6();
|
|
3035
|
-
await
|
|
3159
|
+
await writeFile3(
|
|
3036
3160
|
path6.join(await gitDirectory(directory), "agentrific-project-id"),
|
|
3037
3161
|
id + "\n"
|
|
3038
3162
|
);
|
|
@@ -3071,11 +3195,11 @@ function reconnectProject(root, id, input, store2) {
|
|
|
3071
3195
|
const filename = path6.join(await gitDirectory(directory), "agentrific-project-id");
|
|
3072
3196
|
const replacedId = await identity(directory);
|
|
3073
3197
|
const project = { id, name: path6.basename(directory), path: directory, available: true };
|
|
3074
|
-
await
|
|
3198
|
+
await writeFile3(filename, id + "\n");
|
|
3075
3199
|
try {
|
|
3076
3200
|
await Effect13.runPromise(store2.relocateProject(project, previous.path, replacedId));
|
|
3077
3201
|
} catch (error) {
|
|
3078
|
-
await
|
|
3202
|
+
await writeFile3(filename, replacedId + "\n");
|
|
3079
3203
|
throw error;
|
|
3080
3204
|
}
|
|
3081
3205
|
return project;
|
|
@@ -3089,32 +3213,32 @@ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "n
|
|
|
3089
3213
|
import path7 from "node:path";
|
|
3090
3214
|
|
|
3091
3215
|
// src/push-schema.ts
|
|
3092
|
-
import { z as
|
|
3093
|
-
var PushBindingSchema =
|
|
3094
|
-
bindingId:
|
|
3095
|
-
installationId:
|
|
3096
|
-
deviceId:
|
|
3216
|
+
import { z as z15 } from "zod";
|
|
3217
|
+
var PushBindingSchema = z15.object({
|
|
3218
|
+
bindingId: z15.string().uuid(),
|
|
3219
|
+
installationId: z15.string().uuid(),
|
|
3220
|
+
deviceId: z15.string().uuid()
|
|
3097
3221
|
});
|
|
3098
|
-
var PushStateSchema =
|
|
3099
|
-
bindings:
|
|
3100
|
-
error:
|
|
3222
|
+
var PushStateSchema = z15.object({
|
|
3223
|
+
bindings: z15.array(PushBindingSchema).max(10),
|
|
3224
|
+
error: z15.string().nullable()
|
|
3101
3225
|
});
|
|
3102
3226
|
var PendingPushSchema = PushEventSchema.extend({
|
|
3103
|
-
remaining:
|
|
3104
|
-
createdAt:
|
|
3105
|
-
attempts:
|
|
3106
|
-
nextAt:
|
|
3227
|
+
remaining: z15.array(PushBindingSchema),
|
|
3228
|
+
createdAt: z15.number(),
|
|
3229
|
+
attempts: z15.number().int().nonnegative(),
|
|
3230
|
+
nextAt: z15.number()
|
|
3107
3231
|
});
|
|
3108
3232
|
|
|
3109
3233
|
// src/store.ts
|
|
3110
3234
|
import { DatabaseSync } from "node:sqlite";
|
|
3111
3235
|
import { Context as Context3, Data as Data3, Effect as Effect14, Layer as Layer3 } from "effect";
|
|
3112
|
-
import { z as
|
|
3236
|
+
import { z as z16 } from "zod";
|
|
3113
3237
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
3114
3238
|
var StorageError = class extends Data3.TaggedError("StorageError") {
|
|
3115
3239
|
};
|
|
3116
|
-
var PayloadRow =
|
|
3117
|
-
var EventRow =
|
|
3240
|
+
var PayloadRow = z16.object({ payload: z16.string() });
|
|
3241
|
+
var EventRow = z16.object({ sequence: z16.number(), recorded_at: z16.string(), payload: z16.string() });
|
|
3118
3242
|
var SqliteSessionStore = class {
|
|
3119
3243
|
constructor(db) {
|
|
3120
3244
|
this.db = db;
|
|
@@ -3165,7 +3289,7 @@ var SqliteSessionStore = class {
|
|
|
3165
3289
|
const row = PayloadRow.parse(
|
|
3166
3290
|
this.db.prepare("SELECT payload FROM settings WHERE id = 'server-id'").get()
|
|
3167
3291
|
);
|
|
3168
|
-
return
|
|
3292
|
+
return z16.string().uuid().parse(JSON.parse(row.payload));
|
|
3169
3293
|
});
|
|
3170
3294
|
}
|
|
3171
3295
|
saveDevice(device, tokenHash) {
|
|
@@ -3253,7 +3377,7 @@ var SqliteSessionStore = class {
|
|
|
3253
3377
|
`).all();
|
|
3254
3378
|
const insert = this.db.prepare("INSERT OR IGNORE INTO projects(id, payload) VALUES (?, ?)");
|
|
3255
3379
|
for (const row of legacy) {
|
|
3256
|
-
const id =
|
|
3380
|
+
const id = z16.object({ id: z16.string() }).parse(row).id;
|
|
3257
3381
|
insert.run(
|
|
3258
3382
|
id,
|
|
3259
3383
|
JSON.stringify({ id, name: id, path: path7.join(root, id), available: false })
|
|
@@ -3463,7 +3587,7 @@ var SqliteSessionStore = class {
|
|
|
3463
3587
|
if (event.type === "message.user") this.claimAttachments(event.attachments ?? []);
|
|
3464
3588
|
const result = this.db.prepare("INSERT INTO events(session_id, recorded_at, payload) VALUES (?, ?, ?)").run(sessionId, recordedAt, JSON.stringify(event));
|
|
3465
3589
|
if (notification) {
|
|
3466
|
-
const count =
|
|
3590
|
+
const count = z16.object({ total: z16.number() }).parse(this.db.prepare("SELECT COUNT(*) AS total FROM push_outbox").get());
|
|
3467
3591
|
if (count.total >= 1e3)
|
|
3468
3592
|
this.db.prepare(
|
|
3469
3593
|
"DELETE FROM push_outbox WHERE rowid IN (SELECT rowid FROM push_outbox ORDER BY rowid LIMIT 1)"
|
|
@@ -3515,7 +3639,7 @@ var SessionStore = class extends Context3.Service()(
|
|
|
3515
3639
|
for (const suffix of ["-wal", "-shm"])
|
|
3516
3640
|
if (existsSync2(filename + suffix)) chmodSync(filename + suffix, 384);
|
|
3517
3641
|
try {
|
|
3518
|
-
const schema =
|
|
3642
|
+
const schema = z16.object({ user_version: z16.number() }).parse(db.prepare("PRAGMA user_version").get());
|
|
3519
3643
|
if (schema.user_version > 1)
|
|
3520
3644
|
throw new Error("This database requires a newer Agentrific server.");
|
|
3521
3645
|
db.exec(`
|
|
@@ -3905,7 +4029,7 @@ app.get("/api/projects/:id", async (request, reply) => {
|
|
|
3905
4029
|
return project ?? reply.code(404).send({ error: "Project not found on this server." });
|
|
3906
4030
|
});
|
|
3907
4031
|
app.post("/api/projects/:id/reconnect", async (request, reply) => {
|
|
3908
|
-
const parsed =
|
|
4032
|
+
const parsed = z17.object({ path: z17.string().trim().min(1).max(4096) }).safeParse(request.body);
|
|
3909
4033
|
if (!parsed.success)
|
|
3910
4034
|
return reply.code(400).send({ error: "Enter the project's new folder path." });
|
|
3911
4035
|
const root = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
@@ -4037,9 +4161,9 @@ app.post(
|
|
|
4037
4161
|
}
|
|
4038
4162
|
})
|
|
4039
4163
|
);
|
|
4040
|
-
var HistoryQuery =
|
|
4041
|
-
after:
|
|
4042
|
-
limit:
|
|
4164
|
+
var HistoryQuery = z17.object({
|
|
4165
|
+
after: z17.coerce.number().int().nonnegative().default(0),
|
|
4166
|
+
limit: z17.coerce.number().int().min(1).max(1e3).default(500)
|
|
4043
4167
|
});
|
|
4044
4168
|
app.get("/api/sessions/:id/events", async (request, reply) => {
|
|
4045
4169
|
const query = HistoryQuery.safeParse(request.query);
|
|
@@ -4133,6 +4257,7 @@ async function createAndStartSession(input) {
|
|
|
4133
4257
|
status: "starting",
|
|
4134
4258
|
agent: input.agent,
|
|
4135
4259
|
model: selectedModel,
|
|
4260
|
+
usage: { tokens: 0, turns: 0, tools: 0 },
|
|
4136
4261
|
gitFlow: input.gitFlow,
|
|
4137
4262
|
branch: null,
|
|
4138
4263
|
createdAt: now,
|
|
@@ -4181,7 +4306,7 @@ app.post("/api/sessions", async (request, reply) => {
|
|
|
4181
4306
|
if (!result.session) return reply.code(result.status).send({ error: result.error });
|
|
4182
4307
|
return reply.code(201).send(result.session);
|
|
4183
4308
|
});
|
|
4184
|
-
var StartTaskBody =
|
|
4309
|
+
var StartTaskBody = z17.object({ prompt: z17.string().trim().max(2e4).optional() }).default({});
|
|
4185
4310
|
function startTask(id, instructionsOverride, automated = false) {
|
|
4186
4311
|
return withTaskLock(
|
|
4187
4312
|
"agent-dispatch",
|
|
@@ -4259,7 +4384,7 @@ app.patch("/api/automation", async (request, reply) => {
|
|
|
4259
4384
|
return automation.configure(parsed.data);
|
|
4260
4385
|
});
|
|
4261
4386
|
app.patch("/api/automation/queue", async (request, reply) => {
|
|
4262
|
-
const parsed =
|
|
4387
|
+
const parsed = z17.object({ taskId: z17.string().min(1), beforeId: z17.string().min(1).nullable() }).safeParse(request.body);
|
|
4263
4388
|
if (!parsed.success)
|
|
4264
4389
|
return reply.code(400).send({ error: "Choose a task and a queue position." });
|
|
4265
4390
|
try {
|
|
@@ -4289,7 +4414,13 @@ app.post(
|
|
|
4289
4414
|
attachments.resolve(session.projectId, parsed.data.attachmentIds ?? [])
|
|
4290
4415
|
);
|
|
4291
4416
|
await Effect16.runPromise(
|
|
4292
|
-
runtime.send(
|
|
4417
|
+
runtime.send(
|
|
4418
|
+
session,
|
|
4419
|
+
parsed.data.message,
|
|
4420
|
+
files,
|
|
4421
|
+
parsed.data.clientMessageId,
|
|
4422
|
+
parsed.data.mode
|
|
4423
|
+
)
|
|
4293
4424
|
);
|
|
4294
4425
|
return reply.code(202).send({ accepted: true });
|
|
4295
4426
|
} catch (error) {
|