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