@hachej/boring-agent 0.1.96 → 0.1.98

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.
@@ -6519,6 +6519,7 @@ var HarnessPiChatService = class {
6519
6519
  if (persisted && !channel && !this.harnessMayHaveLiveSession(ctx, sessionId)) return persisted;
6520
6520
  }
6521
6521
  const adapter = await this.getAdapter(ctx, sessionId, "");
6522
+ await this.ensureChannel(ctx, sessionId, adapter);
6522
6523
  if (this.canRefreshFromPersistedState(sessionKey, adapter)) {
6523
6524
  const persisted = await this.readPersistedState(ctx, sessionId);
6524
6525
  if (persisted && this.canRefreshFromPersistedState(sessionKey, adapter) && !this.persistedStateDropsLiveMessages(persisted, adapter, sessionId)) {
@@ -12158,6 +12159,7 @@ function createPiResourceDigestInput(input) {
12158
12159
  additionalSkillPaths: uniqueStrings(input.resourceSets.flatMap((set) => set.additionalSkillPaths ?? [])),
12159
12160
  packages: compactPiPackages(input.resourceSets.flatMap((set) => set.packages ?? [])),
12160
12161
  extensionPaths: uniqueStrings(input.resourceSets.flatMap((set) => set.extensionPaths ?? [])),
12162
+ allowInternalSymlinks: input.allowInternalSymlinks,
12161
12163
  authorizedRoots: uniqueStrings([
12162
12164
  piCwd,
12163
12165
  piAgentDir,
@@ -12181,7 +12183,14 @@ async function digestPiResourceInputs(input) {
12181
12183
  throw stableError(ErrorCode.enum.CONFIG_INVALID, 400, "Pi resource digest requires at least one independently authorized root");
12182
12184
  }
12183
12185
  const limits = normalizeLimits(input.limits);
12184
- const state = { hash, limits, authorizedRoots, nodes: 0, bytes: 0 };
12186
+ const state = {
12187
+ hash,
12188
+ limits,
12189
+ authorizedRoots,
12190
+ allowInternalSymlinks: input.allowInternalSymlinks ?? false,
12191
+ nodes: 0,
12192
+ bytes: 0
12193
+ };
12185
12194
  frameString(hash, "format", FORMAT_VERSION);
12186
12195
  frameString(hash, "pi-cwd", piCwd);
12187
12196
  frameString(hash, "project-settings-dir", projectSettingsDir);
@@ -12254,7 +12263,7 @@ function packageDescriptor(source) {
12254
12263
  async function hashResourceCollection(state, baseDir, kind, paths, digestContainingArtifact) {
12255
12264
  for (const path of [...new Set(paths)].sort()) {
12256
12265
  const absolutePath = resolvePiPath(path, baseDir);
12257
- await assertContainedWithoutSymlinks(absolutePath, state.authorizedRoots);
12266
+ await assertContainedWithoutSymlinks(absolutePath, state.authorizedRoots, state.allowInternalSymlinks);
12258
12267
  try {
12259
12268
  await lstat4(absolutePath);
12260
12269
  } catch (error) {
@@ -12337,7 +12346,7 @@ async function findContainingArtifactRoot(path, authorizedRoots) {
12337
12346
  }
12338
12347
  async function hashLocalResource(state, path, logicalPath, depth) {
12339
12348
  const absolutePath = resolve8(path);
12340
- await assertContainedWithoutSymlinks(absolutePath, state.authorizedRoots);
12349
+ await assertContainedWithoutSymlinks(absolutePath, state.authorizedRoots, state.allowInternalSymlinks);
12341
12350
  if (depth > state.limits.maxDepth) {
12342
12351
  throw limitError(`Pi resource tree exceeds maximum depth ${state.limits.maxDepth}`);
12343
12352
  }
@@ -12356,7 +12365,16 @@ async function hashLocalResource(state, path, logicalPath, depth) {
12356
12365
  throw limitError(`Pi resource tree exceeds maximum node count ${state.limits.maxFiles}`);
12357
12366
  }
12358
12367
  if (stat7.isSymbolicLink()) {
12359
- throw stableError(ErrorCode.enum.PATH_SYMLINK_ESCAPE, 403, `Pi resource symlinks are not allowed: ${absolutePath}`);
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
12378
  }
12361
12379
  if (stat7.isDirectory()) {
12362
12380
  frameString(state.hash, "directory", logicalPath);
@@ -12430,7 +12448,7 @@ async function hashLocalResource(state, path, logicalPath, depth) {
12430
12448
  function assertSameFile(before, after, path) {
12431
12449
  if (before.dev !== after.dev || before.ino !== after.ino || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) throw changedError(path);
12432
12450
  }
12433
- async function assertContainedWithoutSymlinks(path, roots) {
12451
+ async function assertContainedWithoutSymlinks(path, roots, allowInternalSymlinks = false) {
12434
12452
  const root = mostSpecificContainingRoot(path, roots);
12435
12453
  if (!root) {
12436
12454
  throw stableError(ErrorCode.enum.PATH_ESCAPE, 403, `Pi resource path is outside authorized roots: ${path}`);
@@ -12443,7 +12461,13 @@ async function assertContainedWithoutSymlinks(path, roots) {
12443
12461
  current = resolve8(current, segment);
12444
12462
  try {
12445
12463
  if ((await lstat4(current)).isSymbolicLink()) {
12446
- throw stableError(ErrorCode.enum.PATH_SYMLINK_ESCAPE, 403, `Pi resource symlinks are not allowed: ${current}`);
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
12471
  }
12448
12472
  } catch (error) {
12449
12473
  if (isMissing(error)) return;
@@ -373,6 +373,7 @@ interface RemotePiSessionOptions {
373
373
  setTimeoutFn?: typeof globalThis.setTimeout;
374
374
  clearTimeoutFn?: typeof globalThis.clearTimeout;
375
375
  requestTimeoutMs?: number;
376
+ commandTimeoutMs?: number;
376
377
  }
377
378
  interface RemotePiSessionLargeStateWarning {
378
379
  type: 'large-state';
@@ -419,6 +420,7 @@ declare class RemotePiSession {
419
420
  private readonly setTimeoutFn;
420
421
  private readonly clearTimeoutFn;
421
422
  private readonly requestTimeoutMs;
423
+ private readonly commandTimeoutMs;
422
424
  private generation;
423
425
  private streamRunId;
424
426
  private reconnectAttempt;
@@ -4520,6 +4520,7 @@ var SUPPORTED_PROTOCOL_VERSION = 1;
4520
4520
  var DEFAULT_RECONNECT_BASE_MS = 1e3;
4521
4521
  var DEFAULT_RECONNECT_MAX_MS = 3e4;
4522
4522
  var DEFAULT_REQUEST_TIMEOUT_MS = 15e3;
4523
+ var DEFAULT_COMMAND_TIMEOUT_MS = 6e4;
4523
4524
  var DEFAULT_LARGE_STATE_WARNING_BYTES = 5 * 1024 * 1024;
4524
4525
  var DEFAULT_LARGE_STATE_WARNING_MESSAGES = 300;
4525
4526
  var EVENT_TYPE_RING_LIMIT = 20;
@@ -4536,6 +4537,7 @@ var RemotePiSession = class {
4536
4537
  this.setTimeoutFn = options.setTimeoutFn ?? globalThis.setTimeout;
4537
4538
  this.clearTimeoutFn = options.clearTimeoutFn ?? globalThis.clearTimeout;
4538
4539
  this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
4540
+ this.commandTimeoutMs = options.commandTimeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
4539
4541
  this.store = createPiChatStore(createInitialPiChatState({
4540
4542
  sessionId: options.sessionId,
4541
4543
  workspaceId: options.workspaceId,
@@ -4552,6 +4554,7 @@ var RemotePiSession = class {
4552
4554
  setTimeoutFn;
4553
4555
  clearTimeoutFn;
4554
4556
  requestTimeoutMs;
4557
+ commandTimeoutMs;
4555
4558
  generation = 0;
4556
4559
  streamRunId = 0;
4557
4560
  reconnectAttempt = 0;
@@ -4877,7 +4880,7 @@ var RemotePiSession = class {
4877
4880
  method: "POST",
4878
4881
  headers: { ...headers, "Content-Type": "application/json" },
4879
4882
  body: JSON.stringify(this.addressedCommandPayload(path, payload))
4880
- });
4883
+ }, this.commandTimeoutMs);
4881
4884
  if (!this.isGenerationActive(generation)) {
4882
4885
  throw abortError("Remote Pi session disposed before command receipt.");
4883
4886
  }
@@ -4894,21 +4897,21 @@ var RemotePiSession = class {
4894
4897
  if (this.disposed) return;
4895
4898
  this.store.dispatch({ type: "remove-optimistic-user-message", clientNonce }, { flush: true });
4896
4899
  }
4897
- async fetchJson(url, init) {
4900
+ async fetchJson(url, init, timeoutMs = this.requestTimeoutMs) {
4898
4901
  const controller = new AbortController();
4899
4902
  this.fetchControllers.add(controller);
4900
4903
  let timedOut = false;
4901
4904
  const timer = globalThis.setTimeout(() => {
4902
4905
  timedOut = true;
4903
4906
  controller.abort();
4904
- }, this.requestTimeoutMs);
4907
+ }, timeoutMs);
4905
4908
  try {
4906
4909
  const response = await this.fetchImpl(url, { ...init, signal: controller.signal });
4907
4910
  const body = await safeReadJson(response);
4908
4911
  if (!response.ok) throw new RemotePiSessionHttpError(response.status, routeErrorMessage(body, `HTTP ${response.status}`), body, routeErrorCode(body));
4909
4912
  return body;
4910
4913
  } catch (error) {
4911
- if (timedOut) throw new Error(`Request to ${url} timed out after ${this.requestTimeoutMs}ms.`);
4914
+ if (timedOut) throw new Error(`Request to ${url} timed out after ${timeoutMs}ms.`);
4912
4915
  throw error;
4913
4916
  } finally {
4914
4917
  globalThis.clearTimeout(timer);
@@ -1356,6 +1356,8 @@ 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;
1359
1361
  readonly limits?: Partial<PiResourceDigestLimits>;
1360
1362
  }
1361
1363
  interface PiResourceSet {
@@ -1372,6 +1374,7 @@ declare function createPiResourceDigestInput(input: {
1372
1374
  readonly noSkills?: boolean;
1373
1375
  readonly resourceSets: readonly PiResourceSet[];
1374
1376
  readonly authorizedRoots: readonly string[];
1377
+ readonly allowInternalSymlinks?: boolean;
1375
1378
  readonly limits?: Partial<PiResourceDigestLimits>;
1376
1379
  }): PiResourceDigestInput;
1377
1380
  /** Bounded asynchronous content identity for the exact Pi resources considered by a reload candidate. */
@@ -88,7 +88,7 @@ import {
88
88
  shareResourceUri,
89
89
  withResolvedCredential,
90
90
  withRuntimeEnvContributions
91
- } from "../chunk-NSE2RHU3.js";
91
+ } from "../chunk-CXMM24NW.js";
92
92
  import {
93
93
  PI_PACKAGE_RESOURCE_FILTERS,
94
94
  compactPiPackages,
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  WORKER_INTERNAL_TOKEN_HEADER,
3
3
  constantTimeTokenEqual
4
- } from "../../chunk-NSE2RHU3.js";
4
+ } from "../../chunk-CXMM24NW.js";
5
5
  import "../../chunk-WC5KBGP2.js";
6
6
  import "../../chunk-3OEDBRKY.js";
7
7
  import "../../chunk-4IQ4HHBG.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-agent",
3
- "version": "0.1.96",
3
+ "version": "0.1.98",
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.",
@@ -88,7 +88,7 @@
88
88
  "diff": "^9.0.0",
89
89
  "fastify": "^5.11.2",
90
90
  "lucide-react": "^1.27.0",
91
- "motion": "^12.43.0",
91
+ "motion": "^13.0.0",
92
92
  "nanoid": "^6.0.0",
93
93
  "shiki": "^4.4.2",
94
94
  "streamdown": "^2.5.0",
@@ -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.96",
100
- "@hachej/boring-ui-kit": "0.1.96",
101
- "@hachej/boring-sandbox": "0.1.96"
99
+ "@hachej/boring-bash": "0.1.98",
100
+ "@hachej/boring-sandbox": "0.1.98",
101
+ "@hachej/boring-ui-kit": "0.1.98"
102
102
  },
103
103
  "devDependencies": {
104
104
  "@vercel/sandbox": "2.9.2",