@bermudi/pi-delegate 0.1.11 → 0.1.13

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.
package/format.ts CHANGED
@@ -2,6 +2,7 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import { renderOutputForLLM } from "./spill.ts";
5
+ import { getOutputSpillThreshold, getOutputSpillTail } from "./config.ts";
5
6
  import type {
6
7
  ResolvedTask,
7
8
  TaskProgress,
@@ -355,7 +356,13 @@ function isResumableSessionFile(sessionFile: string): boolean {
355
356
  * notice instead of a retry hint — so the parent model is told to re-dispatch
356
357
  * fresh rather than left to fabricate a path or chase a dead resume.
357
358
  */
358
- export function formatFailedTask(r: TaskResult, cwd?: string): string[] {
359
+ export function formatFailedTask(
360
+ r: TaskResult,
361
+ cwd?: string,
362
+ /** Dispatch-scoped snapshot for output-spill bounds; falls back to live
363
+ * config when unset (direct callers, legacy tests). */
364
+ config?: import("./config.ts").DelegateConfig,
365
+ ): string[] {
359
366
  const parts: string[] = [];
360
367
  const isAbort = r.error === "Aborted";
361
368
  // Empty string is falsy but not nullish — `||` covers both undefined and "".
@@ -366,8 +373,16 @@ export function formatFailedTask(r: TaskResult, cwd?: string): string[] {
366
373
  parts.push(`[${isAbort ? "ABORTED" : "FAILED"}: ${failParts.join(" · ")}]`);
367
374
 
368
375
  // Surface partial assistant output even when the task did not complete.
376
+ // Pass the dispatch-scoped spill bounds so a mid-task config change cannot
377
+ // widen (or narrow) the partial output the parent sees — the bounds captured
378
+ // when the task started are the ones the caller committed to.
369
379
  if (r.output && r.output !== "(no output)") {
370
- parts.push(renderOutputForLLM(r.output, r.agent));
380
+ parts.push(
381
+ renderOutputForLLM(r.output, r.agent, {
382
+ thresholdChars: getOutputSpillThreshold(config),
383
+ tailChars: getOutputSpillTail(config),
384
+ }),
385
+ );
371
386
  }
372
387
 
373
388
  if (r.sessionFile && isResumableSessionFile(r.sessionFile)) {
@@ -414,6 +429,8 @@ export function formatFailedTask(r: TaskResult, cwd?: string): string[] {
414
429
  export function formatCompletedTask(
415
430
  task: ResolvedTask,
416
431
  result: TaskResult,
432
+ /** Dispatch-scoped snapshot for output-spill bounds. */
433
+ config?: import("./config.ts").DelegateConfig,
417
434
  ): string[] {
418
435
  const parts: string[] = [];
419
436
  // `|| task.sessionAction` covers action-only tasks (close/list/...) where prompt is
@@ -425,7 +442,7 @@ export function formatCompletedTask(
425
442
  for (const w of task.warnings) parts.push(`[WARNING: ${w}]`);
426
443
  }
427
444
  if (result.error) {
428
- parts.push(...formatFailedTask(result, task.cwd));
445
+ parts.push(...formatFailedTask(result, task.cwd, config));
429
446
  } else {
430
447
  const meta = [
431
448
  `OK | ${fmtDuration(result.durationMs)} | ${fmtTokens(result.tokens)} tokens`,
@@ -434,8 +451,37 @@ export function formatCompletedTask(
434
451
  const touched = relativeTouchedSummary(result.touchedFiles, task.cwd);
435
452
  if (touched) meta.push(`touched (best-effort): ${touched}`);
436
453
  parts.push(
437
- `[${meta.join(" · ")}]\n\n${renderOutputForLLM(result.output, result.agent)}`,
454
+ `[${meta.join(" · ")}]\n\n${renderOutputForLLM(
455
+ result.output,
456
+ result.agent,
457
+ {
458
+ thresholdChars: getOutputSpillThreshold(config),
459
+ tailChars: getOutputSpillTail(config),
460
+ },
461
+ )}`,
462
+ );
463
+ }
464
+ if (result.integration) {
465
+ const integration = result.integration;
466
+ parts.push(
467
+ `[INTEGRATION: ${integration.status} · proposed ${integration.proposedFiles.length} file(s) · applied ${integration.appliedFiles.length} file(s)]`,
438
468
  );
469
+ if (integration.baselineRef)
470
+ parts.push(`baseline ref: ${integration.baselineRef}`);
471
+ if (integration.proposalRef)
472
+ parts.push(`proposal ref: ${integration.proposalRef}`);
473
+ if (integration.patchPath)
474
+ parts.push(`full patch: ${integration.patchPath}`);
475
+ if (integration.worktreePath)
476
+ parts.push(`conflict worktree: ${integration.worktreePath}`);
477
+ for (const conflict of integration.conflicts ?? []) {
478
+ parts.push(`conflict: ${conflict.path}: ${conflict.reason}`);
479
+ }
480
+ if (integration.status === "applied_unverified") {
481
+ parts.push(
482
+ 'Changes were applied but not verified. Suggested next call: delegate({ tasks: [{ agent: "reviewer", workspace: "scratch", prompt: "Review the applied isolated changes and run the relevant tests." }] })',
483
+ );
484
+ }
439
485
  }
440
486
  return parts;
441
487
  }
package/host.ts CHANGED
@@ -89,6 +89,8 @@ export interface HostDepsOptions {
89
89
  * tasks (no named agent) pass undefined to use the discovered prompt.
90
90
  */
91
91
  systemPrompt?: string;
92
+ /** Dispatch-scoped delegate.json snapshot for the provider-extension allowlist. */
93
+ delegateConfig?: import("./config.ts").DelegateConfig;
92
94
  }
93
95
 
94
96
  const hostDepsCache = new GenerationCache<HostDeps>();
@@ -163,6 +165,7 @@ export async function getHostDeps(options: HostDepsOptions): Promise<HostDeps> {
163
165
  options.modelProvider,
164
166
  options.cwd,
165
167
  agentDir,
168
+ options.delegateConfig,
166
169
  );
167
170
 
168
171
  // Provider configs may contain functions (custom stream/OAuth handlers), so a
@@ -199,7 +202,9 @@ export async function getHostDeps(options: HostDepsOptions): Promise<HostDeps> {
199
202
  * allowlist, since editing it changes what a subsequent build would inject.
200
203
  */
201
204
  function hostDepsCacheKey(options: HostDepsOptions, agentDir: string): string {
202
- const providerExtensions = getSubagentProviderExtensionMap();
205
+ const providerExtensions = getSubagentProviderExtensionMap(
206
+ options.delegateConfig,
207
+ );
203
208
  return JSON.stringify({
204
209
  agentDir,
205
210
  cwd: options.cwd,