@hachej/boring-agent 0.1.41 → 0.1.43

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.
Files changed (43) hide show
  1. package/README.md +31 -252
  2. package/dist/{DebugDrawer-2PUDZ2RL.js → DebugDrawer-RYEBQ6CO.js} +1 -1
  3. package/dist/{agentPluginEvents-CKrZLW3g.d.ts → agentPluginEvents-DP-vLNCs.d.ts} +36 -3
  4. package/dist/{session-BRovhe0D.d.ts → chatSubmitPayload-gC61O4Ni.d.ts} +20 -1
  5. package/dist/{chunk-IUJ22EFB.js → chunk-BOIV2I3N.js} +2 -10
  6. package/dist/{chunk-VQ7VSSSX.js → chunk-YUDMSYAO.js} +6 -1
  7. package/dist/front/index.d.ts +22 -5
  8. package/dist/front/index.js +155 -103
  9. package/dist/front/styles.css +0 -6
  10. package/dist/piChatEvent-DR9a-FVz.d.ts +306 -0
  11. package/dist/server/index.d.ts +363 -94
  12. package/dist/server/index.js +1611 -414
  13. package/dist/shared/index.d.ts +29 -23
  14. package/dist/shared/index.js +1 -1
  15. package/docs/ACCESSIBILITY.md +11 -13
  16. package/docs/API.md +86 -59
  17. package/docs/CSP.md +10 -9
  18. package/docs/ERROR_CODES.md +5 -1
  19. package/docs/KNOWN_LIMITATIONS.md +3 -3
  20. package/docs/MIGRATION.md +16 -16
  21. package/docs/PERFORMANCE.md +1 -1
  22. package/docs/PLUGINS.md +42 -12
  23. package/docs/README.md +89 -12
  24. package/docs/RISKS-MULTI-TAB.md +1 -1
  25. package/docs/STYLING.md +6 -4
  26. package/docs/UI-SHADCN.md +7 -7
  27. package/docs/runtime-provisioning.md +7 -3
  28. package/docs/runtime.md +68 -13
  29. package/docs/tools.md +37 -14
  30. package/package.json +2 -2
  31. package/dist/piChatEvent-Ck1BAE_m.d.ts +0 -323
  32. /package/docs/plans/{AGENT_EVAL_FRAMEWORK.md → archive/AGENT_EVAL_FRAMEWORK.md} +0 -0
  33. /package/docs/plans/{agent-package-spec.md → archive/agent-package-spec.md} +0 -0
  34. /package/docs/plans/{harness-followup-capabilities.md → archive/harness-followup-capabilities.md} +0 -0
  35. /package/docs/plans/{harness-tool-ui-capabilities.md → archive/harness-tool-ui-capabilities.md} +0 -0
  36. /package/docs/plans/{pi-followup-history-projection.md → archive/pi-followup-history-projection.md} +0 -0
  37. /package/docs/plans/{pi-tools-migration.md → archive/pi-tools-migration.md} +0 -0
  38. /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-codex-review.md +0 -0
  39. /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-gpt-review.md +0 -0
  40. /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-opus-review.md +0 -0
  41. /package/docs/plans/{reviews → archive/reviews}/pi-followup-history-xai-review.md +0 -0
  42. /package/docs/plans/{vercel-base-snapshot-template-plan.md → archive/vercel-base-snapshot-template-plan.md} +0 -0
  43. /package/docs/plans/{vercel-persistent-sandbox-adapter.md → archive/vercel-persistent-sandbox-adapter.md} +0 -0
@@ -13,12 +13,12 @@ import {
13
13
  StopReceiptSchema,
14
14
  extractToolUiMetadata,
15
15
  sanitizeToolUiMetadata
16
- } from "../chunk-VQ7VSSSX.js";
16
+ } from "../chunk-YUDMSYAO.js";
17
17
  import {
18
18
  DebugDrawer,
19
19
  cn,
20
20
  copyTextToClipboard
21
- } from "../chunk-IUJ22EFB.js";
21
+ } from "../chunk-BOIV2I3N.js";
22
22
 
23
23
  // src/front/upload/uploadFile.ts
