@hachej/boring-agent 0.1.36 → 0.1.38

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.
@@ -73,6 +73,17 @@ interface AgentHarness {
73
73
  getSystemPrompt?: (sessionId: string) => string | undefined;
74
74
  /** Reload native agent resources/extensions for an existing session. */
75
75
  reloadSession?: (sessionId: string) => Promise<boolean>;
76
+ /**
77
+ * Resource (skill/extension) load diagnostics for an existing session.
78
+ * Returns `[]` when the session has no live agent session yet. Lets the
79
+ * /reload route and the `plugin_diagnostics` tool surface silent
80
+ * skill/extension load failures back to the UI and the agent.
81
+ */
82
+ getResourceDiagnostics?: (sessionId: string) => Array<{
83
+ source: string;
84
+ message: string;
85
+ path?: string;
86
+ }>;
76
87
  /** List slash commands registered in the agent runtime for a given session. */
77
88
  getSlashCommands?: (sessionId: string, ctx: RunContext) => ReadonlyArray<AgentSlashCommandSummary> | Promise<ReadonlyArray<AgentSlashCommandSummary>>;
78
89
  /**
@@ -4931,7 +4931,7 @@ function clearPageUnloading() {
4931
4931
  // src/front/chat/session/usePiSessions.ts
4932
4932
  var DEFAULT_SESSIONS_API_PATH = "/api/v1/agent/pi-chat/sessions";
4933
4933
  var SESSION_PAGE_SIZE = 50;
4934
- var DEFAULT_MAX_RETRIES = 8;
4934
+ var DEFAULT_MAX_RETRIES = 60;
4935
4935
  var DEFAULT_RETRY_BASE_MS = 250;
4936
4936
  var DEFAULT_RETRY_MAX_MS = 2e3;
4937
4937
  var SessionsPreparingError = class extends Error {
@@ -4940,6 +4940,9 @@ var SessionsPreparingError = class extends Error {
4940
4940
  this.name = "SessionsPreparingError";
4941
4941
  }
4942
4942
  };
4943
+ function isNetworkFetchError(error) {
4944
+ return error instanceof TypeError;
4945
+ }
4943
4946
  function usePiSessions(options = {}) {
4944
4947
  const enabled = options.enabled ?? true;
4945
4948
  const apiBaseUrl = options.apiBaseUrl?.replace(/\/$/, "") ?? "";
@@ -5073,7 +5076,8 @@ function usePiSessions(options = {}) {
5073
5076
  data = await fetchSessionList(fetchImpl, sessionsListUrl(0, preferredSessionId()), requestHeaders());
5074
5077
  break;
5075
5078
  } catch (err) {
5076
- const retryable = err instanceof SessionsPreparingError && attempt < retryMaxRetries;
5079
+ const transient = err instanceof SessionsPreparingError || isNetworkFetchError(err);
5080
+ const retryable = transient && attempt < retryMaxRetries;
5077
5081
  if (!retryable) throw err;
5078
5082
  if (!isCurrent()) return;
5079
5083
  await delayWithRef(retryDelayMs(attempt, { baseMs: retryBaseMs, maxMs: retryMaxMs }), retryTimerRef);
@@ -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, P as PluginRestartWarning, A as AgentTool, q as AgentHarnessFactory } from '../agentPluginEvents-DgNc3erX.js';
2
- export { r as AgentHarnessFactoryInput } from '../agentPluginEvents-DgNc3erX.js';
1
+ import { o as WorkspaceRuntimeContext, S as Sandbox, g as SandboxHandleStore, f as SandboxHandleRecord, W as Workspace, j as TelemetrySink, F as FileSearch, P as PluginRestartWarning, A as AgentTool, q as AgentHarnessFactory } from '../agentPluginEvents-CKrZLW3g.js';
2
+ export { r as AgentHarnessFactoryInput } from '../agentPluginEvents-CKrZLW3g.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';
@@ -533,6 +533,19 @@ interface CreateAgentAppOptions {
533
533
  * the workspace plugin layer.
534
534
  */
535
535
  systemPromptDynamic?: () => string | undefined | Promise<string | undefined>;
536
+ /**
537
+ * Optional host callback returning current plugin/skill load diagnostics
538
+ * for this workspace. Surfaced by the `plugin_diagnostics` agent tool so the
539
+ * model can iterate on plugin/skill load errors after a /reload.
540
+ */
541
+ getPluginDiagnostics?: (args: {
542
+ workspaceId: string;
543
+ workspaceRoot: string;
544
+ }) => Promise<Array<{
545
+ source: string;
546
+ message: string;
547
+ pluginId?: string;
548
+ }>>;
536
549
  }
