@hachej/boring-agent 0.1.37 → 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
  /**
@@ -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).
@@ -6564,6 +6564,40 @@ function createPiCodingAgentHarness(opts) {
6564
6564
  hasPiSession(sessionId) {
6565
6565
  return piSessions.has(sessionId);
6566
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
+ },
6567
6601
  reloadSession: reloadPiSession,
6568
6602
  async getSlashCommands(sessionId, ctx) {
6569
6603
  const handle = await getOrCreatePiSession(sessionId, { sessionId, message: "" }, ctx);
@@ -8110,8 +8144,10 @@ function skillsRoutes(app, opts, done) {
8110
8144
  }));
8111
8145
  cached.set(cacheKey, { skills, expiresAt: now + CACHE_TTL_MS2 });
8112
8146
  return reply.code(200).send({ skills });
8113
- } catch {
8114
- 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 });
8115
8151
  }
8116
8152
  });
8117
8153
  done();
@@ -8741,13 +8777,27 @@ function reloadRoutes(app, opts, done) {
8741
8777
  const hookResult = await opts.beforeReload?.();
8742
8778
  const reloaded = await opts.harness.reloadSession(sessionId);
8743
8779
  const restart_warnings = hookResult?.restart_warnings;
8744
- 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);
8745
8795
  return {
8746
8796
  ok: true,
8747
8797
  sessionId,
8748
8798
  reloaded,
8749
8799
  ...restart_warnings && restart_warnings.length > 0 ? { restart_warnings } : {},
8750
- ...diagnostics && diagnostics.length > 0 ? { diagnostics } : {}
8800
+ ...diagnostics.length > 0 ? { diagnostics } : {}
8751
8801
  };
8752
8802
  } catch (error) {
8753
8803
  const message = error instanceof Error ? error.message : String(error);
@@ -10402,6 +10452,41 @@ function toSessionCtx(ctx) {
10402
10452
  return { workspaceId: ctx.workspaceId, userId: ctx.authSubject };
10403
10453
  }
10404
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
+
10405
10490
  // src/server/createAgentApp.ts
10406
10491
  var DEFAULT_VERSION = "0.1.0-dev";
10407
10492
  var DEFAULT_SESSION_ID = "default";
@@ -10437,6 +10522,8 @@ async function createAgentApp(opts = {}) {
10437
10522
  ...opts.pi?.additionalSkillPaths ?? []
10438
10523
  ]
10439
10524
  };
10525
+ let harnessRef;
10526
+ let lastReloadDiagnostics = [];
10440
10527
  const tools = [
10441
10528
  ...buildHarnessAgentTools(runtimeBundle, {
10442
10529
  getCurrent: () => {
@@ -10446,7 +10533,14 @@ async function createAgentApp(opts = {}) {
10446
10533
  }),
10447
10534
  ...opts.disableDefaultFileTools ? [] : buildFilesystemAgentTools(runtimeBundle),
10448
10535
  ...opts.extraTools ?? [],
10449
- ...pluginTools
10536
+ ...pluginTools,
10537
+ createPluginDiagnosticsTool({
10538
+ getLastReloadDiagnostics: () => lastReloadDiagnostics,
10539
+ getHarness: () => harnessRef,
10540
+ ...opts.getPluginDiagnostics ? {
10541
+ getPluginErrors: () => opts.getPluginDiagnostics({ workspaceId: sessionId, workspaceRoot })
10542
+ } : {}
10543
+ })
10450
10544
  ];
10451
10545
  const harnessFactory = opts.harnessFactory ?? ((input) => createPiCodingAgentHarness({
10452
10546
  ...input,
@@ -10465,6 +10559,7 @@ async function createAgentApp(opts = {}) {
10465
10559
  systemPromptDynamic: opts.systemPromptDynamic,
10466
10560
  telemetry: opts.telemetry
10467
10561
  });
10562
+ harnessRef = harness;
10468
10563
  const sessionChangesTracker = new InMemorySessionChangesTracker();
10469
10564
  const readyTracker = new ReadyStatusTracker({
10470
10565
  sandboxReady: resolvedMode !== "vercel-sandbox",
@@ -10515,7 +10610,14 @@ async function createAgentApp(opts = {}) {
10515
10610
  await app.register(sessionChangesRoutes, { tracker: sessionChangesTracker });
10516
10611
  await app.register(catalogRoutes, { tools });
10517
10612
  await app.register(commandsRoutes, { harness, defaultSessionId: sessionId, workdir: runtimeBundle.workspace.root });
10518
- 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
+ });
10519
10621
  await app.register(readyStatusRoutes, { tracker: readyTracker });
10520
10622
  return app;
10521
10623
  }
@@ -10984,7 +11086,15 @@ var registerAgentRoutes = async (app, opts) => {
10984
11086
  getReadiness: () => checkReadiness("runtime:python", {})
10985
11087
  }),
10986
11088
  ...buildFilesystemAgentTools(runtimeBundle),
10987
- ...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
+ })
10988
11098
  ];
10989
11099
  const pluginTools = [];
10990
11100
  if (modeAdapter.workspaceFsCapability === "strong") {
@@ -11302,13 +11412,28 @@ var registerAgentRoutes = async (app, opts) => {
11302
11412
  const reloadSessionId = request.body?.sessionId || sessionId;
11303
11413
  const reloaded = await binding.harness.reloadSession(reloadSessionId);
11304
11414
  const restart_warnings = hookResult?.restart_warnings;
11305
- 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;
11306
11431
  return {
11307
11432
  ok: true,
11308
11433
  sessionId: reloadSessionId,
11309
11434
  reloaded,
11310
11435
  ...restart_warnings && restart_warnings.length > 0 ? { restart_warnings } : {},
11311
- ...diagnostics && diagnostics.length > 0 ? { diagnostics } : {}
11436
+ ...diagnostics.length > 0 ? { diagnostics } : {}
11312
11437
  };
11313
11438
  } catch (error) {
11314
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.37",
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.37"
77
+ "@hachej/boring-ui-kit": "0.1.38"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@antithesishq/bombadil": "0.5.0",