@ferris1225/pi-subagents 4.1.8 → 4.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/src/background.ts CHANGED
@@ -27,15 +27,21 @@ interface PendingTask {
27
27
  onError?: (error: unknown) => void | Promise<void>;
28
28
  }
29
29
 
30
- /** How many sub-agent processes may run at once, and how many tasks one
31
- * parallel `subagent` call may contain. Fixed by design: the queue sheds load
32
- * by waiting, so the knob bought nothing worth its maintenance. */
30
+ /** How many sub-agent processes may run at once. This paces execution only
31
+ * it never rejects work, so a wider parallel `subagent` call simply queues.
32
+ * Fixed by design: the queue sheds load by waiting, so the knob bought nothing
33
+ * worth its maintenance. Only manually dispatched top-level generations hold
34
+ * slots; runtime-initiated managed continuations (gate reviews, documentation
35
+ * sync) suspend their task's slot so they never starve manual dispatches. */
33
36
  export const MAX_CONCURRENT_SUBAGENTS = 4;
34
37
 
35
38
  export class BackgroundTaskQueue {
36
39
  private concurrency: number;
37
40
  private readonly pending: PendingTask[] = [];
38
41
  private readonly active = new Set<AbortController>();
42
+ /** Active tasks that no longer count toward the concurrency limit. They keep
43
+ * every other guarantee: abortable, awaited by waitForTask/waitForIdle. */
44
+ private readonly suspended = new Set<AbortController>();
39
45
  private readonly completions = new WeakMap<AbortController, Promise<void>>();
40
46
  private readonly idleWaiters = new Set<() => void>();
41
47
  private stopped = false;
@@ -71,6 +77,16 @@ export class BackgroundTaskQueue {
71
77
  return this.completions.get(controller) ?? Promise.resolve();
72
78
  }
73
79
 
80
+ /** Stop counting a running task toward the concurrency limit. Its body keeps
81
+ * running under the same abort signal; completion still releases everything
82
+ * waitForTask/waitForIdle promise. Frees a slot for queued work immediately. */
83
+ suspend(controller: AbortController | undefined): void {
84
+ if (!controller || this.stopped) return;
85
+ if (!this.active.delete(controller)) return;
86
+ this.suspended.add(controller);
87
+ this.drain();
88
+ }
89
+
74
90
  /** Cancel one queued/running task. Queued entries are removed immediately;
75
91
  * active entries resolve waitForTask only after their body and error handler
76
92
  * have quiesced and the concurrency slot has been released. */
@@ -89,7 +105,7 @@ export class BackgroundTaskQueue {
89
105
 
90
106
  /** Resolve once no queued or running task remains. */
91
107
  waitForIdle(): Promise<void> {
92
- if (this.pending.length === 0 && this.active.size === 0) return Promise.resolve();
108
+ if (this.pending.length === 0 && this.active.size === 0 && this.suspended.size === 0) return Promise.resolve();
93
109
  return new Promise<void>((resolve) => this.idleWaiters.add(resolve));
94
110
  }
95
111
 
@@ -104,6 +120,7 @@ export class BackgroundTaskQueue {
104
120
  entry.complete();
105
121
  }
106
122
  for (const controller of this.active) controller.abort();
123
+ for (const controller of this.suspended) controller.abort();
107
124
  this.resolveIdleWaiters();
108
125
  }
109
126
 
@@ -145,6 +162,7 @@ export class BackgroundTaskQueue {
145
162
  })
146
163
  .finally(() => {
147
164
  this.active.delete(entry.controller);
165
+ this.suspended.delete(entry.controller);
148
166
  entry.complete();
149
167
  this.drain();
150
168
  this.resolveIdleWaiters();
@@ -153,7 +171,7 @@ export class BackgroundTaskQueue {
153
171
  }
154
172
 
155
173
  private resolveIdleWaiters(): void {
156
- if (this.pending.length > 0 || this.active.size > 0) return;
174
+ if (this.pending.length > 0 || this.active.size > 0 || this.suspended.size > 0) return;
157
175
  for (const resolve of this.idleWaiters) resolve();
158
176
  this.idleWaiters.clear();
159
177
  }
package/src/config.ts CHANGED
@@ -26,8 +26,10 @@ export const THINKING_LEVEL_VALUES = ["off", "minimal", "low", "medium", "high",
26
26
  export type ThinkingLevel = (typeof THINKING_LEVEL_VALUES)[number];
27
27
  export const DEFAULT_THINKING_LEVEL: ThinkingLevel = "high";
28
28
 
29
- /** How many lines of a sub-agent result the completion message may carry. Default: 80. */
30
- export const DEFAULT_MAX_RESULT_LINES = 80;
29
+ /** How many lines of a sub-agent result the completion message may carry.
30
+ * Default: 40 — wide fan-outs multiply completion blocks, so deliveries stay
31
+ * compact and the full text lives in the on-disk result artifact. */
32
+ export const DEFAULT_MAX_RESULT_LINES = 40;
31
33
  /** Upper bound accepted for maxResultLines (defensive clamp). */
32
34
  export const MAX_RESULT_LINES_LIMIT = 2000;
33
35