537
550
  declare function createAgentApp(opts?: CreateAgentAppOptions): Promise<FastifyInstance>;
538
551
 
@@ -611,6 +624,19 @@ interface RegisterAgentRoutesOptions {
611
624
  workspaceRoot: string;
612
625
  request: FastifyRequest;
613
626
  }) => void | ReloadHookResult | undefined | Promise<void | ReloadHookResult | undefined>;
627
+ /**
628
+ * Optional host callback returning current plugin/skill load diagnostics
629
+ * for a workspace. Surfaced by the `plugin_diagnostics` agent tool so the
630
+ * model can iterate on plugin/skill load errors after a /reload.
631
+ */
632
+ getPluginDiagnostics?: (args: {
633
+ workspaceId: string;
634
+ workspaceRoot: string;
635
+ }) => Promise<Array<{
636
+ source: string;
637
+ message: string;
638
+ pluginId?: string;
639
+ }>>;
614
640
  }
615
641
  /**
616
642
  * Fastify plugin that mounts agent routes onto a host app (typically core-built).
@@ -433,13 +433,17 @@ var DEFAULT_WATCH_IGNORES = [
433
433
  "node_modules",
434
434
  ".git",
435
435
  ".DS_Store",
436
+ ".worktrees",
437
+ ".boring-agent",
438
+ ".cache",
436
439
  "dist",
437
440
  ".next",
438
441
  ".turbo",
439
442
  "test-results"
440
443
  ];
441
- function shouldIgnoreWatchPath(path4) {
442
- const parts = path4.split(sep);
444
+ function shouldIgnoreWatchPath(root, path4) {
445
+ const relPath = relative2(root, path4);
446
+ const parts = relPath.split(sep);
443
447
  return parts.some(
444
448
  (part) => DEFAULT_WATCH_IGNORES.includes(part) || part.endsWith(".tsbuildinfo")
445
449
  );
@@ -451,7 +455,7 @@ function createNodeWatcher(root) {
451
455
  const ensureFsw = () => {
452
456
  if (fsw) return fsw;
453
457
  fsw = chokidar.watch(root, {
454
- ignored: shouldIgnoreWatchPath,
458
+ ignored: (path4) => shouldIgnoreWatchPath(root, path4),
455
459
  ignoreInitial: true,
456
460
  persistent: true,
457
461
  followSymlinks: false
@@ -6560,6 +6564,40 @@ function createPiCodingAgentHarness(opts) {
6560
6564
  hasPiSession(sessionId) {
6561
6565
  return piSessions.has(sessionId);
6562
6566
  },
6567
+ /**
6568
+ * Surface Pi's skill/extension load diagnostics for a session so silent
6569
+ * load failures (bad SKILL.md, extension import errors) reach the UI and
6570
+ * the agent. Returns [] when the session has no live pi session yet.
6571
+ * The resourceLoader getters are synchronous.
6572
+ */
6573
+ getResourceDiagnostics(sessionId) {
6574
+ const handle = piSessions.get(sessionId);
6575
+ if (!handle) return [];
6576
+ const out = [];
6577
+ const seen = /* @__PURE__ */ new Set();
6578
+ const push = (entry) => {
6579
+ const key = `${entry.source}
6580
+ ${entry.message}`;
6581
+ if (seen.has(key)) return;
6582
+ seen.add(key);
6583
+ out.push(entry);
6584
+ };
6585
+ for (const diagnostic of handle.resourceLoader.getSkills().diagnostics) {
6586
+ push({
6587
+ source: "pi-skills",
6588
+ message: diagnostic.path && !diagnostic.message.includes(diagnostic.path) ? `${diagnostic.message} (${diagnostic.path})` : diagnostic.message,
6589
+ ...diagnostic.path ? { path: diagnostic.path } : {}
6590
+ });
6591
+ }
6592
+ for (const error of handle.resourceLoader.getExtensions().errors) {
6593
+ push({
6594
+ source: "pi-extensions",
6595
+ message: error.error.includes(error.path) ? error.error : `${error.error} (${error.path})`,
6596
+ path: error.path
6597
+ });
6598
+ }
6599
+ return out;
6600
+ },
6563
6601
  reloadSession: reloadPiSession,
