@bermudi/pi-delegate 0.1.18 → 0.1.20

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/types.ts CHANGED
@@ -84,6 +84,8 @@ export interface TicketWaiter {
84
84
  }
85
85
 
86
86
  export interface AsyncTicket {
87
+ /** Orthogonal to lifecycle status: paused tickets remain live reservations. */
88
+ pause?: import("./pause.ts").PauseController;
87
89
  id: string;
88
90
  created: number;
89
91
  completedAt?: number;
@@ -119,6 +121,9 @@ export interface AsyncTicket {
119
121
  workersSettled?: boolean;
120
122
  /** Batch-level warning attached to this dispatch, not to any one task. */
121
123
  dispatchWarning?: string;
124
+ /** Batch-level notice that overlapping shared writers were serialized into
125
+ * task order instead of rejected. Informational, not a warning. */
126
+ serializedNotice?: string;
122
127
  /** Immutable dispatch-scoped delegate.json snapshot used by async workers and
123
128
  * later result formatting. */
124
129
  config?: import("./config.ts").DelegateConfig;
@@ -250,6 +255,12 @@ export type TaskFailureKind =
250
255
  "cancelled" | "stalled" | "model_error" | "deadline_exceeded";
251
256
 
252
257
  export interface TaskProgress {
258
+ /** Bounded assistant-text tail for the live browser; no private thinking. */
259
+ assistantPreview?: string;
260
+ activity?: string;
261
+ /** Index of the same-call writer this task must follow. */
262
+ waitingFor?: number;
263
+ paused?: boolean;
253
264
  id?: string;
254
265
  index: number;
255
266
  agent: string;
@@ -275,6 +286,7 @@ export interface TaskProgress {
275
286
  }
276
287
 
277
288
  export interface DelegateDetails {
289
+ pauseState?: import("./pause.ts").PauseState;
278
290
  tasks: TaskDef[];
279
291
  results: (TaskResult | { error: string })[];
280
292
  progress: TaskProgress[];
@@ -291,9 +303,14 @@ export interface DelegateDetails {
291
303
  overlapWarning?: string;
292
304
  /** Warning that applies to the whole dispatch rather than an individual task. */
293
305
  dispatchWarning?: string;
306
+ /** Notice that overlapping shared writers in this dispatch were serialized
307
+ * into task order instead of rejected. Informational, not a warning. */
308
+ serializedNotice?: string;
294
309
  }
295
310
 
296
311
  export interface TaskResult {
312
+ /** Batch-local ordered-writer group; never suppress incomplete evidence. */
313
+ serializedGroup?: number;
297
314
  id?: string;
298
315
  agent: string;
299
316
  /** Short tag (via `formatResumeTag`) of the transcript this task continued
@@ -342,6 +359,7 @@ export interface TaskResult {
342
359
  export type TaskIntegrationStatus =
343
360
  | "applied_unverified"
344
361
  | "no_changes"
362
+ | "retained"
345
363
  | "conflict"
346
364
  | "discarded"
347
365
  | "apply_failed";
@@ -381,6 +399,15 @@ export type TaskIntegration =
381
399
  * paths. They do not turn the discarded proposal into an apply failure. */
382
400
  classificationIssues?: Array<{ path: string; reason: string }>;
383
401
  })
402
+ | (TaskIntegrationFiles & {
403
+ status: "retained";
404
+ reason: string;
405
+ baselineRef: string;
406
+ proposalRef: string;
407
+ patchPath: string;
408
+ worktreePath?: string;
409
+ conflicts?: never;
410
+ })
384
411
  | (TaskIntegrationFiles & {
385
412
  status: "conflict";
386
413
  conflicts: Array<{ path: string; reason: string }>;
@@ -410,6 +437,8 @@ export interface AgentRunConfig {
410
437
  }
411
438
 
412
439
  export interface AgentProgressUpdate {
440
+ assistantPreview?: string;
441
+ activity?: string;
413
442
  tokens: number;
414
443
  toolUses: number;
415
444
  durationMs: number;
@@ -420,6 +449,7 @@ export interface AgentProgressUpdate {
420
449
  }
421
450
 
422
451
  export interface TaskRunEnv {
452
+ pause?: import("./pause.ts").PauseController;
423
453
  /** Abort signal — parent's for sync, ticket's for async. May be undefined when no parent signal is available. */
424
454
  signal: AbortSignal | undefined;
425
455
  modelRegistry: ModelRegistry;
@@ -444,6 +474,9 @@ export interface TaskRunEnv {
444
474
  * use this instead of the live singleton so retry/stall/output/provider
445
475
  * settings stay stable for the ticket's lifetime. */
446
476
  config?: import("./config.ts").DelegateConfig;
477
+ /** Injectable runtime context. When omitted, task execution falls back to the
478
+ * module-level default runtime for backward compatibility. */
479
+ runtime?: import("./runtime.ts").DelegateRuntime;
447
480
  }
448
481
 
449
482
  /** Structural subset of Pi's `ExtensionContext` used by delegate's
package/workspace.ts CHANGED
@@ -280,9 +280,10 @@ async function assertGitMetadataContained(
280
280
  root: string,
281
281
  signal: AbortSignal,
282
282
  ): Promise<void> {
283
- const hasGitDir = await fs.promises
284
- .stat(path.join(root, ".git"))
285
- .then((stat) => stat.isDirectory(), () => false);
283
+ const hasGitDir = await fs.promises.stat(path.join(root, ".git")).then(
284
+ (stat) => stat.isDirectory(),
285
+ () => false,
286
+ );
286
287
  if (!hasGitDir) return;
287
288
  const effectiveWorktree = path.resolve(
288
289
  (
@@ -749,6 +750,57 @@ async function sweepStaleScratchLeases(
749
750
  }
750
751
  }
751
752
 
753
+ /** Read-only pre-flight every scratch creation runs before anything exists:
754
+ * canonical cwd, safe project-root discovery, and the shared tree/git-metadata
755
+ * rule. Fast-fails on blockers that are knowable before a container, lease, or
756
+ * copy is created — a rejection here costs milliseconds instead of a doomed
757
+ * reflink copy. The post-copy validation stays the authority for trees that
758
+ * change mid-copy. Throws `ScratchSetupError` on any deterministic blocker. */
759
+ async function resolveScratchSource(
760
+ cwd: string,
761
+ signal: AbortSignal,
762
+ parentSignal: AbortSignal | undefined,
763
+ ): Promise<{ sourceCwd: string; sourceRoot: string }> {
764
+ throwIfSetupCancelled(signal, parentSignal);
765
+ const sourceCwd = await fs.promises.realpath(cwd);
766
+ throwIfSetupCancelled(signal, parentSignal);
767
+ const sourceRoot = await findCopyRoot(sourceCwd, signal);
768
+ throwIfSetupCancelled(signal, parentSignal);
769
+ if (!isWithin(sourceRoot, sourceCwd)) {
770
+ throw new ScratchSetupError(
771
+ "Scratch workspace could not map the task cwd into its project root.",
772
+ );
773
+ }
774
+ await validateScratchTree(
775
+ sourceRoot,
776
+ sourceRoot,
777
+ false,
778
+ signal,
779
+ parentSignal,
780
+ );
781
+ await assertGitMetadataContained(sourceRoot, signal);
782
+ return { sourceCwd, sourceRoot };
783
+ }
784
+
785
+ /** Verify that scratch setup would proceed for `cwd`, without creating
786
+ * anything: the same pre-flight `createScratchWorkspace` runs before any
787
+ * container, lease, or copy exists (one walker, one rule — the post-copy
788
+ * validation stays the authority for trees that change mid-copy). Throws
789
+ * `ScratchSetupError` with the same message setup would produce, so callers
790
+ * can recommend scratch only where it can actually run. */
791
+ export async function checkScratchWorkspaceSupport(
792
+ cwd: string,
793
+ signal?: AbortSignal,
794
+ ): Promise<void> {
795
+ if (process.platform !== "linux" || !fs.existsSync("/proc/self/fd")) {
796
+ throw new ScratchSetupError(
797
+ "Scratch workspaces require Linux with GNU cp and /proc/self/fd available.",
798
+ );
799
+ }
800
+ const active = signal ?? new AbortController().signal;
801
+ await resolveScratchSource(cwd, active, active);
802
+ }
803
+
752
804
  /**
753
805
  * Make an ephemeral, same-filesystem CoW copy of the Git repository containing
754
806
  * cwd (or cwd itself outside Git). This is accidental-write isolation, not a
@@ -790,30 +842,9 @@ export async function createScratchWorkspace(
790
842
  let copiedOwnerStat: fs.Stats | undefined;
791
843
  try {
792
844
  if (signal?.aborted) controller.abort(signal.reason);
793
- throwIfSetupCancelled(controller.signal, signal);
794
- sourceCwd = await fs.promises.realpath(cwd);
795
- throwIfSetupCancelled(controller.signal, signal);
796
- sourceRoot = await findCopyRoot(sourceCwd, controller.signal);
797
- throwIfSetupCancelled(controller.signal, signal);
798
- if (!isWithin(sourceRoot, sourceCwd)) {
799
- throw new ScratchSetupError(
800
- "Scratch workspace could not map the task cwd into its project root.",
801
- );
802
- }
803
-
804
- // Fast-fail on blockers that are knowable before anything is created:
805
- // the same rule the copy validation enforces, run read-only against the
806
- // source. A rejection here costs milliseconds instead of a doomed
807
- // reflink copy, and no lease/container is left behind. The post-copy
808
- // validation below stays the authority — the tree can change mid-copy.
809
- await validateScratchTree(
810
- sourceRoot,
811
- sourceRoot,
812
- false,
813
- controller.signal,
814
- signal,
815
- );
816
- await assertGitMetadataContained(sourceRoot, controller.signal);
845
+ const resolved = await resolveScratchSource(cwd, controller.signal, signal);
846
+ sourceCwd = resolved.sourceCwd;
847
+ sourceRoot = resolved.sourceRoot;
817
848
 
818
849
  containerDir = path.join(path.dirname(sourceRoot), SCRATCH_CONTAINER_NAME);
819
850
  const uid = process.getuid?.();