@gotgenes/pi-subagents 4.1.1 → 5.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 +39 -0
- package/docs/plans/0057-structured-debug-logging.md +184 -0
- package/docs/retro/0054-decompose-index-into-modules.md +38 -0
- package/package.json +6 -6
- package/src/agent-manager.ts +6 -5
- package/src/custom-agents.ts +5 -2
- package/src/debug.ts +12 -0
- package/src/env.ts +5 -3
- package/src/memory.ts +5 -2
- package/src/notification.ts +3 -2
- package/src/output-file.ts +4 -1
- package/src/skill-loader.ts +3 -1
- package/src/worktree.ts +14 -12
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,45 @@ 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
|
+
## [5.1.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v5.0.0...pi-subagents-v5.1.0) (2026-05-19)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* add debugLog utility gated on PI_SUBAGENTS_DEBUG ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([2b8874a](https://github.com/gotgenes/pi-packages/commit/2b8874aeaa28bc09550dd0f8977b7b57d996b254))
|
|
14
|
+
* thread debugLog into agent-manager and notification catch blocks ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([07943a3](https://github.com/gotgenes/pi-packages/commit/07943a341fbe0a0a35f25af3f4b15bc28cdee7a2))
|
|
15
|
+
* thread debugLog into custom-agents and memory catch blocks ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([e239925](https://github.com/gotgenes/pi-packages/commit/e2399253410dc644b38269b52de6e6a4bfa75d3a))
|
|
16
|
+
* thread debugLog into env catch blocks ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([f5ff82f](https://github.com/gotgenes/pi-packages/commit/f5ff82f0c06be3c1dfe5d5ec0ff3621105b55ad0))
|
|
17
|
+
* thread debugLog into output-file catch block ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([a72b11b](https://github.com/gotgenes/pi-packages/commit/a72b11bc1f36bf256f520b9f13e546295fc6cb64))
|
|
18
|
+
* thread debugLog into skill-loader catch block ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([b57231c](https://github.com/gotgenes/pi-packages/commit/b57231cc5de56a834c34f9e171b909d03939505c))
|
|
19
|
+
* thread debugLog into worktree catch blocks ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([049f489](https://github.com/gotgenes/pi-packages/commit/049f4891ab5b09c9d1a301f7d8af797cb165cb4c))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Documentation
|
|
23
|
+
|
|
24
|
+
* plan structured debug logging for silenced catch blocks ([#57](https://github.com/gotgenes/pi-packages/issues/57)) ([28e403e](https://github.com/gotgenes/pi-packages/commit/28e403ea2605405da1a57871af946ee2971ee289))
|
|
25
|
+
|
|
26
|
+
## [5.0.0](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v4.1.1...pi-subagents-v5.0.0) (2026-05-19)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### ⚠ BREAKING CHANGES
|
|
30
|
+
|
|
31
|
+
* All @earendil-works/pi-* peerDependencies and devDependencies now require >=0.75.0, aligning with Pi's Node 22 minimum.
|
|
32
|
+
* Minimum supported Node.js version is now >=22, aligning with Pi v0.75.0. tsconfig target raised from ES2023 to ES2024.
|
|
33
|
+
- ES2024 APIs (Promise.withResolvers, Object.groupBy, Map.groupBy, Array.fromAsync) are now allowed.
|
|
34
|
+
- @types/node catalog aligned to ^22.15.3.
|
|
35
|
+
- pi-autoformat now declares engines.node for consistency.
|
|
36
|
+
|
|
37
|
+
### Features
|
|
38
|
+
|
|
39
|
+
* raise minimum Node.js version to 22 and bump tsconfig target to ES2024 ([98a5b01](https://github.com/gotgenes/pi-packages/commit/98a5b01ca20aa1feed14a60bfa7bb9e082c9914b))
|
|
40
|
+
* raise minimum Pi dependency to v0.75.0 ([1068329](https://github.com/gotgenes/pi-packages/commit/10683290d2a789880848bf7eb093d4307b6eff40))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### Documentation
|
|
44
|
+
|
|
45
|
+
* **retro:** add retro notes for issue [#54](https://github.com/gotgenes/pi-packages/issues/54) ([d753eb3](https://github.com/gotgenes/pi-packages/commit/d753eb3f836a28f089197a45dd582dc4be88872d))
|
|
46
|
+
|
|
8
47
|
## [4.1.1](https://github.com/gotgenes/pi-packages/compare/pi-subagents-v4.1.0...pi-subagents-v4.1.1) (2026-05-18)
|
|
9
48
|
|
|
10
49
|
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 57
|
|
3
|
+
issue_title: "feat: structured debug logging for silenced catch blocks"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Structured debug logging for silenced catch blocks
|
|
7
|
+
|
|
8
|
+
## Problem Statement
|
|
9
|
+
|
|
10
|
+
The codebase contains ~20 `catch { /* ignore */ }` blocks spread across
|
|
11
|
+
`agent-manager.ts`, `worktree.ts`, `output-file.ts`, `skill-loader.ts`,
|
|
12
|
+
`custom-agents.ts`, `memory.ts`, `env.ts`, and `notification.ts`.
|
|
13
|
+
Each block correctly suppresses a non-essential side-effect failure, but
|
|
14
|
+
in development the silence makes it impossible to diagnose what is actually
|
|
15
|
+
failing inside those paths.
|
|
16
|
+
|
|
17
|
+
## Goals
|
|
18
|
+
|
|
19
|
+
- Add a `debugLog(context, err)` utility in `src/debug.ts` gated on
|
|
20
|
+
`PI_SUBAGENTS_DEBUG=1`.
|
|
21
|
+
- Thread `debugLog` into every silenced `catch` block listed in the issue scope.
|
|
22
|
+
- Leave production behavior **completely unchanged** — no new log output unless
|
|
23
|
+
the env var is explicitly set.
|
|
24
|
+
- Provide a `debug.test.ts` unit test covering the on/off branching.
|
|
25
|
+
|
|
26
|
+
## Non-Goals
|
|
27
|
+
|
|
28
|
+
- `usage.ts` catch blocks — they return `0`/`null` on failure and are already
|
|
29
|
+
recoverable, not truly "silent drops"; they are out of scope.
|
|
30
|
+
- `settings.ts` catch block — it returns `false` on failure, already surfaced
|
|
31
|
+
to the caller; out of scope.
|
|
32
|
+
- Log rotation, structured JSON output, or stderr vs. stdout routing.
|
|
33
|
+
- Wiring debug output to the Pi UI; `console.warn` to stderr is sufficient.
|
|
34
|
+
|
|
35
|
+
## Background
|
|
36
|
+
|
|
37
|
+
The `AGENTS.md` rule "prefer explicit configuration over hidden behavior" aligns
|
|
38
|
+
with gating noise behind an opt-in env var rather than always-on logging.
|
|
39
|
+
The code-style skill notes that business logic should remain pure — `debug.ts`
|
|
40
|
+
must not import the Pi SDK; it only reads `process.env`.
|
|
41
|
+
|
|
42
|
+
The `DEBUG` constant is evaluated once at module import time (top-level `const`).
|
|
43
|
+
That matches how similar opt-in env vars work in Node.js tooling and keeps the
|
|
44
|
+
hot path to a single boolean branch.
|
|
45
|
+
|
|
46
|
+
### Catch blocks in scope
|
|
47
|
+
|
|
48
|
+
| File | Line(s) | Context label(s) |
|
|
49
|
+
| ------------------ | ------------------------------------ | ------------------------------------------------------- |
|
|
50
|
+
| `env.ts` | 15, 23 | `"git rev-parse"`, `"git branch"` |
|
|
51
|
+
| `skill-loader.ts` | 74 | `"readdirSync skill root"` |
|
|
52
|
+
| `custom-agents.ts` | 37, 47 | `"readdirSync agents dir"`, `"readFileSync agent file"` |
|
|
53
|
+
| `memory.ts` | 33, 47 | `"lstatSync"`, `"readFileSync"` |
|
|
54
|
+
| `output-file.ts` | 83 | `"write JSONL chunk"` |
|
|
55
|
+
| `worktree.ts` | 40, 56, 110, 130, 132, 147, 151, 161 | `"git rev-parse"`, `"git worktree add"`, etc. |
|
|
56
|
+
| `agent-manager.ts` | 233, 249, 266, 275, 480 | `"outputCleanup"`, `"onComplete callback"`, etc. |
|
|
57
|
+
| `notification.ts` | 145 | `"notification render"` |
|
|
58
|
+
|
|
59
|
+
## Design Overview
|
|
60
|
+
|
|
61
|
+
### `src/debug.ts`
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
export const DEBUG = process.env.PI_SUBAGENTS_DEBUG === "1";
|
|
65
|
+
|
|
66
|
+
export function debugLog(context: string, err: unknown): void {
|
|
67
|
+
if (DEBUG) console.warn(`[pi-subagents:debug] ${context}:`, err);
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The module-level `DEBUG` constant is the canonical source.
|
|
72
|
+
`debugLog` is a pure side-effect function: no return value, no SDK dependency,
|
|
73
|
+
no coupling to Pi types.
|
|
74
|
+
|
|
75
|
+
### Threading pattern
|
|
76
|
+
|
|
77
|
+
Every silenced `catch` block is updated from:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
} catch { /* ignore */ }
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
to:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
} catch (err) { debugLog("<context label>", err); }
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
When the block already had a named error (`catch (err)`), only the `debugLog`
|
|
90
|
+
call is added; the existing comment, if descriptive, is removed in favour of
|
|
91
|
+
the context string.
|
|
92
|
+
|
|
93
|
+
### Context label convention
|
|
94
|
+
|
|
95
|
+
Labels read as `"<verb> <noun>"`, lower-case, mirroring the surrounding
|
|
96
|
+
function name when unambiguous (e.g., `"removeWorktree"`, `"outputCleanup"`).
|
|
97
|
+
|
|
98
|
+
## Module-Level Changes
|
|
99
|
+
|
|
100
|
+
| File | Change |
|
|
101
|
+
| ---------------------- | ---------------------------------------------------------------------------- |
|
|
102
|
+
| `src/debug.ts` | **New.** Exports `DEBUG` constant and `debugLog` function. |
|
|
103
|
+
| `test/debug.test.ts` | **New.** Unit tests for `debugLog` on/off behaviour. |
|
|
104
|
+
| `src/env.ts` | Add `debugLog` import; name the two bare `catch` errors; call `debugLog`. |
|
|
105
|
+
| `src/skill-loader.ts` | Add `debugLog` import; name the bare `catch` error; call `debugLog`. |
|
|
106
|
+
| `src/custom-agents.ts` | Add `debugLog` import; name the two bare `catch` errors; call `debugLog`. |
|
|
107
|
+
| `src/memory.ts` | Add `debugLog` import; name the two bare `catch` errors; call `debugLog`. |
|
|
108
|
+
| `src/output-file.ts` | Add `debugLog` import; name the bare `catch` error; call `debugLog`. |
|
|
109
|
+
| `src/worktree.ts` | Add `debugLog` import; name the eight bare `catch` errors; call `debugLog`. |
|
|
110
|
+
| `src/agent-manager.ts` | Add `debugLog` import; name/update the five `catch` blocks; call `debugLog`. |
|
|
111
|
+
| `src/notification.ts` | Add `debugLog` import; name the bare `catch` error; call `debugLog`. |
|
|
112
|
+
|
|
113
|
+
## Test Impact Analysis
|
|
114
|
+
|
|
115
|
+
1. **New tests enabled** — `debug.test.ts` can directly unit-test `debugLog` in
|
|
116
|
+
isolation.
|
|
117
|
+
Asserting `console.warn` is/isn't called based on the env var is now trivial.
|
|
118
|
+
Previously this path was entirely untested.
|
|
119
|
+
|
|
120
|
+
2. **Existing tests become redundant** — None.
|
|
121
|
+
The catch blocks were never exercised by existing tests (they were silent
|
|
122
|
+
drops), so no existing test coverage needs to be removed.
|
|
123
|
+
|
|
124
|
+
3. **Tests that must stay as-is** — All existing tests remain valid.
|
|
125
|
+
The threading adds a call inside each catch but does not change control flow,
|
|
126
|
+
return values, or thrown exceptions; no test expectations change.
|
|
127
|
+
|
|
128
|
+
## TDD Order
|
|
129
|
+
|
|
130
|
+
1. **`src/debug.ts` + `test/debug.test.ts`**
|
|
131
|
+
Test surface: `debugLog` calls `console.warn` when `PI_SUBAGENTS_DEBUG=1`;
|
|
132
|
+
does nothing when unset or `"0"`.
|
|
133
|
+
Use `vi.spyOn(console, 'warn')` + `vi.stubEnv('PI_SUBAGENTS_DEBUG', '1')` +
|
|
134
|
+
`vi.resetModules()` + dynamic import to exercise both branches.
|
|
135
|
+
Suggested commit: `feat: add debugLog utility gated on PI_SUBAGENTS_DEBUG (#57)`
|
|
136
|
+
|
|
137
|
+
2. **Thread into `env.ts`**
|
|
138
|
+
Test surface: `env.test.ts` — existing tests still pass (no new assertions
|
|
139
|
+
required because the catch paths are tested implicitly by the non-git-dir
|
|
140
|
+
test already covering the swallowed failures).
|
|
141
|
+
Suggested commit: `feat: thread debugLog into env catch blocks (#57)`
|
|
142
|
+
|
|
143
|
+
3. **Thread into `skill-loader.ts`**
|
|
144
|
+
Test surface: `skill-loader.test.ts` — existing tests still pass.
|
|
145
|
+
The readdirSync-error catch is exercised when the skill root does not exist
|
|
146
|
+
(covered by existing "not found" test paths).
|
|
147
|
+
Suggested commit: `feat: thread debugLog into skill-loader catch block (#57)`
|
|
148
|
+
|
|
149
|
+
4. **Thread into `custom-agents.ts` and `memory.ts`**
|
|
150
|
+
These two files have structurally identical filesystem-error catch blocks and
|
|
151
|
+
can land in a single step.
|
|
152
|
+
Test surface: `custom-agents.test.ts`, `memory.test.ts` — existing tests
|
|
153
|
+
still pass.
|
|
154
|
+
Suggested commit: `feat: thread debugLog into custom-agents and memory catch blocks (#57)`
|
|
155
|
+
|
|
156
|
+
5. **Thread into `output-file.ts`**
|
|
157
|
+
Test surface: `output-file.test.ts` — existing tests still pass.
|
|
158
|
+
Suggested commit: `feat: thread debugLog into output-file catch block (#57)`
|
|
159
|
+
|
|
160
|
+
6. **Thread into `worktree.ts`**
|
|
161
|
+
Test surface: `worktree.test.ts` — existing tests still pass.
|
|
162
|
+
Eight catch sites; name each with its enclosing function for label clarity.
|
|
163
|
+
Suggested commit: `feat: thread debugLog into worktree catch blocks (#57)`
|
|
164
|
+
|
|
165
|
+
7. **Thread into `agent-manager.ts` and `notification.ts`**
|
|
166
|
+
Test surface: `agent-manager.test.ts`, `notification.test.ts` — existing
|
|
167
|
+
tests still pass.
|
|
168
|
+
Suggested commit: `feat: thread debugLog into agent-manager and notification catch blocks (#57)`
|
|
169
|
+
|
|
170
|
+
## Risks and Mitigations
|
|
171
|
+
|
|
172
|
+
| Risk | Mitigation |
|
|
173
|
+
| ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
174
|
+
| `DEBUG` is a module-level constant — `vi.stubEnv` alone won't flip it mid-test | Use `vi.resetModules()` + dynamic `import()` inside a test that sets the env var before re-importing; the test skill documents this pattern |
|
|
175
|
+
| Adding `(err)` to a bare `catch` in TypeScript is type-safe (`unknown`) but a `noImplicitAny`-adjacent change | `tsconfig` already uses `"useUnknownInCatchVariables": true` (default in strict mode); no cast needed |
|
|
176
|
+
| Verbose worktree.ts threading (8 sites) could miss one | The TDD step runs `pnpm vitest run worktree` and `pnpm run check` before committing |
|
|
177
|
+
|
|
178
|
+
## Open Questions
|
|
179
|
+
|
|
180
|
+
- Should a future issue expose `debugLog` as part of the public `service.ts` API
|
|
181
|
+
so consumer extensions can share the same debug flag?
|
|
182
|
+
Deferred — out of scope for this change; no consumer currently needs it.
|
|
183
|
+
- Should `PI_SUBAGENTS_DEBUG` be documented in the package `README.md`?
|
|
184
|
+
Likely yes, but deferred to a follow-up doc PR.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
---
|
|
2
|
+
issue: 54
|
|
3
|
+
issue_title: "refactor: decompose src/index.ts into tool + menu modules"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Retro: #54 — decompose index.ts into tool + menu modules
|
|
7
|
+
|
|
8
|
+
## Final Retrospective (2026-05-18T02:20:00Z)
|
|
9
|
+
|
|
10
|
+
### Session summary
|
|
11
|
+
|
|
12
|
+
Decomposed `src/index.ts` from 1,619 lines to 265 lines across 8 commits.
|
|
13
|
+
Extracted 7 new modules (`tools/helpers.ts`, `renderer.ts`, `notification.ts`, `tools/agent-tool.ts`, `tools/get-result-tool.ts`, `tools/steer-tool.ts`, `ui/agent-menu.ts`) with 66 new tests (379 → 445 total).
|
|
14
|
+
Released as `pi-subagents-v4.1.1`.
|
|
15
|
+
Filed follow-up #66 (replace `as any` casts with proper SDK types) and #67 (flaky `pi-autoformat` acceptance test).
|
|
16
|
+
|
|
17
|
+
### Observations
|
|
18
|
+
|
|
19
|
+
#### What went well
|
|
20
|
+
|
|
21
|
+
- Leaf-first extraction order worked cleanly — helpers, then renderer, then notification, then tools, then menu. Each step left the repo green with no cascading breakage.
|
|
22
|
+
- The `createNotificationSystem` factory pattern with arrow-closure capture of `widget` (assigned after `AgentManager` construction) preserved the existing deferred-reference semantics without restructuring initialization order.
|
|
23
|
+
|
|
24
|
+
#### What caused friction (agent side)
|
|
25
|
+
|
|
26
|
+
- `wrong-abstraction` — Applied the code-style skill's "keep Pi SDK imports out of business-logic modules" rule to tool/menu modules, which are SDK consumers, not business logic. Used `unknown` for `ExtensionContext`, `AgentSession`, `ModelRegistry` in factory dep interfaces, requiring 9 `as any` casts in `index.ts`. User caught this post-ship. Impact: filed #66 as a follow-up cleanup; the casts are cosmetic (no runtime effect) but degrade type safety. Fixed the code-style skill to clarify the boundary. (user-caught)
|
|
27
|
+
|
|
28
|
+
- `missing-context` — Four test files (`notification.test.ts`, `get-result-tool.test.ts`, `steer-tool.test.ts`, `agent-tool.test.ts`) omitted `compactionCount: 0` from `AgentRecord` factories. Caught at the final `pnpm run check` step, not during test writing. The testing skill already says "grep for ALL test files that construct a compatible mock." Impact: one extra fix cycle delegated to a subagent, no rework beyond that step. (self-identified)
|
|
29
|
+
|
|
30
|
+
- `other` — `Edit` tool failed 3 times matching the UTF-8 middle dot (`·`, U+00B7) in the steer tool's `stateParts.join(" · ")` line. The third attempt produced a partial match that left the file in a broken state (dangling orphan code after the replacement anchor). Required `git restore` and a fallback to `python3` line-range replacement. The same `python3` approach for the menu extraction lost the closing `}` of the default export function. Impact: ~5 minutes of rework across the two extraction steps, plus one `git restore`.
|
|
31
|
+
|
|
32
|
+
#### What caused friction (user side)
|
|
33
|
+
|
|
34
|
+
- The `as any` casts could have been caught earlier if the user had flagged the `unknown` types during the planning phase. However, the plan didn't prescribe exact interface types — that was an implementation decision. The user's post-ship review ("Why did we have to cast `as any`? Take a look at `packages/pi-permission-system/` as a model") was an efficient redirect that immediately scoped the investigation.
|
|
35
|
+
|
|
36
|
+
### Changes made
|
|
37
|
+
|
|
38
|
+
1. `.pi/skills/code-style/SKILL.md` — Clarified SDK-boundary guidance: tool definitions, event handlers, and command handlers may import SDK types directly; the restriction targets pure helpers and domain modules.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gotgenes/pi-subagents",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"exports": {
|
|
5
5
|
".": "./src/service.ts"
|
|
6
6
|
},
|
|
@@ -30,20 +30,20 @@
|
|
|
30
30
|
"access": "public"
|
|
31
31
|
},
|
|
32
32
|
"peerDependencies": {
|
|
33
|
-
"@earendil-works/pi-ai": ">=0.
|
|
34
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
35
|
-
"@earendil-works/pi-tui": ">=0.
|
|
33
|
+
"@earendil-works/pi-ai": ">=0.75.0",
|
|
34
|
+
"@earendil-works/pi-coding-agent": ">=0.75.0",
|
|
35
|
+
"@earendil-works/pi-tui": ">=0.75.0"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@sinclair/typebox": "^0.34.49",
|
|
39
39
|
"nanoid": "^5.0.0"
|
|
40
40
|
},
|
|
41
41
|
"engines": {
|
|
42
|
-
"node": ">=
|
|
42
|
+
"node": ">=22"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@biomejs/biome": "^2.4.14",
|
|
46
|
-
"@types/node": "^
|
|
46
|
+
"@types/node": "^22.15.3",
|
|
47
47
|
"typescript": "^6.0.3",
|
|
48
48
|
"vitest": "^4.1.5",
|
|
49
49
|
"rumdl": "^0.1.93"
|
package/src/agent-manager.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { randomUUID } from "node:crypto";
|
|
|
10
10
|
import type { Model } from "@earendil-works/pi-ai";
|
|
11
11
|
import type { AgentSession, ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
import { resumeAgent, runAgent, type ToolActivity } from "./agent-runner.js";
|
|
13
|
+
import { debugLog } from "./debug.js";
|
|
13
14
|
import type { AgentInvocation, AgentRecord, IsolationMode, SubagentType, ThinkingLevel } from "./types.js";
|
|
14
15
|
import { addUsage } from "./usage.js";
|
|
15
16
|
import { cleanupWorktree, createWorktree, pruneWorktrees, } from "./worktree.js";
|
|
@@ -230,7 +231,7 @@ export class AgentManager {
|
|
|
230
231
|
|
|
231
232
|
// Final flush of streaming output file
|
|
232
233
|
if (record.outputCleanup) {
|
|
233
|
-
try { record.outputCleanup(); } catch {
|
|
234
|
+
try { record.outputCleanup(); } catch (err) { debugLog("outputCleanup", err); }
|
|
234
235
|
record.outputCleanup = undefined;
|
|
235
236
|
}
|
|
236
237
|
|
|
@@ -246,7 +247,7 @@ export class AgentManager {
|
|
|
246
247
|
|
|
247
248
|
if (options.isBackground) {
|
|
248
249
|
this.runningBackground--;
|
|
249
|
-
try { this.onComplete?.(record); } catch {
|
|
250
|
+
try { this.onComplete?.(record); } catch (err) { debugLog("onComplete callback", err); }
|
|
250
251
|
this.drainQueue();
|
|
251
252
|
}
|
|
252
253
|
return responseText;
|
|
@@ -263,7 +264,7 @@ export class AgentManager {
|
|
|
263
264
|
|
|
264
265
|
// Final flush of streaming output file on error
|
|
265
266
|
if (record.outputCleanup) {
|
|
266
|
-
try { record.outputCleanup(); } catch {
|
|
267
|
+
try { record.outputCleanup(); } catch (err) { debugLog("outputCleanup on error", err); }
|
|
267
268
|
record.outputCleanup = undefined;
|
|
268
269
|
}
|
|
269
270
|
|
|
@@ -272,7 +273,7 @@ export class AgentManager {
|
|
|
272
273
|
try {
|
|
273
274
|
const wtResult = cleanupWorktree(ctx.cwd, record.worktree, options.description);
|
|
274
275
|
record.worktreeResult = wtResult;
|
|
275
|
-
} catch {
|
|
276
|
+
} catch (err) { debugLog("cleanupWorktree on agent error", err); }
|
|
276
277
|
}
|
|
277
278
|
|
|
278
279
|
if (options.isBackground) {
|
|
@@ -477,6 +478,6 @@ export class AgentManager {
|
|
|
477
478
|
}
|
|
478
479
|
this.agents.clear();
|
|
479
480
|
// Prune any orphaned git worktrees (crash recovery)
|
|
480
|
-
try { pruneWorktrees(process.cwd()); } catch {
|
|
481
|
+
try { pruneWorktrees(process.cwd()); } catch (err) { debugLog("pruneWorktrees on dispose", err); }
|
|
481
482
|
}
|
|
482
483
|
}
|
package/src/custom-agents.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
|
6
6
|
import { basename, join } from "node:path";
|
|
7
7
|
import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent";
|
|
8
8
|
import { BUILTIN_TOOL_NAMES } from "./agent-types.js";
|
|
9
|
+
import { debugLog } from "./debug.js";
|
|
9
10
|
import type { AgentConfig, MemoryScope, ThinkingLevel } from "./types.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
@@ -34,7 +35,8 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
|
|
|
34
35
|
let files: string[];
|
|
35
36
|
try {
|
|
36
37
|
files = readdirSync(dir).filter(f => f.endsWith(".md"));
|
|
37
|
-
} catch {
|
|
38
|
+
} catch (err) {
|
|
39
|
+
debugLog("readdirSync agents dir", err);
|
|
38
40
|
return;
|
|
39
41
|
}
|
|
40
42
|
|
|
@@ -44,7 +46,8 @@ function loadFromDir(dir: string, agents: Map<string, AgentConfig>, source: "pro
|
|
|
44
46
|
let content: string;
|
|
45
47
|
try {
|
|
46
48
|
content = readFileSync(join(dir, file), "utf-8");
|
|
47
|
-
} catch {
|
|
49
|
+
} catch (err) {
|
|
50
|
+
debugLog("readFileSync agent file", err);
|
|
48
51
|
continue;
|
|
49
52
|
}
|
|
50
53
|
|
package/src/debug.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* debug.ts — Debug logging utility for silenced catch blocks.
|
|
3
|
+
*
|
|
4
|
+
* Set PI_SUBAGENTS_DEBUG=1 to reveal silent failures in catch blocks
|
|
5
|
+
* throughout the package. Production behavior is unchanged when unset.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const DEBUG = process.env.PI_SUBAGENTS_DEBUG === "1";
|
|
9
|
+
|
|
10
|
+
export function debugLog(context: string, err: unknown): void {
|
|
11
|
+
if (DEBUG) console.warn(`[pi-subagents:debug] ${context}:`, err);
|
|
12
|
+
}
|
package/src/env.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
6
|
+
import { debugLog } from "./debug.js";
|
|
6
7
|
import type { EnvInfo } from "./types.js";
|
|
7
8
|
|
|
8
9
|
export async function detectEnv(pi: ExtensionAPI, cwd: string): Promise<EnvInfo> {
|
|
@@ -12,15 +13,16 @@ export async function detectEnv(pi: ExtensionAPI, cwd: string): Promise<EnvInfo>
|
|
|
12
13
|
try {
|
|
13
14
|
const result = await pi.exec("git", ["rev-parse", "--is-inside-work-tree"], { cwd, timeout: 5000 });
|
|
14
15
|
isGitRepo = result.code === 0 && result.stdout.trim() === "true";
|
|
15
|
-
} catch {
|
|
16
|
-
|
|
16
|
+
} catch (err) {
|
|
17
|
+
debugLog("git rev-parse", err);
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
if (isGitRepo) {
|
|
20
21
|
try {
|
|
21
22
|
const result = await pi.exec("git", ["branch", "--show-current"], { cwd, timeout: 5000 });
|
|
22
23
|
branch = result.code === 0 ? result.stdout.trim() : "unknown";
|
|
23
|
-
} catch {
|
|
24
|
+
} catch (err) {
|
|
25
|
+
debugLog("git branch", err);
|
|
24
26
|
branch = "unknown";
|
|
25
27
|
}
|
|
26
28
|
}
|
package/src/memory.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { existsSync, lstatSync, mkdirSync, readFileSync } from "node:fs";
|
|
11
11
|
import { homedir } from "node:os";
|
|
12
12
|
import { join, } from "node:path";
|
|
13
|
+
import { debugLog } from "./debug.js";
|
|
13
14
|
import type { MemoryScope } from "./types.js";
|
|
14
15
|
|
|
15
16
|
/** Maximum lines to read from MEMORY.md */
|
|
@@ -30,7 +31,8 @@ export function isUnsafeName(name: string): boolean {
|
|
|
30
31
|
export function isSymlink(filePath: string): boolean {
|
|
31
32
|
try {
|
|
32
33
|
return lstatSync(filePath).isSymbolicLink();
|
|
33
|
-
} catch {
|
|
34
|
+
} catch (err) {
|
|
35
|
+
debugLog("lstatSync", err);
|
|
34
36
|
return false;
|
|
35
37
|
}
|
|
36
38
|
}
|
|
@@ -44,7 +46,8 @@ export function safeReadFile(filePath: string): string | undefined {
|
|
|
44
46
|
if (isSymlink(filePath)) return undefined;
|
|
45
47
|
try {
|
|
46
48
|
return readFileSync(filePath, "utf-8");
|
|
47
|
-
} catch {
|
|
49
|
+
} catch (err) {
|
|
50
|
+
debugLog("readFileSync", err);
|
|
48
51
|
return undefined;
|
|
49
52
|
}
|
|
50
53
|
}
|
package/src/notification.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { debugLog } from "./debug.js";
|
|
1
2
|
import type { AgentRecord, NotificationDetails } from "./types.js";
|
|
2
3
|
import type { AgentActivity } from "./ui/agent-widget.js";
|
|
3
4
|
import { getLifetimeTotal, getSessionContextPercent } from "./usage.js";
|
|
@@ -142,8 +143,8 @@ export function createNotificationSystem(deps: NotificationDeps): NotificationSy
|
|
|
142
143
|
pendingNudges.delete(key);
|
|
143
144
|
try {
|
|
144
145
|
send();
|
|
145
|
-
} catch {
|
|
146
|
-
|
|
146
|
+
} catch (err) {
|
|
147
|
+
debugLog("notification render", err);
|
|
147
148
|
}
|
|
148
149
|
}, delay),
|
|
149
150
|
);
|
package/src/output-file.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { appendFileSync, chmodSync, mkdirSync, writeFileSync } from "node:fs";
|
|
|
9
9
|
import { tmpdir } from "node:os";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import type { AgentSession, AgentSessionEvent } from "@earendil-works/pi-coding-agent";
|
|
12
|
+
import { debugLog } from "./debug.js";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Encode a cwd path as a filesystem-safe directory name. Handles:
|
|
@@ -80,7 +81,9 @@ export function streamToOutputFile(
|
|
|
80
81
|
};
|
|
81
82
|
try {
|
|
82
83
|
appendFileSync(path, JSON.stringify(entry) + "\n", "utf-8");
|
|
83
|
-
} catch {
|
|
84
|
+
} catch (err) {
|
|
85
|
+
debugLog("write JSONL chunk", err);
|
|
86
|
+
}
|
|
84
87
|
writtenCount++;
|
|
85
88
|
}
|
|
86
89
|
};
|
package/src/skill-loader.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { existsSync, readdirSync } from "node:fs";
|
|
|
23
23
|
import { homedir } from "node:os";
|
|
24
24
|
import { join } from "node:path";
|
|
25
25
|
import { getAgentDir } from "@earendil-works/pi-coding-agent";
|
|
26
|
+
import { debugLog } from "./debug.js";
|
|
26
27
|
import { isSymlink, isUnsafeName, safeReadFile } from "./memory.js";
|
|
27
28
|
|
|
28
29
|
export interface PreloadedSkill {
|
|
@@ -71,7 +72,8 @@ function findSkillDirectory(root: string, name: string): string | undefined {
|
|
|
71
72
|
let entries: Dirent<string>[];
|
|
72
73
|
try {
|
|
73
74
|
entries = readdirSync(current, { withFileTypes: true });
|
|
74
|
-
} catch {
|
|
75
|
+
} catch (err) {
|
|
76
|
+
debugLog("readdirSync skill root", err);
|
|
75
77
|
continue;
|
|
76
78
|
}
|
|
77
79
|
|
package/src/worktree.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { randomUUID } from "node:crypto";
|
|
|
11
11
|
import { existsSync } from "node:fs";
|
|
12
12
|
import { tmpdir } from "node:os";
|
|
13
13
|
import { join } from "node:path";
|
|
14
|
+
import { debugLog } from "./debug.js";
|
|
14
15
|
|
|
15
16
|
export interface WorktreeInfo {
|
|
16
17
|
/** Absolute path to the worktree directory. */
|
|
@@ -37,7 +38,8 @@ export function createWorktree(cwd: string, agentId: string): WorktreeInfo | und
|
|
|
37
38
|
try {
|
|
38
39
|
execFileSync("git", ["rev-parse", "--is-inside-work-tree"], { cwd, stdio: "pipe", timeout: 5000 });
|
|
39
40
|
execFileSync("git", ["rev-parse", "HEAD"], { cwd, stdio: "pipe", timeout: 5000 });
|
|
40
|
-
} catch {
|
|
41
|
+
} catch (err) {
|
|
42
|
+
debugLog("createWorktree git rev-parse", err);
|
|
41
43
|
return undefined;
|
|
42
44
|
}
|
|
43
45
|
|
|
@@ -53,8 +55,8 @@ export function createWorktree(cwd: string, agentId: string): WorktreeInfo | und
|
|
|
53
55
|
timeout: 30000,
|
|
54
56
|
});
|
|
55
57
|
return { path: worktreePath, branch };
|
|
56
|
-
} catch {
|
|
57
|
-
|
|
58
|
+
} catch (err) {
|
|
59
|
+
debugLog("git worktree add", err);
|
|
58
60
|
return undefined;
|
|
59
61
|
}
|
|
60
62
|
}
|
|
@@ -107,8 +109,8 @@ export function cleanupWorktree(
|
|
|
107
109
|
stdio: "pipe",
|
|
108
110
|
timeout: 5000,
|
|
109
111
|
});
|
|
110
|
-
} catch {
|
|
111
|
-
|
|
112
|
+
} catch (err) {
|
|
113
|
+
debugLog("git branch", err);
|
|
112
114
|
branchName = `${worktree.branch}-${Date.now()}`;
|
|
113
115
|
execFileSync("git", ["branch", branchName], {
|
|
114
116
|
cwd: worktree.path,
|
|
@@ -127,9 +129,9 @@ export function cleanupWorktree(
|
|
|
127
129
|
branch: worktree.branch,
|
|
128
130
|
path: worktree.path,
|
|
129
131
|
};
|
|
130
|
-
} catch {
|
|
131
|
-
|
|
132
|
-
try { removeWorktree(cwd, worktree.path); } catch {
|
|
132
|
+
} catch (err) {
|
|
133
|
+
debugLog("cleanupWorktree", err);
|
|
134
|
+
try { removeWorktree(cwd, worktree.path); } catch (removeErr) { debugLog("removeWorktree on cleanup error", removeErr); }
|
|
133
135
|
return { hasChanges: false };
|
|
134
136
|
}
|
|
135
137
|
}
|
|
@@ -144,11 +146,11 @@ function removeWorktree(cwd: string, worktreePath: string): void {
|
|
|
144
146
|
stdio: "pipe",
|
|
145
147
|
timeout: 10000,
|
|
146
148
|
});
|
|
147
|
-
} catch {
|
|
148
|
-
|
|
149
|
+
} catch (err) {
|
|
150
|
+
debugLog("git worktree remove", err);
|
|
149
151
|
try {
|
|
150
152
|
execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
|
|
151
|
-
} catch {
|
|
153
|
+
} catch (pruneErr) { debugLog("git worktree prune", pruneErr); }
|
|
152
154
|
}
|
|
153
155
|
}
|
|
154
156
|
|
|
@@ -158,5 +160,5 @@ function removeWorktree(cwd: string, worktreePath: string): void {
|
|
|
158
160
|
export function pruneWorktrees(cwd: string): void {
|
|
159
161
|
try {
|
|
160
162
|
execFileSync("git", ["worktree", "prune"], { cwd, stdio: "pipe", timeout: 5000 });
|
|
161
|
-
} catch {
|
|
163
|
+
} catch (err) { debugLog("pruneWorktrees", err); }
|
|
162
164
|
}
|