6564
6602
  async getSlashCommands(sessionId, ctx) {
6565
6603
  const handle = await getOrCreatePiSession(sessionId, { sessionId, message: "" }, ctx);
@@ -8106,8 +8144,10 @@ function skillsRoutes(app, opts, done) {
8106
8144
  }));
8107
8145
  cached.set(cacheKey, { skills, expiresAt: now + CACHE_TTL_MS2 });
8108
8146
  return reply.code(200).send({ skills });
8109
- } catch {
8110
- return reply.code(200).send({ skills: [] });
8147
+ } catch (error) {
8148
+ const message = error instanceof Error ? error.message : String(error);
8149
+ request.log.warn({ err: error }, "[agent] failed to load skills");
8150
+ return reply.code(200).send({ skills: [], error: message });
8111
8151
  }
8112
8152
  });
8113
8153
  done();
@@ -8737,13 +8777,27 @@ function reloadRoutes(app, opts, done) {
8737
8777
  const hookResult = await opts.beforeReload?.();
8738
8778
  const reloaded = await opts.harness.reloadSession(sessionId);
8739
8779
  const restart_warnings = hookResult?.restart_warnings;
8740
- const diagnostics = hookResult?.diagnostics;
8780
+ const diagnostics = [
8781
+ ...hookResult?.diagnostics ?? [],
8782
+ ...(opts.harness.getResourceDiagnostics?.(sessionId) ?? []).map((d) => ({
8783
+ source: d.source,
8784
+ // The harness already folds the path into the message.
8785
+ message: d.message
8786
+ }))
8787
+ ];
8788
+ if (!reloaded) {
8789
+ diagnostics.push({
8790
+ source: "reload",
8791
+ message: "No live agent session to reload yet \u2014 changes apply to the next session."
8792
+ });
8793
+ }
8794
+ opts.onDiagnostics?.(diagnostics);
8741
8795
  return {
8742
8796
  ok: true,
8743
8797
  sessionId,
8744
8798
  reloaded,
8745
8799
  ...restart_warnings && restart_warnings.length > 0 ? { restart_warnings } : {},
8746
- ...diagnostics && diagnostics.length > 0 ? { diagnostics } : {}
8800
+ ...diagnostics.length > 0 ? { diagnostics } : {}
8747
8801
  };
8748
8802
  } catch (error) {
8749
8803
  const message = error instanceof Error ? error.message : String(error);
@@ -10398,6 +10452,41 @@ function toSessionCtx(ctx) {
10398
10452
  return { workspaceId: ctx.workspaceId, userId: ctx.authSubject };
10399
10453
  }
10400
10454
 
10455
+ // src/server/tools/pluginDiagnostics.ts
10456
+ function createPluginDiagnosticsTool(deps) {
10457
+ return {
10458
+ name: "plugin_diagnostics",
10459
+ description: [
10460
+ "Return current plugin/skill loading errors plus the diagnostics from the",
10461
+ "last /reload. Call this after asking the user to run /reload \u2014 or whenever a",
10462
+ "plugin or skill you expected does not appear to be loaded \u2014 then fix the",
10463
+ "reported error and ask the user to /reload again. Returns a JSON object with",
10464
+ "lastReloadDiagnostics, resourceDiagnostics, and pluginErrors arrays; an empty",
10465
+ "result means no load errors were detected."
10466
+ ].join(" "),
10467
+ parameters: {
10468
+ type: "object",
10469
+ properties: {},
10470
+ additionalProperties: false
10471
+ },
10472
+ async execute(_params, ctx) {
10473
+ const harness = deps.getHarness();
10474
+ const resourceDiagnostics = harness?.getResourceDiagnostics?.(ctx.sessionId ?? "") ?? [];
10475
+ const pluginErrors = deps.getPluginErrors ? await deps.getPluginErrors() : [];
10476
+ const payload = {
10477
+ lastReloadDiagnostics: deps.getLastReloadDiagnostics(),
10478
+ resourceDiagnostics,
10479
+ pluginErrors
10480
+ };
10481
+ return {
10482
+ content: [{ type: "text", text: JSON.stringify(payload) }],
10483
+ details: payload,
10484
+ isError: false
10485
+ };
10486
+ }
10487
+ };
10488
+ }
10489
+
10401
10490
  // src/server/createAgentApp.ts
10402
10491
  var DEFAULT_VERSION = "0.1.0-dev";
10403
10492
  var DEFAULT_SESSION_ID = "default";
@@ -10433,6 +10522,8 @@ async function createAgentApp(opts = {}) {
10433
10522
  ...opts.pi?.additionalSkillPaths ?? []
10434
10523
  ]
10435
10524
  };