24
24
  function readAsDataUrl(file) {
@@ -2558,10 +2558,8 @@ function writePiComposerThinking(value, options = {}) {
2558
2558
  function writePiComposerShowThoughts(value, options = {}) {
2559
2559
  writeStorage(options, "show-thoughts", value ? "1" : "0");
2560
2560
  }
2561
- function modelOptionsForSelection(options, selected) {
2562
- if (!selected) return options.filter((model) => model.available);
2563
- const available = options.filter((model) => model.available);
2564
- return available.some((model) => model.provider === selected.provider && model.id === selected.id) ? available : [{ ...selected, label: selected.id, available: true }, ...available];
2561
+ function modelOptionsForSelection(options, _selected) {
2562
+ return options.filter((model) => model.available);
2565
2563
  }
2566
2564
  function readStoredScopedModel(options) {
2567
2565
  const userSelected = readStorage(options, "model:user-selected") === "1";
@@ -3348,48 +3346,6 @@ function messageCreatedAtMs(message) {
3348
3346
  return Number.isFinite(timestamp) ? timestamp : void 0;
3349
3347
  }
3350
3348
 
3351
- // src/front/chat/session/activeSessionStorage.ts
3352
- var ACTIVE_SESSION_KEY_PREFIX = "boring-agent:v2";
3353
- var ACTIVE_SESSION_KEY_SUFFIX = "activeSessionId";
3354
- var DEFAULT_STORAGE_SCOPE2 = "default";
3355
- function activeSessionStorageKey(storageScope) {
3356
- const scope = storageScope && storageScope.length > 0 ? storageScope : DEFAULT_STORAGE_SCOPE2;
3357
- return `${ACTIVE_SESSION_KEY_PREFIX}:${scope}:${ACTIVE_SESSION_KEY_SUFFIX}`;
3358
- }
3359
- function readActiveSessionId(options = {}) {
3360
- const storage = resolveStorage2(options.storage);
3361
- if (!storage) return void 0;
3362
- try {
3363
- return storage.getItem(activeSessionStorageKey(options.storageScope)) ?? void 0;
3364
- } catch {
3365
- return void 0;
3366
- }
3367
- }
3368
- function writeActiveSessionId(sessionId, options = {}) {
3369
- const storage = resolveStorage2(options.storage);
3370
- if (!storage) return;
3371
- try {
3372
- const key = activeSessionStorageKey(options.storageScope);
3373
- if (sessionId === void 0 || sessionId.length === 0) storage.removeItem(key);
3374
- else storage.setItem(key, sessionId);
3375
- } catch {
3376
- }
3377
- }
3378
- function clearActiveSessionId(options = {}) {
3379
- writeActiveSessionId(void 0, options);
3380
- }
3381
- function resolveStorage2(storage) {
3382
- if (storage) return storage;
3383
- try {
3384
- return globalThis.localStorage;
3385
- } catch {
3386
- return void 0;
3387
- }
3388
- }
3389
-
3390
- // src/front/chat/session/usePiSessions.ts
3391
- import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo3, useRef as useRef8, useState as useState10 } from "react";
3392
-
3393
3349
  // src/front/chat/pi/piChatAssistantCommit.ts
3394
3350
  function commitFinalMessage(state, messageId, final) {
3395
3351
  const plan = buildAssistantCommitPlan(state, messageId, final);
@@ -4783,7 +4739,7 @@ var RemotePiSession = class {
4783
4739
  try {
4784
4740
  const response = await this.fetchImpl(url, { ...init, signal: controller.signal });
4785
4741
  const body = await safeReadJson(response);
4786
- if (!response.ok) throw new RemotePiSessionHttpError(response.status, routeErrorMessage(body, `HTTP ${response.status}`), body);
4742
+ if (!response.ok) throw new RemotePiSessionHttpError(response.status, routeErrorMessage(body, `HTTP ${response.status}`), body, routeErrorCode(body));
4787
4743
  return body;
4788
4744
  } catch (error) {
4789
4745
  if (timedOut) throw new Error(`Request to ${url} timed out after ${this.requestTimeoutMs}ms.`);
@@ -4858,15 +4814,22 @@ function createRemotePiSession(options) {
4858
4814
  return new RemotePiSession(options);
4859
4815
  }
4860
4816
  var RemotePiSessionHttpError = class extends Error {
4861
- constructor(status, message, body) {
4817
+ constructor(status, message, body, errorCode) {
4862
4818
  super(message);
4863
4819
  this.status = status;
4864
4820
  this.body = body;
4821
+ this.errorCode = errorCode;
4865
4822
  this.name = "RemotePiSessionHttpError";
4866
4823
  }
4867
4824
  status;
4868
4825
  body;
4826
+ errorCode;
4869
4827
  };
4828
+ function piChatErrorCode(error) {
4829
+ if (error instanceof RemotePiSessionHttpError) return error.errorCode;
4830
+ const parsed = ErrorCode.safeParse(error?.errorCode);
4831
+ return parsed.success ? parsed.data : void 0;
4832
+ }
4870
4833
  function toOptimisticUserMessage(payload) {
4871
4834
  const displayText = payload.displayMessage ?? payload.message;
4872
4835
  return {
@@ -4903,6 +4866,13 @@ function routeErrorMessage(body, fallback) {
4903
4866
  const payload = typeof error === "object" && error !== null ? error : body;
4904
4867
  return typeof payload.message === "string" && payload.message ? payload.message : fallback;
4905
4868
  }
4869
+ function routeErrorCode(body) {
4870
+ if (typeof body !== "object" || body === null) return void 0;
4871
+ const error = body.error;
4872
+ const payload = typeof error === "object" && error !== null ? error : body;
4873
+ const parsed = ErrorCode.safeParse(payload.code);
4874
+ return parsed.success ? parsed.data : void 0;
4875
+ }
4906
4876
  function errorMessage(error, fallback) {
4907
4877
  return error instanceof Error && error.message ? error.message : fallback;
4908
4878
  }
@@ -4957,7 +4927,47 @@ function clearPageUnloading() {
4957
4927
  pageUnloading = false;
4958
4928
  }
4959
4929
 
4930
+ // src/front/chat/session/activeSessionStorage.ts
4931
+ var ACTIVE_SESSION_KEY_PREFIX = "boring-agent:v2";
4932
+ var ACTIVE_SESSION_KEY_SUFFIX = "activeSessionId";
4933
+ var DEFAULT_STORAGE_SCOPE2 = "default";
4934
+ function activeSessionStorageKey(storageScope) {
4935
+ const scope = storageScope && storageScope.length > 0 ? storageScope : DEFAULT_STORAGE_SCOPE2;
4936
+ return `${ACTIVE_SESSION_KEY_PREFIX}:${scope}:${ACTIVE_SESSION_KEY_SUFFIX}`;
4937
+ }
4938
+ function readActiveSessionId(options = {}) {
4939
+ const storage = resolveStorage2(options.storage);
4940
+ if (!storage) return void 0;
4941
+ try {
4942
+ return storage.getItem(activeSessionStorageKey(options.storageScope)) ?? void 0;
4943
+ } catch {
4944
+ return void 0;
4945
+ }
4946
+ }
4947
+ function writeActiveSessionId(sessionId, options = {}) {
4948
+ const storage = resolveStorage2(options.storage);
4949
+ if (!storage) return;
4950
+ try {
4951
+ const key = activeSessionStorageKey(options.storageScope);
4952
+ if (sessionId === void 0 || sessionId.length === 0) storage.removeItem(key);
4953
+ else storage.setItem(key, sessionId);
4954
+ } catch {
4955
+ }
4956
+ }
4957
+ function clearActiveSessionId(options = {}) {
4958
+ writeActiveSessionId(void 0, options);
4959
+ }
4960
+ function resolveStorage2(storage) {
4961
+ if (storage) return storage;
4962
+ try {
4963
+ return globalThis.localStorage;
4964
+ } catch {
4965
+ return void 0;
4966
+ }
4967
+ }
4968
+
4960
4969
  // src/front/chat/session/usePiSessions.ts
4970
+ import { useCallback as useCallback7, useEffect as useEffect8, useMemo as useMemo3, useRef as useRef8, useState as useState10 } from "react";
4961
4971
  var DEFAULT_SESSIONS_API_PATH = "/api/v1/agent/pi-chat/sessions";
4962
4972
  var SESSION_PAGE_SIZE = 50;
4963
4973
  var DEFAULT_MAX_RETRIES = 60;
@@ -5637,7 +5647,7 @@ import { memo as memo2 } from "react";
5637
5647
  import { AlertTriangleIcon, InfoIcon, Loader2Icon, PlugZapIcon, RefreshCwIcon, XIcon as XIcon2 } from "lucide-react";
5638
5648
  import { Button as Button8 } from "@hachej/boring-ui-kit";
5639
5649
  import { jsx as jsx15, jsxs as jsxs14 } from "react/jsx-runtime";
5640
- var RuntimeNotices = memo2(({ notices, onDismiss, onAction, className, ...props }) => {
5650
+ var RuntimeNotices = memo2(({ notices, onDismiss, onAction, renderAction, className, ...props }) => {
5641
5651
  if (notices.length === 0) return null;
5642
5652
  return /* @__PURE__ */ jsx15(
5643
5653
  "div",
@@ -5645,15 +5655,16 @@ var RuntimeNotices = memo2(({ notices, onDismiss, onAction, className, ...props
5645
5655
  "data-boring-agent-part": "runtime-notices",
5646
5656
  className: cn("flex flex-col gap-2 px-4 py-2", className),
5647
5657
  ...props,
5648
- children: notices.map((notice) => /* @__PURE__ */ jsx15(RuntimeNoticeRow, { notice, onDismiss, onAction }, notice.id))
5658
+ children: notices.map((notice) => /* @__PURE__ */ jsx15(RuntimeNoticeRow, { notice, onDismiss, onAction, renderAction }, notice.id))
5649
5659
  }
5650
5660
  );
5651
5661
  });
5652
5662
  RuntimeNotices.displayName = "RuntimeNotices";
5653
- function RuntimeNoticeRow({ notice, onDismiss, onAction }) {
5663
+ function RuntimeNoticeRow({ notice, onDismiss, onAction, renderAction }) {
5654
5664
  const kind = inferNoticeKind(notice);
5655
5665
  const Icon = iconForNotice(kind, notice.level);
5656
5666
  const actionLabel = notice.actionLabel ?? defaultActionLabel(kind);
5667
+ const hostAction = renderAction?.(notice);
5657
5668
  return /* @__PURE__ */ jsxs14(
5658
5669
  "div",
5659
5670
  {
@@ -5670,6 +5681,7 @@ function RuntimeNoticeRow({ notice, onDismiss, onAction }) {
5670
5681
  children: [
5671
5682
  /* @__PURE__ */ jsx15(Icon, { className: cn(noticeIconClass(notice.level), kind === "retry" && "animate-spin motion-reduce:animate-none"), "aria-hidden": "true" }),
5672
5683
  /* @__PURE__ */ jsx15("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsx15("p", { className: noticeTextClass(), children: notice.text }) }),
5684
+ hostAction ?? null,
5673
5685
  actionLabel && onAction ? /* @__PURE__ */ jsx15(Button8, { type: "button", variant: "ghost", size: "sm", onClick: () => onAction(notice.id), children: actionLabel }) : null,
5674
5686
  notice.dismissible && onDismiss ? /* @__PURE__ */ jsx15(
5675
5687
  Button8,
@@ -5710,7 +5722,11 @@ function defaultActionLabel(kind) {
5710
5722
 
5711
5723
  // src/front/chat/components/ChatNotices.tsx
5712
5724
  import { jsx as jsx16, jsxs as jsxs15 } from "react/jsx-runtime";
5713
- function RuntimeNoticeMessages({ notices, onDismiss }) {
5725
+ function RuntimeNoticeMessages({
5726
+ notices,
5727
+ onDismiss,
5728
+ renderAction
5729
+ }) {
5714
5730
  const visible = notices.filter(
5715
5731
  (notice) => notice.level === "error" || notice.level === "warning" || notice.id === "connection-reconnecting" || notice.id === "auto-retry" || notice.id === "large-state-warning" || notice.id.startsWith("command:") || notice.id.startsWith("composer-warning:")
5716
5732
  );
@@ -5720,6 +5736,7 @@ function RuntimeNoticeMessages({ notices, onDismiss }) {
5720
5736
  {
5721
5737
  notices: visible,
5722
5738
  onDismiss,
5739
+ renderAction,
5723
5740
  className: "mx-auto w-full max-w-3xl px-0 py-0"
5724
5741
  }
5725
5742
  );
@@ -6976,6 +6993,7 @@ function PiConversationSurface({
6976
6993
  toolRenderers,
6977
6994
  runtimeNotices,
6978
6995
  onDismissNotice,
6996
+ renderNoticeAction,
6979
6997
  onScrollToBottomReady,
6980
6998
  onSuggestionSubmit,
6981
6999
  onRestoreDraft,
@@ -7016,6 +7034,7 @@ function PiConversationSurface({
7016
7034
  eyebrow: emptyState?.eyebrow,
7017
7035
  title: emptyState?.title,
7018
7036
  description: emptyState?.description,
7037
+ footer: emptyState?.footer,
7019
7038
  suggestions,
7020
7039
  className: emptyHero ? "items-center text-center [&>p]:mx-auto" : void 0,
7021
7040
  onSelect: (suggestion) => {
@@ -7039,7 +7058,7 @@ function PiConversationSurface({
7039
7058
  },
7040
7059
  key
7041
7060
  )),
7042
- /* @__PURE__ */ jsx23(RuntimeNoticeMessages, { notices: runtimeNotices, onDismiss: onDismissNotice })
7061
+ /* @__PURE__ */ jsx23(RuntimeNoticeMessages, { notices: runtimeNotices, onDismiss: onDismissNotice, renderAction: renderNoticeAction })
7043
7062
  ] }),
7044
7063
  /* @__PURE__ */ jsx23(ConversationScrollButton, {})
7045
7064
  ]
@@ -7257,7 +7276,7 @@ function PluginUpdateStatus({
7257
7276
  tone: "running",
7258
7277
  dataAttribute: "data-boring-plugin-update",
7259
7278
  maxWidthClassName,
7260
- runningContent: "Updating plugins\u2026"
7279
+ runningContent: "Updating extensions\u2026"
7261
7280
  }
7262
7281
  );
7263
7282
  }
@@ -7267,7 +7286,7 @@ function PluginUpdateStatus({
7267
7286
  const frontEvents = state.frontEvents ?? [];
7268
7287
  const hasWarningsOrDiagnostics = warnings.length > 0 || diagnostics.length > 0;
7269
7288
  const title = state.reloaded ? hasWarningsOrDiagnostics ? "Reload finished with warnings" : "Reload complete" : "Reload queued";
7270
- const detail = state.reloaded ? hasWarningsOrDiagnostics ? "Some plugin changes need attention." : frontEvents.length > 0 ? `${frontEvents.length} plugin module${frontEvents.length === 1 ? "" : "s"} refreshed. Changes are live.` : "Changes are live." : void 0;
7289
+ const detail = state.reloaded ? hasWarningsOrDiagnostics ? "Some extension changes need attention." : frontEvents.length > 0 ? `${frontEvents.length} extension module${frontEvents.length === 1 ? "" : "s"} refreshed. Changes are live.` : "Changes are live." : void 0;
7271
7290
  return /* @__PURE__ */ jsxs23(
7272
7291
  ComposerStatusBanner,
7273
7292
  {
@@ -7293,7 +7312,7 @@ function PluginUpdateStatus({
7293
7312
  /* @__PURE__ */ jsxs23("span", { children: [
7294
7313
  "Reload diagnostics for ",
7295
7314
  diagnostics.length,
7296
- " plugin",
7315
+ " extension",
7297
7316
  diagnostics.length === 1 ? "" : "s"
7298
7317
  ] })
7299
7318
  ] }),
@@ -7319,7 +7338,7 @@ function PluginUpdateStatus({
7319
7338
  /* @__PURE__ */ jsxs23("span", { children: [
7320
7339
  "Restart needed for ",
7321
7340
  warnings.length,
7322
- " plugin",
7341
+ " extension",
7323
7342
  warnings.length === 1 ? "" : "s"
7324
7343
  ] })
7325
7344
  ] }),
@@ -7328,7 +7347,7 @@ function PluginUpdateStatus({
7328
7347
  " \u2014 ",
7329
7348
  w.surfaces.join(" + ")
7330
7349
  ] }, w.id)) }),
7331
- /* @__PURE__ */ jsx25("p", { className: "mt-1 text-foreground/70", children: "The front bundle reloaded successfully, but routes and agent tools were wired at boot. Stop and restart the workspace process (Ctrl-C, then re-run your dev command) to pick up the new code." })
7350
+ /* @__PURE__ */ jsx25("p", { className: "mt-1 text-foreground/70", children: "The interface reloaded successfully, but routes and tools were wired at boot. Restart the workspace process to pick up the new code." })
7332
7351
  ]
7333
7352
  }
7334
7353
  ) : null
@@ -7342,7 +7361,7 @@ function PluginUpdateStatus({
7342
7361
  tone: "error",
7343
7362
  dataAttribute: "data-boring-plugin-update",
7344
7363
  maxWidthClassName,
7345
- title: "Plugin update failed.",
7364
+ title: "Extension update failed.",
7346
7365
  message: state.message,
7347
7366
  onRetry,
7348
7367
  onDismiss,
@@ -7563,22 +7582,10 @@ function ThinkingLevelGlyph({ level }) {
7563
7582
  function modelTriggerLabel(value, options) {
7564
7583
  const current = value ? options.find((m) => m.provider === value.provider && m.id === value.id) : void 0;
7565
7584
  const rawTriggerLabel = current?.label ?? value?.id;
7566
- return value ? rawTriggerLabel && current?.label && current.label !== value.id && /[A-Z]/.test(current.label) ? current.label : displayModelLabel(rawTriggerLabel ?? value.id) : "Pi default";
7585
+ return value ? rawTriggerLabel && current?.label && current.label !== value.id && /[A-Z]/.test(current.label) ? current.label : displayModelLabel(rawTriggerLabel ?? value.id) : "Default model";
7567
7586
  }
7568
- function modelMenuOptions(value, options) {
7569
- const currentKey = value ? encodeModelKey(value) : null;
7570
- const triggerLabel = modelTriggerLabel(value, options);
7571
- const availableOptions = options.filter((m) => m.available);
7572
- const hasCurrentOption = currentKey ? availableOptions.some((m) => encodeModelKey(m) === currentKey) : true;
7573
- return hasCurrentOption || !value ? availableOptions : [
7574
- {
7575
- provider: value.provider,
7576
- id: value.id,
7577
- label: triggerLabel,
7578
- available: true
7579
- },
7580
- ...availableOptions
7581
- ];
7587
+ function modelMenuOptions(_value, options) {
7588
+ return options.filter((m) => m.available);
7582
7589
  }
7583
7590
  function groupModelOptions(options) {
7584
7591
  const groups = /* @__PURE__ */ new Map();
@@ -7634,6 +7641,7 @@ function ModelPickerMenu({
7634
7641
  onChange,
7635
7642
  options,
7636
7643
  disabled,
7644
+ hideDefaultOption = false,
7637
7645
  onClose,
7638
7646
  className
7639
7647
  }) {
@@ -7641,7 +7649,7 @@ function ModelPickerMenu({
7641
7649
  const menuOptions = modelMenuOptions(value, options);
7642
7650
  const groups = groupModelOptions(menuOptions);
7643
7651
  const groupedOptions = [...groups.values()].flat();
7644
- const keyboardOptions = [null, ...groupedOptions];
7652
+ const keyboardOptions = hideDefaultOption ? groupedOptions : [null, ...groupedOptions];
7645
7653
  const selectedIndex = currentKey ? Math.max(0, keyboardOptions.findIndex((option) => option && encodeModelKey(option) === currentKey)) : 0;
7646
7654
  const [activeIndex, setActiveIndex] = useState16(selectedIndex);
7647
7655
  const activeIndexRef = useRef13(selectedIndex);
@@ -7678,7 +7686,8 @@ function ModelPickerMenu({
7678
7686
  window.addEventListener("keydown", handler, { capture: true });
7679
7687
  return () => window.removeEventListener("keydown", handler, { capture: true });
7680
7688
  }, [activeIndex, disabled, keyboardOptions, onChange, onClose]);
7681
- const optionIndexes = new Map(groupedOptions.map((option, index) => [encodeModelKey(option), index + 1]));
7689
+ const optionIndexOffset = hideDefaultOption ? 0 : 1;
7690
+ const optionIndexes = new Map(groupedOptions.map((option, index) => [encodeModelKey(option), index + optionIndexOffset]));
7682
7691
  return /* @__PURE__ */ jsx27("div", { ref: menuRef, "data-boring-agent": "", "data-boring-agent-part": "model-picker-menu", className: cn(composerPickerMenuClass, className), children: /* @__PURE__ */ jsxs25(Command, { className: "bg-transparent text-[color:var(--popover-foreground)]", children: [
7683
7692
  menuOptions.length > 8 && /* @__PURE__ */ jsx27("div", { className: "border-b border-border/60 px-2", children: /* @__PURE__ */ jsx27(
7684
7693
  CommandInput,
@@ -7690,10 +7699,10 @@ function ModelPickerMenu({
7690
7699
  ) }),
7691
7700
  /* @__PURE__ */ jsxs25(CommandList, { className: "max-h-[300px] p-0.5", children: [
7692
7701
  /* @__PURE__ */ jsx27(CommandEmpty, { className: "py-4 text-center text-[13px] text-muted-foreground", children: "No models found" }),
7693
- /* @__PURE__ */ jsx27(CommandGroup, { children: /* @__PURE__ */ jsxs25(
7702
+ !hideDefaultOption ? /* @__PURE__ */ jsx27(CommandGroup, { children: /* @__PURE__ */ jsxs25(
7694
7703
  CommandItem,
7695
7704
  {
7696
- value: "Pi default automatic auto",
7705
+ value: "Default model automatic auto",
7697
7706
  onSelect: () => {
7698
7707
  if (disabled) return;
7699
7708
  onChange(null);
@@ -7710,11 +7719,11 @@ function ModelPickerMenu({
7710
7719
  )
7711
7720
  }
7712
7721
  ),
7713
- /* @__PURE__ */ jsx27("span", { className: "truncate", children: "Pi default" }),
7722
+ /* @__PURE__ */ jsx27("span", { className: "truncate", children: "Default model" }),
7714
7723
  /* @__PURE__ */ jsx27("span", { className: "ml-auto shrink-0 text-[10px] text-muted-foreground/60", children: "auto" })
7715
7724
  ]
7716
7725
  }
7717
- ) }),
7726
+ ) }) : null,
7718
7727
  [...groups.entries()].map(([provider, list]) => /* @__PURE__ */ jsx27(
7719
7728
  CommandGroup,
7720
7729
  {
@@ -8716,6 +8725,8 @@ function PiChatComposerSurface({
8716
8725
  selectedModel,
8717
8726
  modelOptions,
8718
8727
  modelControlled,
8728
+ hideDefaultModelOption = false,
8729
+ hideComposerSettings = false,
8719
8730
  onModelChange,
8720
8731
  onSetModelPickerOpen,
8721
8732
  onOpenModelPicker,
@@ -8854,6 +8865,7 @@ function PiChatComposerSurface({
8854
8865
  onChange: onModelChange,
8855
8866
  options: modelOptions,
8856
8867
  disabled: isStreaming || modelControlled,
8868
+ hideDefaultOption: hideDefaultModelOption,
8857
8869
  onClose: () => onSetModelPickerOpen(false)
8858
8870
  }
8859
8871
  ) : null,
@@ -8969,7 +8981,7 @@ function PiChatComposerSurface({
8969
8981
  )
8970
8982
  }
8971
8983
  ),
8972
- /* @__PURE__ */ jsxs30(
8984
+ hideComposerSettings ? null : /* @__PURE__ */ jsxs30(
8973
8985
  "div",
8974
8986
  {
8975
8987
  "data-boring-agent-part": "composer-settings-row",
@@ -9171,10 +9183,11 @@ function errorMessage2(error, fallback) {
9171
9183
 
9172
9184
  // src/front/chat/PiChatPanel.tsx
9173
9185
  import { jsx as jsx33, jsxs as jsxs31 } from "react/jsx-runtime";
9174
- var DebugDrawer2 = lazy(() => import("../DebugDrawer-2PUDZ2RL.js").then((m) => ({ default: m.DebugDrawer })));
9186
+ var DebugDrawer2 = lazy(() => import("../DebugDrawer-RYEBQ6CO.js").then((m) => ({ default: m.DebugDrawer })));
9175
9187
  var EMPTY_COMMANDS = [];
9176
9188
  var EMPTY_COMMAND_NAMES = [];
9177
9189
  var EMPTY_BLOCKERS = [];
9190
+ var RUN_REJECTED_NOTICE_ID = "run-rejected";
9178
9191
  function PiChatPanel({
9179
9192
  sessionId,
9180
9193
  extraCommands,
@@ -9201,6 +9214,9 @@ function PiChatPanel({
9201
9214
  model,
9202
9215
  defaultModel,
9203
9216
  availableModels,
9217
+ hideDefaultModelOption = false,
9218
+ hideComposerSettings = false,
9219
+ suppressPreSubmitCancelledWarning = false,
9204
9220
  thinkingLevel,
9205
9221
  thinkingControl = true,
9206
9222
  serverResourcesEnabled = true,
@@ -9222,12 +9238,16 @@ function PiChatPanel({
9222
9238
  onOpenArtifact,
9223
9239
  composerBlockers = EMPTY_BLOCKERS,
9224
9240
  onComposerStop,
9225
- onComposerBlockerAction
9241
+ onComposerBlockerAction,
9242
+ onTurnComplete,
9243
+ renderNoticeAction
9226
9244
  }) {
9227
9245
  const externalSessionId = sessionId?.trim() || void 0;
9228
9246
  const showSessionSidebar = showSessions ?? externalSessionId === void 0;
9229
9247
  const onDataRef = useRef16(onData);
9230
9248
  onDataRef.current = onData;
9249
+ const onTurnCompleteRef = useRef16(onTurnComplete);
9250
+ onTurnCompleteRef.current = onTurnComplete;
9231
9251
  const sessionListRefreshRef = useRef16(void 0);
9232
9252
  const requestHeadersKey = useMemo12(() => headersContentKey(requestHeaders), [requestHeaders]);
9233
9253
  const normalizedRequestHeaders = useMemo12(() => normalizedHeadersFromContentKey(requestHeadersKey), [requestHeadersKey]);
@@ -9237,6 +9257,7 @@ function PiChatPanel({
9237
9257
  onEvent: (event) => {
9238
9258
  remoteSessionOptions?.onEvent?.(event);
9239
9259
  onDataRef.current?.(event);
9260
+ if (event.type === "agent-end" && !event.willRetry) onTurnCompleteRef.current?.();
9240
9261
  if (shouldRefreshSessionListAfterEvent(event)) sessionListRefreshRef.current?.();
9241
9262
  }
9242
9263
  }), [hydrateMessages, remoteSessionOptions]);
@@ -9411,7 +9432,7 @@ function PiChatPanel({
9411
9432
  const largeStateNotice = debug && debugState?.largeStateWarning ? [{
9412
9433
  id: "large-state-warning",
9413
9434
  level: "warning",
9414
- text: `Large Pi chat state: ${debugState.largeStateWarning.messageCount} messages, approximately ${debugState.largeStateWarning.approxBytes} bytes.`,
9435
+ text: `Large chat state: ${debugState.largeStateWarning.messageCount} messages, approximately ${debugState.largeStateWarning.approxBytes} bytes.`,
9415
9436
  dismissible: true
9416
9437
  }] : [];
9417
9438
  return [...fromState, ...sessionNotice, ...largeStateNotice, ...localNotices].filter((notice) => !dismissedNoticeIds.has(notice.id));
@@ -9426,10 +9447,19 @@ function PiChatPanel({
9426
9447
  setDismissedNoticeIds((previous) => new Set(previous).add(id));
9427
9448
  setLocalNotices((previous) => previous.filter((notice) => notice.id !== id));
9428
9449
  }, []);
9450
+ const dropLocalNotice = useCallback18((id) => {
9451
+ setDismissedNoticeIds((previous) => {
9452
+ if (!previous.has(id)) return previous;
9453
+ const next = new Set(previous);
9454
+ next.delete(id);
9455
+ return next;
9456
+ });
9457
+ setLocalNotices((previous) => previous.filter((notice) => notice.id !== id));
9458
+ }, []);
9429
9459
  const createSession = useCallback18(() => {
9430
9460
  if (externalSessionId) return;
9431
9461
  void sessions.create().catch((error) => {
9432
- addLocalNotice({ id: "session-create-error", level: "error", text: errorMessage2(error, "Could not create a Pi session."), dismissible: true });
9462
+ addLocalNotice({ id: "session-create-error", level: "error", text: errorMessage2(error, "Could not create a chat session."), dismissible: true });
9433
9463
  });
9434
9464
  }, [addLocalNotice, externalSessionId, sessions.create]);
9435
9465
  useEffect21(() => {
@@ -9439,7 +9469,7 @@ function PiChatPanel({
9439
9469
  autoCreateInFlightRef.current = true;
9440
9470
  void sessions.create().catch((error) => {
9441
9471
  autoCreateInFlightRef.current = false;
9442
- addLocalNotice({ id: "session-auto-create-error", level: "error", text: errorMessage2(error, "Could not create a Pi session."), dismissible: true });
9472
+ addLocalNotice({ id: "session-auto-create-error", level: "error", text: errorMessage2(error, "Could not create a chat session."), dismissible: true });
9443
9473
  });
9444
9474
  }, [activeSessionId, addLocalNotice, externalSessionId, sessionList.length, sessions.create, sessionsError, sessionsLoading]);
9445
9475
  useEffect21(() => {
@@ -9450,7 +9480,7 @@ function PiChatPanel({
9450
9480
  const deleteSession = useCallback18((sessionId2) => {
9451
9481
  if (externalSessionId) return;
9452
9482
  void sessions.delete(sessionId2).catch((error) => {
9453
- addLocalNotice({ id: `session-delete-error:${sessionId2}`, level: "error", text: errorMessage2(error, "Could not delete the Pi session."), dismissible: true });
9483
+ addLocalNotice({ id: `session-delete-error:${sessionId2}`, level: "error", text: errorMessage2(error, "Could not delete the chat session."), dismissible: true });
9454
9484
  });
9455
9485
  }, [addLocalNotice, externalSessionId, sessions.delete]);
9456
9486
  const resetSession = useCallback18(() => {
@@ -9466,7 +9496,7 @@ function PiChatPanel({
9466
9496
  await sessions.create();
9467
9497
  await onSessionReset?.();
9468
9498
  })().catch((error) => {
9469
- addLocalNotice({ id: "session-reset-error", level: "error", text: errorMessage2(error, "Could not reset the Pi session."), dismissible: true });
9499
+ addLocalNotice({ id: "session-reset-error", level: "error", text: errorMessage2(error, "Could not reset the chat session."), dismissible: true });
9470
9500
  }).finally(() => {
9471
9501
  resetInProgressRef.current = false;
9472
9502
  });
@@ -9482,15 +9512,15 @@ function PiChatPanel({
9482
9512
  const failureMessage = pluginReloadFailureMessage(message);
9483
9513
  if (failureMessage) {
9484
9514
  setPluginUpdateState({ kind: "error", message: failureMessage });
9485
- return `Plugin update failed: ${failureMessage}`;
9515
+ return `Extension update failed: ${failureMessage}`;
9486
9516
  }
9487
9517
  setPluginUpdateState({ kind: "success", reloaded: !/will reload on the next message/i.test(message) });
9488
9518
  setServerSkillsRefreshKey((key) => key + 1);
9489
9519
  return message;
9490
9520
  } catch (error) {
9491
- const message = errorMessage2(error, "Agent plugin reload failed.");
9521
+ const message = errorMessage2(error, "Extension reload failed.");
9492
9522
  setPluginUpdateState({ kind: "error", message });
9493
- return `Plugin update failed: ${message}`;
9523
+ return `Extension update failed: ${message}`;
9494
9524
  }
9495
9525
  }, [reloadAgentPlugins]);
9496
9526
  const dismissPluginUpdate = useCallback18(() => setPluginUpdateState(null), []);
@@ -9639,7 +9669,7 @@ function PiChatPanel({
9639
9669
  clearMessages: () => addLocalNotice({
9640
9670
  id: "clear-not-supported",
9641
9671
  level: "info",
9642
- text: "/clear is not available in the Pi-native chat panel.",
9672
+ text: "/clear is not available in this chat panel.",
9643
9673
  dismissible: true
9644
9674
  }),
9645
9675
  resetSession,
@@ -9671,6 +9701,7 @@ function PiChatPanel({
9671
9701
  addLocalNotice({ id: `command:${Date.now()}`, level: "info", text: message, dismissible: true });
9672
9702
  },
9673
9703
  onWarning: (message) => {
9704
+ if (suppressPreSubmitCancelledWarning && message === "Submit was cancelled before sending.") return;
9674
9705
  onComposerWarning?.(message);
9675
9706
  addLocalNotice({ id: `composer-warning:${Date.now()}`, level: "warning", text: message, dismissible: true });
9676
9707
  },
@@ -9679,10 +9710,21 @@ function PiChatPanel({
9679
9710
  onMentionedFilesConsumed?.();
9680
9711
  }
9681
9712
  });
9682
- }, [activeChatSessionId, addLocalNotice, clearMentionedFiles, composerBlocked, composerBlockerLabel, effectiveMentionedFiles, markLocalSubmitted, onBeforeSubmit, onCommandResult, onComposerWarning, onMentionedFilesConsumed, openModelPicker, openThinkingPicker, registry, reloadAgentPlugins, resetSession, runPluginUpdate, selectComposerModel, selectComposerThinking, selectedModel, selectedPiSession, selectedThinking, setComposerDraft, submitThinkingControl]);
9713
+ }, [activeChatSessionId, addLocalNotice, clearMentionedFiles, composerBlocked, composerBlockerLabel, effectiveMentionedFiles, markLocalSubmitted, onBeforeSubmit, onCommandResult, onComposerWarning, onMentionedFilesConsumed, openModelPicker, openThinkingPicker, registry, reloadAgentPlugins, resetSession, runPluginUpdate, selectComposerModel, selectComposerThinking, selectedModel, selectedPiSession, selectedThinking, setComposerDraft, submitThinkingControl, suppressPreSubmitCancelledWarning]);
9714
+ const surfaceRunRejected = useCallback18((error) => {
9715
+ const errorCode = piChatErrorCode(error);
9716
+ dropLocalNotice(RUN_REJECTED_NOTICE_ID);
9717
+ addLocalNotice({
9718
+ id: RUN_REJECTED_NOTICE_ID,
9719
+ level: "error",
9720
+ text: errorMessage2(error, "Could not send your message."),
9721
+ dismissible: true,
9722
+ ...errorCode ? { errorCode } : {}
9723
+ });
9724
+ }, [addLocalNotice, dropLocalNotice]);
9683
9725
  const sendComposerMessage = useCallback18(async ({ text, files, source = "composer" }) => {
9684
9726
  if (!policy) {
9685
- addLocalNotice({ id: "composer-no-session", level: "warning", text: "Create or select a Pi session before sending.", dismissible: true });
9727
+ addLocalNotice({ id: "composer-no-session", level: "warning", text: "Create or select a chat session before sending.", dismissible: true });
9686
9728
  return false;
9687
9729
  }
9688
9730
  const submittedDraft = text;
@@ -9697,6 +9739,9 @@ function PiChatPanel({
9697
9739
  restoreSubmittedDraft();
9698
9740
  return false;
9699
9741
  }
9742
+ if (result.type === "prompt" || result.type === "followup") {
9743
+ dropLocalNotice(RUN_REJECTED_NOTICE_ID);
9744
+ }
9700
9745
  if (result.type === "prompt" && activeChatSessionId) {
9701
9746
  if (shouldHoldLocalSubmitted(selectedPiSession, result.cursor)) markLocalSubmitted(activeChatSessionId);
9702
9747
  else clearLocalSubmitted(activeChatSessionId);
@@ -9705,9 +9750,10 @@ function PiChatPanel({
9705
9750
  } catch (error) {
9706
9751
  clearLocalSubmitted(activeChatSessionId);
9707
9752
  restoreSubmittedDraft();
9708
- throw error;
9753
+ surfaceRunRejected(error);
9754
+ return false;
9709
9755
  }
9710
- }, [activeChatSessionId, addLocalNotice, clearLocalSubmitted, markLocalSubmitted, policy, selectedPiSession, setComposerDraft]);
9756
+ }, [activeChatSessionId, clearLocalSubmitted, dropLocalNotice, markLocalSubmitted, policy, selectedPiSession, setComposerDraft, surfaceRunRejected]);
9711
9757
  const editQueued = useCallback18(() => {
9712
9758
  if (!policy) return;
9713
9759
  void policy.editQueued().then((result) => {
@@ -9719,12 +9765,12 @@ function PiChatPanel({
9719
9765
  const stop = useCallback18(() => {
9720
9766
  onComposerStop?.();
9721
9767
  void policy?.stop().catch((error) => {
9722
- addLocalNotice({ id: "stop-error", level: "error", text: errorMessage2(error, "Could not stop the Pi session."), dismissible: true });
9768
+ addLocalNotice({ id: "stop-error", level: "error", text: errorMessage2(error, "Could not stop the chat session."), dismissible: true });
9723
9769
  });
9724
9770
  }, [addLocalNotice, onComposerStop, policy]);
9725
9771
  const interrupt = useCallback18(() => {
9726
9772
  void policy?.interrupt().catch((error) => {
9727
- addLocalNotice({ id: "interrupt-error", level: "error", text: errorMessage2(error, "Could not interrupt the Pi session."), dismissible: true });
9773
+ addLocalNotice({ id: "interrupt-error", level: "error", text: errorMessage2(error, "Could not interrupt the chat session."), dismissible: true });
9728
9774
  });
9729
9775
  }, [addLocalNotice, policy]);
9730
9776
  useEffect21(() => {
@@ -9779,6 +9825,9 @@ function PiChatPanel({
9779
9825
  }
9780
9826
  return;
9781
9827
  }
9828
+ if (result.type === "prompt" || result.type === "followup") {
9829
+ dropLocalNotice(RUN_REJECTED_NOTICE_ID);
9830
+ }
9782
9831
  if (result.type === "prompt") {
9783
9832
  if (shouldHoldLocalSubmitted(selectedPiSession, result.cursor)) markLocalSubmitted(activeSessionId);
9784
9833
  else clearLocalSubmitted(activeSessionId);
@@ -9792,9 +9841,9 @@ function PiChatPanel({
9792
9841
  clearLocalSubmitted(activeSessionId);
9793
9842
  restoreSubmittedDraft();
9794
9843
  settlePendingAutoSubmit(activeSessionId);
9795
- addLocalNotice({ id: "auto-submit-error", level: "error", text: errorMessage2(error, "Could not auto-submit the initial draft."), dismissible: true });
9844
+ surfaceRunRejected(error);
9796
9845
  });
9797
- }, [activeSessionId, addLocalNotice, autoSubmitInitialDraft, clearLocalSubmitted, composerBlocked, initialDraft, markLocalSubmitted, onAutoSubmitInitialDraftAccepted, policy, selectedPiSession, setComposerDraft, settlePendingAutoSubmit]);
9846
+ }, [activeSessionId, autoSubmitInitialDraft, clearLocalSubmitted, composerBlocked, dropLocalNotice, initialDraft, markLocalSubmitted, onAutoSubmitInitialDraftAccepted, policy, selectedPiSession, setComposerDraft, settlePendingAutoSubmit, surfaceRunRejected]);
9798
9847
  useEffect21(() => {
9799
9848
  if (workspaceWarmupStatus?.status === "ready") {
9800
9849
  clearLocalNotice("workspace-warmup");
@@ -9896,6 +9945,7 @@ function PiChatPanel({
9896
9945
  toolRenderers: mergedToolRenderers,
9897
9946
  runtimeNotices,
9898
9947
  onDismissNotice: clearLocalNotice,
9948
+ renderNoticeAction,
9899
9949
  onScrollToBottomReady: (scrollToBottom) => {
9900
9950
  scrollToBottomRef.current = scrollToBottom;
9901
9951
  },
@@ -9945,6 +9995,8 @@ function PiChatPanel({
9945
9995
  selectedModel,
9946
9996
  modelOptions,
9947
9997
  modelControlled: model !== void 0,
9998
+ hideDefaultModelOption,
9999
+ hideComposerSettings,
9948
10000
  onModelChange: modelDiscovery.setModel,
9949
10001
  onSetModelPickerOpen: setModelPickerOpen,
9950
10002
  onOpenModelPicker: openModelPicker,
@@ -9966,7 +10018,7 @@ function PiChatPanel({
9966
10018
  ]
9967
10019
  }
9968
10020
  ) }),
9969
- debug ? /* @__PURE__ */ jsx33(Suspense, { fallback: null, children: /* @__PURE__ */ jsx33("div", { "aria-label": "Pi chat debug metadata", className: "contents", role: "region", children: /* @__PURE__ */ jsx33(
10021
+ debug ? /* @__PURE__ */ jsx33(Suspense, { fallback: null, children: /* @__PURE__ */ jsx33("div", { "aria-label": "Chat debug metadata", className: "contents", role: "region", children: /* @__PURE__ */ jsx33(
9970
10022
  DebugDrawer2,
9971
10023
  {
9972
10024
  apiBaseUrl,
@@ -1135,12 +1135,6 @@
1135
1135
  background-color: color-mix(in oklab, var(--muted-foreground) 55%, transparent);
1136
1136
  }
1137
1137
  }
1138
- .bg-muted\/10 {
1139
- background-color: var(--muted);
1140
- @supports (color: color-mix(in lab, red, red)) {
1141
- background-color: color-mix(in oklab, var(--muted) 10%, transparent);
1142
- }
1143
- }
1144
1138
  .bg-muted\/20 {
1145
1139
  background-color: var(--muted);
1146
1140
  @supports (color: color-mix(in lab, red, red)) {