@bermudi/pi-delegate 0.1.17 → 0.1.19
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 +14 -13
- package/agents.ts +1 -1
- package/concurrency.ts +7 -0
- package/delegate.ts +6 -0
- package/dispatch.ts +460 -163
- package/extension.ts +35 -26
- package/format.ts +4 -1
- package/host.ts +1 -1
- package/isolated-workspace.ts +154 -8
- package/lifecycle.ts +31 -20
- package/manual.ts +11 -7
- package/model.ts +34 -2
- package/package.json +2 -1
- package/parent-context.ts +1 -1
- package/pool.ts +492 -428
- package/render-branches.ts +2 -1
- package/render-result.ts +6 -0
- package/runtime.ts +36 -0
- package/schema.ts +22 -13
- package/status.ts +24 -11
- package/task-resolution.ts +12 -9
- package/test-harness.ts +81 -0
- package/ticket-format.ts +4 -3
- package/tickets.ts +672 -576
- package/types.ts +19 -0
- package/workspace.ts +58 -27
package/types.ts
CHANGED
|
@@ -119,6 +119,9 @@ export interface AsyncTicket {
|
|
|
119
119
|
workersSettled?: boolean;
|
|
120
120
|
/** Batch-level warning attached to this dispatch, not to any one task. */
|
|
121
121
|
dispatchWarning?: string;
|
|
122
|
+
/** Batch-level notice that overlapping shared writers were serialized into
|
|
123
|
+
* task order instead of rejected. Informational, not a warning. */
|
|
124
|
+
serializedNotice?: string;
|
|
122
125
|
/** Immutable dispatch-scoped delegate.json snapshot used by async workers and
|
|
123
126
|
* later result formatting. */
|
|
124
127
|
config?: import("./config.ts").DelegateConfig;
|
|
@@ -291,6 +294,9 @@ export interface DelegateDetails {
|
|
|
291
294
|
overlapWarning?: string;
|
|
292
295
|
/** Warning that applies to the whole dispatch rather than an individual task. */
|
|
293
296
|
dispatchWarning?: string;
|
|
297
|
+
/** Notice that overlapping shared writers in this dispatch were serialized
|
|
298
|
+
* into task order instead of rejected. Informational, not a warning. */
|
|
299
|
+
serializedNotice?: string;
|
|
294
300
|
}
|
|
295
301
|
|
|
296
302
|
export interface TaskResult {
|
|
@@ -342,6 +348,7 @@ export interface TaskResult {
|
|
|
342
348
|
export type TaskIntegrationStatus =
|
|
343
349
|
| "applied_unverified"
|
|
344
350
|
| "no_changes"
|
|
351
|
+
| "retained"
|
|
345
352
|
| "conflict"
|
|
346
353
|
| "discarded"
|
|
347
354
|
| "apply_failed";
|
|
@@ -381,6 +388,15 @@ export type TaskIntegration =
|
|
|
381
388
|
* paths. They do not turn the discarded proposal into an apply failure. */
|
|
382
389
|
classificationIssues?: Array<{ path: string; reason: string }>;
|
|
383
390
|
})
|
|
391
|
+
| (TaskIntegrationFiles & {
|
|
392
|
+
status: "retained";
|
|
393
|
+
reason: string;
|
|
394
|
+
baselineRef: string;
|
|
395
|
+
proposalRef: string;
|
|
396
|
+
patchPath: string;
|
|
397
|
+
worktreePath?: string;
|
|
398
|
+
conflicts?: never;
|
|
399
|
+
})
|
|
384
400
|
| (TaskIntegrationFiles & {
|
|
385
401
|
status: "conflict";
|
|
386
402
|
conflicts: Array<{ path: string; reason: string }>;
|
|
@@ -444,6 +460,9 @@ export interface TaskRunEnv {
|
|
|
444
460
|
* use this instead of the live singleton so retry/stall/output/provider
|
|
445
461
|
* settings stay stable for the ticket's lifetime. */
|
|
446
462
|
config?: import("./config.ts").DelegateConfig;
|
|
463
|
+
/** Injectable runtime context. When omitted, task execution falls back to the
|
|
464
|
+
* module-level default runtime for backward compatibility. */
|
|
465
|
+
runtime?: import("./runtime.ts").DelegateRuntime;
|
|
447
466
|
}
|
|
448
467
|
|
|
449
468
|
/** 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?.();
|