@hachej/boring-agent 0.1.74 → 0.1.76

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.
@@ -7,8 +7,8 @@ import {
7
7
  createAgent,
8
8
  createAgentRuntimeBridge,
9
9
  normalizeMeteringUsage
10
- } from "../chunk-AQVEZYLH.js";
11
- import "../chunk-WSQ5QNIY.js";
10
+ } from "../chunk-FRFCVP3X.js";
11
+ import "../chunk-ZUEITFIJ.js";
12
12
  import {
13
13
  FollowUpPayloadSchema,
14
14
  InterruptPayloadSchema,
@@ -16,7 +16,7 @@ import {
16
16
  PromptPayloadSchema,
17
17
  QueueClearPayloadSchema,
18
18
  StopPayloadSchema
19
- } from "../chunk-4LXA7OOV.js";
19
+ } from "../chunk-PMREGRGD.js";
20
20
  import {
21
21
  PI_PACKAGE_RESOURCE_FILTERS,
22
22
  PiSessionStore,
@@ -31,7 +31,7 @@ import {
31
31
  registerConfiguredModelProviders,
32
32
  setEnvDefault,
33
33
  withPiHarnessDefaults
34
- } from "../chunk-FDGWCMJD.js";
34
+ } from "../chunk-OABGJVSA.js";
35
35
  import {
36
36
  safeCapture
37
37
  } from "../chunk-AQBXNPMD.js";
@@ -40,7 +40,7 @@ import {
40
40
  } from "../chunk-AJZHR626.js";
41
41
  import {
42
42
  ErrorCode
43
- } from "../chunk-XZKU7FBV.js";
43
+ } from "../chunk-M4AY4VBI.js";
44
44
 
45
45
  // src/server/sandbox/direct/createDirectSandbox.ts
46
46
  import { spawn } from "child_process";
