@tintinweb/pi-subagents 0.4.3 → 0.4.4
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/CHANGELOG.md +14 -0
- package/package.json +1 -1
- package/src/agent-manager.ts +25 -6
- package/src/index.ts +13 -2
- package/src/types.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.4] - 2026-03-16
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
- **Race condition in `get_subagent_result` with `wait: true`** — `resultConsumed` is now set before `await record.promise`, preventing a redundant follow-up notification. Previously the `onComplete` callback (attached at spawn time via `.then()`) always fired before the await resumed, seeing `resultConsumed` as false.
|
|
12
|
+
- **Stale agent records across sessions** — new `clearCompleted()` method removes all completed/stopped/errored agent records on `session_start` and `session_switch` events, so tasks from a prior session don't persist into a new one.
|
|
13
|
+
- **`steer_subagent` race on freshly launched agents** — steering an agent before its session initialized silently dropped the message. Now steers are queued on the record and flushed once `onSessionCreated` fires.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- Extracted `removeRecord()` private helper in `AgentManager` — deduplicates dispose+delete logic between `cleanup()` and `clearCompleted()`.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
- 8 new tests covering `resultConsumed` race condition and `clearCompleted` behavior (185 total).
|
|
20
|
+
|
|
8
21
|
## [0.4.3] - 2026-03-13
|
|
9
22
|
|
|
10
23
|
### Added
|
|
@@ -242,6 +255,7 @@ Initial release.
|
|
|
242
255
|
- **Thinking level** — per-agent extended thinking control
|
|
243
256
|
- **`/agent` and `/agents` commands**
|
|
244
257
|
|
|
258
|
+
[0.4.4]: https://github.com/tintinweb/pi-subagents/compare/v0.4.3...v0.4.4
|
|
245
259
|
[0.4.3]: https://github.com/tintinweb/pi-subagents/compare/v0.4.2...v0.4.3
|
|
246
260
|
[0.4.2]: https://github.com/tintinweb/pi-subagents/compare/v0.4.1...v0.4.2
|
|
247
261
|
[0.4.1]: https://github.com/tintinweb/pi-subagents/compare/v0.4.0...v0.4.1
|
package/package.json
CHANGED
package/src/agent-manager.ts
CHANGED
|
@@ -152,6 +152,13 @@ export class AgentManager {
|
|
|
152
152
|
onTextDelta: options.onTextDelta,
|
|
153
153
|
onSessionCreated: (session) => {
|
|
154
154
|
record.session = session;
|
|
155
|
+
// Flush any steers that arrived before the session was ready
|
|
156
|
+
if (record.pendingSteers?.length) {
|
|
157
|
+
for (const msg of record.pendingSteers) {
|
|
158
|
+
session.steer(msg).catch(() => {});
|
|
159
|
+
}
|
|
160
|
+
record.pendingSteers = undefined;
|
|
161
|
+
}
|
|
155
162
|
options.onSessionCreated?.(session);
|
|
156
163
|
},
|
|
157
164
|
})
|
|
@@ -300,18 +307,30 @@ export class AgentManager {
|
|
|
300
307
|
return true;
|
|
301
308
|
}
|
|
302
309
|
|
|
310
|
+
/** Dispose a record's session and remove it from the map. */
|
|
311
|
+
private removeRecord(id: string, record: AgentRecord): void {
|
|
312
|
+
record.session?.dispose?.();
|
|
313
|
+
record.session = undefined;
|
|
314
|
+
this.agents.delete(id);
|
|
315
|
+
}
|
|
316
|
+
|
|
303
317
|
private cleanup() {
|
|
304
318
|
const cutoff = Date.now() - 10 * 60_000;
|
|
305
319
|
for (const [id, record] of this.agents) {
|
|
306
320
|
if (record.status === "running" || record.status === "queued") continue;
|
|
307
321
|
if ((record.completedAt ?? 0) >= cutoff) continue;
|
|
322
|
+
this.removeRecord(id, record);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
308
325
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
326
|
+
/**
|
|
327
|
+
* Remove all completed/stopped/errored records immediately.
|
|
328
|
+
* Called on session start/switch so tasks from a prior session don't persist.
|
|
329
|
+
*/
|
|
330
|
+
clearCompleted(): void {
|
|
331
|
+
for (const [id, record] of this.agents) {
|
|
332
|
+
if (record.status === "running" || record.status === "queued") continue;
|
|
333
|
+
this.removeRecord(id, record);
|
|
315
334
|
}
|
|
316
335
|
}
|
|
317
336
|
|
package/src/index.ts
CHANGED
|
@@ -284,6 +284,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
284
284
|
getRecord: (id: string) => manager.getRecord(id),
|
|
285
285
|
};
|
|
286
286
|
|
|
287
|
+
// Clear completed tasks when a new session starts (e.g. /new) so stale records don't persist
|
|
288
|
+
pi.on("session_start", () => { manager.clearCompleted(); });
|
|
289
|
+
pi.on("session_switch", () => { manager.clearCompleted(); });
|
|
290
|
+
|
|
287
291
|
// Wait for all subagents on shutdown, then dispose the manager
|
|
288
292
|
pi.on("session_shutdown", async () => {
|
|
289
293
|
delete (globalThis as any)[MANAGER_KEY];
|
|
@@ -812,8 +816,12 @@ Guidelines:
|
|
|
812
816
|
return textResult(`Agent not found: "${params.agent_id}". It may have been cleaned up.`);
|
|
813
817
|
}
|
|
814
818
|
|
|
815
|
-
// Wait for completion if requested
|
|
819
|
+
// Wait for completion if requested.
|
|
820
|
+
// Pre-mark resultConsumed BEFORE awaiting: onComplete fires inside .then()
|
|
821
|
+
// (attached earlier at spawn time) and always runs before this await resumes.
|
|
822
|
+
// Setting the flag here prevents a redundant follow-up notification.
|
|
816
823
|
if (params.wait && record.status === "running" && record.promise) {
|
|
824
|
+
record.resultConsumed = true;
|
|
817
825
|
await record.promise;
|
|
818
826
|
}
|
|
819
827
|
|
|
@@ -877,7 +885,10 @@ Guidelines:
|
|
|
877
885
|
return textResult(`Agent "${params.agent_id}" is not running (status: ${record.status}). Cannot steer a non-running agent.`);
|
|
878
886
|
}
|
|
879
887
|
if (!record.session) {
|
|
880
|
-
|
|
888
|
+
// Session not ready yet — queue the steer for delivery once initialized
|
|
889
|
+
(record.pendingSteers ??= []).push(params.message);
|
|
890
|
+
pi.events.emit("subagents:steered", { id: record.id, message: params.message });
|
|
891
|
+
return textResult(`Steering message queued for agent ${record.id}. It will be delivered once the session initializes.`);
|
|
881
892
|
}
|
|
882
893
|
|
|
883
894
|
try {
|
package/src/types.ts
CHANGED
|
@@ -73,6 +73,8 @@ export interface AgentRecord {
|
|
|
73
73
|
joinMode?: JoinMode;
|
|
74
74
|
/** Set when result was already consumed via get_subagent_result — suppresses completion notification. */
|
|
75
75
|
resultConsumed?: boolean;
|
|
76
|
+
/** Steering messages queued before the session was ready. */
|
|
77
|
+
pendingSteers?: string[];
|
|
76
78
|
/** Worktree info if the agent is running in an isolated worktree. */
|
|
77
79
|
worktree?: { path: string; branch: string };
|
|
78
80
|
/** Worktree cleanup result after agent completion. */
|