@bermudi/pi-delegate 0.1.10 → 0.1.11
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/delegate.ts +3 -2
- package/dispatch.ts +26 -27
- package/extension.ts +2 -4
- package/format.ts +2 -2
- package/host-cache.ts +70 -0
- package/host.ts +164 -811
- package/lifecycle.ts +653 -508
- package/manual.ts +0 -4
- package/package.json +1 -1
- package/pi-package-source.ts +293 -0
- package/provider-extensions.ts +528 -0
- package/quiescence.ts +262 -0
- package/runner.ts +32 -140
- package/schema.ts +77 -153
- package/task-resolution.ts +34 -16
- package/ticket-format.ts +323 -0
- package/tickets.ts +79 -248
- package/tools.ts +12 -0
- package/trusted-paths.ts +71 -0
- package/types.ts +4 -16
package/delegate.ts
CHANGED
|
@@ -5,7 +5,6 @@ export type {
|
|
|
5
5
|
SessionAction,
|
|
6
6
|
WorkspaceMode,
|
|
7
7
|
TicketAction,
|
|
8
|
-
DelegateAction,
|
|
9
8
|
DelegateArguments,
|
|
10
9
|
TaskDef,
|
|
11
10
|
AsyncTicket,
|
|
@@ -72,9 +71,10 @@ export {
|
|
|
72
71
|
notifyWaiters,
|
|
73
72
|
deliverTicketResults,
|
|
74
73
|
resolveFinalTicketStatus,
|
|
74
|
+
settleTicket,
|
|
75
75
|
formatCompletedTicket,
|
|
76
76
|
} from "./tickets.ts";
|
|
77
|
-
export type { TicketDelivery } from "./tickets.ts";
|
|
77
|
+
export type { TicketDelivery, SettleTicketOptions } from "./tickets.ts";
|
|
78
78
|
export {
|
|
79
79
|
recordTreeNavigation,
|
|
80
80
|
getCurrentLeafId,
|
|
@@ -98,6 +98,7 @@ export {
|
|
|
98
98
|
export type { ActiveTicketSummary } from "./status.ts";
|
|
99
99
|
export { getHostDeps, invalidateHostDepsCache } from "./host.ts";
|
|
100
100
|
export type { HostDeps, HostDepsOptions } from "./host.ts";
|
|
101
|
+
export { registerProviderExtensionNotifier } from "./provider-extensions.ts";
|
|
101
102
|
export {
|
|
102
103
|
aggregateTaskResults,
|
|
103
104
|
emptyUsage,
|
package/dispatch.ts
CHANGED
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
deliverTicketResults,
|
|
7
7
|
sweepTickets,
|
|
8
8
|
resolveFinalTicketStatus,
|
|
9
|
-
|
|
9
|
+
settleTicket,
|
|
10
10
|
notifyWaiters,
|
|
11
11
|
} from "./tickets.ts";
|
|
12
12
|
import { getConcurrencyLimit, getMaxAsyncTickets } from "./config.ts";
|
|
@@ -319,6 +319,16 @@ export function dispatchAsync(input: AsyncDispatchInput): DelegateToolResult {
|
|
|
319
319
|
// Worker must store the TaskResult back into ticket.results, since
|
|
320
320
|
// formatCompletedTicket/handlePoll read from there. Without the write,
|
|
321
321
|
// completed async tasks would be reported as PENDING.
|
|
322
|
+
//
|
|
323
|
+
// Worker settlement is complete before these live-runtime observers run.
|
|
324
|
+
// In particular, result delivery is allowed to fail without re-entering the
|
|
325
|
+
// worker completion path; the terminal ticket remains available to poll.
|
|
326
|
+
const finishLiveSettlement = (t: AsyncTicket): void => {
|
|
327
|
+
syncDelegateStatus();
|
|
328
|
+
settleAsyncCall(t, callSpan);
|
|
329
|
+
finishTicketDelivery(pi, t);
|
|
330
|
+
};
|
|
331
|
+
|
|
322
332
|
const completion = mapConcurrentByModel(
|
|
323
333
|
resolved,
|
|
324
334
|
(t) => getModelKey(t.model),
|
|
@@ -346,21 +356,16 @@ export function dispatchAsync(input: AsyncDispatchInput): DelegateToolResult {
|
|
|
346
356
|
// NOT be marked "done" — that would mask incomplete work as
|
|
347
357
|
// complete. resolveFinalTicketStatus returns "failed" for that
|
|
348
358
|
// case and for any case with a failed task.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
syncTicketBusyIndex(ticket);
|
|
360
|
-
}
|
|
361
|
-
syncDelegateStatus();
|
|
362
|
-
settleAsyncCall(ticket, callSpan);
|
|
363
|
-
finishTicketDelivery(pi, ticket);
|
|
359
|
+
// A "cancelling" ticket that outlived its workers settles as
|
|
360
|
+
// "cancelled": the per-task results record what actually happened;
|
|
361
|
+
// the ticket state reports that the batch was aborted by the caller.
|
|
362
|
+
settleTicket(ticket, {
|
|
363
|
+
status:
|
|
364
|
+
ticket.status === "running"
|
|
365
|
+
? resolveFinalTicketStatus(ticket)
|
|
366
|
+
: "cancelled",
|
|
367
|
+
});
|
|
368
|
+
finishLiveSettlement(ticket);
|
|
364
369
|
})
|
|
365
370
|
.catch((err) => {
|
|
366
371
|
// Defense-in-depth — should not happen if individual tasks catch properly.
|
|
@@ -370,17 +375,11 @@ export function dispatchAsync(input: AsyncDispatchInput): DelegateToolResult {
|
|
|
370
375
|
settleAsyncCall(ticket, callSpan);
|
|
371
376
|
return;
|
|
372
377
|
}
|
|
373
|
-
|
|
374
|
-
ticket.status
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
ticket.error = err instanceof Error ? err.message : String(err);
|
|
379
|
-
ticket.completedAt = Date.now();
|
|
380
|
-
syncTicketBusyIndex(ticket);
|
|
381
|
-
syncDelegateStatus();
|
|
382
|
-
settleAsyncCall(ticket, callSpan);
|
|
383
|
-
finishTicketDelivery(pi, ticket);
|
|
378
|
+
settleTicket(ticket, {
|
|
379
|
+
status: ticket.status === "cancelling" ? "cancelled" : "failed",
|
|
380
|
+
error: err instanceof Error ? err.message : String(err),
|
|
381
|
+
});
|
|
382
|
+
finishLiveSettlement(ticket);
|
|
384
383
|
});
|
|
385
384
|
ticket.completion = completion;
|
|
386
385
|
|
package/extension.ts
CHANGED
|
@@ -18,10 +18,8 @@ import {
|
|
|
18
18
|
} from "./dispatch.ts";
|
|
19
19
|
import { renderDelegateCall, renderDelegateResult } from "./render-result.ts";
|
|
20
20
|
import { hostCompatError } from "./host-compat.ts";
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
registerProviderExtensionNotifier,
|
|
24
|
-
} from "./host.ts";
|
|
21
|
+
import { invalidateHostDepsCache } from "./host.ts";
|
|
22
|
+
import { registerProviderExtensionNotifier } from "./provider-extensions.ts";
|
|
25
23
|
import { recordTreeNavigation, resetLeafTracking } from "./leaf.ts";
|
|
26
24
|
import { closeAllPooledAgents } from "./pool.ts";
|
|
27
25
|
import {
|
package/format.ts
CHANGED
|
@@ -442,7 +442,7 @@ export function formatCompletedTask(
|
|
|
442
442
|
|
|
443
443
|
// ── Shared live-progress row helpers ───────────────────────────────────────
|
|
444
444
|
// These dedupe the per-task computations the LLM-facing poll view
|
|
445
|
-
// (
|
|
445
|
+
// (ticket-format.ts) and the TUI branches (render-branches) both need. Each is
|
|
446
446
|
// pure over TaskProgress/TaskResult, so it tests without a renderer.
|
|
447
447
|
|
|
448
448
|
/** The in-flight tool activity (no result yet), or null — the "current thing
|
|
@@ -499,7 +499,7 @@ export function waitingLabel(runningCount: number, cap: number): string {
|
|
|
499
499
|
|
|
500
500
|
/** Touched-files summary relative to cwd ("src/a.ts, src/b.ts"), or null when
|
|
501
501
|
* none resolve under cwd. Was byte-for-byte duplicated in formatCompletedTask
|
|
502
|
-
* and
|
|
502
|
+
* and ticket-format.ts. */
|
|
503
503
|
export function relativeTouchedSummary(
|
|
504
504
|
files: string[],
|
|
505
505
|
cwd: string,
|
package/host-cache.ts
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A generation-guarded async memo.
|
|
3
|
+
*
|
|
4
|
+
* Host deps are expensive (`ResourceLoader.reload()` is ~1.2s cold) and are
|
|
5
|
+
* shared across the parallel tasks of a single delegate dispatch, then thrown
|
|
6
|
+
* away so the next dispatch observes edits to auth, models, settings, and
|
|
7
|
+
* context files. That gives three requirements the plain
|
|
8
|
+
* `Map<string, Promise<T>>` pattern does not meet:
|
|
9
|
+
*
|
|
10
|
+
* - **in-flight dedup** — concurrent tasks with the same key must await one
|
|
11
|
+
* build, not start N reloads;
|
|
12
|
+
* - **generation guard** — an invalidation during a build must not let that
|
|
13
|
+
* older build install its now-stale value, nor let it delete the in-flight
|
|
14
|
+
* marker a newer build has since installed for the same key;
|
|
15
|
+
* - **one invalidation path** — every reset goes through `invalidate()`.
|
|
16
|
+
* Before this was factored out, two test-only helpers cleared the maps
|
|
17
|
+
* directly without bumping the generation, leaving exactly the stale-write
|
|
18
|
+
* window the guard exists to close.
|
|
19
|
+
*/
|
|
20
|
+
export class GenerationCache<T> {
|
|
21
|
+
private entries = new Map<string, T>();
|
|
22
|
+
private inflight = new Map<string, Promise<T>>();
|
|
23
|
+
private generation = 0;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Return the cached value for `key`, joining an in-flight build or starting
|
|
27
|
+
* one. When `cacheable` is false the value is built and returned without ever
|
|
28
|
+
* being stored or shared.
|
|
29
|
+
*/
|
|
30
|
+
async resolve(
|
|
31
|
+
key: string,
|
|
32
|
+
cacheable: boolean,
|
|
33
|
+
build: () => Promise<T>,
|
|
34
|
+
): Promise<T> {
|
|
35
|
+
if (!cacheable) return build();
|
|
36
|
+
|
|
37
|
+
const cached = this.entries.get(key);
|
|
38
|
+
if (cached !== undefined) return cached;
|
|
39
|
+
const pending = this.inflight.get(key);
|
|
40
|
+
if (pending) return pending;
|
|
41
|
+
|
|
42
|
+
const generation = this.generation;
|
|
43
|
+
const promise = build().then((value) => {
|
|
44
|
+
// A build that outlived its generation is stale by definition; return it
|
|
45
|
+
// to its own caller but never publish it.
|
|
46
|
+
if (this.generation === generation) this.entries.set(key, value);
|
|
47
|
+
return value;
|
|
48
|
+
});
|
|
49
|
+
this.inflight.set(key, promise);
|
|
50
|
+
try {
|
|
51
|
+
return await promise;
|
|
52
|
+
} finally {
|
|
53
|
+
// An invalidation can let a newer generation install its own in-flight
|
|
54
|
+
// build for this key. Never let the older promise delete that marker.
|
|
55
|
+
if (this.inflight.get(key) === promise) this.inflight.delete(key);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Drop every cached and in-flight value, invalidating builds already running. */
|
|
60
|
+
invalidate(): void {
|
|
61
|
+
this.generation++;
|
|
62
|
+
this.entries.clear();
|
|
63
|
+
this.inflight.clear();
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Cached values only — in-flight builds are not observable here. */
|
|
67
|
+
values(): Iterable<T> {
|
|
68
|
+
return this.entries.values();
|
|
69
|
+
}
|
|
70
|
+
}
|