@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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# @agentrific/server
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 66aa463: Improve task-board navigation and display accumulated run usage. Add clearer Bash
|
|
8
|
+
command details, highlighted read-file previews and better native sheet sizing.
|
|
9
|
+
Server events now preserve complete Bash command inputs and report session usage.
|
|
10
|
+
|
|
11
|
+
## 0.2.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- fb249d8: Add highlighted tool file-change previews, persistent editable message queues and explicit run steering. Add the multi-server transport and credential foundation while preserving existing connection recovery and timeout behaviour.
|
|
16
|
+
|
|
3
17
|
## 0.1.6
|
|
4
18
|
|
|
5
19
|
### Patch 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.3.0",
|
|
34
34
|
description: "Self-hosted control plane for long-running coding agents",
|
|
35
35
|
keywords: [
|
|
36
36
|
"agentrific",
|
|
@@ -67,6 +67,7 @@ var init_package = __esm({
|
|
|
67
67
|
"@fastify/cors": "^11.3.0",
|
|
68
68
|
"@fastify/multipart": "^10.1.2",
|
|
69
69
|
"@inquirer/select": "^5.2.5",
|
|
70
|
+
diff: "^9.0.0",
|
|
70
71
|
effect: "4.0.0-rc.117",
|
|
71
72
|
fastify: "^5.6.2",
|
|
72
73
|
"pino-pretty": "^13.1.3",
|
|
@@ -245,7 +246,7 @@ function createPairingLink(payload) {
|
|
|
245
246
|
link.searchParams.set("data", JSON.stringify(PairingPayloadSchema.parse(payload)));
|
|
246
247
|
return link.toString();
|
|
247
248
|
}
|
|
248
|
-
var cronBounds, AutomationConfigSchema, AutomationStateSchema, SessionStatusSchema, AgentKindSchema, ModelReferenceSchema, ModelIdentitySchema, ModelOptionSchema, ModelCatalogSchema, GitFlowSchema, GitDeliverySchema, DeliveryRequestSchema, RunPreparationSchema, SessionSchema, MAX_ATTACHMENTS, MAX_ATTACHMENT_BYTES, AttachmentSchema, AttachmentIdsSchema, CreateSessionSchema, SendMessageSchema, 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, ProjectSchema, TaskStatusSchema, TaskSchema, CreateTaskSchema, UpdateTaskSchema, PROTOCOL_VERSION, ServerUpdateStatusSchema, ServerHealthSchema, PairingPayloadSchema, PairingInvitationSchema, PairingRequestSchema, DeviceSchema, PairingResultSchema, HandshakeSchema, HandshakeAckSchema;
|
|
249
250
|
var init_src = __esm({
|
|
250
251
|
"../../packages/contracts/src/index.ts"() {
|
|
251
252
|
cronBounds = [
|
|
@@ -354,6 +355,11 @@ var init_src = __esm({
|
|
|
354
355
|
gitFlow: GitFlowSchema.optional(),
|
|
355
356
|
gitDelivery: GitDeliverySchema.optional(),
|
|
356
357
|
preparation: RunPreparationSchema.optional(),
|
|
358
|
+
usage: z2.object({
|
|
359
|
+
tokens: z2.number().nonnegative(),
|
|
360
|
+
turns: z2.number().int().nonnegative(),
|
|
361
|
+
tools: z2.number().int().nonnegative()
|
|
362
|
+
}).optional(),
|
|
357
363
|
createdAt: z2.string().datetime(),
|
|
358
364
|
updatedAt: z2.string().datetime()
|
|
359
365
|
});
|
|
@@ -381,11 +387,18 @@ var init_src = __esm({
|
|
|
381
387
|
SendMessageSchema = z2.object({
|
|
382
388
|
message: z2.string().trim().max(2e4),
|
|
383
389
|
attachmentIds: AttachmentIdsSchema.optional(),
|
|
384
|
-
clientMessageId: z2.string().min(1).max(120).optional()
|
|
390
|
+
clientMessageId: z2.string().min(1).max(120).optional(),
|
|
391
|
+
mode: z2.enum(["followUp", "steer"]).optional()
|
|
385
392
|
}).refine(
|
|
386
393
|
(input) => input.message.length > 0 || (input.attachmentIds?.length ?? 0) > 0,
|
|
387
394
|
"Write a message or attach a file."
|
|
388
395
|
);
|
|
396
|
+
FileChangeSchema = z2.object({
|
|
397
|
+
path: z2.string(),
|
|
398
|
+
patch: z2.string().optional(),
|
|
399
|
+
content: z2.string().optional(),
|
|
400
|
+
omitted: z2.boolean().optional()
|
|
401
|
+
});
|
|
389
402
|
SessionEventSchema = z2.discriminatedUnion("type", [
|
|
390
403
|
z2.object({ type: z2.literal("session.updated"), session: SessionSchema }),
|
|
391
404
|
z2.object({
|
|
@@ -410,6 +423,7 @@ var init_src = __esm({
|
|
|
410
423
|
}),
|
|
411
424
|
z2.object({
|
|
412
425
|
type: z2.literal("message.completed"),
|
|
426
|
+
tokens: z2.number().nonnegative().optional(),
|
|
413
427
|
model: ModelIdentitySchema.optional(),
|
|
414
428
|
sessionId: z2.string(),
|
|
415
429
|
messageId: z2.string().optional(),
|
|
@@ -420,7 +434,8 @@ var init_src = __esm({
|
|
|
420
434
|
sessionId: z2.string(),
|
|
421
435
|
toolCallId: z2.string().optional(),
|
|
422
436
|
tool: z2.string(),
|
|
423
|
-
input: z2.string()
|
|
437
|
+
input: z2.string(),
|
|
438
|
+
filePath: z2.string().optional()
|
|
424
439
|
}),
|
|
425
440
|
z2.object({
|
|
426
441
|
type: z2.literal("tool.completed"),
|
|
@@ -428,6 +443,7 @@ var init_src = __esm({
|
|
|
428
443
|
toolCallId: z2.string().optional(),
|
|
429
444
|
tool: z2.string(),
|
|
430
445
|
output: z2.string(),
|
|
446
|
+
fileChange: FileChangeSchema.optional(),
|
|
431
447
|
isError: z2.boolean().default(false)
|
|
432
448
|
}),
|
|
433
449
|
z2.object({
|
|
@@ -2069,13 +2085,29 @@ var init_runtime = __esm({
|
|
|
2069
2085
|
const entry = {
|
|
2070
2086
|
session,
|
|
2071
2087
|
agent,
|
|
2072
|
-
unsubscribe:
|
|
2088
|
+
unsubscribe: () => {
|
|
2089
|
+
},
|
|
2073
2090
|
running: false,
|
|
2074
2091
|
stopping: false,
|
|
2075
2092
|
pending: []
|
|
2076
2093
|
};
|
|
2094
|
+
entry.unsubscribe = agent.subscribe((event) => {
|
|
2095
|
+
this.emit(event);
|
|
2096
|
+
if (event.type !== "message.completed" && event.type !== "tool.started") return;
|
|
2097
|
+
const usage = entry.session.usage ?? { tokens: 0, turns: 0, tools: 0 };
|
|
2098
|
+
entry.session = {
|
|
2099
|
+
...entry.session,
|
|
2100
|
+
usage: {
|
|
2101
|
+
tokens: usage.tokens + (event.type === "message.completed" ? event.tokens ?? 0 : 0),
|
|
2102
|
+
turns: usage.turns + (event.type === "message.completed" ? 1 : 0),
|
|
2103
|
+
tools: usage.tools + (event.type === "tool.started" ? 1 : 0)
|
|
2104
|
+
},
|
|
2105
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2106
|
+
};
|
|
2107
|
+
this.emit({ type: "session.updated", session: entry.session });
|
|
2108
|
+
});
|
|
2077
2109
|
if (agent.model) {
|
|
2078
|
-
entry.session = { ...session, model: agent.model };
|
|
2110
|
+
entry.session = { ...entry.session, model: agent.model };
|
|
2079
2111
|
this.emit({ type: "session.updated", session: entry.session });
|
|
2080
2112
|
}
|
|
2081
2113
|
this.active.set(session.id, entry);
|
|
@@ -2125,7 +2157,7 @@ var init_runtime = __esm({
|
|
|
2125
2157
|
Effect8.ensuring(Effect8.sync(() => this.working.delete(entry.session.id)))
|
|
2126
2158
|
);
|
|
2127
2159
|
}
|
|
2128
|
-
send(session, message, attachments2 = [], messageId = randomUUID4()) {
|
|
2160
|
+
send(session, message, attachments2 = [], messageId = randomUUID4(), mode = "followUp") {
|
|
2129
2161
|
return this.lock(session.id).withPermit(
|
|
2130
2162
|
Effect8.gen({ self: this }, function* () {
|
|
2131
2163
|
if (this.closed)
|
|
@@ -2135,6 +2167,16 @@ var init_runtime = __esm({
|
|
|
2135
2167
|
if (this.hasMessage(session.id, messageId)) return;
|
|
2136
2168
|
const entry = yield* this.open(session);
|
|
2137
2169
|
yield* this.operation(async () => entry.agent.validateAttachments?.(attachments2));
|
|
2170
|
+
const steering = entry.running && mode === "steer";
|
|
2171
|
+
if (steering) {
|
|
2172
|
+
if (!entry.agent.steer)
|
|
2173
|
+
return yield* Effect8.fail(
|
|
2174
|
+
new AgentError({ message: "This agent does not support steering." })
|
|
2175
|
+
);
|
|
2176
|
+
yield* this.operation(
|
|
2177
|
+
() => entry.agent.steer(gitFlowPrompt(entry.session, message), attachments2)
|
|
2178
|
+
);
|
|
2179
|
+
}
|
|
2138
2180
|
this.emit({
|
|
2139
2181
|
type: "message.user",
|
|
2140
2182
|
sessionId: session.id,
|
|
@@ -2142,6 +2184,7 @@ var init_runtime = __esm({
|
|
|
2142
2184
|
text: message,
|
|
2143
2185
|
attachments: attachments2
|
|
2144
2186
|
});
|
|
2187
|
+
if (steering) return;
|
|
2145
2188
|
if (entry.running) {
|
|
2146
2189
|
entry.pending.push({ text: message, attachments: attachments2 });
|
|
2147
2190
|
return;
|
|
@@ -3116,6 +3159,74 @@ var init_delivery_tool = __esm({
|
|
|
3116
3159
|
}
|
|
3117
3160
|
});
|
|
3118
3161
|
|
|
3162
|
+
// src/file-change.ts
|
|
3163
|
+
import { createWriteToolDefinition, defineTool as defineTool2 } from "@earendil-works/pi-coding-agent";
|
|
3164
|
+
import { createTwoFilesPatch } from "diff";
|
|
3165
|
+
import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
|
|
3166
|
+
import { z as z13 } from "zod";
|
|
3167
|
+
function boundFileChange(change) {
|
|
3168
|
+
if ((change.patch?.length ?? 0) + (change.content?.length ?? 0) > previewLimit)
|
|
3169
|
+
return { path: change.path, omitted: true };
|
|
3170
|
+
return change;
|
|
3171
|
+
}
|
|
3172
|
+
function completedFileChange(result, path13) {
|
|
3173
|
+
const parsed = EditResultSchema.safeParse(result);
|
|
3174
|
+
if (!parsed.success) return;
|
|
3175
|
+
const details = parsed.data.details;
|
|
3176
|
+
if (details?.fileChange) return boundFileChange(details.fileChange);
|
|
3177
|
+
if (path13 && details?.patch) return boundFileChange({ path: path13, patch: details.patch });
|
|
3178
|
+
}
|
|
3179
|
+
function fileWriteTool(cwd) {
|
|
3180
|
+
const base = createWriteToolDefinition(cwd);
|
|
3181
|
+
return defineTool2({
|
|
3182
|
+
name: base.name,
|
|
3183
|
+
label: base.label,
|
|
3184
|
+
description: base.description,
|
|
3185
|
+
promptSnippet: base.promptSnippet,
|
|
3186
|
+
promptGuidelines: base.promptGuidelines,
|
|
3187
|
+
parameters: base.parameters,
|
|
3188
|
+
constrainedSampling: base.constrainedSampling,
|
|
3189
|
+
async execute(toolCallId, args, signal, _onUpdate, ctx) {
|
|
3190
|
+
let fileChange;
|
|
3191
|
+
const tool = createWriteToolDefinition(cwd, {
|
|
3192
|
+
operations: {
|
|
3193
|
+
mkdir: async (dir) => {
|
|
3194
|
+
await mkdir5(dir, { recursive: true });
|
|
3195
|
+
},
|
|
3196
|
+
// Pi invokes this inside its per-file mutation queue, so the snapshot
|
|
3197
|
+
// and write cannot race another built-in edit/write on the same file.
|
|
3198
|
+
writeFile: async (path13, content) => {
|
|
3199
|
+
const previous = await readFile4(path13, "utf8").catch((error) => {
|
|
3200
|
+
if (error.code === "ENOENT") return void 0;
|
|
3201
|
+
throw error;
|
|
3202
|
+
});
|
|
3203
|
+
await writeFile3(path13, content, "utf8");
|
|
3204
|
+
fileChange = boundFileChange(
|
|
3205
|
+
previous === void 0 ? { path: args.path, content } : {
|
|
3206
|
+
path: args.path,
|
|
3207
|
+
patch: createTwoFilesPatch(args.path, args.path, previous, content)
|
|
3208
|
+
}
|
|
3209
|
+
);
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
});
|
|
3213
|
+
const result = await tool.execute(toolCallId, args, signal, void 0, ctx);
|
|
3214
|
+
return { ...result, details: { fileChange } };
|
|
3215
|
+
}
|
|
3216
|
+
});
|
|
3217
|
+
}
|
|
3218
|
+
var previewLimit, EditResultSchema;
|
|
3219
|
+
var init_file_change = __esm({
|
|
3220
|
+
"src/file-change.ts"() {
|
|
3221
|
+
"use strict";
|
|
3222
|
+
init_src();
|
|
3223
|
+
previewLimit = 256e3;
|
|
3224
|
+
EditResultSchema = z13.object({
|
|
3225
|
+
details: z13.object({ patch: z13.string().optional(), fileChange: FileChangeSchema.optional() }).optional()
|
|
3226
|
+
});
|
|
3227
|
+
}
|
|
3228
|
+
});
|
|
3229
|
+
|
|
3119
3230
|
// src/pi.ts
|
|
3120
3231
|
import { Effect as Effect12 } from "effect";
|
|
3121
3232
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
@@ -3123,7 +3234,7 @@ import {
|
|
|
3123
3234
|
createAgentSession as createAgentSession2,
|
|
3124
3235
|
SessionManager as SessionManager2
|
|
3125
3236
|
} from "@earendil-works/pi-coding-agent";
|
|
3126
|
-
import { z as
|
|
3237
|
+
import { z as z14 } from "zod";
|
|
3127
3238
|
function toolOutput(value) {
|
|
3128
3239
|
const parsed = ToolResultSchema.safeParse(value);
|
|
3129
3240
|
const text = parsed.success ? parsed.data.content?.filter((part) => part.type === "text").map((part) => part.text || "").join("\n") || "Completed without text output." : boundedJson(value, 4e3);
|
|
@@ -3138,7 +3249,10 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
3138
3249
|
model,
|
|
3139
3250
|
modelRuntime: models2.runtime,
|
|
3140
3251
|
sessionManager: SessionManager2.continueRecent(cwd, sessionDirectory),
|
|
3141
|
-
customTools:
|
|
3252
|
+
customTools: [
|
|
3253
|
+
fileWriteTool(cwd),
|
|
3254
|
+
...run?.gitFlow && delivery2 ? [deliveryTool(sessionId, delivery2)] : []
|
|
3255
|
+
]
|
|
3142
3256
|
});
|
|
3143
3257
|
return {
|
|
3144
3258
|
get model() {
|
|
@@ -3162,6 +3276,12 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
3162
3276
|
throw new Error(last.errorMessage ?? "The model request failed");
|
|
3163
3277
|
}
|
|
3164
3278
|
},
|
|
3279
|
+
async steer(message, attachments2 = []) {
|
|
3280
|
+
const context = await Effect12.runPromise(files.prepare(attachments2));
|
|
3281
|
+
if (!session.isStreaming)
|
|
3282
|
+
throw new Error("The response has finished. Send this message as a follow-up instead.");
|
|
3283
|
+
await session.steer(message + context.context, context.images);
|
|
3284
|
+
},
|
|
3165
3285
|
async abort() {
|
|
3166
3286
|
session.clearQueue();
|
|
3167
3287
|
await session.abort();
|
|
@@ -3218,6 +3338,7 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
3218
3338
|
sessionId,
|
|
3219
3339
|
messageId: assistantMessageId,
|
|
3220
3340
|
text,
|
|
3341
|
+
tokens: event.message.usage.totalTokens,
|
|
3221
3342
|
model: models2.identity(event.message.provider, event.message.model)
|
|
3222
3343
|
});
|
|
3223
3344
|
assistantMessageId = void 0;
|
|
@@ -3226,16 +3347,24 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
3226
3347
|
}
|
|
3227
3348
|
return false;
|
|
3228
3349
|
}
|
|
3350
|
+
const filePaths = /* @__PURE__ */ new Map();
|
|
3351
|
+
const FileArgsSchema = z14.object({ path: z14.string() });
|
|
3229
3352
|
function handleTool(event) {
|
|
3230
3353
|
if (event.type === "tool_execution_start") {
|
|
3231
3354
|
flushText();
|
|
3232
3355
|
setActivity("thinking");
|
|
3356
|
+
if (event.toolName === "edit" || event.toolName === "write") {
|
|
3357
|
+
const args = FileArgsSchema.safeParse(event.args);
|
|
3358
|
+
if (args.success) filePaths.set(event.toolCallId, args.data.path);
|
|
3359
|
+
}
|
|
3233
3360
|
emit({
|
|
3234
3361
|
type: "tool.started",
|
|
3235
3362
|
sessionId,
|
|
3236
3363
|
toolCallId: event.toolCallId,
|
|
3237
3364
|
tool: event.toolName,
|
|
3238
|
-
|
|
3365
|
+
// Preserve complete Bash arguments so multiline commands can be inspected.
|
|
3366
|
+
input: event.toolName === "bash" ? JSON.stringify(event.args) : boundedJson(event.args, 1200),
|
|
3367
|
+
filePath: filePaths.get(event.toolCallId)
|
|
3239
3368
|
});
|
|
3240
3369
|
return true;
|
|
3241
3370
|
}
|
|
@@ -3247,8 +3376,10 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
|
|
|
3247
3376
|
toolCallId: event.toolCallId,
|
|
3248
3377
|
tool: event.toolName,
|
|
3249
3378
|
output: toolOutput(event.result),
|
|
3379
|
+
fileChange: event.isError ? void 0 : completedFileChange(event.result, filePaths.get(event.toolCallId)),
|
|
3250
3380
|
isError: event.isError
|
|
3251
3381
|
});
|
|
3382
|
+
filePaths.delete(event.toolCallId);
|
|
3252
3383
|
setActivity("thinking");
|
|
3253
3384
|
return true;
|
|
3254
3385
|
}
|
|
@@ -3281,8 +3412,9 @@ var init_pi = __esm({
|
|
|
3281
3412
|
"use strict";
|
|
3282
3413
|
init_models();
|
|
3283
3414
|
init_delivery_tool();
|
|
3284
|
-
|
|
3285
|
-
|
|
3415
|
+
init_file_change();
|
|
3416
|
+
ToolResultSchema = z14.object({
|
|
3417
|
+
content: z14.array(z14.object({ type: z14.string(), text: z14.string().optional() })).optional()
|
|
3286
3418
|
}).passthrough();
|
|
3287
3419
|
boundedJson = (value, limit) => {
|
|
3288
3420
|
let text;
|
|
@@ -3297,9 +3429,9 @@ var init_pi = __esm({
|
|
|
3297
3429
|
});
|
|
3298
3430
|
|
|
3299
3431
|
// src/projects.ts
|
|
3300
|
-
import { access as access2, readdir as readdir2, realpath, stat as stat3, readFile as
|
|
3432
|
+
import { access as access2, readdir as readdir2, realpath, stat as stat3, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
|
|
3301
3433
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
3302
|
-
import { z as
|
|
3434
|
+
import { z as z15 } from "zod";
|
|
3303
3435
|
import { constants as constants2 } from "node:fs";
|
|
3304
3436
|
import { homedir as homedir3 } from "node:os";
|
|
3305
3437
|
import path8 from "node:path";
|
|
@@ -3334,7 +3466,7 @@ async function gitDirectory(directory) {
|
|
|
3334
3466
|
const location = path8.join(directory, ".git");
|
|
3335
3467
|
const info = await stat3(location);
|
|
3336
3468
|
if (info.isDirectory()) return location;
|
|
3337
|
-
const contents = await
|
|
3469
|
+
const contents = await readFile5(location, "utf8");
|
|
3338
3470
|
const match = /^gitdir: (.+)\s*$/m.exec(contents);
|
|
3339
3471
|
if (!match) throw new Error("Invalid Git worktree metadata.");
|
|
3340
3472
|
const resolved = await realpath(path8.resolve(directory, match[1].trim()));
|
|
@@ -3344,21 +3476,21 @@ async function gitDirectory(directory) {
|
|
|
3344
3476
|
async function identity(directory, preferred) {
|
|
3345
3477
|
const filename = path8.join(await gitDirectory(directory), "agentrific-project-id");
|
|
3346
3478
|
try {
|
|
3347
|
-
return ProjectId.parse(await
|
|
3479
|
+
return ProjectId.parse(await readFile5(filename, "utf8"));
|
|
3348
3480
|
} catch (error) {
|
|
3349
|
-
if (!
|
|
3481
|
+
if (!z15.object({ code: z15.literal("ENOENT") }).safeParse(error).success) throw error;
|
|
3350
3482
|
const legacyFilename = path8.join(await gitDirectory(directory), "chatplans-project-id");
|
|
3351
|
-
const legacy = await
|
|
3352
|
-
if (!
|
|
3483
|
+
const legacy = await readFile5(legacyFilename, "utf8").catch((cause) => {
|
|
3484
|
+
if (!z15.object({ code: z15.literal("ENOENT") }).safeParse(cause).success) throw cause;
|
|
3353
3485
|
return null;
|
|
3354
3486
|
});
|
|
3355
3487
|
const id = legacy === null ? preferred ?? randomUUID6() : ProjectId.parse(legacy);
|
|
3356
3488
|
try {
|
|
3357
|
-
await
|
|
3489
|
+
await writeFile4(filename, id + "\n", { flag: "wx" });
|
|
3358
3490
|
return id;
|
|
3359
3491
|
} catch (writeError) {
|
|
3360
|
-
if (!
|
|
3361
|
-
return ProjectId.parse(await
|
|
3492
|
+
if (!z15.object({ code: z15.literal("EEXIST") }).safeParse(writeError).success) throw writeError;
|
|
3493
|
+
return ProjectId.parse(await readFile5(filename, "utf8"));
|
|
3362
3494
|
}
|
|
3363
3495
|
}
|
|
3364
3496
|
}
|
|
@@ -3378,7 +3510,7 @@ function scanProjects(root, store2) {
|
|
|
3378
3510
|
let existing = registered.find((project2) => project2.id === id);
|
|
3379
3511
|
if (found.some((project2) => project2.id === id) || existing && existing.path !== directory && await stat3(path8.join(existing.path, ".git")).catch(() => null)) {
|
|
3380
3512
|
id = randomUUID6();
|
|
3381
|
-
await
|
|
3513
|
+
await writeFile4(
|
|
3382
3514
|
path8.join(await gitDirectory(directory), "agentrific-project-id"),
|
|
3383
3515
|
id + "\n"
|
|
3384
3516
|
);
|
|
@@ -3417,11 +3549,11 @@ function reconnectProject(root, id, input, store2) {
|
|
|
3417
3549
|
const filename = path8.join(await gitDirectory(directory), "agentrific-project-id");
|
|
3418
3550
|
const replacedId = await identity(directory);
|
|
3419
3551
|
const project = { id, name: path8.basename(directory), path: directory, available: true };
|
|
3420
|
-
await
|
|
3552
|
+
await writeFile4(filename, id + "\n");
|
|
3421
3553
|
try {
|
|
3422
3554
|
await Effect13.runPromise(store2.relocateProject(project, previous.path, replacedId));
|
|
3423
3555
|
} catch (error) {
|
|
3424
|
-
await
|
|
3556
|
+
await writeFile4(filename, replacedId + "\n");
|
|
3425
3557
|
throw error;
|
|
3426
3558
|
}
|
|
3427
3559
|
return project;
|
|
@@ -3434,31 +3566,31 @@ var init_projects = __esm({
|
|
|
3434
3566
|
"src/projects.ts"() {
|
|
3435
3567
|
"use strict";
|
|
3436
3568
|
locks2 = /* @__PURE__ */ new WeakMap();
|
|
3437
|
-
ProjectId =
|
|
3569
|
+
ProjectId = z15.string().trim().min(1).max(255);
|
|
3438
3570
|
}
|
|
3439
3571
|
});
|
|
3440
3572
|
|
|
3441
3573
|
// src/push-schema.ts
|
|
3442
|
-
import { z as
|
|
3574
|
+
import { z as z16 } from "zod";
|
|
3443
3575
|
var PushBindingSchema, PushStateSchema, PendingPushSchema;
|
|
3444
3576
|
var init_push_schema = __esm({
|
|
3445
3577
|
"src/push-schema.ts"() {
|
|
3446
3578
|
"use strict";
|
|
3447
3579
|
init_push();
|
|
3448
|
-
PushBindingSchema =
|
|
3449
|
-
bindingId:
|
|
3450
|
-
installationId:
|
|
3451
|
-
deviceId:
|
|
3580
|
+
PushBindingSchema = z16.object({
|
|
3581
|
+
bindingId: z16.string().uuid(),
|
|
3582
|
+
installationId: z16.string().uuid(),
|
|
3583
|
+
deviceId: z16.string().uuid()
|
|
3452
3584
|
});
|
|
3453
|
-
PushStateSchema =
|
|
3454
|
-
bindings:
|
|
3455
|
-
error:
|
|
3585
|
+
PushStateSchema = z16.object({
|
|
3586
|
+
bindings: z16.array(PushBindingSchema).max(10),
|
|
3587
|
+
error: z16.string().nullable()
|
|
3456
3588
|
});
|
|
3457
3589
|
PendingPushSchema = PushEventSchema.extend({
|
|
3458
|
-
remaining:
|
|
3459
|
-
createdAt:
|
|
3460
|
-
attempts:
|
|
3461
|
-
nextAt:
|
|
3590
|
+
remaining: z16.array(PushBindingSchema),
|
|
3591
|
+
createdAt: z16.number(),
|
|
3592
|
+
attempts: z16.number().int().nonnegative(),
|
|
3593
|
+
nextAt: z16.number()
|
|
3462
3594
|
});
|
|
3463
3595
|
}
|
|
3464
3596
|
});
|
|
@@ -3468,7 +3600,7 @@ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "n
|
|
|
3468
3600
|
import path9 from "node:path";
|
|
3469
3601
|
import { DatabaseSync } from "node:sqlite";
|
|
3470
3602
|
import { Context as Context3, Data as Data3, Effect as Effect14, Layer as Layer3 } from "effect";
|
|
3471
|
-
import { z as
|
|
3603
|
+
import { z as z17 } from "zod";
|
|
3472
3604
|
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
3473
3605
|
var StorageError, PayloadRow, EventRow, SqliteSessionStore, SessionStore;
|
|
3474
3606
|
var init_store = __esm({
|
|
@@ -3481,8 +3613,8 @@ var init_store = __esm({
|
|
|
3481
3613
|
init_src();
|
|
3482
3614
|
StorageError = class extends Data3.TaggedError("StorageError") {
|
|
3483
3615
|
};
|
|
3484
|
-
PayloadRow =
|
|
3485
|
-
EventRow =
|
|
3616
|
+
PayloadRow = z17.object({ payload: z17.string() });
|
|
3617
|
+
EventRow = z17.object({ sequence: z17.number(), recorded_at: z17.string(), payload: z17.string() });
|
|
3486
3618
|
SqliteSessionStore = class {
|
|
3487
3619
|
constructor(db) {
|
|
3488
3620
|
this.db = db;
|
|
@@ -3533,7 +3665,7 @@ var init_store = __esm({
|
|
|
3533
3665
|
const row = PayloadRow.parse(
|
|
3534
3666
|
this.db.prepare("SELECT payload FROM settings WHERE id = 'server-id'").get()
|
|
3535
3667
|
);
|
|
3536
|
-
return
|
|
3668
|
+
return z17.string().uuid().parse(JSON.parse(row.payload));
|
|
3537
3669
|
});
|
|
3538
3670
|
}
|
|
3539
3671
|
saveDevice(device, tokenHash) {
|
|
@@ -3621,7 +3753,7 @@ var init_store = __esm({
|
|
|
3621
3753
|
`).all();
|
|
3622
3754
|
const insert = this.db.prepare("INSERT OR IGNORE INTO projects(id, payload) VALUES (?, ?)");
|
|
3623
3755
|
for (const row of legacy) {
|
|
3624
|
-
const id =
|
|
3756
|
+
const id = z17.object({ id: z17.string() }).parse(row).id;
|
|
3625
3757
|
insert.run(
|
|
3626
3758
|
id,
|
|
3627
3759
|
JSON.stringify({ id, name: id, path: path9.join(root, id), available: false })
|
|
@@ -3831,7 +3963,7 @@ var init_store = __esm({
|
|
|
3831
3963
|
if (event.type === "message.user") this.claimAttachments(event.attachments ?? []);
|
|
3832
3964
|
const result = this.db.prepare("INSERT INTO events(session_id, recorded_at, payload) VALUES (?, ?, ?)").run(sessionId, recordedAt, JSON.stringify(event));
|
|
3833
3965
|
if (notification) {
|
|
3834
|
-
const count =
|
|
3966
|
+
const count = z17.object({ total: z17.number() }).parse(this.db.prepare("SELECT COUNT(*) AS total FROM push_outbox").get());
|
|
3835
3967
|
if (count.total >= 1e3)
|
|
3836
3968
|
this.db.prepare(
|
|
3837
3969
|
"DELETE FROM push_outbox WHERE rowid IN (SELECT rowid FROM push_outbox ORDER BY rowid LIMIT 1)"
|
|
@@ -3883,7 +4015,7 @@ var init_store = __esm({
|
|
|
3883
4015
|
for (const suffix of ["-wal", "-shm"])
|
|
3884
4016
|
if (existsSync2(filename + suffix)) chmodSync(filename + suffix, 384);
|
|
3885
4017
|
try {
|
|
3886
|
-
const schema =
|
|
4018
|
+
const schema = z17.object({ user_version: z17.number() }).parse(db.prepare("PRAGMA user_version").get());
|
|
3887
4019
|
if (schema.user_version > 1)
|
|
3888
4020
|
throw new Error("This database requires a newer Agentrific server.");
|
|
3889
4021
|
db.exec(`
|
|
@@ -4004,7 +4136,7 @@ import { homedir as homedir4 } from "node:os";
|
|
|
4004
4136
|
import path11 from "node:path";
|
|
4005
4137
|
import { WebSocket } from "ws";
|
|
4006
4138
|
import { Effect as Effect16, ManagedRuntime } from "effect";
|
|
4007
|
-
import { z as
|
|
4139
|
+
import { z as z18 } from "zod";
|
|
4008
4140
|
import { ModelRuntime } from "@earendil-works/pi-coding-agent";
|
|
4009
4141
|
function publish(event, workflowUpdate = false) {
|
|
4010
4142
|
const previous = event.type === "session.updated" ? Effect16.runSync(store.get(event.session.id)) : void 0;
|
|
@@ -4124,6 +4256,7 @@ async function createAndStartSession(input) {
|
|
|
4124
4256
|
status: "starting",
|
|
4125
4257
|
agent: input.agent,
|
|
4126
4258
|
model: selectedModel,
|
|
4259
|
+
usage: { tokens: 0, turns: 0, tools: 0 },
|
|
4127
4260
|
gitFlow: input.gitFlow,
|
|
4128
4261
|
branch: null,
|
|
4129
4262
|
createdAt: now,
|
|
@@ -4495,7 +4628,7 @@ var init_index = __esm({
|
|
|
4495
4628
|
return project ?? reply.code(404).send({ error: "Project not found on this server." });
|
|
4496
4629
|
});
|
|
4497
4630
|
app.post("/api/projects/:id/reconnect", async (request, reply) => {
|
|
4498
|
-
const parsed =
|
|
4631
|
+
const parsed = z18.object({ path: z18.string().trim().min(1).max(4096) }).safeParse(request.body);
|
|
4499
4632
|
if (!parsed.success)
|
|
4500
4633
|
return reply.code(400).send({ error: "Enter the project's new folder path." });
|
|
4501
4634
|
const root = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
|
|
@@ -4627,9 +4760,9 @@ var init_index = __esm({
|
|
|
4627
4760
|
}
|
|
4628
4761
|
})
|
|
4629
4762
|
);
|
|
4630
|
-
HistoryQuery =
|
|
4631
|
-
after:
|
|
4632
|
-
limit:
|
|
4763
|
+
HistoryQuery = z18.object({
|
|
4764
|
+
after: z18.coerce.number().int().nonnegative().default(0),
|
|
4765
|
+
limit: z18.coerce.number().int().min(1).max(1e3).default(500)
|
|
4633
4766
|
});
|
|
4634
4767
|
app.get("/api/sessions/:id/events", async (request, reply) => {
|
|
4635
4768
|
const query = HistoryQuery.safeParse(request.query);
|
|
@@ -4648,7 +4781,7 @@ var init_index = __esm({
|
|
|
4648
4781
|
if (!result.session) return reply.code(result.status).send({ error: result.error });
|
|
4649
4782
|
return reply.code(201).send(result.session);
|
|
4650
4783
|
});
|
|
4651
|
-
StartTaskBody =
|
|
4784
|
+
StartTaskBody = z18.object({ prompt: z18.string().trim().max(2e4).optional() }).default({});
|
|
4652
4785
|
app.post("/api/tasks/:id/start", async (request, reply) => {
|
|
4653
4786
|
const body = StartTaskBody.safeParse(request.body ?? {});
|
|
4654
4787
|
if (!body.success) return reply.code(400).send({ error: "The task prompt is too long." });
|
|
@@ -4683,7 +4816,7 @@ var init_index = __esm({
|
|
|
4683
4816
|
return automation.configure(parsed.data);
|
|
4684
4817
|
});
|
|
4685
4818
|
app.patch("/api/automation/queue", async (request, reply) => {
|
|
4686
|
-
const parsed =
|
|
4819
|
+
const parsed = z18.object({ taskId: z18.string().min(1), beforeId: z18.string().min(1).nullable() }).safeParse(request.body);
|
|
4687
4820
|
if (!parsed.success)
|
|
4688
4821
|
return reply.code(400).send({ error: "Choose a task and a queue position." });
|
|
4689
4822
|
try {
|
|
@@ -4713,7 +4846,13 @@ var init_index = __esm({
|
|
|
4713
4846
|
attachments.resolve(session.projectId, parsed.data.attachmentIds ?? [])
|
|
4714
4847
|
);
|
|
4715
4848
|
await Effect16.runPromise(
|
|
4716
|
-
runtime.send(
|
|
4849
|
+
runtime.send(
|
|
4850
|
+
session,
|
|
4851
|
+
parsed.data.message,
|
|
4852
|
+
files,
|
|
4853
|
+
parsed.data.clientMessageId,
|
|
4854
|
+
parsed.data.mode
|
|
4855
|
+
)
|
|
4717
4856
|
);
|
|
4718
4857
|
return reply.code(202).send({ accepted: true });
|
|
4719
4858
|
} catch (error) {
|
|
@@ -4783,7 +4922,7 @@ var init_index = __esm({
|
|
|
4783
4922
|
|
|
4784
4923
|
// src/cli.ts
|
|
4785
4924
|
init_warnings();
|
|
4786
|
-
import { mkdir as
|
|
4925
|
+
import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
|
|
4787
4926
|
import { homedir as homedir5 } from "node:os";
|
|
4788
4927
|
import path12 from "node:path";
|
|
4789
4928
|
import { fileURLToPath } from "node:url";
|
|
@@ -4884,7 +5023,7 @@ init_version();
|
|
|
4884
5023
|
init_pairing();
|
|
4885
5024
|
import { spawn as spawn2 } from "node:child_process";
|
|
4886
5025
|
import { parseArgs } from "node:util";
|
|
4887
|
-
import { z as
|
|
5026
|
+
import { z as z19 } from "zod";
|
|
4888
5027
|
|
|
4889
5028
|
// src/terminal.ts
|
|
4890
5029
|
init_pairing();
|
|
@@ -5365,10 +5504,10 @@ function startTerminal(dataDir, publicUrl, tunnel) {
|
|
|
5365
5504
|
|
|
5366
5505
|
// src/cli.ts
|
|
5367
5506
|
init_cli_output();
|
|
5368
|
-
var ConfigSchema =
|
|
5369
|
-
dataDir:
|
|
5370
|
-
projectsDir:
|
|
5371
|
-
publicUrl:
|
|
5507
|
+
var ConfigSchema = z19.object({
|
|
5508
|
+
dataDir: z19.string(),
|
|
5509
|
+
projectsDir: z19.string(),
|
|
5510
|
+
publicUrl: z19.string().url().optional()
|
|
5372
5511
|
});
|
|
5373
5512
|
function parseCommandArgs() {
|
|
5374
5513
|
return parseArgs({
|
|
@@ -5402,8 +5541,8 @@ async function main() {
|
|
|
5402
5541
|
const filename = path12.join(dataDir, "config.json");
|
|
5403
5542
|
const config = ConfigSchema.parse(
|
|
5404
5543
|
JSON.parse(
|
|
5405
|
-
await
|
|
5406
|
-
if (!
|
|
5544
|
+
await readFile6(filename, "utf8").catch((error) => {
|
|
5545
|
+
if (!z19.object({ code: z19.literal("ENOENT") }).safeParse(error).success) throw error;
|
|
5407
5546
|
return JSON.stringify({ dataDir, projectsDir: path12.join(homedir5(), "projects") });
|
|
5408
5547
|
})
|
|
5409
5548
|
)
|
|
@@ -5421,11 +5560,11 @@ async function main() {
|
|
|
5421
5560
|
throw new Error("Unknown command. Run agentrific --help.");
|
|
5422
5561
|
}
|
|
5423
5562
|
async function initCommand({ values, dataDir, filename, config }) {
|
|
5424
|
-
await
|
|
5563
|
+
await mkdir6(dataDir, { recursive: true, mode: 448 });
|
|
5425
5564
|
const publicUrl = values["public-url"] ?? config.publicUrl;
|
|
5426
5565
|
if (publicUrl && new URL(publicUrl).protocol !== "https:")
|
|
5427
5566
|
throw new Error("Configure an HTTPS URL from Tailscale Serve or Cloudflare Tunnel.");
|
|
5428
|
-
await
|
|
5567
|
+
await writeFile5(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
|
|
5429
5568
|
mode: 384
|
|
5430
5569
|
});
|
|
5431
5570
|
console.log(`Configuration saved to ${filename}. Start with agentrific start.`);
|
|
@@ -5453,8 +5592,8 @@ async function startCommand({ values, dataDir, filename, config }) {
|
|
|
5453
5592
|
const publicUrl = connection.url;
|
|
5454
5593
|
try {
|
|
5455
5594
|
if (connection.remember && publicUrl) {
|
|
5456
|
-
await
|
|
5457
|
-
await
|
|
5595
|
+
await mkdir6(dataDir, { recursive: true, mode: 448 });
|
|
5596
|
+
await writeFile5(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
|
|
5458
5597
|
mode: 384
|
|
5459
5598
|
});
|
|
5460
5599
|
}
|
|
@@ -5510,11 +5649,11 @@ async function pairCommand({ values, dataDir, config }) {
|
|
|
5510
5649
|
return;
|
|
5511
5650
|
}
|
|
5512
5651
|
async function maintenanceCommand({ values, positionals, dataDir }) {
|
|
5513
|
-
const action =
|
|
5652
|
+
const action = z19.enum(["enter", "resume", "drain"]).parse(positionals[1]);
|
|
5514
5653
|
const result = await controlRequest(dataDir, {
|
|
5515
5654
|
command: "maintenance",
|
|
5516
5655
|
action,
|
|
5517
|
-
timeoutMs:
|
|
5656
|
+
timeoutMs: z19.coerce.number().int().min(0).max(6e4).parse(values["timeout-ms"] ?? 3e4)
|
|
5518
5657
|
});
|
|
5519
5658
|
console.log(JSON.stringify(result, null, 2));
|
|
5520
5659
|
if (action === "drain" && "drained" in result && !result.drained) process.exitCode = 1;
|
|
@@ -5523,7 +5662,7 @@ async function maintenanceCommand({ values, positionals, dataDir }) {
|
|
|
5523
5662
|
async function devicesCommand({ dataDir, command }) {
|
|
5524
5663
|
console.log(
|
|
5525
5664
|
JSON.stringify(
|
|
5526
|
-
await controlRequest(dataDir, { command:
|
|
5665
|
+
await controlRequest(dataDir, { command: z19.enum(["devices", "status"]).parse(command) }),
|
|
5527
5666
|
null,
|
|
5528
5667
|
2
|
|
5529
5668
|
)
|
|
@@ -5531,7 +5670,7 @@ async function devicesCommand({ dataDir, command }) {
|
|
|
5531
5670
|
return;
|
|
5532
5671
|
}
|
|
5533
5672
|
async function revokeCommand({ positionals, dataDir }) {
|
|
5534
|
-
const id =
|
|
5673
|
+
const id = z19.string().uuid().parse(positionals[1]);
|
|
5535
5674
|
console.log(JSON.stringify(await controlRequest(dataDir, { command: "revoke", id })));
|
|
5536
5675
|
return;
|
|
5537
5676
|
}
|