@gotgenes/pi-subagents 12.1.0 → 13.1.0
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 +24 -0
- package/dist/public.d.ts +1 -3
- package/docs/architecture/architecture.md +86 -57
- package/docs/plans/0264-remove-extension-lifecycle-control.md +275 -0
- package/docs/plans/0265-born-complete-subagent-session.md +330 -0
- package/docs/retro/0264-remove-extension-lifecycle-control.md +89 -0
- package/docs/retro/0265-born-complete-subagent-session.md +58 -0
- package/package.json +1 -1
- package/src/config/agent-types.ts +0 -2
- package/src/config/custom-agents.ts +0 -30
- package/src/config/default-agents.ts +1 -7
- package/src/config/invocation-config.ts +0 -3
- package/src/index.ts +3 -5
- package/src/lifecycle/agent-manager.ts +9 -10
- package/src/lifecycle/agent.ts +56 -55
- package/src/lifecycle/create-subagent-session.ts +242 -0
- package/src/lifecycle/subagent-session.ts +204 -0
- package/src/lifecycle/turn-limits.ts +13 -0
- package/src/runtime.ts +1 -1
- package/src/service/service-adapter.ts +0 -1
- package/src/service/service.ts +0 -1
- package/src/session/conversation.ts +49 -0
- package/src/session/prompts.ts +2 -23
- package/src/session/session-config.ts +10 -45
- package/src/settings.ts +1 -1
- package/src/tools/agent-tool.ts +0 -5
- package/src/tools/background-spawner.ts +0 -1
- package/src/tools/foreground-runner.ts +0 -1
- package/src/tools/get-result-tool.ts +1 -1
- package/src/tools/spawn-config.ts +1 -5
- package/src/types.ts +0 -7
- package/src/ui/agent-config-editor.ts +0 -5
- package/src/ui/agent-creation-wizard.ts +0 -4
- package/src/ui/display.ts +1 -2
- package/src/lifecycle/agent-runner.ts +0 -472
- package/src/lifecycle/execution-state.ts +0 -17
- package/src/session/safe-fs.ts +0 -45
- package/src/session/skill-loader.ts +0 -104
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 264
|
|
3
|
+
issue_title: "Remove isolated / extensions:false / noSkills from core"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #264 — Remove isolated / extensions:false / noSkills from core
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-30T00:09:21Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Planned Phase 16, Step 4: removing the extension-lifecycle-control axis (`isolated`, `extensions: false`, `noSkills`) from the pi-subagents core per ADR 0002.
|
|
13
|
+
Confirmed all three prerequisite Phase 16 steps (#261, #262, #263) are closed, so the explicit "deny-at-use" dependency is satisfied.
|
|
14
|
+
Produced a four-cycle TDD plan (`isolated` → `extensions` → `skills`/`noSkills`/preload → docs) and committed it.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- Scope expansion decided with the user: the issue names only `isolated` / `extensions: false` / `noSkills`, but `noSkills` is the single mechanism behind **both** skill-restriction modes (`skills: false` and `skills: string[]` preload).
|
|
19
|
+
Removing `noSkills` without also removing `AgentConfig.skills` would leave a field that silently stops restricting.
|
|
20
|
+
Chose the **collapse-skills-fully** option (symmetric with `extensions`): retire `AgentConfig.skills`, `skill-loader.ts`, `safe-fs.ts` (sole consumer was the skill loader), `preloadSkills`, `PromptExtras`, and `extras.skillBlocks`.
|
|
21
|
+
Children always inherit Pi's full skill system — the `skills: true` path.
|
|
22
|
+
- The recursion guard's `if (cfg.extensions)` gate is removed in the `extensions` cycle (cycle 2), since `SessionConfig.extensions` disappears there.
|
|
23
|
+
A guard-always-runs assertion replaces the deleted "extensions: false skips the filter entirely" test.
|
|
24
|
+
- This is a **breaking** change (`feat!:`): public `SpawnOptions.isolated` and the `isolated:` / `extensions:` / `skills:` custom-agent frontmatter keys are removed.
|
|
25
|
+
Custom agents with legacy frontmatter will silently ignore those keys (matches the Phase 14 precedent for `disallowed_tools`).
|
|
26
|
+
- Sequencing note surfaced to the user: some `isolated`-threading removed here (`RunOptions.isolated`, `Agent.run()` plumbing) is structure that Step 5 (#265, dissolve the runner) will delete anyway — small, unavoidable, and #265 depends on this step, so no reordering benefit.
|
|
27
|
+
- Helper-file churn accepted: `test/helpers/runner-io.ts` is touched in all three removal cycles (one field per cycle); ordering is fixed (`isolated` → `extensions` → `skills`) so no cycle leaves a dangling reference.
|
|
28
|
+
- Doc updates identified: `docs/architecture/architecture.md` (Mermaid session subgraph, directory tree, `SpawnOptions`/`RunOptions` field lists, roadmap status) and the `package-pi-subagents` SKILL.md session-domain row (8 → 6 modules).
|
|
29
|
+
|
|
30
|
+
## Stage: Implementation — TDD (2026-05-30T00:38:00Z)
|
|
31
|
+
|
|
32
|
+
### Session summary
|
|
33
|
+
|
|
34
|
+
Executed all four plan cycles (`isolated` → `extensions` + unconditional guard → `skills`/`noSkills`/preload → docs) as three `feat!:` commits plus one `docs:` commit.
|
|
35
|
+
The extension-lifecycle-control axis and the `skills` curation axis are gone; children always inherit the parent's extensions and full skill system, and the recursion guard is unconditional.
|
|
36
|
+
Test count went 1016 → 951 (−65): deleted `skill-loader.test.ts` and `safe-fs.test.ts`, plus removed isolated/extensions/skills/preload-specific cases; `check`, `lint`, `fallow dead-code`, and `verify:public-types` all green.
|
|
37
|
+
|
|
38
|
+
### Observations
|
|
39
|
+
|
|
40
|
+
- The plan's cycle split held up cleanly.
|
|
41
|
+
The only interleaving friction was `test/config/custom-agents.test.ts`, where `extensions` and `skills` assertions shared the same `it` blocks.
|
|
42
|
+
Handled it by retitling the shared tests to skills-only in cycle 2 (keeping skills compiling/passing), then deleting them in cycle 3 — no dangling references between commits.
|
|
43
|
+
- BSD `sed` (macOS) does not support `\|` alternation in basic regex; the standalone-fixture-line deletions needed `sed -E`.
|
|
44
|
+
Worth remembering for future bulk fixture removals.
|
|
45
|
+
- Two in-scope judgment calls beyond the literal plan: (1) kept the generic tag-rendering test in `result-renderer.test.ts` by swapping the example tag `"isolated"` → `"inherit context"` rather than deleting coverage; (2) removed the now-unused `vi` import from `custom-agents.test.ts` after dropping the extensions-deprecation-warning test.
|
|
46
|
+
- The `spawn-config.test.ts` `agentInvocation` snapshot carried a stale `isolation: undefined` leftover (from the #263 worktree eviction) that `toEqual` had been silently ignoring; removed it alongside `isolated: false` for a clean exact-match assertion.
|
|
47
|
+
- `verify:public-types` confirmed the breaking `SpawnOptions.isolated` removal type-checks against an external consumer; no lockfile changes; `dist/` correctly gitignored after the type-bundle build.
|
|
48
|
+
- Pre-completion reviewer: **PASS** — both acceptance criteria code-verified, all deterministic checks green, 6 Mermaid diagrams render, docs accurate, zero dead code.
|
|
49
|
+
|
|
50
|
+
## Stage: Final Retrospective (2026-05-30T01:13:26Z)
|
|
51
|
+
|
|
52
|
+
### Session summary
|
|
53
|
+
|
|
54
|
+
Shipped #264 end-to-end across three stages (Planning → TDD → Ship) in one conversation: planned the four-cycle removal, implemented it as three `feat!:` commits plus docs, and released `pi-subagents-v13.0.0` via the release-please PR (#276).
|
|
55
|
+
The run had zero rework and a PASS pre-completion review; test count moved 1016 → 951.
|
|
56
|
+
|
|
57
|
+
### Observations
|
|
58
|
+
|
|
59
|
+
#### What went well
|
|
60
|
+
|
|
61
|
+
- The planning-stage `ask-user` gate caught a real scope trap before any code was written.
|
|
62
|
+
The issue named three fields (`isolated`, `extensions: false`, `noSkills`), but `noSkills` turned out to be the single mechanism behind **two** skill-restriction modes (skill-disable and `skills: string[]` preload).
|
|
63
|
+
Removing it while keeping `AgentConfig.skills` would have left a field that silently stops restricting — a mid-cycle-3 wall.
|
|
64
|
+
Catching it at planning time expanded the clean scope to four collapsing fields and produced a symmetric, reviewable plan.
|
|
65
|
+
- The four-cycle split (`isolated` → `extensions` + unconditional guard → `skills`/`noSkills`/preload → docs) held with no cross-cycle dangling references, validating the lift-and-shift discipline for shared-interface removal.
|
|
66
|
+
- Verification ran incrementally (`pnpm run check` + `pnpm run test` after every cycle), so each commit landed green; the only end-of-run surprise was a pre-commit `end-of-file-fixer` hook touching `custom-agents.ts` (handled with a re-add, no rework).
|
|
67
|
+
|
|
68
|
+
#### What caused friction (agent side)
|
|
69
|
+
|
|
70
|
+
- `other` (tooling) — BSD `sed` on macOS does not support `\|` alternation in basic regex; the first bulk fixture-deletion attempt silently matched nothing.
|
|
71
|
+
Impact: one wasted tool call, no rework; resolved by switching to `sed -E`.
|
|
72
|
+
|
|
73
|
+
#### What caused friction (user side)
|
|
74
|
+
|
|
75
|
+
- The opening sequencing question ("is there work that should precede this") was first read literally as a prerequisite check (#261/#262/#263), and the user had to rephrase to get at the within-issue sequencing point that surfaced the `noSkills`/`skills` coupling.
|
|
76
|
+
Opportunity, not criticism: offering both readings of an ambiguous "what comes first" question up front would have reached the valuable discovery one turn sooner.
|
|
77
|
+
|
|
78
|
+
#### Diagnostic details
|
|
79
|
+
|
|
80
|
+
- **Model-performance correlation** — one subagent dispatch (`pre-completion-reviewer`, 325s, 43 tool uses) on a judgment-heavy review task; appropriate assignment.
|
|
81
|
+
No Explore/Plan subagents were needed — planning-stage exploration was direct file reads and targeted greps, efficient for this removal's scope.
|
|
82
|
+
- **Escalation-delay tracking** — no rabbit-holes; the `sed` issue resolved in one retry.
|
|
83
|
+
- **Feedback-loop gap analysis** — no gap; `check`/`test`/`lint` ran after each cycle, with `fallow dead-code` and `verify:public-types` at the end.
|
|
84
|
+
|
|
85
|
+
### Changes made
|
|
86
|
+
|
|
87
|
+
1. Appended this Final Retrospective stage entry to `packages/pi-subagents/docs/retro/0264-remove-extension-lifecycle-control.md`.
|
|
88
|
+
2. Considered but **declined** (user: "too narrow") a removal-coupling detection rule for `.pi/prompts/plan-issue.md` — the heuristic that a field named for removal may be the mechanism behind a separate surviving feature.
|
|
89
|
+
No prompt or `AGENTS.md` changes were made this retro.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 265
|
|
3
|
+
issue_title: "Born-complete child execution; dissolve the runner"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #265 — Born-complete child execution; dissolve the runner
|
|
7
|
+
|
|
8
|
+
## Stage: Planning (2026-05-30T02:30:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Produced the implementation plan for dissolving the `agent-runner` and introducing a born-complete `SubagentSession`.
|
|
13
|
+
Most of the session was a design dialogue that resolved naming, the turn-loop home, a discovered Law-of-Demeter cluster, and the workspace-ownership fork before any plan text was written.
|
|
14
|
+
Plan committed as `0265-born-complete-subagent-session.md`; a side-quest filed #277 and added an architecture-doc breadcrumb for discovered debt.
|
|
15
|
+
|
|
16
|
+
### Observations
|
|
17
|
+
|
|
18
|
+
- Vocabulary was pinned down explicitly because "execution" is overloaded: granular execution = one turn loop (one `session.prompt()`, run or resume); the born-complete object spans the whole session lifetime (run + resumes).
|
|
19
|
+
The object is named `SubagentSession` (matches the existing `SubagentType` / `SubagentSessionDir` / `SubagentSessionRegistry` family; cohesive with the deferred `Agent` → `Subagent` rename).
|
|
20
|
+
Turn driving is `runTurnLoop` / `resumeTurnLoop`; resume is *not* an SDK `session.resume()` — it is `session.prompt()` again on the retained session.
|
|
21
|
+
- The turn-loop home is **on `SubagentSession`** (methods), not inline on `Agent` and not a free function.
|
|
22
|
+
The user caught that `subagent.driveTurnLoop(subagentSession.session, …)` is a Law-of-Demeter reach-through; putting the behavior on the object that owns the `AgentSession` is both LoD-correct and more testable (satisfying the user's conditional "inline only if straightforward to test").
|
|
23
|
+
- Workspace ownership locked to **Option A** (session-only `SubagentSession`; `Agent` keeps workspace prepare/dispose).
|
|
24
|
+
Decisive reasoning: the workspace and the session have genuinely different lifetimes (workspace dies at run-completion to fold its `resultAddendum` into the result; session survives to cleanup for resume + the new registry boundary), so they are different resources.
|
|
25
|
+
Option B would fuse them into one object needing two teardown methods, and would thread the `WorkspaceProvider` + prepare-context through the factory just to call `prepare()` — a parameter-relay smell the user flagged.
|
|
26
|
+
The factory takes a resolved `cwd` value (used directly), never the provider.
|
|
27
|
+
- Worktrees are already out of the core (#263) — confirmed zero git code in `pi-subagents/src/` (only doc comments).
|
|
28
|
+
The A/B fork is purely about how the core sequences its abstract `WorkspaceProvider` seam; `@gotgenes/pi-subagents-worktrees` is untouched.
|
|
29
|
+
- Registry semantics: moving `disposed` from run-completion to true session disposal makes resume executions registry-detected (closes the gap deferred from #261).
|
|
30
|
+
The permission system's subscription code does not change; only *when* `disposed` fires moves.
|
|
31
|
+
Edge case planned: `createSubagentSession` must dispose on a post-`session-created` failure to avoid a registry leak.
|
|
32
|
+
- Discovered debt captured (the user's "it is in doing the work that we discover the work to be done"): filed #277 for the remaining `agent.session` reach-throughs (steer buffer-or-deliver duplicated across `steer-tool` + `service-adapter`, conversation viewing, resume-readiness guards) and added a "Session encapsulation debt (Law of Demeter)" subsection to `architecture.md` (commit `038a1283`).
|
|
33
|
+
`SubagentSession` exposes a `.session` accessor in #265 so observer wiring + consumers keep working; #277 retires those.
|
|
34
|
+
- Two follow-ups deliberately deferred and noted in the plan's Non-Goals / Open Questions: the `Agent` → `Subagent` class rename (mechanical, ~19 files — separate issue) and resume-aware workspaces (a worktree's lifetime is one turn loop; worktree + resume is degenerate today).
|
|
35
|
+
- The change is non-breaking (no `feat!:`): the dissolved types (`RunOptions`, `RunResult`, `AgentRunner`) are internal, so `public.d.ts` is unaffected.
|
|
36
|
+
TDD order uses lift-and-shift across 7 steps to keep each commit compiling; transient duplication of the turn-loop helpers/assembly exists between steps 3–5 and is deleted in step 6.
|
|
37
|
+
|
|
38
|
+
## Stage: Implementation — TDD (2026-05-29T22:18:00Z)
|
|
39
|
+
|
|
40
|
+
### Session summary
|
|
41
|
+
|
|
42
|
+
Executed all 7 TDD steps from the plan via lift-and-shift, one commit per step, each leaving the suite green.
|
|
43
|
+
Introduced `SubagentSession` (`runTurnLoop`/`resumeTurnLoop`/`steer`/`dispose`) and the `createSubagentSession()` assembly factory, swapped `Agent`/`AgentManager`/`index.ts` onto them, then deleted `agent-runner.ts` + `execution-state.ts` and the three runner test files.
|
|
44
|
+
Package test count went 951 → 960 (net +9: new `subagent-session`/`create-subagent-session`/`turn-limits` suites added, the redundant runner suites deleted).
|
|
45
|
+
Pre-completion reviewer: initial FAIL (MD060 table alignment in SKILL.md, auto-fixed by `rumdl fmt`), PASS on re-check after fix + stale doc cleanup.
|
|
46
|
+
|
|
47
|
+
### Observations
|
|
48
|
+
|
|
49
|
+
- The plan sketch's `TurnLoopOptions` listed only `maxTurns`/`graceTurns`/`signal`, but preserving the old `runAgent` precedence `per-call ?? agentMaxTurns ?? defaultMaxTurns` required threading `defaultMaxTurns` through `TurnLoopOptions` and storing `agentMaxTurns` + `parentContext` in `SubagentSession` meta (both are session-level facts known at creation).
|
|
50
|
+
This is a correctness-preserving deviation, well covered by three precedence tests plus a parent-context-prepend test in `subagent-session.test.ts`.
|
|
51
|
+
- The atomic call-site swap (step 5) touched more test files than the plan's step-5 list anticipated: every tool/service test that set `record.execution = { session, outputFile }` (`steer-tool`, `agent-tool`, `background-spawner`, `foreground-runner`, `get-result-tool`, `service-adapter`) had to migrate to `record.subagentSession = toSubagentSession(createSubagentSessionStub(...))`.
|
|
52
|
+
Added `createSubagentSessionStub`/`toSubagentSession` to `mock-session.ts` so the migration was a one-line change per call site; the stub's `steer`/`dispose` delegate to the underlying `MockSession` so existing session-spy assertions kept working unchanged.
|
|
53
|
+
- `disposed` moved from `runAgent`'s `finally` (run-completion) to `SubagentSession.dispose()`, invoked by `AgentManager` via the new `Agent.disposeSession()` (routing both `record.session?.dispose?.()` call sites at `agent-manager.ts:235,309`).
|
|
54
|
+
The full cross-package suite confirms the permission system (1504 tests) is unaffected — its subscription code did not change, only *when* `disposed` fires.
|
|
55
|
+
- Test-helper gotcha: `makeSubagentSession`'s `outputFile` default initially swallowed an explicit `undefined` via `?? default`; fixed with an `"outputFile" in metaOverrides` presence check (the testing-skill "Partial spread erases explicit undefined" family).
|
|
56
|
+
- `print-mode.test.ts` now mocks `#src/lifecycle/create-subagent-session` (was `#src/lifecycle/agent-runner`); `index.ts` wraps the factory as `(params) => createSubagentSession(params, deps)`, so the module mock still intercepts it.
|
|
57
|
+
- fallow stayed clean throughout — the transient duplication of IO interfaces + turn-loop helpers between `agent-runner.ts` and the new modules (steps 3–5) was removed in step 6 before the pre-completion gate ran.
|
|
58
|
+
- Reviewer's two minor non-blocking notes: SKILL.md Session-domain count now lists `conversation.ts` but still omits the pre-existing `content-items.ts` (drift predates this issue); `create-subagent-session.ts` keeps an accurate "old runner's runAgent()" provenance comment.
|
package/package.json
CHANGED
|
@@ -114,8 +114,6 @@ export class AgentTypeRegistry implements AgentConfigLookup {
|
|
|
114
114
|
displayName: "Agent",
|
|
115
115
|
description: "General-purpose agent for complex, multi-step tasks",
|
|
116
116
|
builtinToolNames: BUILTIN_TOOL_NAMES,
|
|
117
|
-
extensions: true,
|
|
118
|
-
skills: true,
|
|
119
117
|
systemPrompt: "",
|
|
120
118
|
promptMode: "append",
|
|
121
119
|
};
|
|
@@ -58,8 +58,6 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
|
|
|
58
58
|
displayName: str(fm.display_name),
|
|
59
59
|
description: str(fm.description) ?? name,
|
|
60
60
|
builtinToolNames: csvList(fm.tools, BUILTIN_TOOL_NAMES),
|
|
61
|
-
extensions: resolveBoolExtensions(fm.extensions ?? fm.inherit_extensions),
|
|
62
|
-
skills: inheritField(fm.skills ?? fm.inherit_skills),
|
|
63
61
|
model: str(fm.model),
|
|
64
62
|
thinking: str(fm.thinking) as ThinkingLevel | undefined,
|
|
65
63
|
maxTurns: nonNegativeInt(fm.max_turns),
|
|
@@ -67,7 +65,6 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
|
|
|
67
65
|
promptMode: fm.prompt_mode === "append" ? "append" : "replace",
|
|
68
66
|
inheritContext: fm.inherit_context != null ? fm.inherit_context === true : undefined,
|
|
69
67
|
runInBackground: fm.run_in_background != null ? fm.run_in_background === true : undefined,
|
|
70
|
-
isolated: fm.isolated != null ? fm.isolated === true : undefined,
|
|
71
68
|
enabled: fm.enabled !== false, // default true; explicitly false disables
|
|
72
69
|
source,
|
|
73
70
|
});
|
|
@@ -107,30 +104,3 @@ function csvList(val: unknown, defaults: string[]): string[] {
|
|
|
107
104
|
if (val === undefined || val === null) return defaults;
|
|
108
105
|
return parseCsvField(val) ?? [];
|
|
109
106
|
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Resolve the `extensions` field to a boolean.
|
|
113
|
-
* CSV/array values (legacy allowlist syntax) are coerced to `true` with a warning.
|
|
114
|
-
*/
|
|
115
|
-
function resolveBoolExtensions(val: unknown): boolean {
|
|
116
|
-
const result = inheritField(val);
|
|
117
|
-
if (Array.isArray(result)) {
|
|
118
|
-
console.warn(
|
|
119
|
-
"[pi-subagents] extensions allowlist syntax is deprecated — treating as \"true\" (inherit all).\n" +
|
|
120
|
-
"Use \"permission:\" frontmatter in pi-permission-system for per-tool access control.",
|
|
121
|
-
);
|
|
122
|
-
return true;
|
|
123
|
-
}
|
|
124
|
-
return result;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Parse an inherit field (extensions, skills).
|
|
129
|
-
* omitted/true → true (inherit all); false/"none"/empty → false; csv → listed names.
|
|
130
|
-
*/
|
|
131
|
-
function inheritField(val: unknown): true | string[] | false {
|
|
132
|
-
if (val === undefined || val === null || val === true) return true;
|
|
133
|
-
if (val === false || val === "none") return false;
|
|
134
|
-
const items = csvList(val, []);
|
|
135
|
-
return items.length > 0 ? items : false;
|
|
136
|
-
}
|
|
@@ -16,10 +16,8 @@ export const DEFAULT_AGENTS: Map<string, AgentConfig> = new Map([
|
|
|
16
16
|
displayName: "Agent",
|
|
17
17
|
description: "General-purpose agent for complex, multi-step tasks",
|
|
18
18
|
// builtinToolNames omitted — means "all available tools" (resolved at lookup time)
|
|
19
|
-
// inheritContext / runInBackground
|
|
19
|
+
// inheritContext / runInBackground omitted — strategy fields, callers decide per-call.
|
|
20
20
|
// Setting them to false would lock callsite intent (see resolveAgentInvocationConfig in invocation-config.ts).
|
|
21
|
-
extensions: true,
|
|
22
|
-
skills: true,
|
|
23
21
|
systemPrompt: "",
|
|
24
22
|
promptMode: "append",
|
|
25
23
|
isDefault: true,
|
|
@@ -32,8 +30,6 @@ export const DEFAULT_AGENTS: Map<string, AgentConfig> = new Map([
|
|
|
32
30
|
displayName: "Explore",
|
|
33
31
|
description: "Fast codebase exploration agent (read-only)",
|
|
34
32
|
builtinToolNames: READ_ONLY_TOOLS,
|
|
35
|
-
extensions: true,
|
|
36
|
-
skills: true,
|
|
37
33
|
model: "anthropic/claude-haiku-4-5-20251001",
|
|
38
34
|
systemPrompt: `# CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS
|
|
39
35
|
You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
|
|
@@ -74,8 +70,6 @@ Use Bash ONLY for read-only operations: ls, git status, git log, git diff, find,
|
|
|
74
70
|
displayName: "Plan",
|
|
75
71
|
description: "Software architect for implementation planning (read-only)",
|
|
76
72
|
builtinToolNames: READ_ONLY_TOOLS,
|
|
77
|
-
extensions: true,
|
|
78
|
-
skills: true,
|
|
79
73
|
systemPrompt: `# CRITICAL: READ-ONLY MODE - NO FILE MODIFICATIONS
|
|
80
74
|
You are a software architect and planning specialist.
|
|
81
75
|
Your role is EXCLUSIVELY to explore the codebase and design implementation plans.
|
|
@@ -6,7 +6,6 @@ interface AgentInvocationParams {
|
|
|
6
6
|
max_turns?: number;
|
|
7
7
|
run_in_background?: boolean;
|
|
8
8
|
inherit_context?: boolean;
|
|
9
|
-
isolated?: boolean;
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
export function resolveAgentInvocationConfig(
|
|
@@ -19,7 +18,6 @@ export function resolveAgentInvocationConfig(
|
|
|
19
18
|
maxTurns?: number;
|
|
20
19
|
inheritContext: boolean;
|
|
21
20
|
runInBackground: boolean;
|
|
22
|
-
isolated: boolean;
|
|
23
21
|
} {
|
|
24
22
|
return {
|
|
25
23
|
modelInput: agentConfig?.model ?? params.model,
|
|
@@ -28,6 +26,5 @@ export function resolveAgentInvocationConfig(
|
|
|
28
26
|
maxTurns: agentConfig?.maxTurns ?? params.max_turns,
|
|
29
27
|
inheritContext: agentConfig?.inheritContext ?? params.inherit_context ?? false,
|
|
30
28
|
runInBackground: agentConfig?.runInBackground ?? params.run_in_background ?? false,
|
|
31
|
-
isolated: agentConfig?.isolated ?? params.isolated ?? false,
|
|
32
29
|
};
|
|
33
30
|
}
|
package/src/index.ts
CHANGED
|
@@ -24,9 +24,9 @@ import { AgentTypeRegistry } from "#src/config/agent-types";
|
|
|
24
24
|
import { loadCustomAgents } from "#src/config/custom-agents";
|
|
25
25
|
import { SessionLifecycleHandler, ToolStartHandler } from "#src/handlers/index";
|
|
26
26
|
import { AgentManager, type AgentManagerObserver } from "#src/lifecycle/agent-manager";
|
|
27
|
-
import { ConcreteAgentRunner, type RunnerDeps } from "#src/lifecycle/agent-runner";
|
|
28
27
|
import { createChildLifecyclePublisher } from "#src/lifecycle/child-lifecycle";
|
|
29
28
|
import { ConcurrencyQueue } from "#src/lifecycle/concurrency-queue";
|
|
29
|
+
import { createSubagentSession, type SubagentSessionDeps } from "#src/lifecycle/create-subagent-session";
|
|
30
30
|
import { buildParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
31
31
|
import { buildEventData, type NotificationDetails, NotificationManager } from "#src/observation/notification";
|
|
32
32
|
import { createNotificationRenderer } from "#src/observation/renderer";
|
|
@@ -38,7 +38,6 @@ import { detectEnv } from "#src/session/env";
|
|
|
38
38
|
import { resolveModel } from "#src/session/model-resolver";
|
|
39
39
|
import { buildAgentPrompt } from "#src/session/prompts";
|
|
40
40
|
import { deriveSubagentSessionDir } from "#src/session/session-dir";
|
|
41
|
-
import { preloadSkills } from "#src/session/skill-loader";
|
|
42
41
|
import { SettingsManager } from "#src/settings";
|
|
43
42
|
import { AgentTool } from "#src/tools/agent-tool";
|
|
44
43
|
import { GetResultTool } from "#src/tools/get-result-tool";
|
|
@@ -133,7 +132,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
133
132
|
},
|
|
134
133
|
};
|
|
135
134
|
|
|
136
|
-
const
|
|
135
|
+
const subagentSessionDeps: SubagentSessionDeps = {
|
|
137
136
|
io: {
|
|
138
137
|
detectEnv,
|
|
139
138
|
getAgentDir,
|
|
@@ -143,7 +142,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
143
142
|
createSettingsManager: (cwd, dir) => SdkSettingsManager.create(cwd, dir),
|
|
144
143
|
createSession: (opts) => createAgentSession(opts as any),
|
|
145
144
|
assemblerIO: {
|
|
146
|
-
preloadSkills,
|
|
147
145
|
buildAgentPrompt,
|
|
148
146
|
},
|
|
149
147
|
},
|
|
@@ -164,7 +162,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
164
162
|
);
|
|
165
163
|
|
|
166
164
|
const manager = new AgentManager({
|
|
167
|
-
|
|
165
|
+
createSubagentSession: (params) => createSubagentSession(params, subagentSessionDeps),
|
|
168
166
|
baseCwd: process.cwd(),
|
|
169
167
|
observer,
|
|
170
168
|
queue,
|
|
@@ -10,9 +10,10 @@ import { randomUUID } from "node:crypto";
|
|
|
10
10
|
import type { Model } from "@earendil-works/pi-ai";
|
|
11
11
|
import { debugLog } from "#src/debug";
|
|
12
12
|
import { Agent, type AgentLifecycleObserver } from "#src/lifecycle/agent";
|
|
13
|
-
import type { AgentRunner } from "#src/lifecycle/agent-runner";
|
|
14
13
|
import type { ConcurrencyQueue } from "#src/lifecycle/concurrency-queue";
|
|
14
|
+
import type { CreateSubagentSessionParams } from "#src/lifecycle/create-subagent-session";
|
|
15
15
|
import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
16
|
+
import type { SubagentSession } from "#src/lifecycle/subagent-session";
|
|
16
17
|
import type { WorkspaceProvider } from "#src/lifecycle/workspace";
|
|
17
18
|
|
|
18
19
|
import type { RunConfig } from "#src/runtime";
|
|
@@ -28,7 +29,8 @@ export interface AgentManagerObserver {
|
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
export interface AgentManagerOptions {
|
|
31
|
-
|
|
32
|
+
/** Assembly factory that produces a born-complete SubagentSession per spawn. */
|
|
33
|
+
createSubagentSession: (params: CreateSubagentSessionParams) => Promise<SubagentSession>;
|
|
32
34
|
/** Concurrency queue — owns scheduling, limit checks, and drain logic. */
|
|
33
35
|
queue: ConcurrencyQueue;
|
|
34
36
|
/** Base working directory handed to a workspace provider (the parent cwd). */
|
|
@@ -41,7 +43,6 @@ export interface AgentSpawnConfig {
|
|
|
41
43
|
description: string;
|
|
42
44
|
model?: Model<any>;
|
|
43
45
|
maxTurns?: number;
|
|
44
|
-
isolated?: boolean;
|
|
45
46
|
inheritContext?: boolean;
|
|
46
47
|
thinkingLevel?: ThinkingLevel;
|
|
47
48
|
isBackground?: boolean;
|
|
@@ -65,7 +66,7 @@ export class AgentManager {
|
|
|
65
66
|
private agents = new Map<string, Agent>();
|
|
66
67
|
private cleanupInterval: ReturnType<typeof setInterval>;
|
|
67
68
|
private readonly observer?: AgentManagerObserver;
|
|
68
|
-
private readonly
|
|
69
|
+
private readonly createSubagentSession: (params: CreateSubagentSessionParams) => Promise<SubagentSession>;
|
|
69
70
|
private readonly queue: ConcurrencyQueue;
|
|
70
71
|
private readonly baseCwd: string;
|
|
71
72
|
private getRunConfig?: () => RunConfig;
|
|
@@ -77,7 +78,7 @@ export class AgentManager {
|
|
|
77
78
|
}
|
|
78
79
|
|
|
79
80
|
constructor(options: AgentManagerOptions) {
|
|
80
|
-
this.
|
|
81
|
+
this.createSubagentSession = options.createSubagentSession;
|
|
81
82
|
this.queue = options.queue;
|
|
82
83
|
this.baseCwd = options.baseCwd;
|
|
83
84
|
this.observer = options.observer;
|
|
@@ -149,12 +150,11 @@ export class AgentManager {
|
|
|
149
150
|
prompt,
|
|
150
151
|
model: options.model,
|
|
151
152
|
maxTurns: options.maxTurns,
|
|
152
|
-
isolated: options.isolated,
|
|
153
153
|
thinkingLevel: options.thinkingLevel,
|
|
154
154
|
parentSession: options.parentSession,
|
|
155
155
|
signal: options.signal,
|
|
156
156
|
// Shared deps
|
|
157
|
-
|
|
157
|
+
createSubagentSession: this.createSubagentSession,
|
|
158
158
|
observer: this.buildObserver(options),
|
|
159
159
|
getRunConfig: this.getRunConfig,
|
|
160
160
|
baseCwd: this.baseCwd,
|
|
@@ -233,8 +233,7 @@ export class AgentManager {
|
|
|
233
233
|
|
|
234
234
|
/** Dispose a record's session and remove it from the map. */
|
|
235
235
|
private removeRecord(id: string, record: Agent): void {
|
|
236
|
-
|
|
237
|
-
record.session?.dispose?.();
|
|
236
|
+
record.disposeSession();
|
|
238
237
|
this.agents.delete(id);
|
|
239
238
|
}
|
|
240
239
|
|
|
@@ -308,7 +307,7 @@ export class AgentManager {
|
|
|
308
307
|
// Clear queue
|
|
309
308
|
this.queue.clear();
|
|
310
309
|
for (const record of this.agents.values()) {
|
|
311
|
-
record.
|
|
310
|
+
record.disposeSession();
|
|
312
311
|
}
|
|
313
312
|
this.agents.clear();
|
|
314
313
|
}
|
package/src/lifecycle/agent.ts
CHANGED
|
@@ -14,16 +14,16 @@
|
|
|
14
14
|
* The child's working directory is supplied by a registered WorkspaceProvider
|
|
15
15
|
* (the workspace seam); with no provider the child runs in the parent cwd.
|
|
16
16
|
*
|
|
17
|
-
* Phase-specific collaborators (
|
|
17
|
+
* Phase-specific collaborators (subagentSession, notification) are attached
|
|
18
18
|
* after construction as lifecycle information becomes available.
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
21
|
import type { Model } from "@earendil-works/pi-ai";
|
|
22
22
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
23
23
|
import { debugLog } from "#src/debug";
|
|
24
|
-
import type {
|
|
25
|
-
import type { ExecutionState } from "#src/lifecycle/execution-state";
|
|
24
|
+
import type { CreateSubagentSessionParams } from "#src/lifecycle/create-subagent-session";
|
|
26
25
|
import type { ParentSnapshot } from "#src/lifecycle/parent-snapshot";
|
|
26
|
+
import type { SubagentSession, TurnLoopResult } from "#src/lifecycle/subagent-session";
|
|
27
27
|
import type { LifetimeUsage } from "#src/lifecycle/usage";
|
|
28
28
|
import { addUsage } from "#src/lifecycle/usage";
|
|
29
29
|
import type { Workspace, WorkspaceProvider } from "#src/lifecycle/workspace";
|
|
@@ -36,7 +36,7 @@ import type { AgentInvocation, CompactionInfo, ParentSessionInfo, SubagentType,
|
|
|
36
36
|
export interface AgentLifecycleObserver {
|
|
37
37
|
/** Fires when the agent transitions to running (inside run(), after markRunning). */
|
|
38
38
|
onStarted?(agent: Agent): void;
|
|
39
|
-
/** Fires
|
|
39
|
+
/** Fires once the session is created — delivers the session to external consumers. */
|
|
40
40
|
onSessionCreated?(agent: Agent, session: AgentSession): void;
|
|
41
41
|
/** Fires once when the run completes or fails (for concurrency drain). */
|
|
42
42
|
onRunFinished?(agent: Agent): void;
|
|
@@ -68,7 +68,8 @@ export interface AgentInit {
|
|
|
68
68
|
error?: string;
|
|
69
69
|
|
|
70
70
|
// Shared deps (required for run(), optional for tests)
|
|
71
|
-
|
|
71
|
+
/** Assembly factory that produces a born-complete SubagentSession. */
|
|
72
|
+
createSubagentSession?: (params: CreateSubagentSessionParams) => Promise<SubagentSession>;
|
|
72
73
|
observer?: AgentLifecycleObserver;
|
|
73
74
|
getRunConfig?: () => RunConfig;
|
|
74
75
|
/** Resolves the registered workspace provider (if any) at run-start. */
|
|
@@ -81,7 +82,6 @@ export interface AgentInit {
|
|
|
81
82
|
prompt?: string;
|
|
82
83
|
model?: Model<any>;
|
|
83
84
|
maxTurns?: number;
|
|
84
|
-
isolated?: boolean;
|
|
85
85
|
thinkingLevel?: ThinkingLevel;
|
|
86
86
|
parentSession?: ParentSessionInfo;
|
|
87
87
|
isBackground?: boolean;
|
|
@@ -127,7 +127,7 @@ export class Agent {
|
|
|
127
127
|
promise?: Promise<void>;
|
|
128
128
|
|
|
129
129
|
// Shared deps — optional (required for run())
|
|
130
|
-
private readonly
|
|
130
|
+
private readonly _createSubagentSession?: (params: CreateSubagentSessionParams) => Promise<SubagentSession>;
|
|
131
131
|
readonly observer?: AgentLifecycleObserver;
|
|
132
132
|
private readonly _getRunConfig?: () => RunConfig;
|
|
133
133
|
private readonly _getWorkspaceProvider?: () => WorkspaceProvider | undefined;
|
|
@@ -140,13 +140,13 @@ export class Agent {
|
|
|
140
140
|
private readonly _prompt?: string;
|
|
141
141
|
private readonly _model?: Model<any>;
|
|
142
142
|
private readonly _maxTurns?: number;
|
|
143
|
-
private readonly _isolated?: boolean;
|
|
144
143
|
private readonly _thinkingLevel?: ThinkingLevel;
|
|
145
144
|
private readonly _parentSession?: ParentSessionInfo;
|
|
146
145
|
private readonly _signal?: AbortSignal;
|
|
147
146
|
|
|
148
147
|
// Phase-specific collaborators — each born complete when their info becomes available
|
|
149
|
-
|
|
148
|
+
/** The born-complete child session — set when the factory returns inside run(). */
|
|
149
|
+
subagentSession?: SubagentSession;
|
|
150
150
|
notification?: NotificationState;
|
|
151
151
|
|
|
152
152
|
// Steer buffer — messages queued before the session is ready
|
|
@@ -156,12 +156,12 @@ export class Agent {
|
|
|
156
156
|
|
|
157
157
|
/** The active agent session, or undefined before the session is created. */
|
|
158
158
|
get session(): AgentSession | undefined {
|
|
159
|
-
return this.
|
|
159
|
+
return this.subagentSession?.session;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
/** Path to the agent's session JSONL file, or undefined if not yet available. */
|
|
163
163
|
get outputFile(): string | undefined {
|
|
164
|
-
return this.
|
|
164
|
+
return this.subagentSession?.outputFile;
|
|
165
165
|
}
|
|
166
166
|
|
|
167
167
|
constructor(init: AgentInit) {
|
|
@@ -187,7 +187,7 @@ export class Agent {
|
|
|
187
187
|
this.abortController = new AbortController();
|
|
188
188
|
|
|
189
189
|
// Shared deps
|
|
190
|
-
this.
|
|
190
|
+
this._createSubagentSession = init.createSubagentSession;
|
|
191
191
|
this.observer = init.observer;
|
|
192
192
|
this._getRunConfig = init.getRunConfig;
|
|
193
193
|
this._getWorkspaceProvider = init.getWorkspaceProvider;
|
|
@@ -198,7 +198,6 @@ export class Agent {
|
|
|
198
198
|
this._prompt = init.prompt;
|
|
199
199
|
this._model = init.model;
|
|
200
200
|
this._maxTurns = init.maxTurns;
|
|
201
|
-
this._isolated = init.isolated;
|
|
202
201
|
this._thinkingLevel = init.thinkingLevel;
|
|
203
202
|
this._parentSession = init.parentSession;
|
|
204
203
|
this._signal = init.signal;
|
|
@@ -210,16 +209,16 @@ export class Agent {
|
|
|
210
209
|
}
|
|
211
210
|
|
|
212
211
|
/**
|
|
213
|
-
* Execute the full agent lifecycle: workspace preparation,
|
|
214
|
-
*
|
|
212
|
+
* Execute the full agent lifecycle: workspace preparation, session creation
|
|
213
|
+
* via the factory, observer wiring, the turn loop, workspace disposal, and
|
|
215
214
|
* status transitions.
|
|
216
215
|
*
|
|
217
|
-
* Requires
|
|
216
|
+
* Requires the session factory and snapshot to be set at construction.
|
|
218
217
|
* The returned promise always resolves (errors are captured internally).
|
|
219
218
|
*/
|
|
220
219
|
async run(): Promise<void> {
|
|
221
|
-
if (!this.
|
|
222
|
-
throw new Error("Agent not configured for execution — missing
|
|
220
|
+
if (!this._createSubagentSession) {
|
|
221
|
+
throw new Error("Agent not configured for execution — missing session factory");
|
|
223
222
|
}
|
|
224
223
|
if (!this._snapshot || !this._prompt) {
|
|
225
224
|
throw new Error("Agent not configured for execution — missing snapshot or prompt");
|
|
@@ -250,30 +249,35 @@ export class Agent {
|
|
|
250
249
|
return;
|
|
251
250
|
}
|
|
252
251
|
|
|
253
|
-
const runConfig = this._getRunConfig?.();
|
|
254
252
|
try {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
253
|
+
this.subagentSession = await this._createSubagentSession({
|
|
254
|
+
snapshot: this._snapshot,
|
|
255
|
+
type: this.type,
|
|
256
|
+
cwd,
|
|
257
|
+
parentSession: this._parentSession,
|
|
260
258
|
model: this._model,
|
|
259
|
+
thinkingLevel: this._thinkingLevel,
|
|
260
|
+
});
|
|
261
|
+
} catch (err) {
|
|
262
|
+
// The factory disposed its own session on a post-creation failure.
|
|
263
|
+
this.failRun(err);
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const session = this.subagentSession.session;
|
|
268
|
+
this.flushPendingSteers();
|
|
269
|
+
this.attachObserver(subscribeAgentObserver(session, this, {
|
|
270
|
+
onCompact: (r, info) => this.observer?.onCompacted?.(r, info),
|
|
271
|
+
}));
|
|
272
|
+
this.observer?.onSessionCreated?.(this, session);
|
|
273
|
+
|
|
274
|
+
const runConfig = this._getRunConfig?.();
|
|
275
|
+
try {
|
|
276
|
+
const result = await this.subagentSession.runTurnLoop(this._prompt, {
|
|
261
277
|
maxTurns: this._maxTurns,
|
|
262
278
|
defaultMaxTurns: runConfig?.defaultMaxTurns,
|
|
263
279
|
graceTurns: runConfig?.graceTurns,
|
|
264
|
-
isolated: this._isolated,
|
|
265
|
-
thinkingLevel: this._thinkingLevel,
|
|
266
280
|
signal: this.abortController.signal,
|
|
267
|
-
onSessionCreated: (session) => {
|
|
268
|
-
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- sessionManager is typed as always present but Pi SDK may not provide it
|
|
269
|
-
const outputFile = session.sessionManager?.getSessionFile?.() ?? undefined;
|
|
270
|
-
this.execution = { session, outputFile };
|
|
271
|
-
this.flushPendingSteers(session);
|
|
272
|
-
this.attachObserver(subscribeAgentObserver(session, this, {
|
|
273
|
-
onCompact: (r, info) => this.observer?.onCompacted?.(r, info),
|
|
274
|
-
}));
|
|
275
|
-
this.observer?.onSessionCreated?.(this, session);
|
|
276
|
-
},
|
|
277
281
|
});
|
|
278
282
|
this.completeRun(result);
|
|
279
283
|
} catch (err) {
|
|
@@ -285,27 +289,24 @@ export class Agent {
|
|
|
285
289
|
* Resume an existing session with a new prompt, managing the observer
|
|
286
290
|
* subscription lifecycle internally (same wiring as run()).
|
|
287
291
|
*
|
|
288
|
-
* Requires
|
|
292
|
+
* Requires an existing SubagentSession (set when the original run created it).
|
|
289
293
|
* The returned promise always resolves (errors are captured internally).
|
|
290
|
-
* The parent signal flows straight through to
|
|
294
|
+
* The parent signal flows straight through to resumeTurnLoop — resume does not
|
|
291
295
|
* route through this.abortController.
|
|
292
296
|
*/
|
|
293
297
|
async resume(prompt: string, signal?: AbortSignal): Promise<void> {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
const session = this.session;
|
|
298
|
-
if (!session) {
|
|
298
|
+
const subagentSession = this.subagentSession;
|
|
299
|
+
if (!subagentSession) {
|
|
299
300
|
throw new Error("Agent not configured for resume — missing session");
|
|
300
301
|
}
|
|
301
302
|
|
|
302
303
|
this.resetForResume(Date.now());
|
|
303
|
-
this.attachObserver(subscribeAgentObserver(session, this, {
|
|
304
|
+
this.attachObserver(subscribeAgentObserver(subagentSession.session, this, {
|
|
304
305
|
onCompact: (r, info) => this.observer?.onCompacted?.(r, info),
|
|
305
306
|
}));
|
|
306
307
|
|
|
307
308
|
try {
|
|
308
|
-
const responseText = await
|
|
309
|
+
const responseText = await subagentSession.resumeTurnLoop(prompt, signal);
|
|
309
310
|
this.markCompleted(responseText);
|
|
310
311
|
} catch (err) {
|
|
311
312
|
this.markError(err);
|
|
@@ -411,11 +412,11 @@ export class Agent {
|
|
|
411
412
|
|
|
412
413
|
/**
|
|
413
414
|
* Flush all buffered steer messages to the session and clear the buffer.
|
|
414
|
-
* Called
|
|
415
|
+
* Called once the session is available, delegating to SubagentSession.steer.
|
|
415
416
|
*/
|
|
416
|
-
flushPendingSteers(
|
|
417
|
+
flushPendingSteers(): void {
|
|
417
418
|
for (const msg of this._pendingSteers) {
|
|
418
|
-
|
|
419
|
+
this.subagentSession?.steer(msg).catch(() => {});
|
|
419
420
|
}
|
|
420
421
|
this._pendingSteers = [];
|
|
421
422
|
}
|
|
@@ -455,8 +456,8 @@ export class Agent {
|
|
|
455
456
|
this._detachFn = undefined;
|
|
456
457
|
}
|
|
457
458
|
|
|
458
|
-
/** Complete a run: release listeners, dispose the workspace, status transition,
|
|
459
|
-
completeRun(result:
|
|
459
|
+
/** Complete a run: release listeners, dispose the workspace, status transition, notify observer. */
|
|
460
|
+
completeRun(result: TurnLoopResult): void {
|
|
460
461
|
this.releaseListeners();
|
|
461
462
|
|
|
462
463
|
let finalResult = result.responseText;
|
|
@@ -474,14 +475,14 @@ export class Agent {
|
|
|
474
475
|
else if (result.steered) this.markSteered(finalResult);
|
|
475
476
|
else this.markCompleted(finalResult);
|
|
476
477
|
|
|
477
|
-
this.execution = {
|
|
478
|
-
session: result.session,
|
|
479
|
-
outputFile: result.sessionFile ?? this.execution?.outputFile,
|
|
480
|
-
};
|
|
481
|
-
|
|
482
478
|
this.observer?.onRunFinished?.(this);
|
|
483
479
|
}
|
|
484
480
|
|
|
481
|
+
/** Dispose the wrapped session, firing the `disposed` lifecycle event. */
|
|
482
|
+
disposeSession(): void {
|
|
483
|
+
this.subagentSession?.dispose();
|
|
484
|
+
}
|
|
485
|
+
|
|
485
486
|
/** Fail a run: mark error, release listeners, best-effort workspace dispose, notify observer. */
|
|
486
487
|
failRun(err: unknown): void {
|
|
487
488
|
this.markError(err);
|