@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 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.1.6",
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: agent.subscribe(this.emit),
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 z13 } from "zod";
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: run?.gitFlow && delivery2 ? [deliveryTool(sessionId, delivery2)] : []
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
- input: boundedJson(event.args, 1200)
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
- ToolResultSchema = z13.object({
3285
- content: z13.array(z13.object({ type: z13.string(), text: z13.string().optional() })).optional()
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 readFile4, writeFile as writeFile3 } from "node:fs/promises";
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 z14 } from "zod";
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 readFile4(location, "utf8");
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 readFile4(filename, "utf8"));
3479
+ return ProjectId.parse(await readFile5(filename, "utf8"));
3348
3480
  } catch (error) {
3349
- if (!z14.object({ code: z14.literal("ENOENT") }).safeParse(error).success) throw error;
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 readFile4(legacyFilename, "utf8").catch((cause) => {
3352
- if (!z14.object({ code: z14.literal("ENOENT") }).safeParse(cause).success) throw cause;
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 writeFile3(filename, id + "\n", { flag: "wx" });
3489
+ await writeFile4(filename, id + "\n", { flag: "wx" });
3358
3490
  return id;
3359
3491
  } catch (writeError) {
3360
- if (!z14.object({ code: z14.literal("EEXIST") }).safeParse(writeError).success) throw writeError;
3361
- return ProjectId.parse(await readFile4(filename, "utf8"));
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 writeFile3(
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 writeFile3(filename, id + "\n");
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 writeFile3(filename, replacedId + "\n");
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 = z14.string().trim().min(1).max(255);
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 z15 } from "zod";
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 = z15.object({
3449
- bindingId: z15.string().uuid(),
3450
- installationId: z15.string().uuid(),
3451
- deviceId: z15.string().uuid()
3580
+ PushBindingSchema = z16.object({
3581
+ bindingId: z16.string().uuid(),
3582
+ installationId: z16.string().uuid(),
3583
+ deviceId: z16.string().uuid()
3452
3584
  });
3453
- PushStateSchema = z15.object({
3454
- bindings: z15.array(PushBindingSchema).max(10),
3455
- error: z15.string().nullable()
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: z15.array(PushBindingSchema),
3459
- createdAt: z15.number(),
3460
- attempts: z15.number().int().nonnegative(),
3461
- nextAt: z15.number()
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 z16 } from "zod";
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 = z16.object({ payload: z16.string() });
3485
- EventRow = z16.object({ sequence: z16.number(), recorded_at: z16.string(), payload: z16.string() });
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 z16.string().uuid().parse(JSON.parse(row.payload));
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 = z16.object({ id: z16.string() }).parse(row).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 = z16.object({ total: z16.number() }).parse(this.db.prepare("SELECT COUNT(*) AS total FROM push_outbox").get());
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 = z16.object({ user_version: z16.number() }).parse(db.prepare("PRAGMA user_version").get());
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 z17 } from "zod";
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 = z17.object({ path: z17.string().trim().min(1).max(4096) }).safeParse(request.body);
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 = z17.object({
4631
- after: z17.coerce.number().int().nonnegative().default(0),
4632
- limit: z17.coerce.number().int().min(1).max(1e3).default(500)
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 = z17.object({ prompt: z17.string().trim().max(2e4).optional() }).default({});
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 = z17.object({ taskId: z17.string().min(1), beforeId: z17.string().min(1).nullable() }).safeParse(request.body);
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(session, parsed.data.message, files, parsed.data.clientMessageId)
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 mkdir5, readFile as readFile5, writeFile as writeFile4 } from "node:fs/promises";
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 z18 } from "zod";
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 = z18.object({
5369
- dataDir: z18.string(),
5370
- projectsDir: z18.string(),
5371
- publicUrl: z18.string().url().optional()
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 readFile5(filename, "utf8").catch((error) => {
5406
- if (!z18.object({ code: z18.literal("ENOENT") }).safeParse(error).success) throw error;
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 mkdir5(dataDir, { recursive: true, mode: 448 });
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 writeFile4(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
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 mkdir5(dataDir, { recursive: true, mode: 448 });
5457
- await writeFile4(filename, JSON.stringify({ ...config, publicUrl }, null, 2) + "\n", {
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 = z18.enum(["enter", "resume", "drain"]).parse(positionals[1]);
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: z18.coerce.number().int().min(0).max(6e4).parse(values["timeout-ms"] ?? 3e4)
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: z18.enum(["devices", "status"]).parse(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 = z18.string().uuid().parse(positionals[1]);
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
  }