@hachej/boring-agent 0.1.54 → 0.1.56

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.
@@ -47,6 +47,7 @@ interface AgentHarnessFactoryInput {
47
47
  runtimeCwd?: string;
48
48
  systemPromptAppend?: string;
49
49
  sessionNamespace?: string;
50
+ sessionRoot?: string;
50
51
  sessionDir?: string;
51
52
  /**
52
53
  * Optional dynamic system-prompt source. Harness calls it whenever it
@@ -477,4 +478,4 @@ interface CommandNotifyPayload {
477
478
  command?: string;
478
479
  }
479
480
 
480
- export { type AgentTool as A, type CommandNotifyPayload as C, type Entry as E, type FileSearch as F, type IsolatedCodeInput as I, type JSONSchema as J, type PluginRestartWarning as P, type RunContext as R, type Sandbox as S, type TelemetryEvent as T, type Workspace as W, type AgentHarness as a, type ExecOptions as b, type ExecResult as c, type IsolatedCodeOutput as d, type SandboxCapability as e, type SandboxHandleRecord as f, type SandboxHandleStore as g, type SendMessageInput as h, type Stat as i, type TelemetrySink as j, type ToolExecContext as k, type ToolResult as l, WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT as m, WORKSPACE_COMMAND_NOTIFY_EVENT as n, type WorkspaceRuntimeContext as o, noopTelemetry as p, type WorkspaceChangeEvent as q, type AgentHarnessFactory as r, safeCapture as s, type AgentHarnessFactoryInput as t };
481
+ export { type AgentTool as A, type CommandNotifyPayload as C, type Entry as E, type FileSearch as F, type IsolatedCodeInput as I, type JSONSchema as J, type PluginRestartWarning as P, type RunContext as R, type Sandbox as S, type TelemetryEvent as T, type Workspace as W, type AgentHarness as a, type ExecOptions as b, type ExecResult as c, type IsolatedCodeOutput as d, type SandboxCapability as e, type SandboxHandleRecord as f, type SandboxHandleStore as g, type SendMessageInput as h, type Stat as i, type TelemetrySink as j, type ToolExecContext as k, type ToolResult as l, WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT as m, WORKSPACE_COMMAND_NOTIFY_EVENT as n, type WorkspaceRuntimeContext as o, noopTelemetry as p, type ToolReadinessRequirement as q, type WorkspaceChangeEvent as r, safeCapture as s, type AgentHarnessFactory as t, type AgentHarnessFactoryInput as u };
@@ -68,7 +68,7 @@ function rawWorkspaceFileUrl(path, opts) {
68
68
  }
69
69
 
70
70
  // src/front/chat/PiChatPanel.tsx
71
- import { lazy, Suspense, useCallback as useCallback18, useEffect as useEffect21, useMemo as useMemo12, useRef as useRef16, useState as useState20 } from "react";
71
+ import { lazy, Suspense, useCallback as useCallback18, useEffect as useEffect21, useMemo as useMemo13, useRef as useRef17, useState as useState20 } from "react";
72
72
 
73
73
  // src/front/ArtifactOpenContext.tsx
74
74
  import { createContext, useContext } from "react";
@@ -5091,6 +5091,12 @@ function usePiSessions(options = {}) {
5091
5091
  const loadedDataSourceRef = useRef8(dataSourceKey);
5092
5092
  const requestScopeRef = useRef8(requestScopeKey);
5093
5093
  requestScopeRef.current = requestScopeKey;
5094
+ const remoteSessionOptionsRef = useRef8(options.remoteSessionOptions);
5095
+ remoteSessionOptionsRef.current = options.remoteSessionOptions;
5096
+ const remoteSessionOptionsKey = useMemo3(
5097
+ () => remoteSessionOptionsIdentity(options.remoteSessionOptions),
5098
+ [options.remoteSessionOptions]
5099
+ );
5094
5100
  useEffect8(() => {
5095
5101
  sessionsRef.current = sessions;
5096
5102
  }, [sessions]);
@@ -5253,7 +5259,7 @@ function usePiSessions(options = {}) {
5253
5259
  return;
5254
5260
  }
5255
5261
  const session = createRemoteSession({
5256
- ...options.remoteSessionOptions,
5262
+ ...remoteSessionOptionsRef.current,
5257
5263
  sessionId: activeSessionId,
5258
5264
  workspaceId: options.workspaceId,
5259
5265
  storageScope,
@@ -5265,7 +5271,7 @@ function usePiSessions(options = {}) {
5265
5271
  return () => {
5266
5272
  session.dispose();
5267
5273
  };
5268
- }, [activeSessionId, activeSessionKnown, apiBaseUrl, connectActiveSession, createRemoteSession, enabled, fetchImpl, options.remoteSessionOptions, options.workspaceId, requestHeaders, storageScope]);
5274
+ }, [activeSessionId, activeSessionKnown, apiBaseUrl, connectActiveSession, createRemoteSession, enabled, fetchImpl, remoteSessionOptionsKey, options.workspaceId, requestHeaders, storageScope]);
5269
5275
  const create = useCallback7(async (init) => {
5270
5276
  if (!enabled) throw new Error("Pi sessions are disabled");
5271
5277
  const response = await fetchImpl(sessionsUrl(), {
@@ -5377,6 +5383,40 @@ function toSessionSummary(value) {
5377
5383
  function canonicalPageCount(data) {
5378
5384
  return Math.min(data.length, SESSION_PAGE_SIZE);
5379
5385
  }
5386
+ var remoteSessionOptionObjectIds = /* @__PURE__ */ new WeakMap();
5387
+ var remoteSessionOptionObjectSeq = 0;
5388
+ function remoteSessionOptionObjectIdentity(value) {
5389
+ if (typeof value !== "object" && typeof value !== "function" || value === null) return void 0;
5390
+ const object = value;
5391
+ let id = remoteSessionOptionObjectIds.get(object);
5392
+ if (!id) {
5393
+ id = ++remoteSessionOptionObjectSeq;
5394
+ remoteSessionOptionObjectIds.set(object, id);
5395
+ }
5396
+ return String(id);
5397
+ }
5398
+ function remoteSessionOptionsIdentity(options) {
5399
+ if (!options) return "{}";
5400
+ return JSON.stringify({
5401
+ autoStart: options.autoStart,
5402
+ requestTimeoutMs: options.requestTimeoutMs,
5403
+ onEvent: remoteSessionOptionObjectIdentity(options.onEvent),
5404
+ storeOptions: remoteSessionOptionObjectIdentity(options.storeOptions),
5405
+ setTimeoutFn: remoteSessionOptionObjectIdentity(options.setTimeoutFn),
5406
+ clearTimeoutFn: remoteSessionOptionObjectIdentity(options.clearTimeoutFn),
5407
+ reconnect: options.reconnect ? {
5408
+ baseMs: options.reconnect.baseMs,
5409
+ maxMs: options.reconnect.maxMs,
5410
+ jitterRatio: options.reconnect.jitterRatio,
5411
+ random: remoteSessionOptionObjectIdentity(options.reconnect.random)
5412
+ } : void 0,
5413
+ debug: options.debug ? {
5414
+ largeStateWarningBytes: options.debug.largeStateWarningBytes,
5415
+ largeStateWarningMessages: options.debug.largeStateWarningMessages,
5416
+ onWarning: remoteSessionOptionObjectIdentity(options.debug.onWarning)
5417
+ } : void 0
5418
+ });
5419
+ }
5380
5420
  function mergeSessions(...lists) {
5381
5421
  const seen = /* @__PURE__ */ new Set();
5382
5422
  const merged = [];
@@ -5687,7 +5727,7 @@ var ConversationScrollButton = ({
5687
5727
  };
5688
5728
 
5689
5729
  // src/front/chat/components/ChatNotices.tsx
5690
- import { AlertCircleIcon, ListRestartIcon, Loader2 } from "lucide-react";
5730
+ import { AlertCircleIcon, ExternalLinkIcon as ExternalLinkIcon2, ListRestartIcon, Loader2, XIcon as XIcon3 } from "lucide-react";
5691
5731
  import { IconButton as IconButton2 } from "@hachej/boring-ui-kit";
5692
5732
 
5693
5733
  // src/front/chat/components/noticeStyles.ts
@@ -5847,20 +5887,41 @@ function ComposerBlockerNotice({
5847
5887
  className: noticeSurfaceClass("info", "mx-auto mb-2 w-full max-w-3xl text-xs"),
5848
5888
  children: [
5849
5889
  /* @__PURE__ */ jsx16("span", { children: label }),
5850
- blocker?.actions?.map((action) => /* @__PURE__ */ jsx16(
5851
- "button",
5852
- {
5853
- type: "button",
5854
- className: "ml-2 rounded border border-primary/30 px-2 py-0.5 text-[11px] font-medium hover:bg-primary/10",
5855
- onClick: () => onAction?.(blocker, action.id),
5856
- children: action.label
5857
- },
5858
- action.id
5859
- ))
5890
+ blocker?.actions?.map((action) => {
5891
+ const icon = composerBlockerActionIcon(action.id);
5892
+ return icon ? /* @__PURE__ */ jsx16(
5893
+ IconButton2,
5894
+ {
5895
+ type: "button",
5896
+ variant: "ghost",
5897
+ size: "icon-sm",
5898
+ className: "ml-1.5 h-6 w-6 rounded-md border border-primary/25 text-muted-foreground hover:bg-primary/10 hover:text-foreground",
5899
+ onClick: () => onAction?.(blocker, action.id),
5900
+ "aria-label": action.label,
5901
+ title: action.label,
5902
+ children: icon
5903
+ },
5904
+ action.id
5905
+ ) : /* @__PURE__ */ jsx16(
5906
+ "button",
5907
+ {
5908
+ type: "button",
5909
+ className: "ml-2 rounded border border-primary/30 px-2 py-0.5 text-[11px] font-medium hover:bg-primary/10",
5910
+ onClick: () => onAction?.(blocker, action.id),
5911
+ children: action.label
5912
+ },
5913
+ action.id
5914
+ );
5915
+ })
5860
5916
  ]
5861
5917
  }
5862
5918
  );
5863
5919
  }
5920
+ function composerBlockerActionIcon(actionId) {
5921
+ if (actionId === "open") return /* @__PURE__ */ jsx16(ExternalLinkIcon2, { className: "size-3.5", "aria-hidden": "true" });
5922
+ if (actionId === "cancel") return /* @__PURE__ */ jsx16(XIcon3, { className: "size-3.5", "aria-hidden": "true" });
5923
+ return null;
5924
+ }
5864
5925
  function QueuedComposerNotice({ followUps, onEdit }) {
5865
5926
  return /* @__PURE__ */ jsxs15(
5866
5927
  "div",
@@ -5911,7 +5972,7 @@ import {
5911
5972
  Music2Icon,
5912
5973
  PaperclipIcon,
5913
5974
  VideoIcon,
5914
- XIcon as XIcon3
5975
+ XIcon as XIcon4
5915
5976
  } from "lucide-react";
5916
5977
  import { createContext as createContext3, useCallback as useCallback9, useContext as useContext3, useMemo as useMemo5 } from "react";
5917
5978
  import { jsx as jsx17, jsxs as jsxs16 } from "react/jsx-runtime";
@@ -6125,7 +6186,7 @@ var AttachmentRemove = ({
6125
6186
  variant: "ghost",
6126
6187
  ...props,
6127
6188
  children: [
6128
- children ?? /* @__PURE__ */ jsx17(XIcon3, {}),
6189
+ children ?? /* @__PURE__ */ jsx17(XIcon4, {}),
6129
6190
  /* @__PURE__ */ jsx17("span", { className: "sr-only", children: label })
6130
6191
  ]
6131
6192
  }
@@ -8026,7 +8087,7 @@ import {
8026
8087
  Monitor,
8027
8088
  PlusIcon as PlusIcon2,
8028
8089
  SquareIcon,
8029
- XIcon as XIcon4
8090
+ XIcon as XIcon5
8030
8091
  } from "lucide-react";
8031
8092
  import { nanoid } from "nanoid";
8032
8093
  import { useCallback as useCallback15, useEffect as useEffect18, useMemo as useMemo10, useRef as useRef14, useState as useState17 } from "react";
@@ -8560,7 +8621,7 @@ var PromptInputSubmit = ({
8560
8621
  } else if (status === "streaming") {
8561
8622
  Icon = /* @__PURE__ */ jsx29(SquareIcon, { className: "size-4" });
8562
8623
  } else if (status === "error") {
8563
- Icon = /* @__PURE__ */ jsx29(XIcon4, { className: "size-4" });
8624
+ Icon = /* @__PURE__ */ jsx29(XIcon5, { className: "size-4" });
8564
8625
  }
8565
8626
  const handleClick = useCallback15(
8566
8627
  (e) => {
@@ -9157,7 +9218,7 @@ function getHeaderValue(headers, name) {
9157
9218
  }
9158
9219
 
9159
9220
  // src/front/chat/piChatPanelHooks.ts
9160
- import { useCallback as useCallback17, useEffect as useEffect20, useState as useState19, useSyncExternalStore } from "react";
9221
+ import { useCallback as useCallback17, useEffect as useEffect20, useMemo as useMemo12, useRef as useRef16, useState as useState19, useSyncExternalStore } from "react";
9161
9222
  function useExternalRemotePiSession({
9162
9223
  sessionId,
9163
9224
  workspaceId,
@@ -9169,13 +9230,19 @@ function useExternalRemotePiSession({
9169
9230
  remoteSessionOptions
9170
9231
  }) {
9171
9232
  const [session, setSession] = useState19();
9233
+ const remoteSessionOptionsRef = useRef16(remoteSessionOptions);
9234
+ remoteSessionOptionsRef.current = remoteSessionOptions;
9235
+ const remoteSessionOptionsKey = useMemo12(
9236
+ () => remoteSessionOptionsIdentity2(remoteSessionOptions),
9237
+ [remoteSessionOptions]
9238
+ );
9172
9239
  useEffect20(() => {
9173
9240
  if (!sessionId) {
9174
9241
  setSession(void 0);
9175
9242
  return;
9176
9243
  }
9177
9244
  const next = (createRemoteSession ?? createRemotePiSession)({
9178
- ...remoteSessionOptions,
9245
+ ...remoteSessionOptionsRef.current,
9179
9246
  sessionId,
9180
9247
  workspaceId,
9181
9248
  storageScope,
@@ -9185,9 +9252,43 @@ function useExternalRemotePiSession({
9185
9252
  });
9186
9253
  setSession(next);
9187
9254
  return () => next.dispose();
9188
- }, [apiBaseUrl, createRemoteSession, fetch2, remoteSessionOptions, requestHeaders, sessionId, storageScope, workspaceId]);
9255
+ }, [apiBaseUrl, createRemoteSession, fetch2, remoteSessionOptionsKey, requestHeaders, sessionId, storageScope, workspaceId]);
9189
9256
  return session;
9190
9257
  }
9258
+ var remoteSessionOptionObjectIds2 = /* @__PURE__ */ new WeakMap();
9259
+ var remoteSessionOptionObjectSeq2 = 0;
9260
+ function remoteSessionOptionObjectIdentity2(value) {
9261
+ if (typeof value !== "object" && typeof value !== "function" || value === null) return void 0;
9262
+ const object = value;
9263
+ let id = remoteSessionOptionObjectIds2.get(object);
9264
+ if (!id) {
9265
+ id = ++remoteSessionOptionObjectSeq2;
9266
+ remoteSessionOptionObjectIds2.set(object, id);
9267
+ }
9268
+ return String(id);
9269
+ }
9270
+ function remoteSessionOptionsIdentity2(options) {
9271
+ if (!options) return "{}";
9272
+ return JSON.stringify({
9273
+ autoStart: options.autoStart,
9274
+ requestTimeoutMs: options.requestTimeoutMs,
9275
+ onEvent: remoteSessionOptionObjectIdentity2(options.onEvent),
9276
+ storeOptions: remoteSessionOptionObjectIdentity2(options.storeOptions),
9277
+ setTimeoutFn: remoteSessionOptionObjectIdentity2(options.setTimeoutFn),
9278
+ clearTimeoutFn: remoteSessionOptionObjectIdentity2(options.clearTimeoutFn),
9279
+ reconnect: options.reconnect ? {
9280
+ baseMs: options.reconnect.baseMs,
9281
+ maxMs: options.reconnect.maxMs,
9282
+ jitterRatio: options.reconnect.jitterRatio,
9283
+ random: remoteSessionOptionObjectIdentity2(options.reconnect.random)
9284
+ } : void 0,
9285
+ debug: options.debug ? {
9286
+ largeStateWarningBytes: options.debug.largeStateWarningBytes,
9287
+ largeStateWarningMessages: options.debug.largeStateWarningMessages,
9288
+ onWarning: remoteSessionOptionObjectIdentity2(options.debug.onWarning)
9289
+ } : void 0
9290
+ });
9291
+ }
9191
9292
  function useRemotePiSessionState(session) {
9192
9293
  return useSyncExternalStore(
9193
9294
  useCallback17((listener) => session?.subscribe(listener) ?? (() => {
@@ -9371,14 +9472,14 @@ function PiChatPanel({
9371
9472
  }) {
9372
9473
  const externalSessionId = sessionId?.trim() || void 0;
9373
9474
  const showSessionSidebar = showSessions ?? externalSessionId === void 0;
9374
- const onDataRef = useRef16(onData);
9475
+ const onDataRef = useRef17(onData);
9375
9476
  onDataRef.current = onData;
9376
- const onTurnCompleteRef = useRef16(onTurnComplete);
9477
+ const onTurnCompleteRef = useRef17(onTurnComplete);
9377
9478
  onTurnCompleteRef.current = onTurnComplete;
9378
- const sessionListRefreshRef = useRef16(void 0);
9379
- const requestHeadersKey = useMemo12(() => headersContentKey(requestHeaders), [requestHeaders]);
9380
- const normalizedRequestHeaders = useMemo12(() => normalizedHeadersFromContentKey(requestHeadersKey), [requestHeadersKey]);
9381
- const remoteSessionOptionsWithEvents = useMemo12(() => ({
9479
+ const sessionListRefreshRef = useRef17(void 0);
9480
+ const requestHeadersKey = useMemo13(() => headersContentKey(requestHeaders), [requestHeaders]);
9481
+ const normalizedRequestHeaders = useMemo13(() => normalizedHeadersFromContentKey(requestHeadersKey), [requestHeadersKey]);
9482
+ const remoteSessionOptionsWithEvents = useMemo13(() => ({
9382
9483
  ...remoteSessionOptions,
9383
9484
  ...hydrateMessages ? {} : { autoStart: false },
9384
9485
  onEvent: (event) => {
@@ -9442,7 +9543,7 @@ function PiChatPanel({
9442
9543
  enabled: serverResourcesEnabled && availableModels === void 0
9443
9544
  });
9444
9545
  const selectedModel = model === void 0 ? modelDiscovery.model : model;
9445
- const modelOptions = useMemo12(
9546
+ const modelOptions = useMemo13(
9446
9547
  () => modelOptionsForSelection(availableModels ?? modelDiscovery.availableModels, selectedModel),
9447
9548
  [availableModels, modelDiscovery.availableModels, selectedModel]
9448
9549
  );
@@ -9474,13 +9575,13 @@ function PiChatPanel({
9474
9575
  const [modelPickerOpen, setModelPickerOpen] = useState20(false);
9475
9576
  const [thinkingPickerOpen, setThinkingPickerOpen] = useState20(false);
9476
9577
  const [draft, setDraft] = useState20(() => initialDraft ?? "");
9477
- const draftRef = useRef16(draft);
9578
+ const draftRef = useRef17(draft);
9478
9579
  draftRef.current = draft;
9479
- const initialDraftGuard = useRef16(new InitialDraftAutoSubmitGuard());
9480
- const pendingAutoSubmitSettleRef = useRef16(void 0);
9481
- const acceptedAutoSubmitSettleRef = useRef16(void 0);
9482
- const resetInProgressRef = useRef16(false);
9483
- const autoCreateInFlightRef = useRef16(false);
9580
+ const initialDraftGuard = useRef17(new InitialDraftAutoSubmitGuard());
9581
+ const pendingAutoSubmitSettleRef = useRef17(void 0);
9582
+ const acceptedAutoSubmitSettleRef = useRef17(void 0);
9583
+ const resetInProgressRef = useRef17(false);
9584
+ const autoCreateInFlightRef = useRef17(false);
9484
9585
  const settlePendingAutoSubmit = useCallback18((sessionId2) => {
9485
9586
  const pendingSessionId = pendingAutoSubmitSettleRef.current;
9486
9587
  if (!pendingSessionId || sessionId2 && pendingSessionId !== sessionId2) return false;
@@ -9489,19 +9590,19 @@ function PiChatPanel({
9489
9590
  onAutoSubmitInitialDraftSettled?.();
9490
9591
  return true;
9491
9592
  }, [onAutoSubmitInitialDraftSettled]);
9492
- const prevStatusRef = useRef16("idle");
9493
- const statusRef = useRef16("idle");
9494
- const scrollToBottomRef = useRef16(() => {
9593
+ const prevStatusRef = useRef17("idle");
9594
+ const statusRef = useRef17("idle");
9595
+ const scrollToBottomRef = useRef17(() => {
9495
9596
  });
9496
- const textareaRef = useRef16(null);
9597
+ const textareaRef = useRef17(null);
9497
9598
  const [localNotices, setLocalNotices] = useState20([]);
9498
9599
  const [dismissedNoticeIds, setDismissedNoticeIds] = useState20(() => /* @__PURE__ */ new Set());
9499
9600
  const [pluginUpdateState, setPluginUpdateState] = useState20(null);
9500
9601
  const [commandNotifyState, setCommandNotifyState] = useState20(null);
9501
- const commandRunIdRef = useRef16(0);
9602
+ const commandRunIdRef = useRef17(0);
9502
9603
  const [serverSkillsRefreshKey, setServerSkillsRefreshKey] = useState20(0);
9503
9604
  const [localSubmittedSessionId, setLocalSubmittedSessionId] = useState20();
9504
- const localSubmittedSessionRef = useRef16(void 0);
9605
+ const localSubmittedSessionRef = useRef17(void 0);
9505
9606
  const { attachmentNotice, setAttachmentNotice } = useAttachmentNotice();
9506
9607
  const markLocalSubmitted = useCallback18((sessionId2) => {
9507
9608
  localSubmittedSessionRef.current = sessionId2;
@@ -9512,7 +9613,7 @@ function PiChatPanel({
9512
9613
  localSubmittedSessionRef.current = void 0;
9513
9614
  setLocalSubmittedSessionId(void 0);
9514
9615
  }, []);
9515
- const registry = useMemo12(() => {
9616
+ const registry = useMemo13(() => {
9516
9617
  const excludedBuiltins = new Set(excludeBuiltinCommands);
9517
9618
  const effectiveBuiltins = builtinCommands.filter((command) => {
9518
9619
  if (!hotReloadEnabled && command.name === "reload") return false;
@@ -9533,19 +9634,22 @@ function PiChatPanel({
9533
9634
  refreshKey: serverSkillsRefreshKey,
9534
9635
  enabled: serverResourcesEnabled
9535
9636
  });
9536
- const allCommands = useMemo12(() => registry.list(), [registry, commandsStamp]);
9637
+ const allCommands = useMemo13(() => registry.list(), [registry, commandsStamp]);
9537
9638
  const activeChatSessionId = selectedChatState?.sessionId;
9538
9639
  const warmupNotice = composerNoticeForWarmup(workspaceWarmupStatus);
9539
9640
  const runtimeDependenciesNotice = composerNoticeForRuntimeDependencies(workspaceWarmupStatus);
9540
9641
  const workspaceWarmupBlocked = Boolean(warmupNotice);
9541
- const activeBlockers = useMemo12(
9542
- () => composerBlockers.filter((blocker) => !blocker.sessionId || blocker.sessionId === activeSessionId),
9642
+ const activeBlockers = useMemo13(
9643
+ // A missing active session id means a single/sessionless chat host. In that
9644
+ // mode, keep scoped blockers visible instead of hiding the only attention UI.
9645
+ // Multi-session hosts should pass a session id so unrelated blockers filter out.
9646
+ () => composerBlockers.filter((blocker) => !blocker.sessionId || !activeSessionId || blocker.sessionId === activeSessionId),
9543
9647
  [activeSessionId, composerBlockers]
9544
9648
  );
9545
9649
  const canonicalMessages = selectedChatState ? selectMessagesForRender(selectedChatState) : [];
9546
9650
  const queuePreview = selectedChatState ? selectQueuePreview(selectedChatState) : [];
9547
9651
  const messages = canonicalMessages;
9548
- const userHistory = useMemo12(() => selectComposerHistoryFromCanonicalUsers(canonicalMessages), [canonicalMessages]);
9652
+ const userHistory = useMemo13(() => selectComposerHistoryFromCanonicalUsers(canonicalMessages), [canonicalMessages]);
9549
9653
  const emptyStateHydrating = statusForState(selectedChatState, sessionsLoading || chatStatePending || selectedSessionPending) === "hydrating";
9550
9654
  const emptyHero = emptyPlacement === "hero" && messages.length === 0 && queuePreview.length === 0 && !emptyStateHydrating;
9551
9655
  const debugState = selectedPiSession?.getDebugState();
@@ -9553,7 +9657,7 @@ function PiChatPanel({
9553
9657
  const primaryComposerBlocker = activeBlockers[0];
9554
9658
  const composerBlockerLabel = workspaceWarmupBlocked ? warmupNotice?.title ?? "Preparing workspace..." : primaryComposerBlocker?.label ?? primaryComposerBlocker?.reason ?? "Complete the pending workspace action to continue.";
9555
9659
  const composerStatusNotice = warmupNotice ?? runtimeDependenciesNotice;
9556
- const runtimeNotices = useMemo12(() => {
9660
+ const runtimeNotices = useMemo13(() => {
9557
9661
  const fromState = selectedChatState ? selectRuntimeNotices(selectedChatState) : [];
9558
9662
  const sessionNotice = sessionsError ? [{ id: "session-navigation-error", level: "error", text: sessionsError.message, dismissible: true }] : [];
9559
9663
  const largeStateNotice = debug && debugState?.largeStateWarning ? [{
@@ -9772,7 +9876,7 @@ function PiChatPanel({
9772
9876
  }
9773
9877
  insertSlashCommand(name);
9774
9878
  }, [dismissSlash, insertSlashCommand, openModelPicker, openThinkingPicker, setComposerDraft]);
9775
- const policy = useMemo12(() => {
9879
+ const policy = useMemo13(() => {
9776
9880
  if (!selectedPiSession || !activeChatSessionId) return void 0;
9777
9881
  const policySession = {
9778
9882
  getState: () => {
@@ -9981,8 +10085,8 @@ function PiChatPanel({
9981
10085
  const isStreaming = isPiBusyStatus(status);
9982
10086
  const submitStatus = toPromptSubmitStatus(status);
9983
10087
  const submitDisabled = !policy || sessionsLoading || composerBlocked && !isStreaming;
9984
- const mergedToolRenderers = useMemo12(() => mergeShadcnToolRenderers(toolRenderers), [toolRenderers]);
9985
- const debugMessages = useMemo12(() => messages.map(toDebugUiMessage), [messages]);
10088
+ const mergedToolRenderers = useMemo13(() => mergeShadcnToolRenderers(toolRenderers), [toolRenderers]);
10089
+ const debugMessages = useMemo13(() => messages.map(toDebugUiMessage), [messages]);
9986
10090
  const onTextareaChange = useCallback18((event) => {
9987
10091
  setModelPickerOpen(false);
9988
10092
  setThinkingPickerOpen(false);
@@ -889,6 +889,12 @@
889
889
  border-color: color-mix(in oklab, var(--input) 60%, transparent);
890
890
  }
891
891
  }
892
+ .border-primary\/25 {
893
+ border-color: var(--primary);
894
+ @supports (color: color-mix(in lab, red, red)) {
895
+ border-color: color-mix(in oklab, var(--primary) 25%, transparent);
896
+ }
897
+ }
892
898
  .border-primary\/30 {
893
899
  border-color: var(--primary);
894
900
  @supports (color: color-mix(in lab, red, red)) {
@@ -1,5 +1,5 @@
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';
1
+ import { o as WorkspaceRuntimeContext, S as Sandbox, g as SandboxHandleStore, f as SandboxHandleRecord, W as Workspace, j as TelemetrySink, q as ToolReadinessRequirement, F as FileSearch, i as Stat, E as Entry, c as ExecResult, r as WorkspaceChangeEvent, b as ExecOptions, P as PluginRestartWarning, A as AgentTool, t as AgentHarnessFactory } from '../agentPluginEvents-Ddn5DQ5E.js';
2
+ export { u as AgentHarnessFactoryInput } from '../agentPluginEvents-Ddn5DQ5E.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';
@@ -317,8 +317,107 @@ interface ProvisionWorkspaceRuntimeOptions {
317
317
 
318
318
  declare function provisionWorkspaceRuntime(opts: ProvisionWorkspaceRuntimeOptions): Promise<WorkspaceProvisioningResult>;
319
319
 
320
+ type ReadyState = 'provisioning' | 'ready' | 'degraded';
321
+ type CapabilityState = 'not-started' | 'preparing' | 'ready' | 'failed';
322
+ interface CapabilityReadinessDetail {
323
+ state: CapabilityState;
324
+ requirement?: ToolReadinessRequirement;
325
+ startedAt?: string;
326
+ completedAt?: string;
327
+ errorCode?: string;
328
+ causeCode?: string;
329
+ retryable?: boolean;
330
+ message?: string;
331
+ }
332
+ interface AgentCapabilityReadiness {
333
+ chat: CapabilityReadinessDetail;
334
+ workspace: CapabilityReadinessDetail;
335
+ runtimeDependencies: CapabilityReadinessDetail;
336
+ }
337
+ interface ReadyStatusEvent {
338
+ state: ReadyState;
339
+ sandboxReady: boolean;
340
+ harnessReady: boolean;
341
+ capabilities: AgentCapabilityReadiness;
342
+ message?: string;
343
+ timestamp: string;
344
+ }
345
+ interface ReadinessSnapshot {
346
+ sandboxReady: boolean;
347
+ harnessReady: boolean;
348
+ capabilities: AgentCapabilityReadiness;
349
+ degradedReason?: string;
350
+ }
351
+ type StatusHandler = (event: ReadyStatusEvent) => void;
352
+ declare class ReadyStatusTracker {
353
+ private _sandboxReady;
354
+ private _harnessReady;
355
+ private _degradedReason?;
356
+ private _capabilities;
357
+ private subscribers;
358
+ constructor(opts?: {
359
+ sandboxReady?: boolean;
360
+ harnessReady?: boolean;
361
+ capabilities?: Partial<AgentCapabilityReadiness>;
362
+ });
363
+ get state(): ReadyState;
364
+ isReady(): boolean;
365
+ getReadiness(): ReadinessSnapshot;
366
+ getCapabilities(): AgentCapabilityReadiness;
367
+ updateCapability(name: keyof AgentCapabilityReadiness, detail: CapabilityReadinessDetail): void;
368
+ updateRuntimeDependencies(detail: CapabilityReadinessDetail): void;
369
+ markSandboxReady(): void;
370
+ markHarnessReady(): void;
371
+ markDegraded(reason: string): void;
372
+ clearDegraded(): void;
373
+ subscribe(handler: StatusHandler): () => void;
374
+ private emit;
375
+ private snapshot;
376
+ }
377
+
320
378
  type BuiltinRuntimeModeId = 'direct' | 'local' | 'vercel-sandbox';
321
379
  type RuntimeModeId = BuiltinRuntimeModeId | (string & {});
380
+ interface RuntimeModeReadinessHooks {
381
+ initialSandboxReady?: boolean;
382
+ initialWorkspaceReadiness?: CapabilityReadinessDetail;
383
+ onTrackerCreated?: (tracker: ReadyStatusTracker) => void;
384
+ }
385
+ type RuntimeCachedBindingHealthCheckResult = {
386
+ state: 'ok';
387
+ } | {
388
+ state: 'recreate';
389
+ message?: string;
390
+ error?: unknown;
391
+ };
392
+ interface RuntimeCachedBindingHealthCheck {
393
+ intervalMs?: number;
394
+ check(ctx: {
395
+ runtimeBundle: RuntimeBundle;
396
+ workspaceId: string;
397
+ }): Promise<RuntimeCachedBindingHealthCheckResult>;
398
+ }
399
+ type RuntimeBashStrategy = {
400
+ kind: 'host';
401
+ preserveHostHome?: boolean;
402
+ } | {
403
+ kind: 'local-sandbox';
404
+ sandboxRoot: string;
405
+ } | {
406
+ kind: 'remote';
407
+ defaultPath?: string;
408
+ };
409
+ interface RuntimeRemoteWorkspacePathOptions {
410
+ rootAliases?: string[];
411
+ toRemotePath?: (value: string) => string;
412
+ toRuntimePath?: (value: string) => string;
413
+ sanitizeErrorText?: (value: string) => string;
414
+ }
415
+ type RuntimeFilesystemStrategy = {
416
+ kind: 'host';
417
+ } | {
418
+ kind: 'remote-workspace';
419
+ pathOptions?: RuntimeRemoteWorkspacePathOptions;
420
+ };
322
421
  interface RuntimeModeAdapter {
323
422
  readonly id: RuntimeModeId;
324
423
  /**
@@ -327,8 +426,14 @@ interface RuntimeModeAdapter {
327
426
  * host-side fs checks/prompts are safe without hard-coding sandbox IDs.
328
427
  */
329
428
  readonly workspaceFsCapability?: Workspace['fsCapability'];
429
+ readonly readiness?: RuntimeModeReadinessHooks;
430
+ readonly cachedBindingHealthCheck?: RuntimeCachedBindingHealthCheck;
330
431
  create(ctx: ModeContext): Promise<RuntimeBundle>;
331
432
  createProvisioningAdapter?(runtimeLayout: BoringAgentRuntimePaths, ctx?: ModeContext): WorkspaceProvisioningAdapter;
433
+ getRuntimeLayoutRoot?(ctx: ModeContext): string;
434
+ evictCachedRuntime?(ctx: {
435
+ workspaceId: string;
436
+ }): void;
332
437
  dispose?(): Promise<void>;
333
438
  }
334
439
  interface ModeContext {
@@ -350,6 +455,12 @@ interface RuntimeBundle {
350
455
  workspace: Workspace;
351
456
  sandbox: Sandbox;
352
457
  fileSearch: FileSearch;
458
+ /** Optional per-execution runtime env provider for local/direct operations that do not call Sandbox.exec. */
459
+ getRuntimeEnv?: () => Promise<Record<string, string>>;
460
+ /** Runtime-owned bash execution strategy, consumed by the agent bash tool builder. */
461
+ bash?: RuntimeBashStrategy;
462
+ /** Runtime-owned filesystem strategy, consumed by the agent filesystem tool builder. */
463
+ filesystem?: RuntimeFilesystemStrategy;
353
464
  }
354
465
 
355
466
  declare const REMOTE_WORKER_RUNTIME_CWD = "/workspace";
@@ -578,7 +689,10 @@ declare function createVercelProvisioningAdapter(options: CreateVercelProvisioni
578
689
 
579
690
  declare function hasBwrap(): boolean;
580
691
  declare function autoDetectMode(): RuntimeModeId;
581
- declare function resolveMode(mode?: RuntimeModeId): RuntimeModeAdapter;
692
+ interface ResolveModeOptions {
693
+ sandboxHandleStore?: SandboxHandleStore;
694
+ }
695
+ declare function resolveMode(mode?: RuntimeModeId, opts?: ResolveModeOptions): RuntimeModeAdapter;
582
696
 
583
697
  interface ReloadHookDiagnostic {
584
698
  source: string;
@@ -597,6 +711,17 @@ interface ReloadHookResult {
597
711
  diagnostics?: ReadonlyArray<ReloadHookDiagnostic>;
598
712
  }
599
713
 
714
+ interface RuntimeEnvContributionContext {
715
+ workspaceId: string;
716
+ workspaceRoot: string;
717
+ runtimeMode: RuntimeModeId;
718
+ runtimeBundle: RuntimeBundle;
719
+ }
720
+ interface RuntimeEnvContribution {
721
+ id: string;
722
+ getEnv(ctx: RuntimeEnvContributionContext): Record<string, string> | Promise<Record<string, string>>;
723
+ }
724
+
600
725
  type PiPackageSource = PackageSource;
601
726
  declare const PI_PACKAGE_RESOURCE_FILTERS: readonly ["extensions", "skills", "prompts", "themes"];
602
727
  declare function piPackageSourceKey(source: PiPackageSource): string;
@@ -768,6 +893,14 @@ interface CreateAgentAppOptions {
768
893
  telemetry?: TelemetrySink;
769
894
  /** Optional billing sink for native Pi usage (see AgentMeteringSink). */
770
895
  metering?: AgentMeteringSink;
896
+ /** Generic runtime env contributors. Agent stays workspace-neutral; hosts decide env names/values. */
897
+ runtimeEnvContributions?: RuntimeEnvContribution[];
898
+ /** Runtime-aware provisioning hook. Runs after Workspace/Sandbox creation and before tools/harness. */
899
+ runtimeProvisioner?: (ctx: {
900
+ workspaceRoot: string;
901
+ runtimeMode: RuntimeModeId;
902
+ runtimeBundle: RuntimeBundle;
903
+ }) => Promise<void>;
771
904
  /** Optional explicit file-backed session directory. Mostly for tests/hosts. */
772
905
  sessionDir?: string;
773
906
  /**
@@ -849,6 +982,8 @@ interface RegisterAgentRoutesOptions {
849
982
  request?: FastifyRequest;
850
983
  }) => PiHarnessOptions | undefined | Promise<PiHarnessOptions | undefined>;
851
984
  sessionNamespace?: string;
985
+ /** Optional explicit root for file-backed Pi chat transcript storage. */
986
+ sessionRoot?: string;
852
987
  /** Optional best-effort telemetry sink supplied by an embedding host. */
853
988
  telemetry?: TelemetrySink;
854
989
  /**
@@ -873,6 +1008,8 @@ interface RegisterAgentRoutesOptions {
873
1008
  sandboxHandleStore?: SandboxHandleStore;
874
1009
  getWorkspaceId?: (request: FastifyRequest) => string | Promise<string>;
875
1010
  getWorkspaceRoot?: (workspaceId: string, request: FastifyRequest) => string | Promise<string>;
1011
+ /** Generic runtime env contributors. Agent stays workspace-neutral; hosts decide env names/values. */
1012
+ runtimeEnvContributions?: RuntimeEnvContribution[];
876
1013
  /**
877
1014
  * Optional runtime reconciliation hook. Callers own plugin discovery and may
878
1015
  * call provisionWorkspaceRuntime() with the normalized structural inputs.
@@ -927,4 +1064,4 @@ interface Logger {
927
1064
  }
928
1065
  declare function createLogger(prefix: string): Logger;
929
1066
 
930
- export { AgentHarnessFactory, type AgentMeteringSink, type BoringAgentRuntimePaths, type BuiltinRuntimeModeId, type BwrapResourceLimits, type CreateAgentAppOptions, type CreateBwrapSandboxOptions, type CreateVercelProvisioningAdapterOptions, type DeploymentSnapshotProvider, type DeploymentSnapshotRecipe, type DeploymentSnapshotResult, type DeploymentSnapshotStatus, FileHandleStore, type LogFields, type Logger, type MeteringErrorLogger, type MeteringReleaseInput, type MeteringReleaseReason, type MeteringReservationResult, type MeteringReserveInput, type MeteringRunKind, type MeteringRunScope, type MeteringRunStatus, type MeteringSettleInput, type MeteringUsage, type MeteringUsageInput, type ModeContext, PI_PACKAGE_RESOURCE_FILTERS, type PiExtensionFactory, type PiHarnessOptions, type PiPackageSource, type PluginSkillSource, type ProvisionRuntimeWorkspaceOptions, type ProvisionWorkspaceRuntimeOptions, type ProvisioningArtifactRequest, REMOTE_WORKER_PROVIDER, REMOTE_WORKER_RUNTIME_CWD, type RegisterAgentRoutesOptions, RemoteWorkerClient, RemoteWorkerClientError, type RemoteWorkerClientOptions, type RemoteWorkerErrorPayload, type RemoteWorkerExecRequest, type RemoteWorkerExecResponse, type RemoteWorkerFsEventEnvelope, type RemoteWorkerModeAdapterOptions, type RemoteWorkerWorkspaceOp, type RemoteWorkerWorkspaceResult, type RuntimeBundle, type RuntimeModeAdapter, type RuntimeModeId, type RuntimeNodePackageSpec$1 as RuntimeNodePackageSpec, type RuntimeProvisioningContribution$1 as RuntimeProvisioningContribution, type RuntimePythonSpec$1 as RuntimePythonSpec, type RuntimeTemplateContribution$1 as RuntimeTemplateContribution, type RuntimeWorkspaceProvisioningResult, type SnapshotBakeOptions, type SnapshotBakeResult, UV_SETUP_COMMANDS, VERCEL_PROVISIONING_CACHE_ROOT, VERCEL_SANDBOX_WORKSPACE_ROOT, UV_SETUP_COMMANDS as VERCEL_UV_SETUP_COMMANDS, type VercelBakeClient, type VercelBakeSandbox, type VercelDeploymentSnapshotOptions, WORKER_INTERNAL_TOKEN_HEADER, WORKER_REQUEST_ID_HEADER, WORKER_WORKSPACE_ID_HEADER, type WorkspaceProvisioningAdapter, type WorkspaceProvisioningExecResult, type WorkspaceProvisioningResult, applyCspHeaders, autoDetectMode, bakeSnapshotIfNeeded, buildDeploymentSnapshotRecipe, buildPackageHash, buildSnapshotRecipeHash, compactPiPackages, constantTimeTokenEqual, createAgentApp, createBwrapSandbox, createDirectSandbox, createLogger, createNodeWorkspace, createRemoteWorkerModeAdapter, createRemoteWorkerSandbox, createRemoteWorkerWorkspace, createResourceSettingsManager, createVercelDeploymentSnapshotProvider, createVercelProvisioningAdapter, createVercelSandboxWorkspace, decodeBytesFromWorker, encodeBytesForWorker, fileRoutes, getBoringAgentPathEntries, getBoringAgentRuntimeEnv, getBoringAgentRuntimePaths, hasBwrap, mergePiPackageSources, normalizeMeteringUsage, piPackageSourceKey, prepareDeploymentSnapshot, prepareVercelDeploymentSnapshot, provisionRuntimeWorkspace, provisionWorkspaceRuntime, registerAgentRoutes, resolveMode, resolveSandboxHandle };
1067
+ export { AgentHarnessFactory, type AgentMeteringSink, type BoringAgentRuntimePaths, type BuiltinRuntimeModeId, type BwrapResourceLimits, type CreateAgentAppOptions, type CreateBwrapSandboxOptions, type CreateVercelProvisioningAdapterOptions, type DeploymentSnapshotProvider, type DeploymentSnapshotRecipe, type DeploymentSnapshotResult, type DeploymentSnapshotStatus, FileHandleStore, type LogFields, type Logger, type MeteringErrorLogger, type MeteringReleaseInput, type MeteringReleaseReason, type MeteringReservationResult, type MeteringReserveInput, type MeteringRunKind, type MeteringRunScope, type MeteringRunStatus, type MeteringSettleInput, type MeteringUsage, type MeteringUsageInput, type ModeContext, PI_PACKAGE_RESOURCE_FILTERS, type PiExtensionFactory, type PiHarnessOptions, type PiPackageSource, type PluginSkillSource, type ProvisionRuntimeWorkspaceOptions, type ProvisionWorkspaceRuntimeOptions, type ProvisioningArtifactRequest, REMOTE_WORKER_PROVIDER, REMOTE_WORKER_RUNTIME_CWD, type RegisterAgentRoutesOptions, RemoteWorkerClient, RemoteWorkerClientError, type RemoteWorkerClientOptions, type RemoteWorkerErrorPayload, type RemoteWorkerExecRequest, type RemoteWorkerExecResponse, type RemoteWorkerFsEventEnvelope, type RemoteWorkerModeAdapterOptions, type RemoteWorkerWorkspaceOp, type RemoteWorkerWorkspaceResult, type RuntimeBundle, type RuntimeEnvContribution, type RuntimeEnvContributionContext, type RuntimeModeAdapter, type RuntimeModeId, type RuntimeNodePackageSpec$1 as RuntimeNodePackageSpec, type RuntimeProvisioningContribution$1 as RuntimeProvisioningContribution, type RuntimePythonSpec$1 as RuntimePythonSpec, type RuntimeTemplateContribution$1 as RuntimeTemplateContribution, type RuntimeWorkspaceProvisioningResult, type SnapshotBakeOptions, type SnapshotBakeResult, UV_SETUP_COMMANDS, VERCEL_PROVISIONING_CACHE_ROOT, VERCEL_SANDBOX_WORKSPACE_ROOT, UV_SETUP_COMMANDS as VERCEL_UV_SETUP_COMMANDS, type VercelBakeClient, type VercelBakeSandbox, type VercelDeploymentSnapshotOptions, WORKER_INTERNAL_TOKEN_HEADER, WORKER_REQUEST_ID_HEADER, WORKER_WORKSPACE_ID_HEADER, type WorkspaceProvisioningAdapter, type WorkspaceProvisioningExecResult, type WorkspaceProvisioningResult, applyCspHeaders, autoDetectMode, bakeSnapshotIfNeeded, buildDeploymentSnapshotRecipe, buildPackageHash, buildSnapshotRecipeHash, compactPiPackages, constantTimeTokenEqual, createAgentApp, createBwrapSandbox, createDirectSandbox, createLogger, createNodeWorkspace, createRemoteWorkerModeAdapter, createRemoteWorkerSandbox, createRemoteWorkerWorkspace, createResourceSettingsManager, createVercelDeploymentSnapshotProvider, createVercelProvisioningAdapter, createVercelSandboxWorkspace, decodeBytesFromWorker, encodeBytesForWorker, fileRoutes, getBoringAgentPathEntries, getBoringAgentRuntimeEnv, getBoringAgentRuntimePaths, hasBwrap, mergePiPackageSources, normalizeMeteringUsage, piPackageSourceKey, prepareDeploymentSnapshot, prepareVercelDeploymentSnapshot, provisionRuntimeWorkspace, provisionWorkspaceRuntime, registerAgentRoutes, resolveMode, resolveSandboxHandle };