@@ -180,7 +180,7 @@ function createDirectSandbox(opts = {}) {
180
180
  const maxOutputBytes = opts2?.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
181
181
  const workspaceRoot = runtimeContext.runtimeCwd;
182
182
  const cwd = opts2?.cwd ?? workspaceRoot;
183
- return await new Promise((resolve10, reject) => {
183
+ return await new Promise((resolve11, reject) => {
184
184
  const child = spawn(cmd, {
185
185
  cwd,
186
186
  env: withWorkspacePythonEnv({ workspaceRoot, env: opts2?.env, preserveHostHome: true }),
@@ -209,7 +209,7 @@ function createDirectSandbox(opts = {}) {
209
209
  if (settled) return;
210
210
  settled = true;
211
211
  cleanup();
212
- resolve10({
212
+ resolve11({
213
213
  stdout: new Uint8Array(Buffer.concat(stdoutChunks)),
214
214
  stderr: new Uint8Array(Buffer.concat(stderrChunks)),
215
215
  exitCode: typeof exitCode === "number" ? exitCode : timedOut ? 124 : 1,
@@ -558,9 +558,9 @@ function createNodeWatcher(root) {
558
558
  }
559
559
  startFsw();
560
560
  if (fsw) {
561
- await new Promise((resolve10) => {
562
- fsw.once("ready", resolve10);
563
- fsw.once("error", () => resolve10());
561
+ await new Promise((resolve11) => {
562
+ fsw.once("ready", resolve11);
563
+ fsw.once("error", () => resolve11());
564
564
  });
565
565
  }
566
566
  if (closed) return { ok: false, reason: "closed" };
@@ -969,7 +969,7 @@ function createBwrapSandbox(opts = {}) {
969
969
  ...withSandboxCwd(baseArgs, sandboxCwd),
970
970
  ...buildCommandArgs(cmd, sandboxOptions.resourceLimits)
971
971
  ];
972
- return await new Promise((resolve10, reject) => {
972
+ return await new Promise((resolve11, reject) => {
973
973
  const child = spawn2("bwrap", args, {
974
974
  env: {
975
975
  ...withWorkspacePythonEnv({ workspaceRoot, env: opts2?.env, sandboxRoot: SANDBOX_HOME2 }),
@@ -999,7 +999,7 @@ function createBwrapSandbox(opts = {}) {
999
999
  if (settled) return;
1000
1000
  settled = true;
1001
1001
  cleanup();
1002
- resolve10({
1002
+ resolve11({
1003
1003
  stdout: new Uint8Array(Buffer.concat(stdoutChunks)),
1004
1004
  stderr: new Uint8Array(Buffer.concat(stderrChunks)),
1005
1005
  exitCode: typeof exitCode === "number" ? exitCode : timedOut ? 124 : 1,
@@ -5242,11 +5242,11 @@ async function buildTarGz(files) {
5242
5242
  }
5243
5243
  chunks.push(Buffer.alloc(1024));
5244
5244
  const tarBuffer = Buffer.concat(chunks);
5245
- return new Promise((resolve10, reject) => {
5245
+ return new Promise((resolve11, reject) => {
5246
5246
  const gzip = createGzip({ level: 6 });
5247
5247
  const gzChunks = [];
5248
5248
  gzip.on("data", (chunk) => gzChunks.push(chunk));
5249
- gzip.on("end", () => resolve10(Buffer.concat(gzChunks)));
5249
+ gzip.on("end", () => resolve11(Buffer.concat(gzChunks)));
5250
5250
  gzip.on("error", reject);
5251
5251
  Readable.from(tarBuffer).pipe(gzip);
5252
5252
  });
@@ -6466,13 +6466,13 @@ function resultTool(result) {
6466
6466
  };
6467
6467
  }
6468
6468
  function deferred() {
6469
- let resolve10;
6469
+ let resolve11;
6470
6470
  let reject;
6471
6471
  const promise = new Promise((res, rej) => {
6472
- resolve10 = res;
6472
+ resolve11 = res;
6473
6473
  reject = rej;
6474
6474
  });
6475
- return { promise, resolve: resolve10, reject };
6475
+ return { promise, resolve: resolve11, reject };
6476
6476
  }
6477
6477
  function errorTool(error) {
6478
6478
  const safe = safeError(error);
@@ -7983,33 +7983,182 @@ function buildHarnessAgentTools(bundle, runtime) {
7983
7983
  return tools;
7984
7984
  }
7985
7985
 
7986
- // src/server/http/routes/health.ts
7987
- function healthRoutes(app, opts, done) {
7988
- const startTime = Date.now();
7989
- app.get("/health", async () => {
7986
+ // src/server/http/sessionChangesTracker.ts
7987
+ var InMemorySessionChangesTracker = class _InMemorySessionChangesTracker {
7988
+ static MAX_CHANGES_PER_SESSION = 1e3;
7989
+ bySession = /* @__PURE__ */ new Map();
7990
+ record(sessionId, change) {
7991
+ const existing = this.bySession.get(sessionId);
7992
+ if (existing) {
7993
+ existing.push(change);
7994
+ if (existing.length > _InMemorySessionChangesTracker.MAX_CHANGES_PER_SESSION) {
7995
+ existing.splice(
7996
+ 0,
7997
+ existing.length - _InMemorySessionChangesTracker.MAX_CHANGES_PER_SESSION
7998
+ );
7999
+ }
8000
+ return;
8001
+ }
8002
+ this.bySession.set(sessionId, [change]);
8003
+ }
8004
+ list(sessionId) {
8005
+ return [...this.bySession.get(sessionId) ?? []];
8006
+ }
8007
+ clear(sessionId) {
8008
+ this.bySession.delete(sessionId);
8009
+ }
8010
+ };
8011
+
8012
+ // src/server/runtime/readyStatus.ts
8013
+ function defaultCapabilities(sandboxReady, harnessReady) {
8014
+ return {
8015
+ chat: { state: harnessReady ? "ready" : "preparing" },
8016
+ workspace: { state: sandboxReady ? "ready" : "preparing" },
8017
+ runtimeDependencies: { state: "ready" }
8018
+ };
8019
+ }
8020
+ var ReadyStatusTracker = class {
8021
+ _sandboxReady;
8022
+ _harnessReady;
8023
+ _degradedReason;
8024
+ _capabilities;
8025
+ subscribers = /* @__PURE__ */ new Set();
8026
+ constructor(opts) {
8027
+ this._sandboxReady = opts?.sandboxReady ?? false;
8028
+ this._harnessReady = opts?.harnessReady ?? false;
8029
+ this._capabilities = {
8030
+ ...defaultCapabilities(this._sandboxReady, this._harnessReady),
8031
+ ...opts?.capabilities ?? {}
8032
+ };
8033
+ }
8034
+ get state() {
8035
+ if (this._degradedReason) return "degraded";
8036
+ return this._sandboxReady && this._harnessReady ? "ready" : "provisioning";
8037
+ }
8038
+ isReady() {
8039
+ return this.state === "ready";
8040
+ }
8041
+ getReadiness() {
7990
8042
  return {
7991
- status: "ok",
7992
- version: opts.version,
7993
- uptime: Math.floor((Date.now() - startTime) / 1e3)
8043
+ sandboxReady: this._sandboxReady,
8044
+ harnessReady: this._harnessReady,
8045
+ capabilities: this.getCapabilities(),
8046
+ degradedReason: this._degradedReason
7994
8047
  };
7995
- });
7996
- app.get("/ready", async (_request, reply) => {
7997
- const state = opts.getReadiness();
7998
- if (state.degradedReason) {
7999
- return reply.code(503).send({
8000
- status: "degraded",
8001
- reason: state.degradedReason
8002
- });
8048
+ }
8049
+ getCapabilities() {
8050
+ return {
8051
+ chat: { ...this._capabilities.chat },
8052
+ workspace: { ...this._capabilities.workspace },
8053
+ runtimeDependencies: { ...this._capabilities.runtimeDependencies }
8054
+ };
8055
+ }
8056
+ updateCapability(name, detail) {
8057
+ this._capabilities = {
8058
+ ...this._capabilities,
8059
+ [name]: { ...detail }
8060
+ };
8061
+ this.emit();
8062
+ }
8063
+ updateRuntimeDependencies(detail) {
8064
+ this.updateCapability("runtimeDependencies", detail);
8065
+ }
8066
+ markSandboxReady() {
8067
+ if (this._sandboxReady) return;
8068
+ this._sandboxReady = true;
8069
+ if (this._capabilities.workspace.state === "preparing") {
8070
+ this._capabilities.workspace = { state: "ready" };
8003
8071
  }
8004
- if (!state.sandboxReady || !state.harnessReady) {
8005
- return reply.code(503).send({
8006
- status: "provisioning",
8007
- retryAfter: 2
8008
- });
8072
+ this.emit();
8073
+ }
8074
+ markHarnessReady() {
8075
+ if (this._harnessReady) return;
8076
+ this._harnessReady = true;
8077
+ if (this._capabilities.chat.state === "preparing") {
8078
+ this._capabilities.chat = { state: "ready" };
8079
+ }
8080
+ this.emit();
8081
+ }
8082
+ markDegraded(reason) {
8083
+ this._degradedReason = reason;
8084
+ this.emit();
8085
+ }
8086
+ clearDegraded() {
8087
+ if (!this._degradedReason) return;
8088
+ this._degradedReason = void 0;
8089
+ this.emit();
8090
+ }
8091
+ subscribe(handler) {
8092
+ this.subscribers.add(handler);
8093
+ handler(this.snapshot());
8094
+ return () => {
8095
+ this.subscribers.delete(handler);
8096
+ };
8097
+ }
8098
+ emit() {
8099
+ const event = this.snapshot();
8100
+ for (const h of this.subscribers) h(event);
8101
+ }
8102
+ snapshot() {
8103
+ return {
8104
+ state: this.state,
8105
+ sandboxReady: this._sandboxReady,
8106
+ harnessReady: this._harnessReady,
8107
+ capabilities: this.getCapabilities(),
8108
+ message: this._degradedReason,
8109
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
8110
+ };
8111
+ }
8112
+ };
8113
+
8114
+ // src/server/runtime/modeReadiness.ts
8115
+ function createRuntimeReadyStatusTracker(modeAdapter, opts) {
8116
+ const readiness = modeAdapter.readiness;
8117
+ const tracker = new ReadyStatusTracker({
8118
+ sandboxReady: readiness?.initialSandboxReady ?? true,
8119
+ harnessReady: opts.harnessReady,
8120
+ capabilities: {
8121
+ ...readiness?.initialWorkspaceReadiness ? { workspace: readiness.initialWorkspaceReadiness } : {},
8122
+ ...opts.capabilities ?? {}
8009
8123
  }
8010
- return { status: "ready" };
8011
8124
  });
8012
- done();
8125
+ readiness?.onTrackerCreated?.(tracker);
8126
+ return tracker;
8127
+ }
8128
+
8129
+ // src/server/tools/pluginDiagnostics.ts
8130
+ function createPluginDiagnosticsTool(deps) {
8131
+ return {
8132
+ name: "plugin_diagnostics",
8133
+ description: [
8134
+ "Return current plugin/skill loading errors plus the diagnostics from the",
8135
+ "last /reload. Call this after asking the user to run /reload \u2014 or whenever a",
8136
+ "plugin or skill you expected does not appear to be loaded \u2014 then fix the",
8137
+ "reported error and ask the user to /reload again. Returns a JSON object with",
8138
+ "lastReloadDiagnostics, resourceDiagnostics, and pluginErrors arrays; an empty",
8139
+ "result means no load errors were detected."
8140
+ ].join(" "),
8141
+ parameters: {
8142
+ type: "object",
8143
+ properties: {},
8144
+ additionalProperties: false
8145
+ },
8146
+ async execute(_params, ctx) {
8147
+ const harness = deps.getHarness();
8148
+ const resourceDiagnostics = harness?.getResourceDiagnostics?.(ctx.sessionId ?? "") ?? [];
8149
+ const pluginErrors = deps.getPluginErrors ? await deps.getPluginErrors() : [];
8150
+ const payload = {
8151
+ lastReloadDiagnostics: deps.getLastReloadDiagnostics(),
8152
+ resourceDiagnostics,
8153
+ pluginErrors
8154
+ };
8155
+ return {
8156
+ content: [{ type: "text", text: JSON.stringify(payload) }],
8157
+ details: payload,
8158
+ isError: false
8159
+ };
8160
+ }
8161
+ };
8013
8162
  }
8014
8163
 
8015
8164
  // src/server/http/fsEventBroadcaster.ts
@@ -8404,12 +8553,20 @@ function modelsRoutes(app, opts, done) {
8404
8553
  }
8405
8554
 
8406
8555
  // src/server/http/routes/skills.ts
8556
+ import { isAbsolute as isAbsolute9, relative as relative13, resolve as resolve10, sep as sep7 } from "path";
8407
8557
  import {
8408
8558
  DefaultPackageManager,
8409
8559
  getAgentDir,
8410
8560
  loadSkills
8411
8561
  } from "@mariozechner/pi-coding-agent";
8412
8562
  var CACHE_TTL_MS2 = 3e4;
8563
+ function pathForWorkspaceEditor(workspaceRoot, filePath) {
8564
+ const pathWithinWorkspace = relative13(resolve10(workspaceRoot), resolve10(filePath));
8565
+ if (pathWithinWorkspace === "" || pathWithinWorkspace === ".." || pathWithinWorkspace.startsWith(`..${sep7}`) || isAbsolute9(pathWithinWorkspace)) {
8566
+ return filePath;
8567
+ }
8568
+ return pathWithinWorkspace.split(sep7).join("/");
8569
+ }
8413
8570
  function skillsRoutes(app, opts, done) {
8414
8571
  const cached = /* @__PURE__ */ new Map();
8415
8572
  async function resolveSkillsForRequest(request, refresh = false) {
@@ -8448,7 +8605,7 @@ function skillsRoutes(app, opts, done) {
8448
8605
  const skills = result.skills.map((s) => ({
8449
8606
  name: String(s.name),
8450
8607
  description: String(s.description ?? ""),
8451
- ...typeof s.filePath === "string" ? { filePath: s.filePath } : {},
8608
+ ...typeof s.filePath === "string" ? { filePath: pathForWorkspaceEditor(workspaceRoot, s.filePath) } : {},
8452
8609
  ...typeof s.sourceInfo?.scope === "string" ? { source: s.sourceInfo.scope } : {}
8453
8610
  }));
8454
8611
  const entry = { skills, expiresAt: now + CACHE_TTL_MS2 };
@@ -8510,7 +8667,7 @@ function piChatRoutes(app, opts, done) {
8510
8667
  try {
8511
8668
  const service = await resolveService(opts, request);
8512
8669
  if (!service.listSessions) throw unsupportedServiceMethod("list Pi chat sessions");
8513
- return reply.send(await service.listSessions(getRequestContext(request), sessionListOptions(request)));
8670
+ return reply.send(await service.listSessions(getRequestContext(request, opts), sessionListOptions(request)));
8514
8671
  } catch (err) {
8515
8672
  return sendRouteError(reply, err, "list pi chat sessions failed");
8516
8673
  }
@@ -8521,7 +8678,7 @@ function piChatRoutes(app, opts, done) {
8521
8678
  try {
8522
8679
  const service = await resolveService(opts, request);
8523
8680
  if (!service.createSession) throw unsupportedServiceMethod("create Pi chat session");
8524
- return reply.code(201).send(await service.createSession(getRequestContext(request), body));
8681
+ return reply.code(201).send(await service.createSession(getRequestContext(request, opts), body));
8525
8682
  } catch (err) {
8526
8683
  return sendRouteError(reply, err, "create pi chat session failed");
8527
8684
  }
@@ -8532,7 +8689,7 @@ function piChatRoutes(app, opts, done) {
8532
8689
  try {
8533
8690
  const service = await resolveService(opts, request);
8534
8691
  if (!service.deleteSession) throw unsupportedServiceMethod("delete Pi chat session");
8535
- await service.deleteSession(getRequestContext(request), params.sessionId);
8692
+ await service.deleteSession(getRequestContext(request, opts), params.sessionId);
8536
8693
  return reply.code(204).send();
8537
8694
  } catch (err) {
8538
8695
  return sendRouteError(reply, err, "delete pi chat session failed");
@@ -8543,7 +8700,7 @@ function piChatRoutes(app, opts, done) {
8543
8700
  if (!params) return;
8544
8701
  try {
8545
8702
  const service = await resolveService(opts, request);
8546
- const snapshot = await service.readState(getRequestContext(request), params.sessionId);
8703
+ const snapshot = await service.readState(getRequestContext(request, opts), params.sessionId);
8547
8704
  return reply.send(PiChatSnapshotSchema.parse(snapshot));
8548
8705
  } catch (err) {
8549
8706
  return sendRouteError(reply, err, "read pi chat state failed");
@@ -8564,7 +8721,7 @@ function piChatRoutes(app, opts, done) {
8564
8721
  };
8565
8722
  try {
8566
8723
  const service = await resolveService(opts, request);
8567
- const result = await service.subscribe(getRequestContext(request), params.sessionId, query.cursor, writeFrame);
8724
+ const result = await service.subscribe(getRequestContext(request, opts), params.sessionId, query.cursor, writeFrame);
8568
8725
  if (result.type !== "ok") {
8569
8726
  return sendReplayRangeError(reply, result);
8570
8727
  }
@@ -8598,7 +8755,7 @@ function piChatRoutes(app, opts, done) {
8598
8755
  if (!body) return;
8599
8756
  try {
8600
8757
  const service = await resolveService(opts, request);
8601
- const receipt = await service.prompt(getRequestContext(request), params.sessionId, body);
8758
+ const receipt = await service.prompt(getRequestContext(request, opts), params.sessionId, body);
8602
8759
  return reply.code(202).send(receipt);
8603
8760
  } catch (err) {
8604
8761
  return sendRouteError(reply, err, "prompt rejected");
@@ -8611,7 +8768,7 @@ function piChatRoutes(app, opts, done) {
8611
8768
  if (!body) return;
8612
8769
  try {
8613
8770
  const service = await resolveService(opts, request);
8614
- const receipt = await service.followUp(getRequestContext(request), params.sessionId, body);
8771
+ const receipt = await service.followUp(getRequestContext(request, opts), params.sessionId, body);
8615
8772
  return reply.code(202).send(receipt);
8616
8773
  } catch (err) {
8617
8774
  return sendRouteError(reply, err, "follow-up rejected");
@@ -8624,7 +8781,7 @@ function piChatRoutes(app, opts, done) {
8624
8781
  if (!body) return;
8625
8782
  try {
8626
8783
  const service = await resolveService(opts, request);
8627
- const receipt = await service.clearQueue(getRequestContext(request), params.sessionId, body);
8784
+ const receipt = await service.clearQueue(getRequestContext(request, opts), params.sessionId, body);
8628
8785
  return reply.code(202).send(receipt);
8629
8786
  } catch (err) {
8630
8787
  return sendRouteError(reply, err, "queue clear rejected");
@@ -8637,7 +8794,7 @@ function piChatRoutes(app, opts, done) {
8637
8794
  if (!body) return;
8638
8795
  try {
8639
8796
  const service = await resolveService(opts, request);
8640
- const receipt = await service.interrupt(getRequestContext(request), params.sessionId, body);
8797
+ const receipt = await service.interrupt(getRequestContext(request, opts), params.sessionId, body);
8641
8798
  return reply.code(202).send(receipt);
8642
8799
  } catch (err) {
8643
8800
  return sendRouteError(reply, err, "interrupt rejected");
@@ -8650,7 +8807,7 @@ function piChatRoutes(app, opts, done) {
8650
8807
  if (!body) return;
8651
8808
  try {
8652
8809
  const service = await resolveService(opts, request);
8653
- const receipt = await service.stop(getRequestContext(request), params.sessionId, body);
8810
+ const receipt = await service.stop(getRequestContext(request, opts), params.sessionId, body);
8654
8811
  return reply.code(202).send(receipt);
8655
8812
  } catch (err) {
8656
8813
  return sendRouteError(reply, err, "stop rejected");
@@ -8710,13 +8867,14 @@ async function resolveService(opts, request) {
8710
8867
  }
8711
8868
  return service;
8712
8869
  }
8713
- function getRequestContext(request) {
8870
+ function getRequestContext(request, opts) {
8714
8871
  const storageScopeHeader = request.headers["x-boring-storage-scope"];
8715
8872
  const user = request.user;
8716
8873
  const authSubject = user?.id;
8717
8874
  const authEmail = user?.email;
8875
+ const workspaceId = opts.defaultWorkspaceId === false ? void 0 : request.workspaceContext?.workspaceId ?? opts.defaultWorkspaceId ?? DEFAULT_WORKSPACE_ID2;
8718
8876
  return {
8719
- workspaceId: request.workspaceContext?.workspaceId ?? DEFAULT_WORKSPACE_ID2,
8877
+ workspaceId,
8720
8878
  storageScope: typeof storageScopeHeader === "string" && storageScopeHeader.length > 0 ? storageScopeHeader : void 0,
8721
8879
  authSubject: typeof authSubject === "string" && authSubject.length > 0 ? authSubject : void 0,
8722
8880
  authEmail: typeof authEmail === "string" && authEmail.length > 0 ? authEmail : void 0,
@@ -8809,32 +8967,6 @@ function systemPromptRoutes(app, opts, done) {
8809
8967
  done();
8810
8968
  }
8811
8969
 
8812
- // src/server/http/sessionChangesTracker.ts
8813
- var InMemorySessionChangesTracker = class _InMemorySessionChangesTracker {
8814
- static MAX_CHANGES_PER_SESSION = 1e3;
8815
- bySession = /* @__PURE__ */ new Map();
8816
- record(sessionId, change) {
8817
- const existing = this.bySession.get(sessionId);
8818
- if (existing) {
8819
- existing.push(change);
8820
- if (existing.length > _InMemorySessionChangesTracker.MAX_CHANGES_PER_SESSION) {
8821
- existing.splice(
8822
- 0,
8823
- existing.length - _InMemorySessionChangesTracker.MAX_CHANGES_PER_SESSION
8824
- );
8825
- }
8826
- return;
8827
- }
8828
- this.bySession.set(sessionId, [change]);
8829
- }
8830
- list(sessionId) {
8831
- return [...this.bySession.get(sessionId) ?? []];
8832
- }
8833
- clear(sessionId) {
8834
- this.bySession.delete(sessionId);
8835
- }
8836
- };
8837
-
8838
8970
  // src/server/http/routes/sessionChanges.ts
8839
8971
  function requireSessionId(value, reply) {
8840
8972
  if (typeof value !== "string" || value.length === 0) {
@@ -9163,7 +9295,7 @@ function searchRoutes(app, opts, done) {
9163
9295
  }
9164
9296
 
9165
9297
  // src/server/git/gitFileUrl.ts
9166
- import { dirname as dirname12, relative as relative13 } from "path";
9298
+ import { dirname as dirname12, relative as relative14 } from "path";
9167
9299
  import { execFile as execFile3 } from "child_process";
9168
9300
  import { promisify as promisify3 } from "util";
9169
9301
 
@@ -9246,7 +9378,7 @@ async function resolveGitFileUrl(workspaceRoot, path4) {
9246
9378
  commitSha = null;
9247
9379
  }
9248
9380
  }
9249
- const repoRelativePath = relative13(repoRoot, absolutePath).replace(/\\/g, "/");
9381
+ const repoRelativePath = relative14(repoRoot, absolutePath).replace(/\\/g, "/");
9250
9382
  const url = buildGitFileUrl({ remoteUrl, repoRelativePath, branch, commitSha });
9251
9383
  if (!url) {
9252
9384
  return disabled("Only GitHub SSH/HTTPS remotes are supported right now.");
@@ -9298,167 +9430,136 @@ function gitRoutes(app, opts, done) {
9298
9430
  done();
9299
9431
  }
9300
9432
 
9301
- // src/server/runtime/readyStatus.ts
9302
- function defaultCapabilities(sandboxReady, harnessReady) {
9303
- return {
9304
- chat: { state: harnessReady ? "ready" : "preparing" },
9305
- workspace: { state: sandboxReady ? "ready" : "preparing" },
9306
- runtimeDependencies: { state: "ready" }
9307
- };
9308
- }
9309
- var ReadyStatusTracker = class {
9310
- _sandboxReady;
9311
- _harnessReady;
9312
- _degradedReason;
9313
- _capabilities;
9314
- subscribers = /* @__PURE__ */ new Set();
9315
- constructor(opts) {
9316
- this._sandboxReady = opts?.sandboxReady ?? false;
9317
- this._harnessReady = opts?.harnessReady ?? false;
9318
- this._capabilities = {
9319
- ...defaultCapabilities(this._sandboxReady, this._harnessReady),
9320
- ...opts?.capabilities ?? {}
9321
- };
9322
- }
9323
- get state() {
9324
- if (this._degradedReason) return "degraded";
9325
- return this._sandboxReady && this._harnessReady ? "ready" : "provisioning";
9326
- }
9327
- isReady() {
9328
- return this.state === "ready";
9329
- }
9330
- getReadiness() {
9433
+ // src/server/http/routes/health.ts
9434
+ function healthRoutes(app, opts, done) {
9435
+ const startTime = Date.now();
9436
+ app.get("/health", async () => {
9331
9437
  return {
9332
- sandboxReady: this._sandboxReady,
9333
- harnessReady: this._harnessReady,
9334
- capabilities: this.getCapabilities(),
9335
- degradedReason: this._degradedReason
9336
- };
9337
- }
9338
- getCapabilities() {
9339
- return {
9340
- chat: { ...this._capabilities.chat },
9341
- workspace: { ...this._capabilities.workspace },
9342
- runtimeDependencies: { ...this._capabilities.runtimeDependencies }
9343
- };
9344
- }
9345
- updateCapability(name, detail) {
9346
- this._capabilities = {
9347
- ...this._capabilities,
9348
- [name]: { ...detail }
9438
+ status: "ok",
9439
+ version: opts.version,
9440
+ uptime: Math.floor((Date.now() - startTime) / 1e3)
9349
9441
  };
9350
- this.emit();
9351
- }
9352
- updateRuntimeDependencies(detail) {
9353
- this.updateCapability("runtimeDependencies", detail);
9354
- }
9355
- markSandboxReady() {
9356
- if (this._sandboxReady) return;
9357
- this._sandboxReady = true;
9358
- if (this._capabilities.workspace.state === "preparing") {
9359
- this._capabilities.workspace = { state: "ready" };
9360
- }
9361
- this.emit();
9362
- }
9363
- markHarnessReady() {
9364
- if (this._harnessReady) return;
9365
- this._harnessReady = true;
9366
- if (this._capabilities.chat.state === "preparing") {
9367
- this._capabilities.chat = { state: "ready" };
9442
+ });
9443
+ app.get("/ready", async (_request, reply) => {
9444
+ const state = opts.getReadiness();
9445
+ if (state.degradedReason) {
9446
+ return reply.code(503).send({
9447
+ status: "degraded",
9448
+ reason: state.degradedReason
9449
+ });
9368
9450
  }
9369
- this.emit();
9370
- }
9371
- markDegraded(reason) {
9372
- this._degradedReason = reason;
9373
- this.emit();
9374
- }
9375
- clearDegraded() {
9376
- if (!this._degradedReason) return;
9377
- this._degradedReason = void 0;
9378
- this.emit();
9379
- }
9380
- subscribe(handler) {
9381
- this.subscribers.add(handler);
9382
- handler(this.snapshot());
9383
- return () => {
9384
- this.subscribers.delete(handler);
9385
- };
9386
- }
9387
- emit() {
9388
- const event = this.snapshot();
9389
- for (const h of this.subscribers) h(event);
9390
- }
9391
- snapshot() {
9392
- return {
9393
- state: this.state,
9394
- sandboxReady: this._sandboxReady,
9395
- harnessReady: this._harnessReady,
9396
- capabilities: this.getCapabilities(),
9397
- message: this._degradedReason,
9398
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
9399
- };
9400
- }
9401
- };
9402
-
9403
- // src/server/runtime/modeReadiness.ts
9404
- function createRuntimeReadyStatusTracker(modeAdapter, opts) {
9405
- const readiness = modeAdapter.readiness;
9406
- const tracker = new ReadyStatusTracker({
9407
- sandboxReady: readiness?.initialSandboxReady ?? true,
9408
- harnessReady: opts.harnessReady,
9409
- capabilities: {
9410
- ...readiness?.initialWorkspaceReadiness ? { workspace: readiness.initialWorkspaceReadiness } : {},
9411
- ...opts.capabilities ?? {}
9451
+ if (!state.sandboxReady || !state.harnessReady) {
9452
+ return reply.code(503).send({
9453
+ status: "provisioning",
9454
+ retryAfter: 2
9455
+ });
9412
9456
  }
9457
+ return { status: "ready" };
9413
9458
  });
9414
- readiness?.onTrackerCreated?.(tracker);
9415
- return tracker;
9459
+ done();
9416
9460
  }
9417
9461
 
9418
- // src/server/tools/pluginDiagnostics.ts
9419
- function createPluginDiagnosticsTool(deps) {
9420
- return {
9421
- name: "plugin_diagnostics",
9422
- description: [
9423
- "Return current plugin/skill loading errors plus the diagnostics from the",
9424
- "last /reload. Call this after asking the user to run /reload \u2014 or whenever a",
9425
- "plugin or skill you expected does not appear to be loaded \u2014 then fix the",
9426
- "reported error and ask the user to /reload again. Returns a JSON object with",
9427
- "lastReloadDiagnostics, resourceDiagnostics, and pluginErrors arrays; an empty",
9428
- "result means no load errors were detected."
9429
- ].join(" "),
9430
- parameters: {
9431
- type: "object",
9432
- properties: {},
9433
- additionalProperties: false
9434
- },
9435
- async execute(_params, ctx) {
9436
- const harness = deps.getHarness();
9437
- const resourceDiagnostics = harness?.getResourceDiagnostics?.(ctx.sessionId ?? "") ?? [];
9438
- const pluginErrors = deps.getPluginErrors ? await deps.getPluginErrors() : [];
9439
- const payload = {
9440
- lastReloadDiagnostics: deps.getLastReloadDiagnostics(),
9441
- resourceDiagnostics,
9442
- pluginErrors
9443
- };
9444
- return {
9445
- content: [{ type: "text", text: JSON.stringify(payload) }],
9446
- details: payload,
9447
- isError: false
9448
- };
9462
+ // src/server/agentRouteBindingProfile.ts
9463
+ function toolNames(tools) {
9464
+ return tools.map((tool) => tool.name);
9465
+ }
9466
+ async function registerAgentRouteBindingProfile(app, profile) {
9467
+ if (profile.dispose) {
9468
+ app.addHook("onClose", async () => {
9469
+ await profile.dispose?.();
9470
+ });
9471
+ }
9472
+ await profile.beforeRegister?.(app);
9473
+ const { register = true, ...health } = profile.health;
9474
+ if (register) await app.register(healthRoutes, health);
9475
+ if (profile.filesystem) {
9476
+ await app.register(fileRoutes, profile.filesystem.file);
9477
+ await app.register(fsEventsRoutes, profile.filesystem.fsEvents);
9478
+ await app.register(treeRoutes, profile.filesystem.tree);
9479
+ await app.register(searchRoutes, profile.filesystem.search);
9480
+ await app.register(gitRoutes, profile.filesystem.git);
9481
+ }
9482
+ await app.register(piChatRoutes, profile.chat);
9483
+ if (profile.systemPrompt) await app.register(systemPromptRoutes, profile.systemPrompt);
9484
+ await app.register(modelsRoutes, profile.models ?? {});
9485
+ if (profile.skills) await app.register(skillsRoutes, profile.skills);
9486
+ await app.register(sessionChangesRoutes, { tracker: profile.sessionChangesTracker });
9487
+ if (profile.reload) {
9488
+ if (typeof profile.reload === "function") {
9489
+ await profile.reload(app);
9490
+ } else {
9491
+ await app.register(reloadRoutes, profile.reload);
9449
9492
  }
9450
- };
9493
+ }
9494
+ await app.register(catalogRoutes, profile.catalog);
9495
+ if (profile.commands) await app.register(commandsRoutes, profile.commands);
9496
+ await app.register(readyStatusRoutes, profile.readyStatus);
9451
9497
  }
9452
9498
 
9453
9499
  // src/server/createAgentApp.ts
9454
9500
  var DEFAULT_VERSION = "0.1.0-dev";
9455
9501
  var DEFAULT_SESSION_ID = "default";
9502
+ var PURE_RUNTIME_MODE = "none";
9456
9503
  async function createAgentApp(opts = {}) {
9457
- const workspaceRoot = opts.workspaceRoot ?? process.cwd();
9458
9504
  const sessionId = opts.sessionId ?? DEFAULT_SESSION_ID;
9459
- const templatePath = opts.templatePath ?? getEnv("BORING_AGENT_TEMPLATE_PATH");
9460
9505
  const app = Fastify({ logger: opts.logger ?? true, bodyLimit: 16 * 1024 * 1024 });
9461
9506
  const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
9507
+ const profile = !opts.runtimeModeAdapter && resolvedMode === PURE_RUNTIME_MODE ? await createPureAgentAppProfile(opts, resolvedMode) : await createWorkspaceAgentAppProfile(opts, sessionId, resolvedMode, app);
9508
+ app.addHook(
9509
+ "onRequest",
9510
+ createAuthMiddleware({
9511
+ authToken: opts.authToken,
9512
+ publicPaths: ["/health", "/ready", "/api/v1/ready-status"]
9513
+ })
9514
+ );
9515
+ await registerAgentRouteBindingProfile(app, profile);
9516
+ return app;
9517
+ }
9518
+ async function createPureAgentAppProfile(opts, resolvedMode) {
9519
+ const runtimePi = withPiHarnessDefaults(opts.pi);
9520
+ const baseHarnessFactory = opts.harnessFactory ?? ((input) => createPiCodingAgentHarness({
9521
+ ...input,
9522
+ pi: runtimePi
9523
+ }));
9524
+ const harnessFactory = ((input) => baseHarnessFactory({
9525
+ ...input,
9526
+ sessionNamespace: opts.sessionNamespace,
9527
+ sessionRoot: opts.sessionRoot,
9528
+ sessionDir: opts.sessionDir ?? input.sessionDir
9529
+ }));
9530
+ const tools = opts.extraTools ?? [];
9531
+ const coreAgent = createAgentRuntimeBridge({
9532
+ runtime: "none",
9533
+ tools,
9534
+ harnessFactory,
9535
+ systemPromptAppend: opts.systemPromptAppend,
9536
+ systemPromptDynamic: opts.systemPromptDynamic,
9537
+ telemetry: opts.telemetry,
9538
+ metering: opts.metering,
9539
+ sessionStorageRoot: opts.sessionRoot
9540
+ });
9541
+ const agentRuntime = await coreAgent.getRuntime();
9542
+ const readyTracker = new ReadyStatusTracker({ sandboxReady: true, harnessReady: true });
9543
+ return {
9544
+ runtimeMode: resolvedMode,
9545
+ capabilities: { tools: toolNames(tools) },
9546
+ sessionChangesTracker: new InMemorySessionChangesTracker(),
9547
+ health: {
9548
+ version: opts.version ?? DEFAULT_VERSION,
9549
+ getReadiness: () => ({ sandboxReady: true, harnessReady: true })
9550
+ },
9551
+ chat: {
9552
+ service: agentRuntime.service,
9553
+ defaultWorkspaceId: false
9554
+ },
9555
+ catalog: { tools },
9556
+ readyStatus: { tracker: readyTracker },
9557
+ dispose: () => coreAgent.agent.dispose()
9558
+ };
9559
+ }
9560
+ async function createWorkspaceAgentAppProfile(opts, sessionId, resolvedMode, app) {
9561
+ const workspaceRoot = opts.workspaceRoot ?? process.cwd();
9562
+ const templatePath = opts.templatePath ?? getEnv("BORING_AGENT_TEMPLATE_PATH");
9462
9563
  const modeAdapter = opts.runtimeModeAdapter ?? resolveMode(resolvedMode);
9463
9564
  let runtimeBundle = await modeAdapter.create({
9464
9565
  workspaceRoot,
@@ -9536,7 +9637,8 @@ async function createAgentApp(opts = {}) {
9536
9637
  const harnessFactory = ((input) => baseHarnessFactory({
9537
9638
  ...input,
9538
9639
  sessionNamespace: opts.sessionNamespace,
9539
- sessionDir: opts.sessionDir
9640
+ sessionRoot: opts.sessionRoot,
9641
+ sessionDir: opts.sessionDir ?? input.sessionDir
9540
9642
  }));
9541
9643
  const coreAgent = createAgentRuntimeBridge({
9542
9644
  runtime: modeAdapter,
@@ -9546,6 +9648,7 @@ async function createAgentApp(opts = {}) {
9546
9648
  systemPromptDynamic: opts.systemPromptDynamic,
9547
9649
  telemetry: opts.telemetry,
9548
9650
  metering: opts.metering,
9651
+ sessionStorageRoot: opts.sessionRoot,
9549
9652
  workdir: workspaceRoot
9550
9653
  }, {
9551
9654
  service: {
@@ -9556,21 +9659,9 @@ async function createAgentApp(opts = {}) {
9556
9659
  const agentRuntime = await coreAgent.getRuntime();
9557
9660
  const harness = agentRuntime.harness;
9558
9661
  harnessRef = harness;
9559
- const sessionChangesTracker = new InMemorySessionChangesTracker();
9560
9662
  const readyTracker = createRuntimeReadyStatusTracker(modeAdapter, {
9561
9663
  harnessReady: true
9562
9664
  });
9563
- app.addHook(
9564
- "onRequest",
9565
- createAuthMiddleware({
9566
- authToken: opts.authToken,
9567
- publicPaths: ["/health", "/ready", "/api/v1/ready-status"]
9568
- })
9569
- );
9570
- await app.register(healthRoutes, {
9571
- version: opts.version ?? DEFAULT_VERSION,
9572
- getReadiness: () => readyTracker.getReadiness()
9573
- });
9574
9665
  const filesystemBindingsForRequest = opts.getFilesystemBindings ? (request) => {
9575
9666
  const user = request.user;
9576
9667
  return opts.getFilesystemBindings?.({
@@ -9583,46 +9674,65 @@ async function createAgentApp(opts = {}) {
9583
9674
  requestId: request.id
9584
9675
  });
9585
9676
  } : void 0;
9586
- await app.register(fileRoutes, { workspace: runtimeBundle.workspace, getFilesystemBindings: filesystemBindingsForRequest, filesystemBindings: runtimeBundle.filesystemBindings });
9587
- await app.register(fsEventsRoutes, { workspace: runtimeBundle.workspace });
9588
- await app.register(treeRoutes, { workspace: runtimeBundle.workspace, getFilesystemBindings: filesystemBindingsForRequest, filesystemBindings: runtimeBundle.filesystemBindings });
9589
- await app.register(searchRoutes, { fileSearch: runtimeBundle.fileSearch });
9590
- await app.register(gitRoutes, {
9591
- getWorkspaceRoot: () => getOptionalRuntimeBundleStorageRoot(runtimeBundle)
9592
- });
9593
- await app.register(piChatRoutes, {
9594
- service: agentRuntime.service
9595
- });
9596
- await app.register(systemPromptRoutes, { harness });
9597
- await app.register(modelsRoutes);
9598
- await app.register(skillsRoutes, {
9599
- workspaceRoot,
9600
- additionalSkillPaths: runtimePi.additionalSkillPaths,
9601
- piPackages: runtimePi.packages,
9602
- noSkills: runtimePi.noSkills,
9603
- getAdditionalSkillPaths: () => [
9604
- ...getRuntimeProvisioning()?.skillPaths ?? [],
9605
- ...opts.pi?.additionalSkillPaths ?? [],
9606
- ...opts.pi?.getHotReloadableResources?.().additionalSkillPaths ?? []
9607
- ],
9608
- getPiPackages: () => [
9609
- ...opts.pi?.packages ?? [],
9610
- ...opts.pi?.getHotReloadableResources?.().packages ?? []
9611
- ]
9612
- });
9613
- await app.register(sessionChangesRoutes, { tracker: sessionChangesTracker });
9614
- await app.register(catalogRoutes, { tools });
9615
- await app.register(commandsRoutes, { harness, defaultSessionId: sessionId, workdir: runtimeBundle.workspace.root, metering: opts.metering });
9616
- await app.register(reloadRoutes, {
9617
- harness,
9618
- defaultSessionId: sessionId,
9619
- beforeReload: opts.beforeReload,
9620
- onDiagnostics: (diagnostics) => {
9621
- lastReloadDiagnostics = diagnostics;
9622
- }
9623
- });
9624
- await app.register(readyStatusRoutes, { tracker: readyTracker });
9625
- return app;
9677
+ return {
9678
+ runtimeMode: resolvedMode,
9679
+ capabilities: { tools: toolNames(tools) },
9680
+ sessionChangesTracker: new InMemorySessionChangesTracker(),
9681
+ health: {
9682
+ version: opts.version ?? DEFAULT_VERSION,
9683
+ getReadiness: () => readyTracker.getReadiness()
9684
+ },
9685
+ filesystem: {
9686
+ file: {
9687
+ workspace: runtimeBundle.workspace,
9688
+ getFilesystemBindings: filesystemBindingsForRequest,
9689
+ filesystemBindings: runtimeBundle.filesystemBindings
9690
+ },
9691
+ fsEvents: { workspace: runtimeBundle.workspace },
9692
+ tree: {
9693
+ workspace: runtimeBundle.workspace,
9694
+ getFilesystemBindings: filesystemBindingsForRequest,
9695
+ filesystemBindings: runtimeBundle.filesystemBindings
9696
+ },
9697
+ // File search shares the same bound implementation as the model tool.
9698
+ search: { fileSearch: runtimeBundle.fileSearch },
9699
+ // Git metadata resolves against host storage, not a sandbox-internal cwd.
9700
+ git: { getWorkspaceRoot: () => getOptionalRuntimeBundleStorageRoot(runtimeBundle) }
9701
+ },
9702
+ chat: { service: agentRuntime.service },
9703
+ systemPrompt: { harness },
9704
+ skills: {
9705
+ workspaceRoot,
9706
+ additionalSkillPaths: runtimePi.additionalSkillPaths,
9707
+ piPackages: runtimePi.packages,
9708
+ noSkills: runtimePi.noSkills,
9709
+ getAdditionalSkillPaths: () => [
9710
+ ...getRuntimeProvisioning()?.skillPaths ?? [],
9711
+ ...opts.pi?.additionalSkillPaths ?? [],
9712
+ ...opts.pi?.getHotReloadableResources?.().additionalSkillPaths ?? []
9713
+ ],
9714
+ getPiPackages: () => [
9715
+ ...opts.pi?.packages ?? [],
9716
+ ...opts.pi?.getHotReloadableResources?.().packages ?? []
9717
+ ]
9718
+ },
9719
+ catalog: { tools },
9720
+ commands: {
9721
+ harness,
9722
+ defaultSessionId: sessionId,
9723
+ workdir: runtimeBundle.workspace.root,
9724
+ metering: opts.metering
9725
+ },
9726
+ reload: {
9727
+ harness,
9728
+ defaultSessionId: sessionId,
9729
+ beforeReload: opts.beforeReload,
9730
+ onDiagnostics: (diagnostics) => {
9731
+ lastReloadDiagnostics = diagnostics;
9732
+ }
9733
+ },
9734
+ readyStatus: { tracker: readyTracker }
9735
+ };
9626
9736
  }
9627
9737
 
9628
9738
  // src/server/http/csp.ts
@@ -9650,6 +9760,7 @@ function applyCspHeaders(response, opts = {}) {
9650
9760
  }
9651
9761
 
9652
9762
  // src/server/registerAgentRoutes.ts
9763
+ import { createHash as createHash6 } from "crypto";
9653
9764
  import { basename as basename2 } from "path";
9654
9765
  import { AuthStorage as AuthStorage2, ModelRegistry as ModelRegistry2 } from "@mariozechner/pi-coding-agent";
9655
9766
 
@@ -9795,6 +9906,8 @@ function buildUploadAgentTools(bundle) {
9795
9906
  // src/server/registerAgentRoutes.ts
9796
9907
  var DEFAULT_VERSION2 = "0.1.0-dev";
9797
9908
  var DEFAULT_WORKSPACE_ID4 = "default";
9909
+ var PURE_RUNTIME_MODE2 = "none";
9910
+ var MAX_PURE_BINDINGS = 256;
9798
9911
  var STANDARD_AGENT_TOOL_NAMES = ["bash", "read", "write", "edit", "find", "grep", "ls"];
9799
9912
  function pluginNameFromPath(path4) {
9800
9913
  const fileName = basename2(path4);
@@ -9814,6 +9927,20 @@ function getAvailableModelProviders() {
9814
9927
  new Set(availableModels.map((model) => model.provider))
9815
9928
  ).sort((a, b) => a.localeCompare(b));
9816
9929
  }
9930
+ function registerAgentCapabilitiesContributor(app, profile) {
9931
+ const host = app;
9932
+ if (typeof host.registerCapabilitiesContributor !== "function") return;
9933
+ host.registerCapabilitiesContributor(
9934
+ "agent",
9935
+ () => ({
9936
+ agent: {
9937
+ runtimeMode: profile.runtimeMode,
9938
+ tools: profile.capabilities.tools,
9939
+ modelProviders: getAvailableModelProviders()
9940
+ }
9941
+ })
9942
+ );
9943
+ }
9817
9944
  function getRequestWorkspaceId2(request) {
9818
9945
  return request.workspaceContext?.workspaceId ?? DEFAULT_WORKSPACE_ID4;
9819
9946
  }
@@ -9839,6 +9966,14 @@ function normalizeSessionNamespace(value) {
9839
9966
  const trimmed = value.trim();
9840
9967
  return trimmed.length > 0 ? trimmed : void 0;
9841
9968
  }
9969
+ function pureSessionNamespaceFromWorkspaceId(workspaceId) {
9970
+ const digest = createHash6("sha256").update(workspaceId).digest("hex").slice(0, 16);
9971
+ return `workspace-${digest}`;
9972
+ }
9973
+ function pureSessionNamespaceFromScope(parts) {
9974
+ const digest = createHash6("sha256").update(JSON.stringify(parts)).digest("hex").slice(0, 16);
9975
+ return `scope-${digest}`;
9976
+ }
9842
9977
  function getRequestAuthSubject2(request) {
9843
9978
  const userId = request?.user?.id;
9844
9979
  if (typeof userId === "string" && userId.trim()) return userId.trim();
@@ -9895,10 +10030,14 @@ function createRuntimeReadinessCheck(workspaceId, getRuntimeDependencies) {
9895
10030
  };
9896
10031
  }
9897
10032
  var registerAgentRoutes = async (app, opts) => {
9898
- const workspaceRoot = opts.workspaceRoot ?? process.cwd();
9899
10033
  const sessionId = opts.sessionId ?? DEFAULT_WORKSPACE_ID4;
9900
- const templatePath = opts.templatePath ?? getEnv("BORING_AGENT_TEMPLATE_PATH");
9901
10034
  const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
10035
+ if (!opts.runtimeModeAdapter && resolvedMode === PURE_RUNTIME_MODE2) {
10036
+ await registerPureAgentRoutes(app, opts, sessionId, resolvedMode);
10037
+ return;
10038
+ }
10039
+ const workspaceRoot = opts.workspaceRoot ?? process.cwd();
10040
+ const templatePath = opts.templatePath ?? getEnv("BORING_AGENT_TEMPLATE_PATH");
9902
10041
  const modeAdapter = opts.runtimeModeAdapter ?? resolveMode(resolvedMode, { sandboxHandleStore: opts.sandboxHandleStore });
9903
10042
  app.addHook("onClose", async () => {
9904
10043
  await modeAdapter.dispose?.();
@@ -10517,6 +10656,144 @@ var registerAgentRoutes = async (app, opts) => {
10517
10656
  staticBinding ? { tracker: staticBinding.readyTracker } : { getTracker: async (request) => (await getBindingForRequest(request)).readyTracker }
10518
10657
  );
10519
10658
  };
10659
+ async function registerPureAgentRoutes(app, opts, sessionId, resolvedMode) {
10660
+ const pureBindings = /* @__PURE__ */ new Map();
10661
+ const requestScopedPure = typeof opts.getWorkspaceId === "function" || typeof opts.getWorkspaceRoot === "function" || typeof opts.getExtraTools === "function" || typeof opts.getPi === "function" || typeof opts.getSessionNamespace === "function" || typeof opts.getSystemPromptDynamic === "function";
10662
+ function evictPureBindings() {
10663
+ if (pureBindings.size <= MAX_PURE_BINDINGS) return;
10664
+ const keys = Array.from(pureBindings.keys());
10665
+ for (let i = 0; i < keys.length - MAX_PURE_BINDINGS; i += 1) {
10666
+ const key = keys[i];
10667
+ if (!key) continue;
10668
+ const binding = pureBindings.get(key);
10669
+ pureBindings.delete(key);
10670
+ binding?.then((resolved) => resolved.agent.dispose()).catch(() => {
10671
+ });
10672
+ }
10673
+ }
10674
+ async function createPureBinding(request) {
10675
+ const workspaceId = request ? getRequestWorkspaceId2(request) : sessionId;
10676
+ const workspaceRoot = request && opts.getWorkspaceRoot ? await opts.getWorkspaceRoot(workspaceId, request) : opts.workspaceRoot ?? "";
10677
+ const pi = withPiHarnessDefaults(opts.getPi ? await opts.getPi({ workspaceId, workspaceRoot, request }) : opts.pi);
10678
+ const configuredSessionNamespace = normalizeSessionNamespace(opts.getSessionNamespace ? await opts.getSessionNamespace({ workspaceId, workspaceRoot, request }) : opts.sessionNamespace);
10679
+ const authSubject = opts.getExtraTools ? getRequestAuthSubject2(request) : void 0;
10680
+ const pureScopeNamespace = opts.getWorkspaceId ? pureSessionNamespaceFromWorkspaceId(workspaceId) : requestScopedPure ? pureSessionNamespaceFromScope([resolvedMode, workspaceId, workspaceRoot, pi, authSubject ?? null]) : void 0;
10681
+ const sessionNamespace = configuredSessionNamespace && pureScopeNamespace ? `${configuredSessionNamespace}-${pureScopeNamespace}` : configuredSessionNamespace ?? pureScopeNamespace;
10682
+ const key = JSON.stringify([
10683
+ resolvedMode,
10684
+ workspaceId,
10685
+ workspaceRoot,
10686
+ pi,
10687
+ sessionNamespace ?? null,
10688
+ authSubject ?? null
10689
+ ]);
10690
+ const existing = pureBindings.get(key);
10691
+ if (existing) return existing;
10692
+ const bindingPromise = (async () => {
10693
+ const scopedExtraTools = opts.getExtraTools ? await opts.getExtraTools({
10694
+ workspaceId,
10695
+ workspaceRoot,
10696
+ runtimeMode: resolvedMode,
10697
+ workspaceFsCapability: "none",
10698
+ authSubject
10699
+ }) : [];
10700
+ const tools = mergeTools({
10701
+ standardTools: [],
10702
+ extraTools: [...opts.extraTools ?? [], ...scopedExtraTools],
10703
+ logger: app.log
10704
+ });
10705
+ const baseHarnessFactory = opts.harnessFactory ?? ((input) => createPiCodingAgentHarness({
10706
+ ...input,
10707
+ pi
10708
+ }));
10709
+ const systemPromptDynamic = opts.getSystemPromptDynamic ? () => opts.getSystemPromptDynamic?.({ workspaceId, workspaceRoot }) : opts.systemPromptDynamic;
10710
+ const harnessFactory = ((input) => baseHarnessFactory({
10711
+ ...input,
10712
+ sessionNamespace,
10713
+ sessionRoot: opts.sessionRoot,
10714
+ sessionDir: input.sessionDir
10715
+ }));
10716
+ const coreAgent = createAgentRuntimeBridge({
10717
+ runtime: "none",
10718
+ tools,
10719
+ harnessFactory,
10720
+ systemPromptAppend: opts.systemPromptAppend,
10721
+ systemPromptDynamic,
10722
+ telemetry: opts.telemetry,
10723
+ metering: opts.metering,
10724
+ sessionStorageRoot: opts.sessionRoot
10725
+ });
10726
+ const agentRuntime = await coreAgent.getRuntime();
10727
+ return {
10728
+ agent: coreAgent.agent,
10729
+ tools,
10730
+ readyTracker: new ReadyStatusTracker({ sandboxReady: true, harnessReady: true }),
10731
+ piChatService: agentRuntime.service
10732
+ };
10733
+ })();
10734
+ pureBindings.set(key, bindingPromise);
10735
+ bindingPromise.catch(() => {
10736
+ if (pureBindings.get(key) === bindingPromise) pureBindings.delete(key);
10737
+ });
10738
+ evictPureBindings();
10739
+ return bindingPromise;
10740
+ }
10741
+ const staticBinding = requestScopedPure ? void 0 : await createPureBinding();
10742
+ const profile = {
10743
+ runtimeMode: resolvedMode,
10744
+ capabilities: {
10745
+ tools: staticBinding ? toolNames(staticBinding.tools) : toolNames(opts.extraTools ?? [])
10746
+ },
10747
+ sessionChangesTracker: new InMemorySessionChangesTracker(),
10748
+ health: {
10749
+ register: opts.registerHealthRoute ?? true,
10750
+ version: opts.version ?? DEFAULT_VERSION2,
10751
+ getReadiness: () => ({ sandboxReady: true, harnessReady: true })
10752
+ },
10753
+ chat: {
10754
+ ...staticBinding ? { service: staticBinding.piChatService } : { getService: async (request) => (await createPureBinding(request)).piChatService },
10755
+ defaultWorkspaceId: false
10756
+ },
10757
+ models: { filterModels: opts.filterModels },
10758
+ catalog: staticBinding ? { tools: staticBinding.tools } : { getTools: async (request) => (await createPureBinding(request)).tools },
10759
+ readyStatus: staticBinding ? { tracker: staticBinding.readyTracker } : { getTracker: async (request) => (await createPureBinding(request)).readyTracker },
10760
+ dispose: async () => {
10761
+ const bindings = await Promise.allSettled(pureBindings.values());
10762
+ await Promise.all(bindings.filter((result) => result.status === "fulfilled").map((result) => result.value.agent.dispose()));
10763
+ },
10764
+ beforeRegister: (profileApp) => {
10765
+ profileApp.addHook("onRequest", async (request, reply) => {
10766
+ const user = request.user;
10767
+ let workspaceId = DEFAULT_WORKSPACE_ID4;
10768
+ if (opts.getWorkspaceId && !isWorkspaceAgnosticAgentRequest(request, {
10769
+ readyStatusWorkspaceScoped: requestScopedPure,
10770
+ modelsWorkspaceScoped: Boolean(opts.filterModels)
10771
+ })) {
10772
+ try {
10773
+ workspaceId = (await opts.getWorkspaceId(request)).trim();
10774
+ } catch (error) {
10775
+ const statusCode = typeof error?.statusCode === "number" ? error.statusCode : 400;
10776
+ const message = statusCode >= 500 ? "workspace scope failed" : error instanceof Error ? error.message : "workspace id is required";
10777
+ return reply.code(statusCode).send({
10778
+ error: { code: ErrorCode.enum.WORKSPACE_UNINITIALIZED, message }
10779
+ });
10780
+ }
10781
+ if (workspaceId.length === 0) {
10782
+ return reply.code(400).send({
10783
+ error: {
10784
+ code: ErrorCode.enum.WORKSPACE_UNINITIALIZED,
10785
+ message: "workspace id is required"
10786
+ }
10787
+ });
10788
+ }
10789
+ }
10790
+ request.workspaceContext = { workspaceId, authenticated: !!user };
10791
+ });
10792
+ }
10793
+ };
10794
+ registerAgentCapabilitiesContributor(app, profile);
10795
+ await registerAgentRouteBindingProfile(app, profile);
10796
+ }
10520
10797
  export {
10521
10798
  FileHandleStore,
10522
10799
  MANAGED_AGENT_MCP_DELIVERY_RULE,