pi-task-dispatcher 3.2.0 → 3.2.1

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.
@@ -104,7 +104,6 @@ interface WorkerAction {
104
104
 
105
105
  /** Keep one replaceable presentation record for each assistant response or child tool lifecycle. */
106
106
  interface WorkerPresentationEntry {
107
- sequence: number;
108
107
  id: string;
109
108
  kind: "assistant" | "tool";
110
109
  label: string;
@@ -253,7 +252,6 @@ interface ProxyWireResult {
253
252
  }
254
253
 
255
254
  interface ExecutorLease {
256
- generation: number;
257
255
  rootToolCallId: string;
258
256
  ctx: ExtensionToolContext;
259
257
  signal: AbortSignal | undefined;
@@ -880,7 +878,7 @@ class WorkerJob {
880
878
  private requestSequence = 0;
881
879
  private actionSequence = 0;
882
880
  private completionSequence = 0;
883
- private presentationSequence = 0;
881
+ private assistantIdSequence = 0;
884
882
  private readonly presentationEntries: WorkerPresentationEntry[] = [];
885
883
  private readonly presentationById = new Map<string, WorkerPresentationEntry>();
886
884
  private readonly assistantTextBlocks = new Map<number, string>();
@@ -899,7 +897,6 @@ class WorkerJob {
899
897
  private pausedLease: ReturnType<typeof setTimeout> | undefined;
900
898
  private closePromise: Promise<void> | undefined;
901
899
  private executorLease: ExecutorLease | undefined;
902
- private executorLeaseSequence = 0;
903
900
  private readonly proxyRequests = new Map<string, ProxyRequestState>();
904
901
  private readonly seenProxyRequests = new Set<string>();
905
902
 
@@ -1315,8 +1312,7 @@ class WorkerJob {
1315
1312
  if (this.activeAssistantPresentation) return this.activeAssistantPresentation;
1316
1313
  this.assistantTextBlocks.clear();
1317
1314
  const entry: WorkerPresentationEntry = {
1318
- sequence: ++this.presentationSequence,
1319
- id: `assistant-${this.presentationSequence}`,
1315
+ id: `assistant-${++this.assistantIdSequence}`,
1320
1316
  kind: "assistant",
1321
1317
  label: "assistant",
1322
1318
  status: "streaming",
@@ -1365,7 +1361,6 @@ class WorkerJob {
1365
1361
  if (existing) return existing;
1366
1362
  const action = this.activeActions.get(toolCallId);
1367
1363
  const entry: WorkerPresentationEntry = {
1368
- sequence: ++this.presentationSequence,
1369
1364
  id,
1370
1365
  kind: "tool",
1371
1366
  label: action?.label ?? formatActivity(toolName, args),
@@ -1580,7 +1575,7 @@ class WorkerJob {
1580
1575
  }
1581
1576
 
1582
1577
  /** Enforce selected capabilities during a lease and reject delayed calls from every revoked lease. */
1583
- authorizeNestedToolCall(_toolCallId: string, parentToolCallId: string | undefined, toolName: string): { block: boolean; reason: string } | undefined {
1578
+ authorizeNestedToolCall(parentToolCallId: string | undefined, toolName: string): { block: boolean; reason: string } | undefined {
1584
1579
  if (!parentToolCallId) return undefined;
1585
1580
  const lease = this.executorLease;
1586
1581
  if (lease && (parentToolCallId === lease.rootToolCallId || parentToolCallId.startsWith(`${lease.rootToolCallId}/`))) {
@@ -1595,7 +1590,7 @@ class WorkerJob {
1595
1590
  /** Grant parent execution only while a start or continuation call actively owns the worker. */
1596
1591
  private openExecutorLease(rootToolCallId: string, ctx: ExtensionToolContext, signal: AbortSignal | undefined): ExecutorLease {
1597
1592
  if (this.executorLease) throw new Error("A manager executor lease is already active for this worker.");
1598
- const lease: ExecutorLease = { generation: ++this.executorLeaseSequence, rootToolCallId, ctx, signal };
1593
+ const lease: ExecutorLease = { rootToolCallId, ctx, signal };
1599
1594
  this.executorLease = lease;
1600
1595
  return lease;
1601
1596
  }
@@ -1692,43 +1687,41 @@ class WorkerJob {
1692
1687
  removeParentAbort: lease.signal ? () => lease.signal?.removeEventListener("abort", abortFromParent) : undefined,
1693
1688
  };
1694
1689
  this.proxyRequests.set(requestId, request);
1695
- void (async () => {
1696
- try {
1697
- const outcome = await lease.ctx.executeTool(message.toolName as string, args, {
1698
- signal: controller.signal,
1699
- onUpdate: (partial) => {
1700
- if (this.executorLease !== lease || this.proxyRequests.get(requestId) !== request) return;
1701
- this.sendProxy({
1702
- type: "task_dispatcher_proxy_update",
1703
- nonce: this.proxyNonce,
1704
- requestId,
1705
- result: serializeProxyResult(partial),
1706
- });
1707
- },
1708
- });
1709
- if (this.executorLease === lease && this.proxyRequests.get(requestId) === request) {
1710
- this.sendProxy({
1711
- type: "task_dispatcher_proxy_result",
1712
- nonce: this.proxyNonce,
1713
- requestId,
1714
- result: serializeProxyResult(outcome.result, outcome.isError),
1715
- });
1716
- }
1717
- } catch (error) {
1718
- if (this.executorLease === lease && this.proxyRequests.get(requestId) === request) {
1690
+ try {
1691
+ const outcome = await lease.ctx.executeTool(message.toolName as string, args, {
1692
+ signal: controller.signal,
1693
+ onUpdate: (partial) => {
1694
+ if (this.executorLease !== lease || this.proxyRequests.get(requestId) !== request) return;
1719
1695
  this.sendProxy({
1720
- type: "task_dispatcher_proxy_result",
1696
+ type: "task_dispatcher_proxy_update",
1721
1697
  nonce: this.proxyNonce,
1722
1698
  requestId,
1723
- result: proxyErrorResult(`Manager execution of ${message.toolName as string} failed: ${error instanceof Error ? error.message : String(error)}`),
1699
+ result: serializeProxyResult(partial),
1724
1700
  });
1725
- }
1726
- } finally {
1727
- request.removeParentAbort?.();
1728
- request.removeParentAbort = undefined;
1729
- if (this.proxyRequests.get(requestId) === request) this.proxyRequests.delete(requestId);
1701
+ },
1702
+ });
1703
+ if (this.executorLease === lease && this.proxyRequests.get(requestId) === request) {
1704
+ this.sendProxy({
1705
+ type: "task_dispatcher_proxy_result",
1706
+ nonce: this.proxyNonce,
1707
+ requestId,
1708
+ result: serializeProxyResult(outcome.result, outcome.isError),
1709
+ });
1730
1710
  }
1731
- })();
1711
+ } catch (error) {
1712
+ if (this.executorLease === lease && this.proxyRequests.get(requestId) === request) {
1713
+ this.sendProxy({
1714
+ type: "task_dispatcher_proxy_result",
1715
+ nonce: this.proxyNonce,
1716
+ requestId,
1717
+ result: proxyErrorResult(`Manager execution of ${message.toolName as string} failed: ${error instanceof Error ? error.message : String(error)}`),
1718
+ });
1719
+ }
1720
+ } finally {
1721
+ request.removeParentAbort?.();
1722
+ request.removeParentAbort = undefined;
1723
+ if (this.proxyRequests.get(requestId) === request) this.proxyRequests.delete(requestId);
1724
+ }
1732
1725
  }
1733
1726
 
1734
1727
  /** Propagate a child-side proxy cancellation to the current manager nested-tool signal. */
@@ -1851,7 +1844,7 @@ class WorkerJob {
1851
1844
  let activeJob: WorkerJob | undefined;
1852
1845
  let lastSnapshot: WorkerSnapshot | undefined;
1853
1846
  const latestRoleSnapshots = new Map<Role, WorkerSnapshot>();
1854
- const reportedUsage = new Map<string, UsageTotals>();
1847
+ let reportedUsage: { jobId: string; usage: UsageTotals } | undefined;
1855
1848
 
1856
1849
  /** Resolve role overrides against the active parent model without assuming provider availability. */
1857
1850
  function resolveWorkerConfig(config: WorkerRoleConfig, ctx: ExtensionToolContext): { role: ResolvedWorkerRoleConfig; contextWindow: number; maxTokens: number } {
@@ -1965,7 +1958,7 @@ function buildDelegatedToolManifests(requested: string[], pi: ExtensionAPI, ctx:
1965
1958
  if (executionMode !== undefined && executionMode !== "sequential" && executionMode !== "parallel") {
1966
1959
  throw new Error(`Delegated tool ${name} has unsupported execution-mode metadata.`);
1967
1960
  }
1968
- const comparable = cloneJsonValue({
1961
+ const manifest: Omit<DelegatedToolManifest, "fingerprint"> = {
1969
1962
  name,
1970
1963
  label,
1971
1964
  description,
@@ -1975,19 +1968,10 @@ function buildDelegatedToolManifests(requested: string[], pi: ExtensionAPI, ctx:
1975
1968
  ...(annotations === undefined ? {} : { annotations }),
1976
1969
  ...(promptGuidelines === undefined ? {} : { promptGuidelines }),
1977
1970
  ...(executionMode === undefined ? {} : { executionMode }),
1978
- sourceInfo,
1979
- }, `${name} delegated manifest`);
1971
+ };
1980
1972
  return {
1981
- name,
1982
- label,
1983
- description,
1984
- parameters,
1985
- ...(outputSchema === undefined ? {} : { outputSchema }),
1986
- ...(namespace === undefined ? {} : { namespace }),
1987
- ...(annotations === undefined ? {} : { annotations }),
1988
- ...(promptGuidelines === undefined ? {} : { promptGuidelines }),
1989
- ...(executionMode === undefined ? {} : { executionMode }),
1990
- fingerprint: stableJson(comparable),
1973
+ ...manifest,
1974
+ fingerprint: stableJson(cloneJsonValue({ ...manifest, sourceInfo }, `${name} delegated manifest`)),
1991
1975
  };
1992
1976
  });
1993
1977
  assertJsonByteLimit(manifests as unknown as JsonValue, "delegated tool manifest", PROXY_MANIFEST_LIMIT_BYTES);
@@ -2246,12 +2230,11 @@ function renderWorkerResult(
2246
2230
  /** Package a snapshot as bounded model content plus complete machine-readable details. */
2247
2231
  function snapshotResult(snapshot: WorkerSnapshot, isError = false): { content: Array<{ type: "text"; text: string }>; details: WorkerSnapshot; usage: UsageTotals; isError: boolean } {
2248
2232
  lastSnapshot = snapshot;
2249
- for (const jobId of reportedUsage.keys()) {
2250
- if (jobId !== snapshot.jobId) reportedUsage.delete(jobId);
2251
- }
2252
- const previous = reportedUsage.get(snapshot.jobId) ?? emptyUsage();
2233
+ const previous = reportedUsage && reportedUsage.jobId === snapshot.jobId
2234
+ ? reportedUsage.usage
2235
+ : emptyUsage();
2253
2236
  const usage = usageDifference(snapshot.usage, previous);
2254
- reportedUsage.set(snapshot.jobId, structuredClone(snapshot.usage));
2237
+ reportedUsage = { jobId: snapshot.jobId, usage: structuredClone(snapshot.usage) };
2255
2238
  return {
2256
2239
  content: [{ type: "text", text: formatSnapshot(snapshot) }],
2257
2240
  details: snapshot,
@@ -2433,7 +2416,7 @@ export default function taskDispatcher(pi: ExtensionAPI): void {
2433
2416
  registerControl(pi);
2434
2417
  registerStatus(pi);
2435
2418
  pi.on("tool_call", (event) => activeJob
2436
- ? activeJob.authorizeNestedToolCall(event.toolCallId, event.parentToolCallId, event.toolName)
2419
+ ? activeJob.authorizeNestedToolCall(event.parentToolCallId, event.toolName)
2437
2420
  : revokedExecutorDecision(event.parentToolCallId, event.toolName));
2438
2421
  pi.on("session_shutdown", async () => {
2439
2422
  if (activeJob) await activeJob.dispose();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-task-dispatcher",
3
- "version": "3.2.0",
3
+ "version": "3.2.1",
4
4
  "description": "Run configurable Pi worker models as supervised, observable, resumable jobs.",
5
5
  "type": "module",
6
6
  "keywords": [