10525
+ let harnessRef;
10526
+ let lastReloadDiagnostics = [];
10436
10527
  const tools = [
10437
10528
  ...buildHarnessAgentTools(runtimeBundle, {
10438
10529
  getCurrent: () => {
@@ -10442,7 +10533,14 @@ async function createAgentApp(opts = {}) {
10442
10533
  }),
10443
10534
  ...opts.disableDefaultFileTools ? [] : buildFilesystemAgentTools(runtimeBundle),
10444
10535
  ...opts.extraTools ?? [],
10445
- ...pluginTools
10536
+ ...pluginTools,
10537
+ createPluginDiagnosticsTool({
10538
+ getLastReloadDiagnostics: () => lastReloadDiagnostics,
10539
+ getHarness: () => harnessRef,
10540
+ ...opts.getPluginDiagnostics ? {
10541
+ getPluginErrors: () => opts.getPluginDiagnostics({ workspaceId: sessionId, workspaceRoot })
10542
+ } : {}
10543
+ })
10446
10544
  ];
10447
10545
  const harnessFactory = opts.harnessFactory ?? ((input) => createPiCodingAgentHarness({
10448
10546
  ...input,
@@ -10461,6 +10559,7 @@ async function createAgentApp(opts = {}) {
10461
10559
  systemPromptDynamic: opts.systemPromptDynamic,
10462
10560
  telemetry: opts.telemetry
10463
10561
  });
10562
+ harnessRef = harness;
10464
10563
  const sessionChangesTracker = new InMemorySessionChangesTracker();
10465
10564
  const readyTracker = new ReadyStatusTracker({
10466
10565
  sandboxReady: resolvedMode !== "vercel-sandbox",
@@ -10511,7 +10610,14 @@ async function createAgentApp(opts = {}) {
10511
10610
  await app.register(sessionChangesRoutes, { tracker: sessionChangesTracker });
10512
10611
  await app.register(catalogRoutes, { tools });
10513
10612
  await app.register(commandsRoutes, { harness, defaultSessionId: sessionId, workdir: runtimeBundle.workspace.root });
10514
- await app.register(reloadRoutes, { harness, defaultSessionId: sessionId, beforeReload: opts.beforeReload });
10613
+ await app.register(reloadRoutes, {
10614
+ harness,
10615
+ defaultSessionId: sessionId,
10616
+ beforeReload: opts.beforeReload,
10617
+ onDiagnostics: (diagnostics) => {
10618
+ lastReloadDiagnostics = diagnostics;
10619
+ }
10620
+ });
10515
10621
  await app.register(readyStatusRoutes, { tracker: readyTracker });
10516
10622
  return app;
10517
10623
  }
@@ -10980,7 +11086,15 @@ var registerAgentRoutes = async (app, opts) => {
10980
11086
  getReadiness: () => checkReadiness("runtime:python", {})
10981
11087
  }),
10982
11088
  ...buildFilesystemAgentTools(runtimeBundle),
10983
- ...buildUploadAgentTools(runtimeBundle)
11089
+ ...buildUploadAgentTools(runtimeBundle),
11090
+ createPluginDiagnosticsTool({
11091
+ // `binding` is assigned later in this function; read through thunks.
11092
+ getLastReloadDiagnostics: () => binding?.lastReloadDiagnostics ?? [],
11093
+ getHarness: () => binding?.harness,
11094
+ ...opts.getPluginDiagnostics ? {
11095
+ getPluginErrors: () => opts.getPluginDiagnostics({ workspaceId, workspaceRoot: root })
11096
+ } : {}
11097
+ })
10984
11098
  ];
10985
11099
  const pluginTools = [];
10986
11100
  if (modeAdapter.workspaceFsCapability === "strong") {
@@ -11298,13 +11412,28 @@ var registerAgentRoutes = async (app, opts) => {
11298
11412
  const reloadSessionId = request.body?.sessionId || sessionId;
11299
11413
  const reloaded = await binding.harness.reloadSession(reloadSessionId);
11300
11414
  const restart_warnings = hookResult?.restart_warnings;
11301
- const diagnostics = hookResult?.diagnostics;
11415
+ const diagnostics = [
11416
+ ...hookResult?.diagnostics ?? [],
11417
+ ...(binding.harness.getResourceDiagnostics?.(reloadSessionId) ?? []).map((d) => ({
11418
+ source: d.source,
11419
+ // The harness already folds the path into the message (front only
11420
+ // renders `.message`).
11421
+ message: d.message
11422
+ }))
11423
+ ];
11424
+ if (!reloaded) {
11425
+ diagnostics.push({
11426
+ source: "reload",
11427
+ message: "No live agent session to reload yet \u2014 changes apply to the next session."
11428
+ });
11429
+ }
11430
+ binding.lastReloadDiagnostics = diagnostics;
11302
11431
  return {
11303
11432
  ok: true,
11304
11433
  sessionId: reloadSessionId,
11305
11434
  reloaded,
11306
11435
  ...restart_warnings && restart_warnings.length > 0 ? { restart_warnings } : {},
11307
- ...diagnostics && diagnostics.length > 0 ? { diagnostics } : {}
11436
+ ...diagnostics.length > 0 ? { diagnostics } : {}
11308
11437
  };
11309
11438
  } catch (error) {
11310
11439
  const message = error instanceof Error ? error.message : String(error);
@@ -1,5 +1,5 @@
1
- import { W as Workspace, S as Sandbox, F as FileSearch, A as AgentTool } from '../agentPluginEvents-DgNc3erX.js';
2
- export { a as AgentHarness, C as CommandNotifyPayload, E as Entry, b as ExecOptions, c as ExecResult, I as IsolatedCodeInput, d as IsolatedCodeOutput, J as JSONSchema, R as RunContext, e as SandboxCapability, f as SandboxHandleRecord, g as SandboxHandleStore, h as SendMessageInput, i as Stat, T as TelemetryEvent, j as TelemetrySink, k as ToolExecContext, l as ToolResult, m as WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT, n as WORKSPACE_COMMAND_NOTIFY_EVENT, o as WorkspaceRuntimeContext, p as noopTelemetry, s as safeCapture } from '../agentPluginEvents-DgNc3erX.js';
1
+ import { W as Workspace, S as Sandbox, F as FileSearch, A as AgentTool } from '../agentPluginEvents-CKrZLW3g.js';
2
+ export { a as AgentHarness, C as CommandNotifyPayload, E as Entry, b as ExecOptions, c as ExecResult, I as IsolatedCodeInput, d as IsolatedCodeOutput, J as JSONSchema, R as RunContext, e as SandboxCapability, f as SandboxHandleRecord, g as SandboxHandleStore, h as SendMessageInput, i as Stat, T as TelemetryEvent, j as TelemetrySink, k as ToolExecContext, l as ToolResult, m as WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT, n as WORKSPACE_COMMAND_NOTIFY_EVENT, o as WorkspaceRuntimeContext, p as noopTelemetry, s as safeCapture } from '../agentPluginEvents-CKrZLW3g.js';
3
3
  import { B as BoringChatPart, T as ToolUiMetadata } from '../piChatEvent-Ck1BAE_m.js';
4
4
  export { A as ApiErrorPayload, a as ApiErrorPayloadSchema, b as ApiErrorResponse, c as ApiErrorResponseSchema, d as BoringChatMessage, C as ChatAttachmentPayload, e as ChatError, f as ChatModelSelection, g as ChatSubmitPayload, h as CommandReceipt, E as ERROR_CODES, i as ErrorCode, j as ErrorLogFields, k as ErrorLogFieldsSchema, F as FollowUpPayload, l as FollowUpReceipt, I as InterruptPayload, m as InterruptReceipt, P as PiChatEvent, n as PiChatHeartbeatFrame, o as PiChatSnapshot, p as PiChatStatus, q as PiChatStreamFrame, r as PromptPayload, s as PromptReceipt, Q as QueueClearPayload, t as QueueClearReceipt, u as QueuedUserMessage, S as StopPayload, v as StopReceipt, w as ThinkingLevel, x as extractToolUiMetadata, y as isToolUiMetadata } from '../piChatEvent-Ck1BAE_m.js';
5
5
  export { S as SessionCtx, a as SessionDetail, b as SessionStore, c as SessionSummary } from '../session-BRovhe0D.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-agent",
3
- "version": "0.1.36",
3
+ "version": "0.1.38",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Pane-embeddable coding agent. Ships direct/local/vercel-sandbox execution modes behind one interface.",
@@ -74,7 +74,7 @@
74
74
  "use-stick-to-bottom": "^1.1.3",
75
75
  "yaml": "^2.8.3",
76
76
  "zod": "^3.25.76",
77
- "@hachej/boring-ui-kit": "0.1.36"
77
+ "@hachej/boring-ui-kit": "0.1.38"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@antithesishq/bombadil": "0.5.0",