@hachej/boring-agent 0.1.98 → 0.1.99

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.
@@ -1832,6 +1832,32 @@ ${extra}` };
1832
1832
  });
1833
1833
  };
1834
1834
  }
1835
+ var PI_RELATIVE_SKILL_PATH_GUIDANCE = "When a skill file references a relative path, resolve it against the skill directory (parent of SKILL.md / dirname of the path) and use that absolute path in tool commands.";
1836
+ var RESOURCE_RELATIVE_SKILL_PATH_GUIDANCE = "When a skill location is a JSON resource locator, pass its filesystem and path fields to the read tool. Resolve referenced relative paths against the locator's directory in the same filesystem; never convert a resource locator to a host path.";
1837
+ function unescapeXmlText(value) {
1838
+ return value.replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&amp;/g, "&");
1839
+ }
1840
+ function escapeXmlText(value) {
1841
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
1842
+ }
1843
+ function projectSkillResourceLocations(systemPrompt, locate) {
1844
+ let projected = false;
1845
+ const output = systemPrompt.replace(/<location>([^<]*)<\/location>/g, (match, encodedPath) => {
1846
+ const resource = locate(unescapeXmlText(encodedPath));
1847
+ if (!resource) return match;
1848
+ projected = true;
1849
+ return `<location>${escapeXmlText(JSON.stringify(resource))}</location>`;
1850
+ });
1851
+ if (!projected) return systemPrompt;
1852
+ return output.replace(PI_RELATIVE_SKILL_PATH_GUIDANCE, RESOURCE_RELATIVE_SKILL_PATH_GUIDANCE);
1853
+ }
1854
+ function buildSkillResourceProjectionExtension(locate) {
1855
+ return (pi) => {
1856
+ pi.on("before_agent_start", async (event) => ({
1857
+ systemPrompt: projectSkillResourceLocations(event.systemPrompt, locate)
1858
+ }));
1859
+ };
1860
+ }
1835
1861
  function buildToolErrorResultExtension() {
1836
1862
  return (pi) => {
1837
1863
  pi.on("tool_result", async (event) => {
@@ -2084,10 +2110,12 @@ function createPiCodingAgentHarness(opts) {
2084
2110
  const dynamicPromptExtension = opts.systemPromptDynamic ? buildDynamicPromptExtension(opts.systemPromptDynamic) : void 0;
2085
2111
  const agentDir = getAgentDir2();
2086
2112
  const toolErrorResultExtension = buildToolErrorResultExtension();
2113
+ const skillResourceProjectionExtension = pi.locateSkillResource ? buildSkillResourceProjectionExtension(pi.locateSkillResource) : void 0;
2087
2114
  const extensionFactories = [
2088
2115
  toolErrorResultExtension,
2089
2116
  ...dynamicPromptExtension ? [dynamicPromptExtension] : [],
2090
- ...pi.extensionFactories ?? []
2117
+ ...pi.extensionFactories ?? [],
2118
+ ...skillResourceProjectionExtension ? [skillResourceProjectionExtension] : []
2091
2119
  ];
2092
2120
  const settingsManager = createResourceSettingsManager(
2093
2121
  opts.cwd,
@@ -2287,6 +2315,7 @@ export {
2287
2315
  mergePiPackageSources,
2288
2316
  createResourceSettingsManager,
2289
2317
  withPiHarnessDefaults,
2318
+ projectSkillResourceLocations,
2290
2319
  deriveSourcePlugin,
2291
2320
  createPiCodingAgentHarness
2292
2321
  };
@@ -7,7 +7,7 @@ import {
7
7
  readConfiguredDefaultModel,
8
8
  registerConfiguredModelProviders,
9
9
  withPiHarnessDefaults
10
- } from "./chunk-WC5KBGP2.js";
10
+ } from "./chunk-GRMDMWBW.js";
11
11
  import {
12
12
  AGENT_KNOWLEDGE_FILESYSTEM_ID,
13
13
  AgentDefinitionValidationError,
@@ -7931,7 +7931,7 @@ async function buildAgentComposition(input) {
7931
7931
  sessionNamespace: configuredNamespace,
7932
7932
  sessionDir: runtimeScope.sessionDir ?? factoryInput.sessionDir
7933
7933
  }) : async (factoryInput) => {
7934
- const { createPiCodingAgentHarness } = await import("./createHarness-PW3A2MRB.js");
7934
+ const { createPiCodingAgentHarness } = await import("./createHarness-5TEXLUYX.js");
7935
7935
  return createPiCodingAgentHarness({
7936
7936
  ...factoryInput,
7937
7937
  pi,
@@ -12159,7 +12159,6 @@ function createPiResourceDigestInput(input) {
12159
12159
  additionalSkillPaths: uniqueStrings(input.resourceSets.flatMap((set) => set.additionalSkillPaths ?? [])),
12160
12160
  packages: compactPiPackages(input.resourceSets.flatMap((set) => set.packages ?? [])),
12161
12161
  extensionPaths: uniqueStrings(input.resourceSets.flatMap((set) => set.extensionPaths ?? [])),
12162
- allowInternalSymlinks: input.allowInternalSymlinks,
12163
12162
  authorizedRoots: uniqueStrings([
12164
12163
  piCwd,
12165
12164
  piAgentDir,
@@ -12183,14 +12182,7 @@ async function digestPiResourceInputs(input) {
12183
12182
  throw stableError(ErrorCode.enum.CONFIG_INVALID, 400, "Pi resource digest requires at least one independently authorized root");
12184
12183
  }
12185
12184
  const limits = normalizeLimits(input.limits);
12186
- const state = {
12187
- hash,
12188
- limits,
12189
- authorizedRoots,
12190
- allowInternalSymlinks: input.allowInternalSymlinks ?? false,
12191
- nodes: 0,
12192
- bytes: 0
12193
- };
12185
+ const state = { hash, limits, authorizedRoots, nodes: 0, bytes: 0 };
12194
12186
  frameString(hash, "format", FORMAT_VERSION);
12195
12187
  frameString(hash, "pi-cwd", piCwd);
12196
12188
  frameString(hash, "project-settings-dir", projectSettingsDir);
@@ -12263,7 +12255,7 @@ function packageDescriptor(source) {
12263
12255
  async function hashResourceCollection(state, baseDir, kind, paths, digestContainingArtifact) {
12264
12256
  for (const path of [...new Set(paths)].sort()) {
12265
12257
  const absolutePath = resolvePiPath(path, baseDir);
12266
- await assertContainedWithoutSymlinks(absolutePath, state.authorizedRoots, state.allowInternalSymlinks);
12258
+ await assertContainedWithoutSymlinks(absolutePath, state.authorizedRoots);
12267
12259
  try {
12268
12260
  await lstat4(absolutePath);
12269
12261
  } catch (error) {
@@ -12346,7 +12338,7 @@ async function findContainingArtifactRoot(path, authorizedRoots) {
12346
12338
  }
12347
12339
  async function hashLocalResource(state, path, logicalPath, depth) {
12348
12340
  const absolutePath = resolve8(path);
12349
- await assertContainedWithoutSymlinks(absolutePath, state.authorizedRoots, state.allowInternalSymlinks);
12341
+ await assertContainedWithoutSymlinks(absolutePath, state.authorizedRoots);
12350
12342
  if (depth > state.limits.maxDepth) {
12351
12343
  throw limitError(`Pi resource tree exceeds maximum depth ${state.limits.maxDepth}`);
12352
12344
  }
@@ -12365,16 +12357,7 @@ async function hashLocalResource(state, path, logicalPath, depth) {
12365
12357
  throw limitError(`Pi resource tree exceeds maximum node count ${state.limits.maxFiles}`);
12366
12358
  }
12367
12359
  if (stat7.isSymbolicLink()) {
12368
- if (!state.allowInternalSymlinks) {
12369
- throw stableError(ErrorCode.enum.PATH_SYMLINK_ESCAPE, 403, `Pi resource symlinks are not allowed: ${absolutePath}`);
12370
- }
12371
- const target = await realpath5(absolutePath);
12372
- if (!mostSpecificContainingRoot(target, state.authorizedRoots)) {
12373
- throw stableError(ErrorCode.enum.PATH_SYMLINK_ESCAPE, 403, `Pi resource symlink resolves outside authorized roots: ${absolutePath}`);
12374
- }
12375
- frameString(state.hash, "symlink-target", target);
12376
- await hashLocalResource(state, target, logicalPath, depth);
12377
- return;
12360
+ throw stableError(ErrorCode.enum.PATH_SYMLINK_ESCAPE, 403, `Pi resource symlinks are not allowed: ${absolutePath}`);
12378
12361
  }
12379
12362
  if (stat7.isDirectory()) {
12380
12363
  frameString(state.hash, "directory", logicalPath);
@@ -12448,7 +12431,7 @@ async function hashLocalResource(state, path, logicalPath, depth) {
12448
12431
  function assertSameFile(before, after, path) {
12449
12432
  if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) throw changedError(path);
12450
12433
  }
12451
- async function assertContainedWithoutSymlinks(path, roots, allowInternalSymlinks = false) {
12434
+ async function assertContainedWithoutSymlinks(path, roots) {
12452
12435
  const root = mostSpecificContainingRoot(path, roots);
12453
12436
  if (!root) {
12454
12437
  throw stableError(ErrorCode.enum.PATH_ESCAPE, 403, `Pi resource path is outside authorized roots: ${path}`);
@@ -12461,13 +12444,7 @@ async function assertContainedWithoutSymlinks(path, roots, allowInternalSymlinks
12461
12444
  current = resolve8(current, segment);
12462
12445
  try {
12463
12446
  if ((await lstat4(current)).isSymbolicLink()) {
12464
- if (!allowInternalSymlinks) {
12465
- throw stableError(ErrorCode.enum.PATH_SYMLINK_ESCAPE, 403, `Pi resource symlinks are not allowed: ${current}`);
12466
- }
12467
- const target = await realpath5(current);
12468
- if (!mostSpecificContainingRoot(target, roots)) {
12469
- throw stableError(ErrorCode.enum.PATH_SYMLINK_ESCAPE, 403, `Pi resource symlink resolves outside authorized roots: ${current}`);
12470
- }
12447
+ throw stableError(ErrorCode.enum.PATH_SYMLINK_ESCAPE, 403, `Pi resource symlinks are not allowed: ${current}`);
12471
12448
  }
12472
12449
  } catch (error) {
12473
12450
  if (isMissing(error)) return;
@@ -3,8 +3,9 @@ import {
3
3
  createResourceSettingsManager,
4
4
  deriveSourcePlugin,
5
5
  mergePiPackageSources,
6
+ projectSkillResourceLocations,
6
7
  withPiHarnessDefaults
7
- } from "./chunk-WC5KBGP2.js";
8
+ } from "./chunk-GRMDMWBW.js";
8
9
  import "./chunk-4IQ4HHBG.js";
9
10
  import "./chunk-VXF5DWS6.js";
10
11
  export {
@@ -12,5 +13,6 @@ export {
12
13
  createResourceSettingsManager,
13
14
  deriveSourcePlugin,
14
15
  mergePiPackageSources,
16
+ projectSkillResourceLocations,
15
17
  withPiHarnessDefaults
16
18
  };
@@ -462,6 +462,7 @@ declare class RemotePiSession {
462
462
  private sessionUrl;
463
463
  private addressedCommandPayload;
464
464
  private dispatchProtocolError;
465
+ private dispatchTerminalSessionError;
465
466
  private recordEventType;
466
467
  private recordLargeStateWarning;
467
468
  private clearReconnectTimer;
@@ -695,6 +696,8 @@ interface PiChatPanelProps<TComposerBlocker extends ComposerBlocker = ComposerBl
695
696
  allowPromptDuringInitialHydration?: boolean;
696
697
  workspaceWarmupStatus?: ChatPanelWorkspaceWarmupStatus;
697
698
  onSessionReset?: () => void | Promise<void>;
699
+ /** Creates and selects a session when the host controls sessionId. */
700
+ onCreateSession?: () => void | Promise<void>;
698
701
  onBeforeSubmit?: (draft: string, context: ChatSubmitContext) => false | void | boolean | Promise<false | void | boolean>;
699
702
  onReloadAgentPlugins?: () => Promise<AgentPluginReloadResult | string>;
700
703
  onCommandResult?: (message: string) => void;
@@ -720,7 +723,7 @@ interface PiChatPanelProps<TComposerBlocker extends ComposerBlocker = ComposerBl
720
723
  * knowing what the code means or what the action does. */
721
724
  renderNoticeAction?: (notice: PiChatRuntimeNotice) => ReactNode;
722
725
  }
723
- declare function PiChatPanel<TComposerBlocker extends ComposerBlocker = ComposerBlocker>({ sessionId, agentTypeId, extraCommands, apiBaseUrl, workspaceId, storageScope, requestHeaders, storage, fetch, className, chrome, debug, showSessions, hotReloadEnabled, suggestions, emptyState, emptyPlacement, composerPlaceholder, initialDraft, autoSubmitInitialDraft, onDraftRestored, onAutoSubmitInitialDraftAccepted, onAutoSubmitInitialDraftSettled, model, defaultModel, availableModels, hideDefaultModelOption, hideComposerSettings, suppressPreSubmitCancelledWarning, thinkingLevel, thinkingControl, serverResourcesEnabled, mentionedFiles, commands, excludeBuiltinCommands, toolRenderers, createRemoteSession, remoteSessionOptions, hydrateMessages, allowPromptDuringInitialHydration, workspaceWarmupStatus, onSessionReset, onBeforeSubmit, onReloadAgentPlugins, onCommandResult, onComposerWarning, onMentionedFilesConsumed, onPromptSubmitStarted, onData, onOpenArtifact, composerBlockers, onComposerStop, onComposerBlockerAction, onTurnComplete, renderNoticeAction, }: PiChatPanelProps<TComposerBlocker>): react.JSX.Element;
726
+ declare function PiChatPanel<TComposerBlocker extends ComposerBlocker = ComposerBlocker>({ sessionId, agentTypeId, extraCommands, apiBaseUrl, workspaceId, storageScope, requestHeaders, storage, fetch, className, chrome, debug, showSessions, hotReloadEnabled, suggestions, emptyState, emptyPlacement, composerPlaceholder, initialDraft, autoSubmitInitialDraft, onDraftRestored, onAutoSubmitInitialDraftAccepted, onAutoSubmitInitialDraftSettled, model, defaultModel, availableModels, hideDefaultModelOption, hideComposerSettings, suppressPreSubmitCancelledWarning, thinkingLevel, thinkingControl, serverResourcesEnabled, mentionedFiles, commands, excludeBuiltinCommands, toolRenderers, createRemoteSession, remoteSessionOptions, hydrateMessages, allowPromptDuringInitialHydration, workspaceWarmupStatus, onSessionReset, onCreateSession, onBeforeSubmit, onReloadAgentPlugins, onCommandResult, onComposerWarning, onMentionedFilesConsumed, onPromptSubmitStarted, onData, onOpenArtifact, composerBlockers, onComposerStop, onComposerBlockerAction, onTurnComplete, renderNoticeAction, }: PiChatPanelProps<TComposerBlocker>): react.JSX.Element;
724
727
 
725
728
  type AddressedAgentOption = Pick<AgentSummary, 'agentTypeId' | 'label' | 'description' | 'pluginIds'>;
726
729
  interface UseAddressedAgentSelectionOptions {
@@ -75,6 +75,7 @@ function rawWorkspaceFileUrl(path, opts) {
75
75
 
76
76
  // src/front/chat/PiChatPanel.tsx
77
77
  import { lazy, Suspense, useCallback as useCallback18, useEffect as useEffect21, useMemo as useMemo16, useRef as useRef18, useState as useState20 } from "react";
78
+ import { Button as Button12 } from "@hachej/boring-ui-kit";
78
79
 
79
80
  // src/front/ArtifactOpenContext.tsx
80
81
  import { createContext, useContext } from "react";
@@ -3412,6 +3413,71 @@ function messageCreatedAtMs(message) {
3412
3413
  return Number.isFinite(timestamp) ? timestamp : void 0;
3413
3414
  }
3414
3415
 
3416
+ // src/front/chat/gatewayResponseError.ts
3417
+ var RUNTIME_SCOPE_MISMATCH_MESSAGE = "This chat belongs to a previous runtime configuration and can no longer be changed.";
3418
+ var GatewayResponseError = class extends Error {
3419
+ constructor(status, message, code3, details, body, operation) {
3420
+ super(message);
3421
+ this.status = status;
3422
+ this.code = code3;
3423
+ this.details = details;
3424
+ this.body = body;
3425
+ this.operation = operation;
3426
+ this.name = "GatewayResponseError";
3427
+ this.errorCode = code3;
3428
+ }
3429
+ status;
3430
+ code;
3431
+ details;
3432
+ body;
3433
+ operation;
3434
+ errorCode;
3435
+ };
3436
+ async function gatewayResponseError(response, fallbackMessage, operation) {
3437
+ const body = await safeReadJson(response);
3438
+ return gatewayResponseErrorFromBody(response.status, body, fallbackMessage, operation);
3439
+ }
3440
+ function gatewayResponseErrorFromBody(status, body, fallbackMessage, operation) {
3441
+ const payload = errorPayload(body);
3442
+ const code3 = gatewayResponseErrorCode(payload?.code);
3443
+ const serverMessage = typeof payload?.message === "string" && payload.message.trim().length > 0 ? payload.message : fallbackMessage;
3444
+ const message = code3 === AgentGatewayErrorCode.AGENT_SESSION_RUNTIME_SCOPE_MISMATCH ? RUNTIME_SCOPE_MISMATCH_MESSAGE : serverMessage;
3445
+ const details = isJsonValue(payload?.details) ? payload.details : void 0;
3446
+ return new GatewayResponseError(status, message, code3, details, body, operation);
3447
+ }
3448
+ function gatewayResponseErrorCode(value) {
3449
+ const sharedCode = ErrorCode.safeParse(value);
3450
+ if (sharedCode.success) return sharedCode.data;
3451
+ return typeof value === "string" && AGENT_GATEWAY_ERROR_CODES.includes(value) ? value : void 0;
3452
+ }
3453
+ function errorResponseCode(error) {
3454
+ if (error instanceof GatewayResponseError) return error.code;
3455
+ const record = typeof error === "object" && error !== null ? error : void 0;
3456
+ return gatewayResponseErrorCode(record?.code ?? record?.errorCode);
3457
+ }
3458
+ function isRuntimeScopeMismatchError(error) {
3459
+ return errorResponseCode(error) === AgentGatewayErrorCode.AGENT_SESSION_RUNTIME_SCOPE_MISMATCH;
3460
+ }
3461
+ async function safeReadJson(response) {
3462
+ const text = await response.text();
3463
+ if (!text) return void 0;
3464
+ try {
3465
+ return JSON.parse(text);
3466
+ } catch {
3467
+ return void 0;
3468
+ }
3469
+ }
3470
+ function errorPayload(body) {
3471
+ if (typeof body !== "object" || body === null) return void 0;
3472
+ const record = body;
3473
+ return typeof record.error === "object" && record.error !== null ? record.error : record;
3474
+ }
3475
+ function isJsonValue(value) {
3476
+ if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
3477
+ if (Array.isArray(value)) return value.every(isJsonValue);
3478
+ return typeof value === "object" && value !== null && Object.values(value).every(isJsonValue);
3479
+ }
3480
+
3415
3481
  // src/front/chat/pi/piChatAssistantCommit.ts
3416
3482
  function commitFinalMessage(state, messageId, final) {
3417
3483
  const plan = buildAssistantCommitPlan(state, messageId, final);
@@ -3793,6 +3859,20 @@ function piChatReducer(state, action) {
3793
3859
  dismissible: true
3794
3860
  })
3795
3861
  };
3862
+ case "terminal-session-error":
3863
+ return {
3864
+ ...state,
3865
+ status: "error",
3866
+ hydrated: true,
3867
+ connection: { ...state.connection, state: "suspended" },
3868
+ retryNotice: void 0,
3869
+ notices: upsertNotice(state.notices, {
3870
+ id: "terminal-session-error",
3871
+ level: "error",
3872
+ text: action.message,
3873
+ errorCode: action.errorCode
3874
+ })
3875
+ };
3796
3876
  case "clear-notice":
3797
3877
  return { ...state, notices: state.notices.filter((notice) => notice.id !== action.id) };
3798
3878
  }
@@ -4718,6 +4798,10 @@ var RemotePiSession = class {
4718
4798
  this.connectEvents(snapshot.seq, generation);
4719
4799
  } catch (error) {
4720
4800
  if (!this.isHydrationActive(generation, hydrationRunId) || isAbortError(error)) return;
4801
+ if (isRuntimeScopeMismatchError(error)) {
4802
+ this.dispatchTerminalSessionError(error);
4803
+ return;
4804
+ }
4721
4805
  this.dispatchProtocolError(errorMessage(error, "Failed to hydrate Pi chat session state."));
4722
4806
  this.scheduleReconnect(generation);
4723
4807
  }
@@ -4772,7 +4856,7 @@ var RemotePiSession = class {
4772
4856
  return;
4773
4857
  }
4774
4858
  if (!response.ok) {
4775
- const body = await safeReadJson(response);
4859
+ const body = await safeReadJson2(response);
4776
4860
  if (!this.isStreamActive(generation, runId)) {
4777
4861
  markOpen();
4778
4862
  return;
@@ -4784,8 +4868,18 @@ var RemotePiSession = class {
4784
4868
  markOpen();
4785
4869
  return;
4786
4870
  }
4787
- this.dispatchProtocolError(routeErrorMessage(body, `Pi chat event stream failed with HTTP ${response.status}.`));
4788
- this.scheduleReconnect(generation);
4871
+ const responseError = gatewayResponseErrorFromBody(
4872
+ response.status,
4873
+ body,
4874
+ `Pi chat event stream failed with HTTP ${response.status}.`,
4875
+ "events"
4876
+ );
4877
+ if (isRuntimeScopeMismatchError(responseError)) {
4878
+ this.dispatchTerminalSessionError(responseError);
4879
+ } else {
4880
+ this.dispatchProtocolError(responseError.message);
4881
+ this.scheduleReconnect(generation);
4882
+ }
4789
4883
  markOpen();
4790
4884
  return;
4791
4885
  }
@@ -4907,8 +5001,8 @@ var RemotePiSession = class {
4907
5001
  }, timeoutMs);
4908
5002
  try {
4909
5003
  const response = await this.fetchImpl(url, { ...init, signal: controller.signal });
4910
- const body = await safeReadJson(response);
4911
- if (!response.ok) throw new RemotePiSessionHttpError(response.status, routeErrorMessage(body, `HTTP ${response.status}`), body, routeErrorCode(body));
5004
+ const body = await safeReadJson2(response);
5005
+ if (!response.ok) throw gatewayResponseErrorFromBody(response.status, body, `HTTP ${response.status}`, url);
4912
5006
  return body;
4913
5007
  } catch (error) {
4914
5008
  if (timedOut) throw new Error(`Request to ${url} timed out after ${timeoutMs}ms.`);
@@ -4972,6 +5066,20 @@ var RemotePiSession = class {
4972
5066
  const error = { code: ErrorCode.enum.INTERNAL_ERROR, message, retryable: true };
4973
5067
  this.store.dispatch({ type: "protocol-error", error }, { flush: true });
4974
5068
  }
5069
+ dispatchTerminalSessionError(error) {
5070
+ if (this.disposed) return;
5071
+ const errorCode = errorResponseCode(error);
5072
+ if (!errorCode) {
5073
+ this.dispatchProtocolError(errorMessage(error, "Chat session is unavailable."));
5074
+ return;
5075
+ }
5076
+ this.suspendStream();
5077
+ this.store.dispatch({
5078
+ type: "terminal-session-error",
5079
+ message: errorMessage(error, "Chat session is unavailable."),
5080
+ errorCode
5081
+ }, { flush: true });
5082
+ }
4975
5083
  recordEventType(type) {
4976
5084
  this.recentEventTypes.push(type);
4977
5085
  if (this.recentEventTypes.length > EVENT_TYPE_RING_LIMIT) this.recentEventTypes.shift();
@@ -5026,22 +5134,8 @@ function addressedSnapshot(value) {
5026
5134
  function createRemotePiSession(options) {
5027
5135
  return new RemotePiSession(options);
5028
5136
  }
5029
- var RemotePiSessionHttpError = class extends Error {
5030
- constructor(status, message, body, errorCode) {
5031
- super(message);
5032
- this.status = status;
5033
- this.body = body;
5034
- this.errorCode = errorCode;
5035
- this.name = "RemotePiSessionHttpError";
5036
- }
5037
- status;
5038
- body;
5039
- errorCode;
5040
- };
5041
5137
  function piChatErrorCode(error) {
5042
- if (error instanceof RemotePiSessionHttpError) return error.errorCode;
5043
- const parsed = ErrorCode.safeParse(error?.errorCode);
5044
- return parsed.success ? parsed.data : void 0;
5138
+ return errorResponseCode(error);
5045
5139
  }
5046
5140
  function toOptimisticUserMessage(payload) {
5047
5141
  const displayText = payload.displayMessage ?? payload.message;
@@ -5065,7 +5159,7 @@ function toOptimisticUserMessage(payload) {
5065
5159
  ]
5066
5160
  };
5067
5161
  }
5068
- async function safeReadJson(response) {
5162
+ async function safeReadJson2(response) {
5069
5163
  const text = await response.text();
5070
5164
  if (!text) return void 0;
5071
5165
  try {
@@ -5074,19 +5168,6 @@ async function safeReadJson(response) {
5074
5168
  return void 0;
5075
5169
  }
5076
5170
  }
5077
- function routeErrorMessage(body, fallback) {
5078
- if (typeof body !== "object" || body === null) return fallback;
5079
- const error = body.error;
5080
- const payload = typeof error === "object" && error !== null ? error : body;
5081
- return typeof payload.message === "string" && payload.message ? payload.message : fallback;
5082
- }
5083
- function routeErrorCode(body) {
5084
- if (typeof body !== "object" || body === null) return void 0;
5085
- const error = body.error;
5086
- const payload = typeof error === "object" && error !== null ? error : body;
5087
- const parsed = ErrorCode.safeParse(payload.code);
5088
- return parsed.success ? parsed.data : void 0;
5089
- }
5090
5171
  function errorMessage(error, fallback) {
5091
5172
  return error instanceof Error && error.message ? error.message : fallback;
5092
5173
  }
@@ -10480,6 +10561,7 @@ function PiChatPanel({
10480
10561
  allowPromptDuringInitialHydration = false,
10481
10562
  workspaceWarmupStatus,
10482
10563
  onSessionReset,
10564
+ onCreateSession,
10483
10565
  onBeforeSubmit,
10484
10566
  onReloadAgentPlugins,
10485
10567
  onCommandResult,
@@ -10734,12 +10816,33 @@ function PiChatPanel({
10734
10816
  });
10735
10817
  setLocalNotices((previous) => previous.filter((notice) => notice.id !== id));
10736
10818
  }, []);
10819
+ const createSessionInFlightRef = useRef18(null);
10820
+ const [creatingSession, setCreatingSession] = useState20(false);
10737
10821
  const createSession = useCallback18(() => {
10738
- if (externalSessionId) return;
10739
- void sessions.create().catch((error) => {
10822
+ if (createSessionInFlightRef.current) return createSessionInFlightRef.current;
10823
+ if (externalSessionId && !onCreateSession) return Promise.resolve();
10824
+ setCreatingSession(true);
10825
+ const operation = (async () => {
10826
+ await Promise.resolve();
10827
+ if (externalSessionId) await onCreateSession?.();
10828
+ else await sessions.create();
10829
+ })().catch((error) => {
10740
10830
  addLocalNotice({ id: "session-create-error", level: "error", text: errorMessage2(error, "Could not create a chat session."), dismissible: true });
10831
+ }).finally(() => {
10832
+ if (createSessionInFlightRef.current === operation) createSessionInFlightRef.current = null;
10833
+ setCreatingSession(false);
10741
10834
  });
10742
- }, [addLocalNotice, externalSessionId, sessions.create]);
10835
+ createSessionInFlightRef.current = operation;
10836
+ return operation;
10837
+ }, [addLocalNotice, externalSessionId, onCreateSession, sessions.create]);
10838
+ const renderRuntimeNoticeAction = useCallback18((notice) => {
10839
+ const hostAction = renderNoticeAction?.(notice);
10840
+ if (hostAction != null) return hostAction;
10841
+ if (notice.errorCode === AgentGatewayErrorCode.AGENT_SESSION_RUNTIME_SCOPE_MISMATCH && (!externalSessionId || onCreateSession)) {
10842
+ return /* @__PURE__ */ jsx36(Button12, { type: "button", size: "sm", variant: "outline", disabled: creatingSession, onClick: createSession, children: creatingSession ? "Starting new chat\u2026" : "Start new chat" });
10843
+ }
10844
+ return null;
10845
+ }, [createSession, creatingSession, externalSessionId, onCreateSession, renderNoticeAction]);
10743
10846
  useEffect21(() => {
10744
10847
  if (externalSessionId || sessionsLoading || sessionsError || activeSessionId || sessionList.length > 0) return;
10745
10848
  if (resetInProgressRef.current) return;
@@ -11350,7 +11453,7 @@ function PiChatPanel({
11350
11453
  onMentionActivate: activateAssistantMention,
11351
11454
  runtimeNotices,
11352
11455
  onDismissNotice: clearLocalNotice,
11353
- renderNoticeAction,
11456
+ renderNoticeAction: renderRuntimeNoticeAction,
11354
11457
  onScrollToBottomReady: (scrollToBottom) => {
11355
11458
  scrollToBottomRef.current = scrollToBottom;
11356
11459
  },
@@ -11396,73 +11499,6 @@ function PiChatPanel({
11396
11499
 
11397
11500
  // src/front/chat/useAddressedAgentSelection.ts
11398
11501
  import { useCallback as useCallback19, useEffect as useEffect22, useMemo as useMemo17, useState as useState21 } from "react";
11399
-
11400
- // src/front/chat/gatewayResponseError.ts
11401
- var RUNTIME_SCOPE_MISMATCH_MESSAGE = "This chat belongs to a previous runtime configuration and can no longer be changed.";
11402
- var GatewayResponseError = class extends Error {
11403
- constructor(status, message, code3, details, body, operation) {
11404
- super(message);
11405
- this.status = status;
11406
- this.code = code3;
11407
- this.details = details;
11408
- this.body = body;
11409
- this.operation = operation;
11410
- this.name = "GatewayResponseError";
11411
- this.errorCode = code3;
11412
- }
11413
- status;
11414
- code;
11415
- details;
11416
- body;
11417
- operation;
11418
- errorCode;
11419
- };
11420
- async function gatewayResponseError(response, fallbackMessage, operation) {
11421
- const body = await safeReadJson2(response);
11422
- return gatewayResponseErrorFromBody(response.status, body, fallbackMessage, operation);
11423
- }
11424
- function gatewayResponseErrorFromBody(status, body, fallbackMessage, operation) {
11425
- const payload = errorPayload(body);
11426
- const code3 = gatewayResponseErrorCode(payload?.code);
11427
- const serverMessage = typeof payload?.message === "string" && payload.message.trim().length > 0 ? payload.message : fallbackMessage;
11428
- const message = code3 === AgentGatewayErrorCode.AGENT_SESSION_RUNTIME_SCOPE_MISMATCH ? RUNTIME_SCOPE_MISMATCH_MESSAGE : serverMessage;
11429
- const details = isJsonValue(payload?.details) ? payload.details : void 0;
11430
- return new GatewayResponseError(status, message, code3, details, body, operation);
11431
- }
11432
- function gatewayResponseErrorCode(value) {
11433
- const sharedCode = ErrorCode.safeParse(value);
11434
- if (sharedCode.success) return sharedCode.data;
11435
- return typeof value === "string" && AGENT_GATEWAY_ERROR_CODES.includes(value) ? value : void 0;
11436
- }
11437
- function errorResponseCode(error) {
11438
- if (error instanceof GatewayResponseError) return error.code;
11439
- const record = typeof error === "object" && error !== null ? error : void 0;
11440
- return gatewayResponseErrorCode(record?.code ?? record?.errorCode);
11441
- }
11442
- function isRuntimeScopeMismatchError(error) {
11443
- return errorResponseCode(error) === AgentGatewayErrorCode.AGENT_SESSION_RUNTIME_SCOPE_MISMATCH;
11444
- }
11445
- async function safeReadJson2(response) {
11446
- const text = await response.text();
11447
- if (!text) return void 0;
11448
- try {
11449
- return JSON.parse(text);
11450
- } catch {
11451
- return void 0;
11452
- }
11453
- }
11454
- function errorPayload(body) {
11455
- if (typeof body !== "object" || body === null) return void 0;
11456
- const record = body;
11457
- return typeof record.error === "object" && record.error !== null ? record.error : record;
11458
- }
11459
- function isJsonValue(value) {
11460
- if (value === null || ["string", "number", "boolean"].includes(typeof value)) return true;
11461
- if (Array.isArray(value)) return value.every(isJsonValue);
11462
- return typeof value === "object" && value !== null && Object.values(value).every(isJsonValue);
11463
- }
11464
-
11465
- // src/front/chat/useAddressedAgentSelection.ts
11466
11502
  var inFlightDiscoveries = /* @__PURE__ */ new WeakMap();
11467
11503
  function useAddressedAgentSelection({
11468
11504
  apiBaseUrl,
@@ -1,5 +1,5 @@
1
1
  import { b as AgentGateway, c as AuthorizedAgentScope, d as AgentSessionRef, e as AgentSessionActivity } from '../../../errors-cNC4HMpV.js';
2
- import { A as AgentGatewayEffect } from '../../../types-D_SD-7_l.js';
2
+ import { A as AgentGatewayEffect } from '../../../types-BPzq-RwN.js';
3
3
  import '../../../piChatEvent-B3ehh9V0.js';
4
4
  import 'zod';
5
5
  import 'fastify';
@@ -1,5 +1,5 @@
1
- import { P as ProvisionWorkspaceRuntimeOptions, W as WorkspaceProvisioningResult, a as PiPackageSource, R as RuntimeModeAdapter, b as RuntimeModeId, B as BuiltinRuntimeModeId, c as AgentRuntimeHostOperations, d as RuntimeBashStrategy, e as RuntimeFilesystemStrategy, M as ModeContext, f as WorkspaceProvisioningAdapter, g as AgentInstructionFileRef, C as ConfiguredAgentHostAgentSpec, h as AgentHostAgentSpec, i as RuntimeBundle, j as PiHarnessOptions, k as AgentMeteringSink, l as RuntimeFilesystemBinding, m as CreatedAgentHost, n as MeteringErrorLogger, o as ReadyStatusTracker, p as ResolvedAgentRuntimeScope, q as CreateAgentHostOptions, r as CompiledAgentHostAgentSpec, s as AgentRequestLedger, t as AgentEffectAdmission, u as AuthorizedEnvironmentIntent, v as AgentHostEnvironmentScope, w as AgentRequestKey, x as AgentRequestLedgerPrepareResult, y as AgentRequestFailure, z as AgentRequestLedgerRecord, D as AgentFleetCompiler, E as AgentRequestTarget, F as RuntimeFilesystemAccessDecision } from '../types-D_SD-7_l.js';
2
- export { A as AgentGatewayEffect, G as AgentHostDescription, H as AgentHostDirectProjectionOptions, I as AgentHostDispatcherRunInput, J as AgentHostEnvironmentLease, K as AgentHostHandle, L as LegacyDefaultAgentHostSpec, N as MeteringReleaseInput, O as MeteringReleaseReason, Q as MeteringReservationResult, S as MeteringReserveInput, T as MeteringRunKind, U as MeteringRunScope, V as MeteringRunStatus, X as MeteringSettleInput, Y as MeteringUsage, Z as MeteringUsageInput, _ as PI_PACKAGE_RESOURCE_FILTERS, $ as PiExtensionFactory, a0 as PluginSkillSource, a1 as ResolvedEnvironmentScope, a2 as RuntimeFilesystemBindingOperations, a3 as RuntimeNodePackageSpec, a4 as RuntimeProvisioningContribution, a5 as RuntimePythonSpec, a6 as RuntimeTemplateContribution, a7 as WorkspaceProvisioningExecResult, a8 as compactPiPackages, a9 as mergePiPackageSources, aa as normalizeMeteringUsage, ab as piPackageSourceKey } from '../types-D_SD-7_l.js';
1
+ import { P as ProvisionWorkspaceRuntimeOptions, W as WorkspaceProvisioningResult, a as PiPackageSource, R as RuntimeModeAdapter, b as RuntimeModeId, B as BuiltinRuntimeModeId, c as AgentRuntimeHostOperations, d as RuntimeBashStrategy, e as RuntimeFilesystemStrategy, M as ModeContext, f as WorkspaceProvisioningAdapter, g as AgentInstructionFileRef, C as ConfiguredAgentHostAgentSpec, h as AgentHostAgentSpec, i as RuntimeBundle, j as PiHarnessOptions, k as AgentMeteringSink, l as RuntimeFilesystemBinding, m as CreatedAgentHost, n as MeteringErrorLogger, o as ReadyStatusTracker, p as ResolvedAgentRuntimeScope, q as CreateAgentHostOptions, r as CompiledAgentHostAgentSpec, s as AgentRequestLedger, t as AgentEffectAdmission, u as AuthorizedEnvironmentIntent, v as AgentHostEnvironmentScope, w as AgentRequestKey, x as AgentRequestLedgerPrepareResult, y as AgentRequestFailure, z as AgentRequestLedgerRecord, D as AgentFleetCompiler, E as AgentRequestTarget, F as RuntimeFilesystemAccessDecision } from '../types-BPzq-RwN.js';
2
+ export { A as AgentGatewayEffect, G as AgentHostDescription, H as AgentHostDirectProjectionOptions, I as AgentHostDispatcherRunInput, J as AgentHostEnvironmentLease, K as AgentHostHandle, L as LegacyDefaultAgentHostSpec, N as MeteringReleaseInput, O as MeteringReleaseReason, Q as MeteringReservationResult, S as MeteringReserveInput, T as MeteringRunKind, U as MeteringRunScope, V as MeteringRunStatus, X as MeteringSettleInput, Y as MeteringUsage, Z as MeteringUsageInput, _ as PI_PACKAGE_RESOURCE_FILTERS, $ as PiExtensionFactory, a0 as PluginSkillSource, a1 as ResolvedEnvironmentScope, a2 as RuntimeFilesystemBindingOperations, a3 as RuntimeNodePackageSpec, a4 as RuntimeProvisioningContribution, a5 as RuntimePythonSpec, a6 as RuntimeTemplateContribution, a7 as WorkspaceProvisioningExecResult, a8 as compactPiPackages, a9 as mergePiPackageSources, aa as normalizeMeteringUsage, ab as piPackageSourceKey } from '../types-BPzq-RwN.js';
3
3
  import { E as ExecOptions, a as ExecResult, W as WorkspaceChangeEvent, b as Workspace, S as Sandbox } from '../sandbox-KQiEdz4W.js';
4
4
  export { R as RuntimeFilesystemCapability } from '../sandbox-KQiEdz4W.js';
5
5
  import { R as RemoteWorkerWorkspaceOp, a as RemoteWorkerWorkspaceResult, b as RemoteWorkerExecRequest } from '../middleware-Cq_cYHjJ.js';
@@ -1356,8 +1356,6 @@ interface PiResourceDigestInput {
1356
1356
  readonly extensionPaths?: readonly string[];
1357
1357
  /** Lexical roots explicitly authorized by the embedding Workspace/CLI. */
1358
1358
  readonly authorizedRoots: readonly string[];
1359
- /** CLI workspaces may contain repo-managed symlinks (e.g. .pi/skills/*). */
1360
- readonly allowInternalSymlinks?: boolean;
1361
1359
  readonly limits?: Partial<PiResourceDigestLimits>;
1362
1360
  }
1363
1361
  interface PiResourceSet {
@@ -1374,7 +1372,6 @@ declare function createPiResourceDigestInput(input: {
1374
1372
  readonly noSkills?: boolean;
1375
1373
  readonly resourceSets: readonly PiResourceSet[];
1376
1374
  readonly authorizedRoots: readonly string[];
1377
- readonly allowInternalSymlinks?: boolean;
1378
1375
  readonly limits?: Partial<PiResourceDigestLimits>;
1379
1376
  }): PiResourceDigestInput;
1380
1377
  /** Bounded asynchronous content identity for the exact Pi resources considered by a reload candidate. */
@@ -88,14 +88,14 @@ import {
88
88
  shareResourceUri,
89
89
  withResolvedCredential,
90
90
  withRuntimeEnvContributions
91
- } from "../chunk-CXMM24NW.js";
91
+ } from "../chunk-ZOHN72OS.js";
92
92
  import {
93
93
  PI_PACKAGE_RESOURCE_FILTERS,
94
94
  compactPiPackages,
95
95
  createResourceSettingsManager,
96
96
  mergePiPackageSources,
97
97
  piPackageSourceKey
98
- } from "../chunk-WC5KBGP2.js";
98
+ } from "../chunk-GRMDMWBW.js";
99
99
  import "../chunk-3OEDBRKY.js";
100
100
  import "../chunk-4IQ4HHBG.js";
101
101
  import "../chunk-WSQ5QNIY.js";
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  WORKER_INTERNAL_TOKEN_HEADER,
3
3
  constantTimeTokenEqual
4
- } from "../../chunk-CXMM24NW.js";
5
- import "../../chunk-WC5KBGP2.js";
4
+ } from "../../chunk-ZOHN72OS.js";
5
+ import "../../chunk-GRMDMWBW.js";
6
6
  import "../../chunk-3OEDBRKY.js";
7
7
  import "../../chunk-4IQ4HHBG.js";
8
8
  import "../../chunk-WSQ5QNIY.js";
@@ -567,6 +567,11 @@ declare function mergePiPackageSources(base?: PiPackageSource[], additional?: Pi
567
567
 
568
568
  interface PiHarnessOptions {
569
569
  noContextFiles?: boolean;
570
+ /** Projects server-internal skill files to model-visible resource locators. */
571
+ locateSkillResource?: (filePath: string) => {
572
+ filesystem: string;
573
+ path: string;
574
+ } | undefined;
570
575
  noSkills?: boolean;
571
576
  /** Disable ambient Pi extensions (required when tools execute in a remote runtime). */
572
577
  noExtensions?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-agent",
3
- "version": "0.1.98",
3
+ "version": "0.1.99",
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.",
@@ -96,9 +96,9 @@
96
96
  "use-stick-to-bottom": "^1.1.6",
97
97
  "yaml": "^2.9.0",
98
98
  "zod": "^3.25.76",
99
- "@hachej/boring-bash": "0.1.98",
100
- "@hachej/boring-sandbox": "0.1.98",
101
- "@hachej/boring-ui-kit": "0.1.98"
99
+ "@hachej/boring-bash": "0.1.99",
100
+ "@hachej/boring-ui-kit": "0.1.99",
101
+ "@hachej/boring-sandbox": "0.1.99"
102
102
  },
103
103
  "devDependencies": {
104
104
  "@vercel/sandbox": "2.9.2",