@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/README.md +64 -15
- package/agents.ts +1 -1
- package/assistant-preview.ts +31 -0
- package/browser-state.ts +250 -0
- package/browser.ts +334 -0
- package/concurrency.ts +7 -0
- package/delegate.ts +8 -0
- package/dispatch.ts +512 -163
- package/extension.ts +65 -28
- package/format.ts +27 -9
- package/host.ts +1 -1
- package/isolated-workspace.ts +154 -8
- package/lifecycle.ts +34 -20
- package/manual.ts +21 -8
- package/package.json +2 -1
- package/parent-context.ts +1 -1
- package/pause.ts +81 -0
- package/pool.ts +492 -428
- package/render-branches.ts +19 -5
- package/render-result.ts +7 -0
- package/runner.ts +55 -1
- package/runtime.ts +36 -0
- package/schema.ts +29 -18
- package/status.ts +38 -11
- package/task-resolution.ts +12 -9
- package/test-harness.ts +81 -0
- package/ticket-format.ts +29 -7
- package/tickets.ts +724 -576
- package/types.ts +33 -0
- package/workspace.ts +58 -27
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
|
-
|
|
285
|
-
|
|
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
|
-
|
|
794
|
-
sourceCwd =
|
|
795
|
-
|
|
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?.();
|