@hachej/boring-agent 0.1.43 → 0.1.45

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.
@@ -1,4 +1,4 @@
1
- import { d as SessionStore } from './chatSubmitPayload-gC61O4Ni.js';
1
+ import { d as SessionStore } from './chatSubmitPayload-DHqQL2wD.js';
2
2
 
3
3
  interface TelemetrySink {
4
4
  capture(event: TelemetryEvent): void | Promise<void>;
@@ -33,6 +33,8 @@ interface ChatAttachmentPayload {
33
33
  filename?: string;
34
34
  mediaType?: string;
35
35
  url: string;
36
+ /** Workspace-relative path when the browser upload endpoint persisted the attachment. */
37
+ path?: string;
36
38
  }
37
39
  interface ChatSubmitPayload {
38
40
  message: string;
@@ -291,7 +291,8 @@ var ThinkingLevelSchema = z2.enum(["off", "low", "medium", "high"]);
291
291
  var ChatAttachmentPayloadSchema = z2.object({
292
292
  filename: z2.string().optional(),
293
293
  mediaType: z2.string().optional(),
294
- url: nonEmptyString
294
+ url: nonEmptyString,
295
+ path: z2.string().optional()
295
296
  });
296
297
  var PromptPayloadSchema = z2.object({
297
298
  message: z2.string().min(1).max(1e6),
@@ -2,8 +2,8 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ComponentType, ReactNode, HTMLAttributes, ComponentProps, FormEvent } from 'react';
4
4
  import { FileUIPart, UIMessage, ChatStatus } from 'ai';
5
- import { T as ToolUiMetadata, B as BoringChatPart, d as BoringChatMessage, m as PiChatStatus, r as QueuedUserMessage, C as ChatError, P as PiChatEvent, o as PromptPayload, p as PromptReceipt, F as FollowUpPayload, i as FollowUpReceipt, Q as QueueClearPayload, q as QueueClearReceipt, I as InterruptPayload, e as CommandReceipt, S as StopPayload, s as StopReceipt } from '../piChatEvent-DR9a-FVz.js';
6
- import { e as SessionSummary } from '../chatSubmitPayload-gC61O4Ni.js';
5
+ import { T as ToolUiMetadata, B as BoringChatPart, d as BoringChatMessage, m as PiChatStatus, r as QueuedUserMessage, C as ChatError, P as PiChatEvent, o as PromptPayload, p as PromptReceipt, F as FollowUpPayload, i as FollowUpReceipt, Q as QueueClearPayload, q as QueueClearReceipt, I as InterruptPayload, e as CommandReceipt, S as StopPayload, s as StopReceipt } from '../piChatEvent-DNKo77Xw.js';
6
+ import { e as SessionSummary } from '../chatSubmitPayload-DHqQL2wD.js';
7
7
  import { Button, Collapsible, CollapsibleContent, CollapsibleTrigger, InputGroupAddon, InputGroupButton, InputGroupTextarea } from '@hachej/boring-ui-kit';
8
8
  import { Streamdown } from 'streamdown';
9
9
  import { StickToBottom } from 'use-stick-to-bottom';
@@ -15,6 +15,7 @@ interface UploadFileOptions {
15
15
  workspaceRequestId?: string | null;
16
16
  directory?: string;
17
17
  sourcePath?: string;
18
+ fetch?: typeof globalThis.fetch;
18
19
  }
19
20
  interface UploadFileResult {
20
21
  url: string;
@@ -390,6 +391,7 @@ declare class RemotePiSession {
390
391
  private connectEvents;
391
392
  private runEventStream;
392
393
  private rehydrateAfterStreamReset;
394
+ private ensureReconnectScheduled;
393
395
  private scheduleReconnect;
394
396
  private postCommand;
395
397
  private rollbackOptimisticMessage;
@@ -695,6 +697,7 @@ type PromptInputProps = Omit<HTMLAttributes<HTMLFormElement>, "onSubmit" | "onEr
695
697
  * attachment URL is replaced with the stable server path before submit. */
696
698
  onUploadFile?: (file: File) => Promise<{
697
699
  url: string;
700
+ path?: string;
698
701
  }>;
699
702
  };
700
703
  declare const PromptInput: ({ className, accept, multiple, globalDrop, syncHiddenInput, maxFiles, maxFileSize, onError, onSubmit, onUploadFile, children, ...props }: PromptInputProps) => react_jsx_runtime.JSX.Element;
@@ -13,7 +13,7 @@ import {
13
13
  StopReceiptSchema,
14
14
  extractToolUiMetadata,
15
15
  sanitizeToolUiMetadata
16
- } from "../chunk-YUDMSYAO.js";
16
+ } from "../chunk-HEEJSOFF.js";
17
17
  import {
18
18
  DebugDrawer,
19
19
  cn,
@@ -30,14 +30,14 @@ function readAsDataUrl(file) {
30
30
  });
31
31
  }
32
32
  async function uploadFile(file, opts = {}) {
33
- const { apiBaseUrl = "", workspaceRequestId, directory, sourcePath } = opts;
33
+ const { apiBaseUrl = "", workspaceRequestId, directory, sourcePath, fetch: fetchImpl = globalThis.fetch } = opts;
34
34
  const dataUrl = await readAsDataUrl(file);
35
35
  const comma = dataUrl.indexOf(",");
36
36
  const contentBase64 = comma >= 0 ? dataUrl.slice(comma + 1) : "";
37
37
  const headers = { "Content-Type": "application/json" };
38
38
  if (workspaceRequestId) headers["x-boring-workspace-id"] = workspaceRequestId;
39
39
  const base = apiBaseUrl.replace(/\/$/, "");
40
- const res = await fetch(`${base}/api/v1/files/upload`, {
40
+ const res = await fetchImpl(`${base}/api/v1/files/upload`, {
41
41
  method: "POST",
42
42
  headers,
43
43
  credentials: "include",
@@ -2230,7 +2230,8 @@ async function resolveAttachmentUrls(files) {
2230
2230
  return Promise.all(files.map(async (file) => ({
2231
2231
  filename: file.filename,
2232
2232
  mediaType: file.mediaType,
2233
- url: file.url.startsWith("blob:") ? await convertBlobUrlToDataUrl(file.url) ?? file.url : file.url
2233
+ url: file.url.startsWith("blob:") ? await convertBlobUrlToDataUrl(file.url) ?? file.url : file.url,
2234
+ ...typeof file.path === "string" ? { path: file.path } : {}
2234
2235
  })));
2235
2236
  }
2236
2237
  async function readFileAsText(file) {
@@ -2255,14 +2256,18 @@ async function createEnrichedSubmitPayload({
2255
2256
  for (const file of files ?? []) {
2256
2257
  const label = file.filename ?? "attachment";
2257
2258
  const mime = file.mediaType ?? "application/octet-stream";
2259
+ const workspacePath = typeof file.path === "string" ? file.path : void 0;
2260
+ const pathNote = workspacePath ? `
2261
+ Saved in workspace at: ${workspacePath}
2262
+ Use the workspace file/read tools with this path if you need to inspect it.` : "";
2258
2263
  const content = await readFileAsText(file);
2259
2264
  if (content !== null) {
2260
- attachmentSummaries.push(`[attached: ${label} (${mime})]
2265
+ attachmentSummaries.push(`[attached: ${label} (${mime})${pathNote}]
2261
2266
  \`\`\`
2262
2267
  ${content}
2263
2268
  \`\`\``);
2264
2269
  } else {
2265
- attachmentSummaries.push(`[attached: ${label} (${mime}, not inlined \u2014 binary)]`);
2270
+ attachmentSummaries.push(`[attached: ${label} (${mime}, not inlined \u2014 binary)${pathNote}]`);
2266
2271
  }
2267
2272
  }
2268
2273
  const mentionNote = mentionedFiles.length > 0 ? `@files: ${mentionedFiles.join(", ")}` : null;
@@ -3246,7 +3251,7 @@ function selectQueuePreview(state) {
3246
3251
  }
3247
3252
  function selectRuntimeNotices(state) {
3248
3253
  const notices = [...state.notices];
3249
- if (state.connection.state === "reconnecting") {
3254
+ if (state.connection.state === "reconnecting" && hasVisibleReconnectWork(state)) {
3250
3255
  notices.push({ id: "connection-reconnecting", level: "warning", text: "Reconnecting to the agent session\u2026" });
3251
3256
  }
3252
3257
  if (state.retryNotice) {
@@ -3261,6 +3266,9 @@ function selectRuntimeNotices(state) {
3261
3266
  }
3262
3267
  return notices;
3263
3268
  }
3269
+ function hasVisibleReconnectWork(state) {
3270
+ return state.status !== "idle" || state.queue.followUps.length > 0 || Object.keys(state.optimisticOutbox).length > 0 || state.pendingToolCallIds.size > 0;
3271
+ }
3264
3272
  function optimisticText(message) {
3265
3273
  const text = message.parts.filter((part) => part.type === "text").map((part) => part.text).join("\n\n");
3266
3274
  return text;
@@ -3703,10 +3711,18 @@ function piChatReducer(state, action) {
3703
3711
  queue: { followUps: clearQueuedFollowUps(state.queue.followUps, action) },
3704
3712
  optimisticOutbox: clearOptimisticFollowUps(state.optimisticOutbox, action)
3705
3713
  };
3706
- case "connection-state":
3707
- return { ...state, connection: { ...state.connection, state: action.state } };
3714
+ case "connection-state": {
3715
+ const nextState = {
3716
+ ...state,
3717
+ connection: { ...state.connection, state: action.state }
3718
+ };
3719
+ return action.state === "connected" ? clearProtocolError(nextState) : nextState;
3720
+ }
3708
3721
  case "heartbeat":
3709
- return { ...state, connection: { ...state.connection, lastHeartbeatAt: action.now ?? Date.now() } };
3722
+ return clearProtocolError({
3723
+ ...state,
3724
+ connection: { ...state.connection, lastHeartbeatAt: action.now ?? Date.now() }
3725
+ });
3710
3726
  case "protocol-error":
3711
3727
  return {
3712
3728
  ...state,
@@ -3723,6 +3739,14 @@ function piChatReducer(state, action) {
3723
3739
  return { ...state, notices: state.notices.filter((notice) => notice.id !== action.id) };
3724
3740
  }
3725
3741
  }
3742
+ function clearProtocolError(state) {
3743
+ if (!state.notices.some((notice) => notice.id === "protocol-error")) return state;
3744
+ return {
3745
+ ...state,
3746
+ error: void 0,
3747
+ notices: state.notices.filter((notice) => notice.id !== "protocol-error")
3748
+ };
3749
+ }
3726
3750
  function syncCursor(state, cursor) {
3727
3751
  if (cursor <= state.lastSeq) return { ...state, hydrated: true, needsResync: void 0 };
3728
3752
  return { ...state, lastSeq: cursor, hydrated: true, needsResync: void 0 };
@@ -3730,7 +3754,9 @@ function syncCursor(state, cursor) {
3730
3754
  function hydrateFromSnapshot(state, snapshot, options = {}) {
3731
3755
  if (snapshot.seq < state.lastSeq && !options.allowSeqRewind) return state;
3732
3756
  const queue = { followUps: enrichQueueWithKnownMetadata(snapshot.queue.followUps, state.optimisticOutbox, state.queue.followUps) };
3733
- const committedMessages = normalizeSnapshotMessages(snapshot.messages);
3757
+ const snapshotMessages = normalizeSnapshotMessages(snapshot.messages);
3758
+ const preserveLocalHistory = shouldPreserveLocalCommittedHistory(state, snapshot, snapshotMessages, options);
3759
+ const committedMessages = preserveLocalHistory ? mergeSnapshotMessagesIntoLocal(state.committedMessages, snapshotMessages) : snapshotMessages;
3734
3760
  const serverNonces = /* @__PURE__ */ new Set();
3735
3761
  for (const message of committedMessages) {
3736
3762
  if (message.clientNonce) serverNonces.add(message.clientNonce);
@@ -3739,7 +3765,12 @@ function hydrateFromSnapshot(state, snapshot, options = {}) {
3739
3765
  if (followUp.clientNonce) serverNonces.add(followUp.clientNonce);
3740
3766
  }
3741
3767
  const nextOutbox = {};
3742
- const staleOutbox = Object.values(state.optimisticOutbox).filter((message) => !serverNonces.has(message.clientNonce));
3768
+ if (preserveLocalHistory) {
3769
+ for (const message of Object.values(state.optimisticOutbox)) {
3770
+ if (!serverNonces.has(message.clientNonce)) nextOutbox[message.clientNonce] = message;
3771
+ }
3772
+ }
3773
+ const staleOutbox = preserveLocalHistory ? [] : Object.values(state.optimisticOutbox).filter((message) => !serverNonces.has(message.clientNonce));
3743
3774
  const notices = staleOutbox.length > 0 ? upsertNotice(state.notices, {
3744
3775
  id: "stale-outbox-cleared",
3745
3776
  level: "warning",
@@ -3766,6 +3797,22 @@ function hydrateFromSnapshot(state, snapshot, options = {}) {
3766
3797
  needsResync: void 0
3767
3798
  };
3768
3799
  }
3800
+ function shouldPreserveLocalCommittedHistory(state, snapshot, snapshotMessages, options) {
3801
+ if (options.allowSeqRewind) return false;
3802
+ if (!state.hydrated) return false;
3803
+ if (state.sessionId !== snapshot.sessionId) return false;
3804
+ if (state.committedMessages.length === 0) return false;
3805
+ if (snapshotMessages.length < state.committedMessages.length) return true;
3806
+ for (let index = 0; index < state.committedMessages.length; index += 1) {
3807
+ if (snapshotMessages[index]?.id !== state.committedMessages[index]?.id) return true;
3808
+ }
3809
+ return false;
3810
+ }
3811
+ function mergeSnapshotMessagesIntoLocal(currentMessages, snapshotMessages) {
3812
+ let merged = currentMessages;
3813
+ for (const message of snapshotMessages) merged = replaceOrAppendMessage(merged, message);
3814
+ return merged;
3815
+ }
3769
3816
  function applySequencedEvent(state, event) {
3770
3817
  if (event.seq <= state.lastSeq) return state;
3771
3818
  const expectedSeq = state.lastSeq + 1;
@@ -4494,6 +4541,7 @@ var RemotePiSession = class {
4494
4541
  this.store.dispatch({ type: "optimistic-user-message", message: toOptimisticUserMessage(payload) }, { flush: true });
4495
4542
  }
4496
4543
  if (!this.started) await this.start(this.store.getState().lastSeq);
4544
+ else this.ensureReconnectScheduled();
4497
4545
  try {
4498
4546
  const receipt = await this.postCommand("/prompt", payload, PromptReceiptSchema);
4499
4547
  return receipt;
@@ -4507,6 +4555,7 @@ var RemotePiSession = class {
4507
4555
  this.store.dispatch({ type: "optimistic-user-message", message: toOptimisticUserMessage(payload) }, { flush: true });
4508
4556
  }
4509
4557
  if (!this.started) await this.start(this.store.getState().lastSeq);
4558
+ else this.ensureReconnectScheduled();
4510
4559
  try {
4511
4560
  const receipt = await this.postCommand("/followup", payload, FollowUpReceiptSchema);
4512
4561
  return receipt;
@@ -4675,7 +4724,9 @@ var RemotePiSession = class {
4675
4724
  markOpen();
4676
4725
  if (!this.isStreamActive(generation, runId)) return;
4677
4726
  if (!connectTimedOut && shouldIgnoreStreamClose(error, controller)) return;
4678
- this.dispatchProtocolError(connectTimedOut ? `Pi chat event stream timed out after ${this.requestTimeoutMs}ms.` : errorMessage(error, "Pi chat event stream disconnected."));
4727
+ if (!connectTimedOut) {
4728
+ this.dispatchProtocolError(errorMessage(error, "Pi chat event stream disconnected."));
4729
+ }
4679
4730
  this.scheduleReconnect(generation);
4680
4731
  } finally {
4681
4732
  clearConnectTimer();
@@ -4687,6 +4738,13 @@ var RemotePiSession = class {
4687
4738
  this.abortEventStream();
4688
4739
  void this.hydrateAndConnect(generation, options);
4689
4740
  }
4741
+ ensureReconnectScheduled() {
4742
+ if (!this.isGenerationActive(this.generation)) return;
4743
+ const state = this.store.getState();
4744
+ if (state.connection.state === "connected" || state.connection.state === "connecting") return;
4745
+ if (this.reconnectTimer !== void 0) return;
4746
+ void this.hydrateAndConnect(this.generation);
4747
+ }
4690
4748
  scheduleReconnect(generation) {
4691
4749
  if (!this.isGenerationActive(generation)) return;
4692
4750
  this.clearReconnectTimer();
@@ -7988,11 +8046,11 @@ var PromptInput = ({
7988
8046
  const inputRef = useRef14(null);
7989
8047
  const formRef = useRef14(null);
7990
8048
  const [items, setItems] = useState17([]);
7991
- const setFileUrlLocal = useCallback15((id, url, status) => {
8049
+ const setFileUrlLocal = useCallback15((id, url, status, path) => {
7992
8050
  setItems((prev) => prev.map((f) => {
7993
8051
  if (f.id !== id) return f;
7994
8052
  if (f.url !== url && f.url.startsWith("blob:")) URL.revokeObjectURL(f.url);
7995
- return { ...f, url, status };
8053
+ return { ...f, url, status, ...path ? { path } : {} };
7996
8054
  }));
7997
8055
  }, []);
7998
8056
  const files = usingProvider ? controller.attachments.files : items;
@@ -8061,7 +8119,7 @@ var PromptInput = ({
8061
8119
  for (let i = 0; i < entries.length; i++) {
8062
8120
  const entry = entries[i];
8063
8121
  const file = capped[i];
8064
- onUploadFile(file).then(({ url }) => setFileUrlLocal(entry.id, url, "ready")).catch(() => setFileUrlLocal(entry.id, entry.url, "error"));
8122
+ onUploadFile(file).then(({ url, path }) => setFileUrlLocal(entry.id, url, "ready", path)).catch(() => setFileUrlLocal(entry.id, entry.url, "error"));
8065
8123
  }
8066
8124
  }
8067
8125
  },
@@ -8744,6 +8802,11 @@ function PiChatComposerSurface({
8744
8802
  onSubmitMessage,
8745
8803
  onStop
8746
8804
  }) {
8805
+ const uploadAttachment = useCallback16((file) => uploadFile(file, {
8806
+ apiBaseUrl,
8807
+ workspaceRequestId: requestHeaders?.["x-boring-workspace-id"],
8808
+ fetch: fetch2
8809
+ }), [apiBaseUrl, fetch2, requestHeaders]);
8747
8810
  const resizeTextarea = useCallback16((node) => {
8748
8811
  if (!node) return;
8749
8812
  node.style.height = "auto";
@@ -8903,6 +8966,7 @@ function PiChatComposerSurface({
8903
8966
  {
8904
8967
  "data-boring-state": status,
8905
8968
  onSubmit: (message) => onSubmitMessage({ text: message.text, files: message.files }),
8969
+ onUploadFile: uploadAttachment,
8906
8970
  multiple: true,
8907
8971
  maxFiles: disabled || isStreaming ? 0 : MAX_PROMPT_ATTACHMENTS,
8908
8972
  maxFileSize: MAX_PROMPT_ATTACHMENT_BYTES,
@@ -1,4 +1,4 @@
1
- import { b as ChatSubmitPayload } from './chatSubmitPayload-gC61O4Ni.js';
1
+ import { b as ChatSubmitPayload } from './chatSubmitPayload-DHqQL2wD.js';
2
2
  import { z } from 'zod';
3
3
 
4
4
  interface ToolUiMetadata {
@@ -1,9 +1,9 @@
1
- import { o as WorkspaceRuntimeContext, S as Sandbox, g as SandboxHandleStore, f as SandboxHandleRecord, W as Workspace, j as TelemetrySink, F as FileSearch, i as Stat, E as Entry, c as ExecResult, q as WorkspaceChangeEvent, b as ExecOptions, P as PluginRestartWarning, A as AgentTool, r as AgentHarnessFactory } from '../agentPluginEvents-DP-vLNCs.js';
2
- export { t as AgentHarnessFactoryInput } from '../agentPluginEvents-DP-vLNCs.js';
1
+ import { o as WorkspaceRuntimeContext, S as Sandbox, g as SandboxHandleStore, f as SandboxHandleRecord, W as Workspace, j as TelemetrySink, F as FileSearch, i as Stat, E as Entry, c as ExecResult, q as WorkspaceChangeEvent, b as ExecOptions, P as PluginRestartWarning, A as AgentTool, r as AgentHarnessFactory } from '../agentPluginEvents-Dgm57gEU.js';
2
+ export { t as AgentHarnessFactoryInput } from '../agentPluginEvents-Dgm57gEU.js';
3
3
  import { Sandbox as Sandbox$1 } from '@vercel/sandbox';
4
4
  import { FastifyInstance, FastifyRequest, FastifyPluginAsync } from 'fastify';
5
5
  import { PackageSource, ExtensionFactory, SettingsManager } from '@mariozechner/pi-coding-agent';
6
- import { a as ChatModelSelection } from '../chatSubmitPayload-gC61O4Ni.js';
6
+ import { a as ChatModelSelection } from '../chatSubmitPayload-DHqQL2wD.js';
7
7
 
8
8
  interface CreateDirectSandboxOptions {
9
9
  runtimeContext?: WorkspaceRuntimeContext;
@@ -13,7 +13,7 @@ import {
13
13
  StopPayloadSchema,
14
14
  extractToolUiMetadata,
15
15
  sanitizeToolUiMetadata2 as sanitizeToolUiMetadata
16
- } from "../chunk-YUDMSYAO.js";
16
+ } from "../chunk-HEEJSOFF.js";
17
17
 
18
18
  // src/server/sandbox/direct/createDirectSandbox.ts
19
19
  import { spawn } from "child_process";
@@ -1795,13 +1795,15 @@ async function bakeSnapshotIfNeeded(opts) {
1795
1795
  }
1796
1796
 
1797
1797
  // src/server/sandbox/snapshots/deploymentSnapshot.ts
1798
- var VERCEL_UV_BIN = "/home/vercel-sandbox/.local/bin/uv";
1798
+ var VERCEL_UV_BIN_DIR = "/workspace/.boring-agent/sdk/uv/bin";
1799
+ var VERCEL_UV_BIN = `${VERCEL_UV_BIN_DIR}/uv`;
1799
1800
  var UV_SETUP_COMMANDS = [
1800
1801
  "command -v uv >/dev/null 2>&1 || python3 -m pip install --upgrade uv",
1801
1802
  "uv --version"
1802
1803
  ];
1803
1804
  var NODE_UV_SETUP_COMMANDS = [
1804
- `[ -x ${VERCEL_UV_BIN} ] || curl -LsSf https://astral.sh/uv/install.sh | sh`,
1805
+ `mkdir -p ${VERCEL_UV_BIN_DIR}`,
1806
+ `[ -x ${VERCEL_UV_BIN} ] || curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR=${VERCEL_UV_BIN_DIR} sh`,
1805
1807
  `${VERCEL_UV_BIN} --version`
1806
1808
  ];
1807
1809
  function isNodeFamilyRuntime(runtime) {
@@ -8723,19 +8725,25 @@ import { AuthStorage as AuthStorage2, ModelRegistry as ModelRegistry2 } from "@m
8723
8725
  function modelsRoutes(app, _opts, done) {
8724
8726
  const authStorage = AuthStorage2.create();
8725
8727
  const registry = ModelRegistry2.create(authStorage);
8726
- registerConfiguredModelProviders(registry);
8728
+ const configuredModels = registerConfiguredModelProviders(registry);
8729
+ const configuredModelSet = new Set(
8730
+ configuredModels.map((model) => `${model.provider}:${model.id}`)
8731
+ );
8727
8732
  app.get("/api/v1/agent/models", async (_request, reply) => {
8728
8733
  const availableModels = registry.getAvailable();
8729
8734
  const availableSet = new Set(
8730
8735
  availableModels.map((m) => `${m.provider}:${m.id}`)
8731
8736
  );
8732
- const models = registry.getAll().map((m) => ({
8737
+ const allModels = configuredModelSet.size > 0 ? registry.getAll().filter((m) => configuredModelSet.has(`${m.provider}:${m.id}`)) : registry.getAll();
8738
+ const models = allModels.map((m) => ({
8733
8739
  provider: m.provider,
8734
8740
  id: m.id,
8735
8741
  label: m.label ?? m.id,
8736
8742
  // Keep this endpoint cheap: it is fetched on chat mount, so it must never
8737
8743
  // block workspace load on deep provider auth resolution. ModelRegistry's
8738
- // available set is already derived from configured auth sources.
8744
+ // available set is already derived from configured auth sources. When
8745
+ // hosts configure launch/custom providers, those configured models are an
8746
+ // allowlist: do not leak the built-in registry's unavailable catalog.
8739
8747
  available: availableSet.has(`${m.provider}:${m.id}`)
8740
8748
  }));
8741
8749
  models.sort((a, b) => {
@@ -9210,7 +9218,7 @@ function statusCodeFromError(err) {
9210
9218
 
9211
9219
  // src/server/http/routes/systemPrompt.ts
9212
9220
  function systemPromptRoutes(app, opts, done) {
9213
- async function resolveHarness(request) {
9221
+ async function resolveHarness2(request) {
9214
9222
  if (opts.getHarness) return await opts.getHarness(request);
9215
9223
  if (opts.harness) return opts.harness;
9216
9224
  throw new Error("system prompt route requires harness or getHarness");
@@ -9229,7 +9237,7 @@ function systemPromptRoutes(app, opts, done) {
9229
9237
  }
9230
9238
  });
9231
9239
  }
9232
- const harness = await resolveHarness(request);
9240
+ const harness = await resolveHarness2(request);
9233
9241
  if (typeof harness.getSystemPrompt !== "function") {
9234
9242
  return reply.code(501).send({
9235
9243
  error: {
@@ -9387,14 +9395,24 @@ function normalizeSessionId(value, fallback) {
9387
9395
  const trimmed = value.trim();
9388
9396
  return trimmed.length > 0 ? trimmed : fallback;
9389
9397
  }
9398
+ function resolveHarness(opts, request) {
9399
+ return "getHarness" in opts ? opts.getHarness(request) : opts.harness;
9400
+ }
9401
+ function resolveWorkdir(opts, request) {
9402
+ return "getWorkdir" in opts ? opts.getWorkdir(request) : opts.workdir;
9403
+ }
9390
9404
  function commandsRoutes(app, opts, done) {
9391
9405
  app.get("/api/v1/agent/commands", async (request, reply) => {
9392
9406
  try {
9393
9407
  const query = request.query;
9394
9408
  const sessionId = normalizeSessionId(query.sessionId, opts.defaultSessionId);
9395
- const commands = await opts.harness.getSlashCommands?.(sessionId, {
9409
+ const [harness, workdir] = await Promise.all([
9410
+ resolveHarness(opts, request),
9411
+ resolveWorkdir(opts, request)
9412
+ ]);
9413
+ const commands = await harness.getSlashCommands?.(sessionId, {
9396
9414
  abortSignal: new AbortController().signal,
9397
- workdir: opts.workdir
9415
+ workdir
9398
9416
  }) ?? [];
9399
9417
  return reply.code(200).send({ commands });
9400
9418
  } catch (error) {
@@ -9403,19 +9421,23 @@ function commandsRoutes(app, opts, done) {
9403
9421
  }
9404
9422
  });
9405
9423
  app.post("/api/v1/agent/commands/execute", async (request, reply) => {
9406
- if (!opts.harness.executeSlashCommand) {
9407
- return reply.code(501).send({ error: "Command execution not supported by this harness." });
9408
- }
9409
9424
  try {
9425
+ const [harness, workdir] = await Promise.all([
9426
+ resolveHarness(opts, request),
9427
+ resolveWorkdir(opts, request)
9428
+ ]);
9429
+ if (!harness.executeSlashCommand) {
9430
+ return reply.code(501).send({ error: "Command execution not supported by this harness." });
9431
+ }
9410
9432
  const query = request.query;
9411
9433
  const body = request.body && typeof request.body === "object" ? request.body : {};
9412
9434
  const sessionId = normalizeSessionId(query.sessionId, opts.defaultSessionId);
9413
9435
  const name = typeof body.name === "string" ? body.name.trim() : "";
9414
9436
  const args = typeof body.args === "string" ? body.args : "";
9415
9437
  if (!name) return reply.code(400).send({ error: "name body field is required" });
9416
- await opts.harness.executeSlashCommand(sessionId, name, args, {
9438
+ await harness.executeSlashCommand(sessionId, name, args, {
9417
9439
  abortSignal: new AbortController().signal,
9418
- workdir: opts.workdir
9440
+ workdir
9419
9441
  });
9420
9442
  return reply.code(200).send({ ok: true });
9421
9443
  } catch (error) {
@@ -12032,9 +12054,13 @@ function pluginNameFromPath(path4) {
12032
12054
  function getAvailableModelProviders() {
12033
12055
  const authStorage = AuthStorage3.create();
12034
12056
  const registry = ModelRegistry3.create(authStorage);
12035
- registerConfiguredModelProviders(registry);
12057
+ const configuredModels = registerConfiguredModelProviders(registry);
12058
+ const configuredModelSet = new Set(
12059
+ configuredModels.map((model) => `${model.provider}:${model.id}`)
12060
+ );
12061
+ const availableModels = configuredModelSet.size > 0 ? registry.getAvailable().filter((model) => configuredModelSet.has(`${model.provider}:${model.id}`)) : registry.getAvailable();
12036
12062
  return Array.from(
12037
- new Set(registry.getAvailable().map((model) => model.provider))
12063
+ new Set(availableModels.map((model) => model.provider))
12038
12064
  ).sort((a, b) => a.localeCompare(b));
12039
12065
  }
12040
12066
  function selectRuntimeModeAdapter(mode, sandboxHandleStore) {
@@ -12682,6 +12708,18 @@ var registerAgentRoutes = async (app, opts) => {
12682
12708
  catalogRoutes,
12683
12709
  staticBinding ? { tools: staticBinding.tools } : { getTools: async (request) => (await getBindingForRequest(request)).tools }
12684
12710
  );
12711
+ await app.register(
12712
+ commandsRoutes,
12713
+ staticBinding ? {
12714
+ harness: staticBinding.harness,
12715
+ defaultSessionId: sessionId,
12716
+ workdir: staticBinding.runtimeBundle.workspace.root
12717
+ } : {
12718
+ defaultSessionId: sessionId,
12719
+ getHarness: async (request) => (await getBindingForRequest(request)).harness,
12720
+ getWorkdir: async (request) => (await getBindingForRequest(request)).runtimeBundle.workspace.root
12721
+ }
12722
+ );
12685
12723
  await app.register(
12686
12724
  readyStatusRoutes,
12687
12725
  staticBinding ? { tracker: staticBinding.readyTracker } : { getTracker: async (request) => (await getBindingForRequest(request)).readyTracker }
@@ -1,8 +1,8 @@
1
- import { W as Workspace, S as Sandbox, F as FileSearch, A as AgentTool } from '../agentPluginEvents-DP-vLNCs.js';
2
- export { a as AgentHarness, C as CommandNotifyPayload, E as Entry, b as ExecOptions, c as ExecResult, I as IsolatedCodeInput, d as IsolatedCodeOutput, J as JSONSchema, R as RunContext, e as SandboxCapability, f as SandboxHandleRecord, g as SandboxHandleStore, h as SendMessageInput, i as Stat, T as TelemetryEvent, j as TelemetrySink, k as ToolExecContext, l as ToolResult, m as WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT, n as WORKSPACE_COMMAND_NOTIFY_EVENT, o as WorkspaceRuntimeContext, p as noopTelemetry, s as safeCapture } from '../agentPluginEvents-DP-vLNCs.js';
3
- import { B as BoringChatPart, T as ToolUiMetadata } from '../piChatEvent-DR9a-FVz.js';
4
- export { A as ApiErrorPayload, a as ApiErrorPayloadSchema, b as ApiErrorResponse, c as ApiErrorResponseSchema, d as BoringChatMessage, C as ChatError, e as CommandReceipt, E as ERROR_CODES, f as ErrorCode, g as ErrorLogFields, h as ErrorLogFieldsSchema, F as FollowUpPayload, i as FollowUpReceipt, I as InterruptPayload, j as InterruptReceipt, P as PiChatEvent, k as PiChatHeartbeatFrame, l as PiChatSnapshot, m as PiChatStatus, n as PiChatStreamFrame, o as PromptPayload, p as PromptReceipt, Q as QueueClearPayload, q as QueueClearReceipt, r as QueuedUserMessage, S as StopPayload, s as StopReceipt, t as extractToolUiMetadata, u as isToolUiMetadata } from '../piChatEvent-DR9a-FVz.js';
5
- export { C as ChatAttachmentPayload, a as ChatModelSelection, b as ChatSubmitPayload, S as SessionCtx, c as SessionDetail, d as SessionStore, e as SessionSummary, T as ThinkingLevel } from '../chatSubmitPayload-gC61O4Ni.js';
1
+ import { W as Workspace, S as Sandbox, F as FileSearch, A as AgentTool } from '../agentPluginEvents-Dgm57gEU.js';
2
+ export { a as AgentHarness, C as CommandNotifyPayload, E as Entry, b as ExecOptions, c as ExecResult, I as IsolatedCodeInput, d as IsolatedCodeOutput, J as JSONSchema, R as RunContext, e as SandboxCapability, f as SandboxHandleRecord, g as SandboxHandleStore, h as SendMessageInput, i as Stat, T as TelemetryEvent, j as TelemetrySink, k as ToolExecContext, l as ToolResult, m as WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT, n as WORKSPACE_COMMAND_NOTIFY_EVENT, o as WorkspaceRuntimeContext, p as noopTelemetry, s as safeCapture } from '../agentPluginEvents-Dgm57gEU.js';
3
+ import { B as BoringChatPart, T as ToolUiMetadata } from '../piChatEvent-DNKo77Xw.js';
4
+ export { A as ApiErrorPayload, a as ApiErrorPayloadSchema, b as ApiErrorResponse, c as ApiErrorResponseSchema, d as BoringChatMessage, C as ChatError, e as CommandReceipt, E as ERROR_CODES, f as ErrorCode, g as ErrorLogFields, h as ErrorLogFieldsSchema, F as FollowUpPayload, i as FollowUpReceipt, I as InterruptPayload, j as InterruptReceipt, P as PiChatEvent, k as PiChatHeartbeatFrame, l as PiChatSnapshot, m as PiChatStatus, n as PiChatStreamFrame, o as PromptPayload, p as PromptReceipt, Q as QueueClearPayload, q as QueueClearReceipt, r as QueuedUserMessage, S as StopPayload, s as StopReceipt, t as extractToolUiMetadata, u as isToolUiMetadata } from '../piChatEvent-DNKo77Xw.js';
5
+ export { C as ChatAttachmentPayload, a as ChatModelSelection, b as ChatSubmitPayload, S as SessionCtx, c as SessionDetail, d as SessionStore, e as SessionSummary, T as ThinkingLevel } from '../chatSubmitPayload-DHqQL2wD.js';
6
6
  import { z } from 'zod';
7
7
 
8
8
  interface CatalogDeps {
@@ -1347,12 +1347,15 @@ declare const ChatAttachmentPayloadSchema: z.ZodObject<{
1347
1347
  filename: z.ZodOptional<z.ZodString>;
1348
1348
  mediaType: z.ZodOptional<z.ZodString>;
1349
1349
  url: z.ZodString;
1350
+ path: z.ZodOptional<z.ZodString>;
1350
1351
  }, "strip", z.ZodTypeAny, {
1351
1352
  url: string;
1353
+ path?: string | undefined;
1352
1354
  filename?: string | undefined;
1353
1355
  mediaType?: string | undefined;
1354
1356
  }, {
1355
1357
  url: string;
1358
+ path?: string | undefined;
1356
1359
  filename?: string | undefined;
1357
1360
  mediaType?: string | undefined;
1358
1361
  }>;
@@ -1375,12 +1378,15 @@ declare const PromptPayloadSchema: z.ZodObject<{
1375
1378
  filename: z.ZodOptional<z.ZodString>;
1376
1379
  mediaType: z.ZodOptional<z.ZodString>;
1377
1380
  url: z.ZodString;
1381
+ path: z.ZodOptional<z.ZodString>;
1378
1382
  }, "strip", z.ZodTypeAny, {
1379
1383
  url: string;
1384
+ path?: string | undefined;
1380
1385
  filename?: string | undefined;
1381
1386
  mediaType?: string | undefined;
1382
1387
  }, {
1383
1388
  url: string;
1389
+ path?: string | undefined;
1384
1390
  filename?: string | undefined;
1385
1391
  mediaType?: string | undefined;
1386
1392
  }>, "many">>;
@@ -1395,6 +1401,7 @@ declare const PromptPayloadSchema: z.ZodObject<{
1395
1401
  thinkingLevel?: "off" | "low" | "medium" | "high" | undefined;
1396
1402
  attachments?: {
1397
1403
  url: string;
1404
+ path?: string | undefined;
1398
1405
  filename?: string | undefined;
1399
1406
  mediaType?: string | undefined;
1400
1407
  }[] | undefined;
@@ -1409,6 +1416,7 @@ declare const PromptPayloadSchema: z.ZodObject<{
1409
1416
  thinkingLevel?: "off" | "low" | "medium" | "high" | undefined;
1410
1417
  attachments?: {
1411
1418
  url: string;
1419
+ path?: string | undefined;
1412
1420
  filename?: string | undefined;
1413
1421
  mediaType?: string | undefined;
1414
1422
  }[] | undefined;
@@ -39,7 +39,7 @@ import {
39
39
  extractToolUiMetadata,
40
40
  isToolUiMetadata,
41
41
  sanitizeToolUiMetadata2 as sanitizeToolUiMetadata
42
- } from "../chunk-YUDMSYAO.js";
42
+ } from "../chunk-HEEJSOFF.js";
43
43
 
44
44
  // src/shared/config-schema.ts
45
45
  import { z } from "zod";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-agent",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Pane-embeddable coding agent. Ships direct/local/vercel-sandbox execution modes behind one interface.",
@@ -74,7 +74,7 @@
74
74
  "use-stick-to-bottom": "^1.1.3",
75
75
  "yaml": "^2.8.3",
76
76
  "zod": "^3.25.76",
77
- "@hachej/boring-ui-kit": "0.1.43"
77
+ "@hachej/boring-ui-kit": "0.1.45"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@antithesishq/bombadil": "0.5.0",