@agentrific/server 0.1.6 → 0.2.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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @agentrific/server
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 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.
8
+
3
9
  ## 0.1.6
4
10
 
5
11
  ### 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.1.6",
33
+ version: "0.2.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 = [
@@ -381,11 +382,18 @@ var init_src = __esm({
381
382
  SendMessageSchema = z2.object({
382
383
  message: z2.string().trim().max(2e4),
383
384
  attachmentIds: AttachmentIdsSchema.optional(),
384
- clientMessageId: z2.string().min(1).max(120).optional()
385
+ clientMessageId: z2.string().min(1).max(120).optional(),
386
+ mode: z2.enum(["followUp", "steer"]).optional()
385
387
  }).refine(
386
388
  (input) => input.message.length > 0 || (input.attachmentIds?.length ?? 0) > 0,
387
389
  "Write a message or attach a file."
388
390
  );
391
+ FileChangeSchema = z2.object({
392
+ path: z2.string(),
393
+ patch: z2.string().optional(),
394
+ content: z2.string().optional(),
395
+ omitted: z2.boolean().optional()
396
+ });
389
397
  SessionEventSchema = z2.discriminatedUnion("type", [
390
398
  z2.object({ type: z2.literal("session.updated"), session: SessionSchema }),
391
399
  z2.object({
@@ -420,7 +428,8 @@ var init_src = __esm({
420
428
  sessionId: z2.string(),
421
429
  toolCallId: z2.string().optional(),
422
430
  tool: z2.string(),
423
- input: z2.string()
431
+ input: z2.string(),
432
+ filePath: z2.string().optional()
424
433
  }),
425
434
  z2.object({
426
435
  type: z2.literal("tool.completed"),
@@ -428,6 +437,7 @@ var init_src = __esm({
428
437
  toolCallId: z2.string().optional(),
429
438
  tool: z2.string(),
430
439
  output: z2.string(),
440
+ fileChange: FileChangeSchema.optional(),
431
441
  isError: z2.boolean().default(false)
432
442
  }),
433
443
  z2.object({
@@ -2125,7 +2135,7 @@ var init_runtime = __esm({
2125
2135
  Effect8.ensuring(Effect8.sync(() => this.working.delete(entry.session.id)))
2126
2136
  );
2127
2137
  }
2128
- send(session, message, attachments2 = [], messageId = randomUUID4()) {
2138
+ send(session, message, attachments2 = [], messageId = randomUUID4(), mode = "followUp") {
2129
2139
  return this.lock(session.id).withPermit(
2130
2140
  Effect8.gen({ self: this }, function* () {
2131
2141
  if (this.closed)
@@ -2135,6 +2145,16 @@ var init_runtime = __esm({
2135
2145
  if (this.hasMessage(session.id, messageId)) return;
2136
2146
  const entry = yield* this.open(session);
2137
2147
  yield* this.operation(async () => entry.agent.validateAttachments?.(attachments2));
2148
+ const steering = entry.running && mode === "steer";
2149
+ if (steering) {
2150
+ if (!entry.agent.steer)
2151
+ return yield* Effect8.fail(
2152
+ new AgentError({ message: "This agent does not support steering." })
2153
+ );
2154
+ yield* this.operation(
2155
+ () => entry.agent.steer(gitFlowPrompt(entry.session, message), attachments2)
2156
+ );
2157
+ }
2138
2158
  this.emit({
2139
2159
  type: "message.user",
2140
2160
  sessionId: session.id,
@@ -2142,6 +2162,7 @@ var init_runtime = __esm({
2142
2162
  text: message,
2143
2163
  attachments: attachments2
2144
2164
  });
2165
+ if (steering) return;
2145
2166
  if (entry.running) {
2146
2167
  entry.pending.push({ text: message, attachments: attachments2 });
2147
2168
  return;
@@ -3116,6 +3137,74 @@ var init_delivery_tool = __esm({
3116
3137
  }
3117
3138
  });
3118
3139
 
3140
+ // src/file-change.ts
3141
+ import { createWriteToolDefinition, defineTool as defineTool2 } from "@earendil-works/pi-coding-agent";
3142
+ import { createTwoFilesPatch } from "diff";
3143
+ import { mkdir as mkdir5, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
3144
+ import { z as z13 } from "zod";
3145
+ function boundFileChange(change) {
3146
+ if ((change.patch?.length ?? 0) + (change.content?.length ?? 0) > previewLimit)
3147
+ return { path: change.path, omitted: true };
3148
+ return change;
3149
+ }
3150
+ function completedFileChange(result, path13) {
3151
+ const parsed = EditResultSchema.safeParse(result);
3152
+ if (!parsed.success) return;
3153
+ const details = parsed.data.details;
3154
+ if (details?.fileChange) return boundFileChange(details.fileChange);
3155
+ if (path13 && details?.patch) return boundFileChange({ path: path13, patch: details.patch });
3156
+ }
3157
+ function fileWriteTool(cwd) {
3158
+ const base = createWriteToolDefinition(cwd);
3159
+ return defineTool2({
3160
+ name: base.name,
3161
+ label: base.label,
3162
+ description: base.description,
3163
+ promptSnippet: base.promptSnippet,
3164
+ promptGuidelines: base.promptGuidelines,
3165
+ parameters: base.parameters,
3166
+ constrainedSampling: base.constrainedSampling,
3167
+ async execute(toolCallId, args, signal, _onUpdate, ctx) {
3168
+ let fileChange;
3169
+ const tool = createWriteToolDefinition(cwd, {
3170
+ operations: {
3171
+ mkdir: async (dir) => {
3172
+ await mkdir5(dir, { recursive: true });
3173
+ },
3174
+ // Pi invokes this inside its per-file mutation queue, so the snapshot
3175
+ // and write cannot race another built-in edit/write on the same file.
3176
+ writeFile: async (path13, content) => {
3177
+ const previous = await readFile4(path13, "utf8").catch((error) => {
3178
+ if (error.code === "ENOENT") return void 0;
3179
+ throw error;
3180
+ });
3181
+ await writeFile3(path13, content, "utf8");
3182
+ fileChange = boundFileChange(
3183
+ previous === void 0 ? { path: args.path, content } : {
3184
+ path: args.path,
3185
+ patch: createTwoFilesPatch(args.path, args.path, previous, content)
3186
+ }
3187
+ );
3188
+ }
3189
+ }
3190
+ });
3191
+ const result = await tool.execute(toolCallId, args, signal, void 0, ctx);
3192
+ return { ...result, details: { fileChange } };
3193
+ }
3194
+ });
3195
+ }
3196
+ var previewLimit, EditResultSchema;
3197
+ var init_file_change = __esm({
3198
+ "src/file-change.ts"() {
3199
+ "use strict";
3200
+ init_src();
3201
+ previewLimit = 256e3;
3202
+ EditResultSchema = z13.object({
3203
+ details: z13.object({ patch: z13.string().optional(), fileChange: FileChangeSchema.optional() }).optional()
3204
+ });
3205
+ }
3206
+ });
3207
+
3119
3208
  // src/pi.ts
3120
3209
  import { Effect as Effect12 } from "effect";
3121
3210
  import { randomUUID as randomUUID5 } from "node:crypto";
@@ -3123,7 +3212,7 @@ import {
3123
3212
  createAgentSession as createAgentSession2,
3124
3213
  SessionManager as SessionManager2
3125
3214
  } from "@earendil-works/pi-coding-agent";
3126
- import { z as z13 } from "zod";
3215
+ import { z as z14 } from "zod";
3127
3216
  function toolOutput(value) {
3128
3217
  const parsed = ToolResultSchema.safeParse(value);
3129
3218
  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 +3227,10 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
3138
3227
  model,
3139
3228
  modelRuntime: models2.runtime,
3140
3229
  sessionManager: SessionManager2.continueRecent(cwd, sessionDirectory),
3141
- customTools: run?.gitFlow && delivery2 ? [deliveryTool(sessionId, delivery2)] : []
3230
+ customTools: [
3231
+ fileWriteTool(cwd),
3232
+ ...run?.gitFlow && delivery2 ? [deliveryTool(sessionId, delivery2)] : []
3233
+ ]
3142
3234
  });
3143
3235
  return {
3144
3236
  get model() {
@@ -3162,6 +3254,12 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
3162
3254
  throw new Error(last.errorMessage ?? "The model request failed");
3163
3255
  }
3164
3256
  },
3257
+ async steer(message, attachments2 = []) {
3258
+ const context = await Effect12.runPromise(files.prepare(attachments2));
3259
+ if (!session.isStreaming)
3260
+ throw new Error("The response has finished. Send this message as a follow-up instead.");
3261
+ await session.steer(message + context.context, context.images);
3262
+ },
3165
3263
  async abort() {
3166
3264
  session.clearQueue();
3167
3265
  await session.abort();
@@ -3226,16 +3324,23 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
3226
3324
  }
3227
3325
  return false;
3228
3326
  }
3327
+ const filePaths = /* @__PURE__ */ new Map();
3328
+ const FileArgsSchema = z14.object({ path: z14.string() });
3229
3329
  function handleTool(event) {
3230
3330
  if (event.type === "tool_execution_start") {
3231
3331
  flushText();
3232
3332
  setActivity("thinking");
3333
+ if (event.toolName === "edit" || event.toolName === "write") {
3334
+ const args = FileArgsSchema.safeParse(event.args);
3335
+ if (args.success) filePaths.set(event.toolCallId, args.data.path);
3336
+ }
3233
3337
  emit({
3234
3338
  type: "tool.started",
3235
3339
  sessionId,
3236
3340
  toolCallId: event.toolCallId,
3237
3341
  tool: event.toolName,
3238
- input: boundedJson(event.args, 1200)
3342
+ input: boundedJson(event.args, 1200),
3343
+ filePath: filePaths.get(event.toolCallId)
3239
3344
  });
3240
3345
  return true;
3241
3346
  }
@@ -3247,8 +3352,10 @@ function createPiFactory(files, models2, delivery2, createSession = createAgentS
3247
3352
  toolCallId: event.toolCallId,
3248
3353
  tool: event.toolName,
3249
3354
  output: toolOutput(event.result),
3355
+ fileChange: event.isError ? void 0 : completedFileChange(event.result, filePaths.get(event.toolCallId)),
3250
3356
  isError: event.isError
3251
3357
  });
3358
+ filePaths.delete(event.toolCallId);
3252
3359
  setActivity("thinking");
3253
3360
  return true;
3254
3361
  }
@@ -3281,8 +3388,9 @@ var init_pi = __esm({
3281
3388
  "use strict";
3282
3389
  init_models();
3283
3390
  init_delivery_tool();
3284
- ToolResultSchema = z13.object({
3285
- content: z13.array(z13.object({ type: z13.string(), text: z13.string().optional() })).optional()
3391
+ init_file_change();
3392
+ ToolResultSchema = z14.object({
3393
+ content: z14.array(z14.object({ type: z14.string(), text: z14.string().optional() })).optional()
3286
3394
  }).passthrough();
3287
3395
  boundedJson = (value, limit) => {
3288
3396
  let text;
@@ -3297,9 +3405,9 @@ var init_pi = __esm({
3297
3405
  });
3298
3406
 
3299
3407
  // src/projects.ts
3300
- import { access as access2, readdir as readdir2, realpath, stat as stat3, readFile as readFile4, writeFile as writeFile3 } from "node:fs/promises";
3408
+ import { access as access2, readdir as readdir2, realpath, stat as stat3, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
3301
3409
  import { randomUUID as randomUUID6 } from "node:crypto";
3302
- import { z as z14 } from "zod";
3410
+ import { z as z15 } from "zod";
3303
3411
  import { constants as constants2 } from "node:fs";
3304
3412
  import { homedir as homedir3 } from "node:os";
3305
3413
  import path8 from "node:path";
@@ -3334,7 +3442,7 @@ async function gitDirectory(directory) {
3334
3442
  const location = path8.join(directory, ".git");
3335
3443
  const info = await stat3(location);
3336
3444
  if (info.isDirectory()) return location;
3337
- const contents = await readFile4(location, "utf8");
3445
+ const contents = await readFile5(location, "utf8");
3338
3446
  const match = /^gitdir: (.+)\s*$/m.exec(contents);
3339
3447
  if (!match) throw new Error("Invalid Git worktree metadata.");
3340
3448
  const resolved = await realpath(path8.resolve(directory, match[1].trim()));
@@ -3344,21 +3452,21 @@ async function gitDirectory(directory) {
3344
3452
  async function identity(directory, preferred) {
3345
3453
  const filename = path8.join(await gitDirectory(directory), "agentrific-project-id");
3346
3454
  try {
3347
- return ProjectId.parse(await readFile4(filename, "utf8"));
3455
+ return ProjectId.parse(await readFile5(filename, "utf8"));
3348
3456
  } catch (error) {
3349
- if (!z14.object({ code: z14.literal("ENOENT") }).safeParse(error).success) throw error;
3457
+ if (!z15.object({ code: z15.literal("ENOENT") }).safeParse(error).success) throw error;
3350
3458
  const legacyFilename = path8.join(await gitDirectory(directory), "chatplans-project-id");
3351
- const legacy = await readFile4(legacyFilename, "utf8").catch((cause) => {
3352
- if (!z14.object({ code: z14.literal("ENOENT") }).safeParse(cause).success) throw cause;
3459
+ const legacy = await readFile5(legacyFilename, "utf8").catch((cause) => {
3460
+ if (!z15.object({ code: z15.literal("ENOENT") }).safeParse(cause).success) throw cause;
3353
3461
  return null;
3354
3462
  });
3355
3463
  const id = legacy === null ? preferred ?? randomUUID6() : ProjectId.parse(legacy);
3356
3464
  try {
3357
- await writeFile3(filename, id + "\n", { flag: "wx" });
3465
+ await writeFile4(filename, id + "\n", { flag: "wx" });
3358
3466
  return id;
3359
3467
  } catch (writeError) {
3360
- if (!z14.object({ code: z14.literal("EEXIST") }).safeParse(writeError).success) throw writeError;
3361
- return ProjectId.parse(await readFile4(filename, "utf8"));
3468
+ if (!z15.object({ code: z15.literal("EEXIST") }).safeParse(writeError).success) throw writeError;
3469
+ return ProjectId.parse(await readFile5(filename, "utf8"));
3362
3470
  }
3363
3471
  }
3364
3472
  }
@@ -3378,7 +3486,7 @@ function scanProjects(root, store2) {
3378
3486
  let existing = registered.find((project2) => project2.id === id);
3379
3487
  if (found.some((project2) => project2.id === id) || existing && existing.path !== directory && await stat3(path8.join(existing.path, ".git")).catch(() => null)) {
3380
3488
  id = randomUUID6();
3381
- await writeFile3(
3489
+ await writeFile4(
3382
3490
  path8.join(await gitDirectory(directory), "agentrific-project-id"),
3383
3491
  id + "\n"
3384
3492
  );
@@ -3417,11 +3525,11 @@ function reconnectProject(root, id, input, store2) {
3417
3525
  const filename = path8.join(await gitDirectory(directory), "agentrific-project-id");
3418
3526
  const replacedId = await identity(directory);
3419
3527
  const project = { id, name: path8.basename(directory), path: directory, available: true };
3420
- await writeFile3(filename, id + "\n");
3528
+ await writeFile4(filename, id + "\n");
3421
3529
  try {
3422
3530
  await Effect13.runPromise(store2.relocateProject(project, previous.path, replacedId));
3423
3531
  } catch (error) {
3424
- await writeFile3(filename, replacedId + "\n");
3532
+ await writeFile4(filename, replacedId + "\n");
3425
3533
  throw error;
3426
3534
  }
3427
3535
  return project;
@@ -3434,31 +3542,31 @@ var init_projects = __esm({
3434
3542
  "src/projects.ts"() {
3435
3543
  "use strict";
3436
3544
  locks2 = /* @__PURE__ */ new WeakMap();
3437
- ProjectId = z14.string().trim().min(1).max(255);
3545
+ ProjectId = z15.string().trim().min(1).max(255);
3438
3546
  }
3439
3547
  });
3440
3548
 
3441
3549
  // src/push-schema.ts
3442
- import { z as z15 } from "zod";
3550
+ import { z as z16 } from "zod";
3443
3551
  var PushBindingSchema, PushStateSchema, PendingPushSchema;
3444
3552
  var init_push_schema = __esm({
3445
3553
  "src/push-schema.ts"() {
3446
3554
  "use strict";
3447
3555
  init_push();
3448
- PushBindingSchema = z15.object({
3449
- bindingId: z15.string().uuid(),
3450
- installationId: z15.string().uuid(),
3451
- deviceId: z15.string().uuid()
3556
+ PushBindingSchema = z16.object({
3557
+ bindingId: z16.string().uuid(),
3558
+ installationId: z16.string().uuid(),
3559
+ deviceId: z16.string().uuid()
3452
3560
  });
3453
- PushStateSchema = z15.object({
3454
- bindings: z15.array(PushBindingSchema).max(10),
3455
- error: z15.string().nullable()
3561
+ PushStateSchema = z16.object({
3562
+ bindings: z16.array(PushBindingSchema).max(10),
3563
+ error: z16.string().nullable()
3456
3564
  });
3457
3565
  PendingPushSchema = PushEventSchema.extend({
3458
- remaining: z15.array(PushBindingSchema),
3459
- createdAt: z15.number(),
3460
- attempts: z15.number().int().nonnegative(),
3461
- nextAt: z15.number()
3566
+ remaining: z16.array(PushBindingSchema),
3567
+ createdAt: z16.number(),
3568
+ attempts: z16.number().int().nonnegative(),
3569
+ nextAt: z16.number()
3462
3570
  });
3463
3571
  }
3464
3572
  });
@@ -3468,7 +3576,7 @@ import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "n
3468
3576
  import path9 from "node:path";
3469
3577
  import { DatabaseSync } from "node:sqlite";
3470
3578
  import { Context as Context3, Data as Data3, Effect as Effect14, Layer as Layer3 } from "effect";
3471
- import { z as z16 } from "zod";
3579
+ import { z as z17 } from "zod";
3472
3580
  import { randomUUID as randomUUID7 } from "node:crypto";
3473
3581
  var StorageError, PayloadRow, EventRow, SqliteSessionStore, SessionStore;
3474
3582
  var init_store = __esm({
@@ -3481,8 +3589,8 @@ var init_store = __esm({
3481
3589
  init_src();
3482
3590
  StorageError = class extends Data3.TaggedError("StorageError") {
3483
3591
  };
3484
- PayloadRow = z16.object({ payload: z16.string() });
3485
- EventRow = z16.object({ sequence: z16.number(), recorded_at: z16.string(), payload: z16.string() });
3592
+ PayloadRow = z17.object({ payload: z17.string() });
3593
+ EventRow = z17.object({ sequence: z17.number(), recorded_at: z17.string(), payload: z17.string() });
3486
3594
  SqliteSessionStore = class {
3487
3595
  constructor(db) {
3488
3596
  this.db = db;
@@ -3533,7 +3641,7 @@ var init_store = __esm({
3533
3641
  const row = PayloadRow.parse(
3534
3642
  this.db.prepare("SELECT payload FROM settings WHERE id = 'server-id'").get()
3535
3643
  );
3536
- return z16.string().uuid().parse(JSON.parse(row.payload));
3644
+ return z17.string().uuid().parse(JSON.parse(row.payload));
3537
3645
  });
3538
3646
  }
3539
3647
  saveDevice(device, tokenHash) {
@@ -3621,7 +3729,7 @@ var init_store = __esm({
3621
3729
  `).all();
3622
3730
  const insert = this.db.prepare("INSERT OR IGNORE INTO projects(id, payload) VALUES (?, ?)");
3623
3731
  for (const row of legacy) {
3624
- const id = z16.object({ id: z16.string() }).parse(row).id;
3732
+ const id = z17.object({ id: z17.string() }).parse(row).id;
3625
3733
  insert.run(
3626
3734
  id,
3627
3735
  JSON.stringify({ id, name: id, path: path9.join(root, id), available: false })
@@ -3831,7 +3939,7 @@ var init_store = __esm({
3831
3939
  if (event.type === "message.user") this.claimAttachments(event.attachments ?? []);
3832
3940
  const result = this.db.prepare("INSERT INTO events(session_id, recorded_at, payload) VALUES (?, ?, ?)").run(sessionId, recordedAt, JSON.stringify(event));
3833
3941
  if (notification) {
3834
- const count = z16.object({ total: z16.number() }).parse(this.db.prepare("SELECT COUNT(*) AS total FROM push_outbox").get());
3942
+ const count = z17.object({ total: z17.number() }).parse(this.db.prepare("SELECT COUNT(*) AS total FROM push_outbox").get());
3835
3943
  if (count.total >= 1e3)
3836
3944
  this.db.prepare(
3837
3945
  "DELETE FROM push_outbox WHERE rowid IN (SELECT rowid FROM push_outbox ORDER BY rowid LIMIT 1)"
@@ -3883,7 +3991,7 @@ var init_store = __esm({
3883
3991
  for (const suffix of ["-wal", "-shm"])
3884
3992
  if (existsSync2(filename + suffix)) chmodSync(filename + suffix, 384);
3885
3993
  try {
3886
- const schema = z16.object({ user_version: z16.number() }).parse(db.prepare("PRAGMA user_version").get());
3994
+ const schema = z17.object({ user_version: z17.number() }).parse(db.prepare("PRAGMA user_version").get());
3887
3995
  if (schema.user_version > 1)
3888
3996
  throw new Error("This database requires a newer Agentrific server.");
3889
3997
  db.exec(`
@@ -4004,7 +4112,7 @@ import { homedir as homedir4 } from "node:os";
4004
4112
  import path11 from "node:path";
4005
4113
  import { WebSocket } from "ws";
4006
4114
  import { Effect as Effect16, ManagedRuntime } from "effect";
4007
- import { z as z17 } from "zod";
4115
+ import { z as z18 } from "zod";
4008
4116
  import { ModelRuntime } from "@earendil-works/pi-coding-agent";
4009
4117
  function publish(event, workflowUpdate = false) {
4010
4118
  const previous = event.type === "session.updated" ? Effect16.runSync(store.get(event.session.id)) : void 0;
@@ -4495,7 +4603,7 @@ var init_index = __esm({
4495
4603
  return project ?? reply.code(404).send({ error: "Project not found on this server." });
4496
4604
  });
4497
4605
  app.post("/api/projects/:id/reconnect", async (request, reply) => {
4498
- const parsed = z17.object({ path: z17.string().trim().min(1).max(4096) }).safeParse(request.body);
4606
+ const parsed = z18.object({ path: z18.string().trim().min(1).max(4096) }).safeParse(request.body);
4499
4607
  if (!parsed.success)
4500
4608
  return reply.code(400).send({ error: "Enter the project's new folder path." });
4501
4609
  const root = await Effect16.runPromise(store.projectsRoot(defaultProjectsRoot));
@@ -4627,9 +4735,9 @@ var init_index = __esm({
4627
4735
  }
4628
4736
  })
4629
4737
  );
4630
- HistoryQuery = z17.object({
4631
- after: z17.coerce.number().int().nonnegative().default(0),
4632
- limit: z17.coerce.number().int().min(1).max(1e3).default(500)
4738
+ HistoryQuery = z18.object({
4739
+ after: z18.coerce.number().int().nonnegative().default(0),
4740
+ limit: z18.coerce.number().int().min(1).max(1e3).default(500)
4633
4741
  });
4634
4742
  app.get("/api/sessions/:id/events", async (request, reply) => {
4635
4743
  const query = HistoryQuery.safeParse(request.query);
@@ -4648,7 +4756,7 @@ var init_index = __esm({
4648
4756
  if (!result.session) return reply.code(result.status).send({ error: result.error });
4649
4757
  return reply.code(201).send(result.session);
4650
4758
  });
4651
- StartTaskBody = z17.object({ prompt: z17.string().trim().max(2e4).optional() }).default({});
4759
+ StartTaskBody = z18.object({ prompt: z18.string().trim().max(2e4).optional() }).default({});
4652
4760
  app.post("/api/tasks/:id/start", async (request, reply) => {
4653
4761
  const body = StartTaskBody.safeParse(request.body ?? {});
4654
4762
  if (!body.success) return reply.code(400).send({ error: "The task prompt is too long." });
@@ -4683,7 +4791,7 @@ var init_index = __esm({
4683
4791
  return automation.configure(parsed.data);
4684
4792
  });
4685
4793
  app.patch("/api/automation/queue", async (request, reply) => {
4686
- const parsed = z17.object({ taskId: z17.string().min(1), beforeId: z17.string().min(1).nullable() }).safeParse(request.body);
4794
+ const parsed = z18.object({ taskId: z18.string().min(1), beforeId: z18.string().min(1).nullable() }).safeParse(request.body);
4687
4795
  if (!parsed.success)
4688
4796
  return reply.code(400).send({ error: "Choose a task and a queue position." });
4689
4797
  try {
@@ -4713,7 +4821,13 @@ var init_index = __esm({
4713
4821
  attachments.resolve(session.projectId, parsed.data.attachmentIds ?? [])
4714
4822
  );
4715
4823
  await Effect16.runPromise(
4716
- runtime.send(session, parsed.data.message, files, parsed.data.clientMessageId)
4824
+ runtime.send(
4825
+ session,
4826
+ parsed.data.message,
4827
+ files,
4828
+ parsed.data.clientMessageId,
4829
+ parsed.data.mode
4830
+ )
4717
4831
  );
4718
4832
  return reply.code(202).send({ accepted: true });
4719
4833
  } catch (error) {
@@ -4783,7 +4897,7 @@ var init_index = __esm({
4783
4897
 
4784
4898
  // src/cli.ts
4785
4899
  init_warnings();
4786
- import { mkdir as mkdir5, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
4900
+ import { mkdir as mkdir6, readFile as readFile6, writeFile as writeFile5 } from "node:fs/promises";
4787
4901
  import { homedir as homedir5 } from "node:os";
4788
4902
  import path12 from "node:path";
4789
4903
  import { fileURLToPath } from "node:url";
@@ -4884,7 +4998,7 @@ init_version();
4884
4998
  init_pairing();
4885
4999
  import { spawn as spawn2 } from "node:child_process";
4886
5000
  import { parseArgs } from "node:util";
4887
- import { z as z18 } from "zod";
5001
+ import { z as z19 } from "zod";
4888
5002
 
4889
5003
  // src/terminal.ts
4890
5004
  init_pairing();
@@ -5365,10 +5479,10 @@ function startTerminal(dataDir, publicUrl, tunnel) {
5365
5479
 
5366
5480
  // src/cli.ts
5367
5481
  init_cli_output();
5368
- var ConfigSchema = z18.object({
5369
- dataDir: z18.string(),
5370
- projectsDir: z18.string(),
5371
- publicUrl: z18.string().url().optional()
5482
+ var ConfigSchema = z19.object({
5483
+ dataDir: z19.string(),
5484
+ projectsDir: z19.string(),
5485
+ publicUrl: z19.string().url().optional()
5372
5486
  });
5373
5487
  function parseCommandArgs() {
5374
5488
  return parseArgs({
@@ -5402,8 +5516,8 @@ async function main() {
5402
5516
  const filename = path12.join(dataDir, "config.json");
5403
5517
  const config = ConfigSchema.parse(
5404
5518
  JSON.parse(
5405
- await readFile5(filename, "utf8").catch((error) => {
5406
- if (!z18.object({ code: z18.literal("ENOENT") }).safeParse(error).success) throw error;
5519
+ await readFile6(filename, "utf8").catch((error) => {
5520
+ if (!z19.object({ code: z19.literal("ENOENT") }).safeParse(error).success) throw error;
5407
5521
  return JSON.stringify({ dataDir, projectsDir: path12.join(homedir5(), "projects") });
5408
5522
  })
5409
5523
  )
@@ -5421,11 +5535,11 @@ async function main() {
5421
5535
  throw new Error("Unknown command. Run agentrific --help.");
5422
5536
  }
5423
5537
  async function initCommand({ values, dataDir, filename, config }) {
5424
- await mkdir5(dataDir, { recursive: true, mode: 448 });
5538
+ await mkdir6(dataDir, { recursive: true, mode: 448 });
5425
5539
  const publicUrl = values["public-url"] ?? config.publicUrl;
5426
5540
  if (publicUrl && new URL(publicUrl).protocol !== "https:")
5427
5541
  throw new Error("Configure an HTTPS URL from Tailscale Serve or Cloudflare Tunnel.");
5428
- await writeFile4(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
5542
+ await writeFile5(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
5429
5543
  mode: 384
5430
5544
  });
5431
5545
  console.log(`Configuration saved to ${filename}. Start with agentrific start.`);
@@ -5453,8 +5567,8 @@ async function startCommand({ values, dataDir, filename, config }) {
5453
5567
  const publicUrl = connection.url;
5454
5568
  try {
5455
5569
  if (connection.remember && publicUrl) {
5456
- await mkdir5(dataDir, { recursive: true, mode: 448 });
5457
- await writeFile4(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
5570
+ await mkdir6(dataDir, { recursive: true, mode: 448 });
5571
+ await writeFile5(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
5458
5572
  mode: 384
5459
5573
  });
5460
5574
  }
@@ -5510,11 +5624,11 @@ async function pairCommand({ values, dataDir, config }) {
5510
5624
  return;
5511
5625
  }
5512
5626
  async function maintenanceCommand({ values, positionals, dataDir }) {
5513
- const action = z18.enum(["enter", "resume", "drain"]).parse(positionals[1]);
5627
+ const action = z19.enum(["enter", "resume", "drain"]).parse(positionals[1]);
5514
5628
  const result = await controlRequest(dataDir, {
5515
5629
  command: "maintenance",
5516
5630
  action,
5517
- timeoutMs: z18.coerce.number().int().min(0).max(6e4).parse(values["timeout-ms"] ?? 3e4)
5631
+ timeoutMs: z19.coerce.number().int().min(0).max(6e4).parse(values["timeout-ms"] ?? 3e4)
5518
5632
  });
5519
5633
  console.log(JSON.stringify(result, null, 2));
5520
5634
  if (action === "drain" && "drained" in result && !result.drained) process.exitCode = 1;
@@ -5523,7 +5637,7 @@ async function maintenanceCommand({ values, positionals, dataDir }) {
5523
5637
  async function devicesCommand({ dataDir, command }) {
5524
5638
  console.log(
5525
5639
  JSON.stringify(
5526
- await controlRequest(dataDir, { command: z18.enum(["devices", "status"]).parse(command) }),
5640
+ await controlRequest(dataDir, { command: z19.enum(["devices", "status"]).parse(command) }),
5527
5641
  null,
5528
5642
  2
5529
5643
  )
@@ -5531,7 +5645,7 @@ async function devicesCommand({ dataDir, command }) {
5531
5645
  return;
5532
5646
  }
5533
5647
  async function revokeCommand({ positionals, dataDir }) {
5534
- const id = z18.string().uuid().parse(positionals[1]);
5648
+ const id = z19.string().uuid().parse(positionals[1]);
5535
5649
  console.log(JSON.stringify(await controlRequest(dataDir, { command: "revoke", id })));
5536
5650
  return;
5537
5651
  }