pi-minimal 0.4.0 → 0.5.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 +46 -0
- package/CONTEXT.md +83 -0
- package/README.md +3 -3
- package/dist/config.js +2 -1
- package/dist/edit-diff.js +74 -0
- package/dist/footer-contribution-events.js +5 -3
- package/dist/footer-contributions.js +15 -13
- package/dist/git-state.js +31 -11
- package/dist/index.js +24 -208
- package/dist/reclaim.js +0 -3
- package/dist/session.js +155 -0
- package/dist/tools.js +213 -374
- package/dist/tracker.js +2 -2
- package/dist/working-status.js +2 -1
- package/doc/archive/README.md +3 -0
- package/doc/footer-contributions.md +7 -7
- package/package.json +1 -1
- package/release-notes.md +46 -12
- package/src/config.ts +2 -1
- package/src/edit-diff.ts +73 -0
- package/src/footer-contribution-events.ts +7 -4
- package/src/footer-contributions.ts +15 -13
- package/src/git-state.ts +29 -10
- package/src/index.ts +26 -201
- package/src/reclaim.ts +0 -4
- package/src/session.ts +159 -0
- package/src/tools.ts +261 -389
- package/src/tracker.ts +11 -5
- package/src/working-status.ts +2 -1
- package/test/config.test.ts +2 -2
- package/test/footer-contribution-events.test.ts +15 -0
- package/test/git-state.test.ts +35 -0
- package/test/index.test.ts +45 -3
- package/test/tools.test.ts +36 -3
- package/test/working-status.test.ts +53 -0
- /package/doc/{plans/2026-09-22-pi-minimal-package.md → archive/2026-09-22-plan-pi-minimal-package.md} +0 -0
- /package/doc/{specs/2026-09-22-pi-minimal-package.md → archive/2026-09-22-spec-pi-minimal-package.md} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,200 +1,29 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { loadPiMinimalConfig } from "./config.js"
|
|
3
|
-
import { installHeader } from "./header.js"
|
|
4
|
-
import { installFooter } from "./footer.js"
|
|
5
|
-
import { createFooterContributions } from "./footer-contributions.js"
|
|
6
|
-
import { bindFooterContributionEvents } from "./footer-contribution-events.js"
|
|
7
|
-
import { installCwd } from "./cwd.js"
|
|
8
|
-
import { listSiblings, parseAgentList, parseTabList, type Sibling } from "./herdr.js"
|
|
9
|
-
import { hideGauntletWidgets } from "./tracker.js"
|
|
10
|
-
import { installToolRenderers, setEditDiffMode, stopLiveWatch } from "./tools.js"
|
|
11
|
-
import { setSessionCwd } from "./tool-rows.js"
|
|
12
|
-
import { applyReclaim, reclaimPolicy, type ReclaimReason } from "./reclaim.js"
|
|
13
|
-
import { installWorkingStatus } from "./working-status.js"
|
|
14
|
-
import { installTurnRule } from "./turn-rule.js"
|
|
15
|
-
import { installTranscript } from "./transcript.js"
|
|
16
|
-
import { createGitState } from "./git-state.js"
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"
|
|
17
2
|
import { installCodeBox } from "./code-box.js"
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
3
|
+
import { createSession, type Session } from "./session.js"
|
|
4
|
+
import { installToolRenderers, stopLiveWatch } from "./tools.js"
|
|
5
|
+
import { installTranscript } from "./transcript.js"
|
|
6
|
+
import { installTurnRule } from "./turn-rule.js"
|
|
7
|
+
import { installWorkingStatus } from "./working-status.js"
|
|
20
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Process-wide hooks are installed once at load (renderers, transformers, the code-box patch);
|
|
11
|
+
* everything else lives on a `Session` created at session_start and disposed at session_shutdown
|
|
12
|
+
* or when the next session starts.
|
|
13
|
+
*/
|
|
21
14
|
export default function piMinimalExtension(pi: ExtensionAPI): void {
|
|
22
15
|
const stopWorkingStatus = installWorkingStatus(pi)
|
|
23
16
|
const stopTurnRule = installTurnRule(pi)
|
|
24
17
|
installToolRenderers(pi)
|
|
25
18
|
installTranscript(pi)
|
|
26
19
|
installCodeBox()
|
|
27
|
-
const contributions = createFooterContributions()
|
|
28
|
-
let stopFooter = () => {}
|
|
29
|
-
let stopContributionEvents = () => {}
|
|
30
|
-
let sessionActive = false
|
|
31
|
-
let fastTimer: ReturnType<typeof setInterval> | undefined
|
|
32
|
-
let slowTimer: ReturnType<typeof setInterval> | undefined
|
|
33
|
-
let stealTimer: ReturnType<typeof setInterval> | undefined
|
|
34
|
-
let inFlight: AbortController | undefined
|
|
35
|
-
let siblings: Sibling[] | undefined
|
|
36
|
-
let generation = 0
|
|
37
|
-
let hideWidgets = true
|
|
38
|
-
let busy = false
|
|
39
|
-
let currentCwd = process.cwd()
|
|
40
|
-
const gitState = createGitState(pi, () => currentCwd)
|
|
41
|
-
const stopTimers = () => {
|
|
42
|
-
if (fastTimer) clearInterval(fastTimer)
|
|
43
|
-
if (slowTimer) clearInterval(slowTimer)
|
|
44
|
-
if (stealTimer) clearInterval(stealTimer)
|
|
45
|
-
fastTimer = undefined
|
|
46
|
-
slowTimer = undefined
|
|
47
|
-
stealTimer = undefined
|
|
48
|
-
inFlight?.abort()
|
|
49
|
-
inFlight = undefined
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const scheduleFast = (ctx: ExtensionContext) => {
|
|
53
|
-
if (fastTimer) return
|
|
54
|
-
if (slowTimer) clearInterval(slowTimer)
|
|
55
|
-
slowTimer = undefined
|
|
56
|
-
fastTimer = setInterval(() => {
|
|
57
|
-
void probe(ctx)
|
|
58
|
-
}, 2000)
|
|
59
|
-
}
|
|
60
20
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (fastTimer) clearInterval(fastTimer)
|
|
64
|
-
fastTimer = undefined
|
|
65
|
-
slowTimer = setInterval(() => {
|
|
66
|
-
void probe(ctx)
|
|
67
|
-
}, 10000)
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const probe = async (ctx: ExtensionContext) => {
|
|
71
|
-
if (process.env.HERDR_ENV !== "1") return
|
|
72
|
-
if (inFlight) return
|
|
73
|
-
const ac = new AbortController()
|
|
74
|
-
const gen = generation
|
|
75
|
-
inFlight = ac
|
|
76
|
-
try {
|
|
77
|
-
const agentRun = await pi.exec("herdr", ["agent", "list"], {
|
|
78
|
-
timeout: 3000,
|
|
79
|
-
signal: ac.signal,
|
|
80
|
-
})
|
|
81
|
-
if (generation !== gen) return
|
|
82
|
-
const tabRun = await pi.exec("herdr", ["tab", "list"], {
|
|
83
|
-
timeout: 3000,
|
|
84
|
-
signal: ac.signal,
|
|
85
|
-
})
|
|
86
|
-
if (generation !== gen) return
|
|
87
|
-
if (agentRun.code !== 0 || tabRun.code !== 0 || agentRun.killed || tabRun.killed) {
|
|
88
|
-
siblings = undefined
|
|
89
|
-
scheduleSlow(ctx)
|
|
90
|
-
return
|
|
91
|
-
}
|
|
92
|
-
siblings = listSiblings(
|
|
93
|
-
parseAgentList(agentRun.stdout),
|
|
94
|
-
parseTabList(tabRun.stdout),
|
|
95
|
-
process.env.HERDR_PANE_ID,
|
|
96
|
-
process.env.HERDR_TAB_ID,
|
|
97
|
-
)
|
|
98
|
-
if (siblings) scheduleFast(ctx)
|
|
99
|
-
else scheduleSlow(ctx)
|
|
100
|
-
} catch {
|
|
101
|
-
if (generation !== gen) return
|
|
102
|
-
siblings = undefined
|
|
103
|
-
scheduleSlow(ctx)
|
|
104
|
-
} finally {
|
|
105
|
-
if (inFlight === ac) inFlight = undefined
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
const hideForeignChrome = (ctx: ExtensionContext, reason: ReclaimReason) => {
|
|
110
|
-
if (ctx.mode !== "tui") return
|
|
111
|
-
applyReclaim(
|
|
112
|
-
reason,
|
|
113
|
-
ctx.ui,
|
|
114
|
-
{
|
|
115
|
-
widgets: () => {
|
|
116
|
-
if (hideWidgets) hideGauntletWidgets(ctx)
|
|
117
|
-
},
|
|
118
|
-
chrome: () => {
|
|
119
|
-
installHeader(ctx)
|
|
120
|
-
installChromeFooter(ctx)
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
)
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const contextPct = (ctx: ExtensionContext): number | undefined => {
|
|
127
|
-
const usage = ctx.getContextUsage?.()
|
|
128
|
-
const window = usage?.contextWindow || ctx.model?.contextWindow
|
|
129
|
-
if (typeof usage?.tokens !== "number" || !window) return undefined
|
|
130
|
-
return Math.max(0, Math.min(100, Math.round((usage.tokens / window) * 100)))
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const installChromeFooter = (ctx: ExtensionContext) => {
|
|
134
|
-
stopFooter()
|
|
135
|
-
stopFooter = installFooter(ctx, {
|
|
136
|
-
busy: () => busy,
|
|
137
|
-
editorText: () => (typeof ctx.ui.getEditorText === "function" ? ctx.ui.getEditorText() : ""),
|
|
138
|
-
contextPct: () => contextPct(ctx),
|
|
139
|
-
}, contributions)
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
const installStatusLine = (ctx: ExtensionContext) => {
|
|
143
|
-
installCwd(ctx, {
|
|
144
|
-
gitState: gitState.get,
|
|
145
|
-
siblings: () => siblings,
|
|
146
|
-
onGitChange: gitState.onChange,
|
|
147
|
-
})
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
const pinChrome = (ctx: ExtensionContext, reason: ReclaimReason) => {
|
|
151
|
-
hideForeignChrome(ctx, reason)
|
|
152
|
-
if (ctx.mode !== "tui" || !reclaimPolicy(reason).reinstallChrome) return
|
|
153
|
-
ctx.ui.setWidget("tw-cwd", undefined)
|
|
154
|
-
installStatusLine(ctx)
|
|
155
|
-
}
|
|
21
|
+
let active: Session | undefined
|
|
22
|
+
let busy = false
|
|
156
23
|
|
|
157
|
-
pi.on("session_start",
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
sessionActive = true
|
|
161
|
-
generation += 1
|
|
162
|
-
stopContributionEvents = bindFooterContributionEvents(pi.events, contributions, ctx.sessionManager.getSessionId(), ctx.mode)
|
|
163
|
-
if (ctx.mode !== "tui") return
|
|
164
|
-
const config = loadPiMinimalConfig({
|
|
165
|
-
agentDir: getAgentDir(),
|
|
166
|
-
cwd: ctx.cwd,
|
|
167
|
-
projectTrusted: ctx.isProjectTrusted(),
|
|
168
|
-
})
|
|
169
|
-
hideWidgets = config.hideGauntletWidgets
|
|
170
|
-
setEditDiffMode(config.editDiff)
|
|
171
|
-
currentCwd = ctx.cwd
|
|
172
|
-
setSessionCwd(ctx.cwd)
|
|
173
|
-
try {
|
|
174
|
-
const settings = SettingsManager.create(ctx.cwd, undefined, { projectTrusted: ctx.isProjectTrusted() })
|
|
175
|
-
setChromeMargin(settings.getOutputPad())
|
|
176
|
-
} catch {
|
|
177
|
-
// Keep the default margin when settings are unavailable.
|
|
178
|
-
}
|
|
179
|
-
installHeader(ctx)
|
|
180
|
-
installChromeFooter(ctx)
|
|
181
|
-
installStatusLine(ctx)
|
|
182
|
-
void gitState.refresh(true)
|
|
183
|
-
pinChrome(ctx, "session_start")
|
|
184
|
-
ctx.ui.setHiddenThinkingLabel("◆ Thought")
|
|
185
|
-
ctx.ui.setWorkingIndicator()
|
|
186
|
-
const sessionGeneration = generation
|
|
187
|
-
const settle = () => {
|
|
188
|
-
if (sessionActive && generation === sessionGeneration) pinChrome(ctx, "session_settle")
|
|
189
|
-
}
|
|
190
|
-
setTimeout(settle, 0)
|
|
191
|
-
setTimeout(settle, 50)
|
|
192
|
-
if (!stealTimer) {
|
|
193
|
-
stealTimer = setInterval(() => hideForeignChrome(ctx, "interval"), 200)
|
|
194
|
-
}
|
|
195
|
-
if (process.env.HERDR_ENV === "1") {
|
|
196
|
-
await probe(ctx)
|
|
197
|
-
}
|
|
24
|
+
pi.on("session_start", (_event, ctx) => {
|
|
25
|
+
active?.dispose()
|
|
26
|
+
active = createSession(pi, ctx, { busy: () => busy })
|
|
198
27
|
})
|
|
199
28
|
|
|
200
29
|
pi.on("agent_start", () => {
|
|
@@ -202,27 +31,23 @@ export default function piMinimalExtension(pi: ExtensionAPI): void {
|
|
|
202
31
|
})
|
|
203
32
|
pi.on("agent_end", () => {
|
|
204
33
|
busy = false
|
|
205
|
-
|
|
34
|
+
active?.refreshGit()
|
|
206
35
|
})
|
|
207
|
-
pi.on("turn_end", (
|
|
208
|
-
|
|
209
|
-
|
|
36
|
+
pi.on("turn_end", () => {
|
|
37
|
+
active?.pin("turn_end")
|
|
38
|
+
active?.refreshGit()
|
|
210
39
|
})
|
|
211
|
-
pi.on("tool_execution_end", (
|
|
212
|
-
|
|
213
|
-
|
|
40
|
+
pi.on("tool_execution_end", () => {
|
|
41
|
+
active?.pin("tool_execution_end")
|
|
42
|
+
active?.refreshGit()
|
|
214
43
|
})
|
|
215
44
|
|
|
216
|
-
pi.on("session_shutdown",
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
stopContributionEvents()
|
|
45
|
+
pi.on("session_shutdown", () => {
|
|
46
|
+
active?.dispose()
|
|
47
|
+
active = undefined
|
|
220
48
|
busy = false
|
|
221
|
-
stopTimers()
|
|
222
49
|
stopWorkingStatus()
|
|
223
50
|
stopTurnRule()
|
|
224
51
|
stopLiveWatch()
|
|
225
|
-
generation += 1
|
|
226
|
-
siblings = undefined
|
|
227
52
|
})
|
|
228
53
|
}
|
package/src/reclaim.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
export type ReclaimReason =
|
|
2
2
|
| "session_start"
|
|
3
3
|
| "session_settle"
|
|
4
|
-
| "interval"
|
|
5
4
|
| "turn_end"
|
|
6
5
|
| "tool_execution_end"
|
|
7
6
|
|
|
@@ -14,9 +13,6 @@ export function reclaimPolicy(reason: ReclaimReason): ReclaimPolicy {
|
|
|
14
13
|
if (reason === "session_start") {
|
|
15
14
|
return { resetEditor: true, reinstallChrome: true }
|
|
16
15
|
}
|
|
17
|
-
if (reason === "interval") {
|
|
18
|
-
return { resetEditor: false, reinstallChrome: false }
|
|
19
|
-
}
|
|
20
16
|
// session_settle / turn_end / tool_execution_end: restore chrome, keep editor
|
|
21
17
|
return { resetEditor: false, reinstallChrome: true }
|
|
22
18
|
}
|
package/src/session.ts
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { getAgentDir, SettingsManager, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent"
|
|
2
|
+
import { setChromeMargin } from "./chrome.js"
|
|
3
|
+
import { loadPiMinimalConfig } from "./config.js"
|
|
4
|
+
import { installCwd } from "./cwd.js"
|
|
5
|
+
import { installFooter } from "./footer.js"
|
|
6
|
+
import { bindFooterContributionEvents } from "./footer-contribution-events.js"
|
|
7
|
+
import { createFooterContributions } from "./footer-contributions.js"
|
|
8
|
+
import { createGitState } from "./git-state.js"
|
|
9
|
+
import { installHeader } from "./header.js"
|
|
10
|
+
import { listSiblings, parseAgentList, parseTabList, type Sibling } from "./herdr.js"
|
|
11
|
+
import { applyReclaim, reclaimPolicy, type ReclaimReason } from "./reclaim.js"
|
|
12
|
+
import { setSessionCwd } from "./tool-rows.js"
|
|
13
|
+
import { setEditDiffMode } from "./tools.js"
|
|
14
|
+
import { hideGauntletWidgets } from "./tracker.js"
|
|
15
|
+
|
|
16
|
+
export type SessionDeps = {
|
|
17
|
+
/** Whether an agent run is in progress; owned by the extension because agent events are not session-scoped. */
|
|
18
|
+
busy: () => boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type Session = {
|
|
22
|
+
readonly id: string
|
|
23
|
+
/** Re-asserts chrome after an event that may have let another extension draw over it. */
|
|
24
|
+
pin(reason: ReclaimReason): void
|
|
25
|
+
refreshGit(force?: boolean): void
|
|
26
|
+
dispose(): void
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const HERDR_FAST_MS = 2000
|
|
30
|
+
const HERDR_SLOW_MS = 10000
|
|
31
|
+
const HERDR_EXEC_TIMEOUT_MS = 3000
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Everything pi-minimal owns for one session: config, cwd, the contribution registry, the git and
|
|
35
|
+
* Herdr pollers, and the header/footer/status-line chrome. Disposal stops every timer and makes
|
|
36
|
+
* late async results inert, so two sessions in one process never share or overwrite each other's
|
|
37
|
+
* state. Non-TUI sessions get a session too, but it only binds the contribution protocol (which
|
|
38
|
+
* replies `not-tui`) and installs no chrome.
|
|
39
|
+
*/
|
|
40
|
+
export function createSession(pi: ExtensionAPI, ctx: ExtensionContext, deps: SessionDeps): Session {
|
|
41
|
+
const id = ctx.sessionManager.getSessionId()
|
|
42
|
+
let alive = true
|
|
43
|
+
const contributions = createFooterContributions()
|
|
44
|
+
const stopContributionEvents = bindFooterContributionEvents(pi.events, contributions, id, ctx.mode)
|
|
45
|
+
if (ctx.mode !== "tui") {
|
|
46
|
+
return { id, pin() {}, refreshGit() {}, dispose() { if (!alive) return; alive = false; stopContributionEvents() } }
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const config = loadPiMinimalConfig({ agentDir: getAgentDir(), cwd: ctx.cwd, projectTrusted: ctx.isProjectTrusted() })
|
|
50
|
+
setSessionCwd(ctx.cwd)
|
|
51
|
+
setEditDiffMode(config.editDiff)
|
|
52
|
+
try {
|
|
53
|
+
const settings = SettingsManager.create(ctx.cwd, undefined, { projectTrusted: ctx.isProjectTrusted() })
|
|
54
|
+
setChromeMargin(settings.getOutputPad())
|
|
55
|
+
} catch {
|
|
56
|
+
// Keep the current margin when settings are unavailable.
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const gitState = createGitState(pi, () => ctx.cwd)
|
|
60
|
+
let stopFooter = () => {}
|
|
61
|
+
let siblings: Sibling[] | undefined
|
|
62
|
+
let herdrTimer: ReturnType<typeof setInterval> | undefined
|
|
63
|
+
let herdrPeriod = 0
|
|
64
|
+
let herdrInFlight: AbortController | undefined
|
|
65
|
+
const settleTimers: ReturnType<typeof setTimeout>[] = []
|
|
66
|
+
|
|
67
|
+
const contextPct = (): number | undefined => {
|
|
68
|
+
const usage = ctx.getContextUsage?.()
|
|
69
|
+
const window = usage?.contextWindow || ctx.model?.contextWindow
|
|
70
|
+
if (typeof usage?.tokens !== "number" || !window) return undefined
|
|
71
|
+
return Math.max(0, Math.min(100, Math.round((usage.tokens / window) * 100)))
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const installChromeFooter = () => {
|
|
75
|
+
stopFooter()
|
|
76
|
+
stopFooter = installFooter(ctx, {
|
|
77
|
+
busy: deps.busy,
|
|
78
|
+
editorText: () => (typeof ctx.ui.getEditorText === "function" ? ctx.ui.getEditorText() : ""),
|
|
79
|
+
contextPct,
|
|
80
|
+
}, contributions)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const installStatusLine = () => {
|
|
84
|
+
installCwd(ctx, { gitState: gitState.get, siblings: () => siblings, onGitChange: gitState.onChange })
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const pin = (reason: ReclaimReason) => {
|
|
88
|
+
if (!alive) return
|
|
89
|
+
applyReclaim(reason, ctx.ui, {
|
|
90
|
+
widgets: () => { if (config.hideGauntletWidgets) hideGauntletWidgets(ctx) },
|
|
91
|
+
chrome: () => { installHeader(ctx); installChromeFooter() },
|
|
92
|
+
})
|
|
93
|
+
if (!reclaimPolicy(reason).reinstallChrome) return
|
|
94
|
+
ctx.ui.setWidget("tw-cwd", undefined)
|
|
95
|
+
installStatusLine()
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const scheduleHerdr = (periodMs: number) => {
|
|
99
|
+
if (!alive || herdrPeriod === periodMs) return
|
|
100
|
+
if (herdrTimer) clearInterval(herdrTimer)
|
|
101
|
+
herdrPeriod = periodMs
|
|
102
|
+
herdrTimer = setInterval(() => { void probeHerdr() }, periodMs)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const probeHerdr = async () => {
|
|
106
|
+
if (!alive || herdrInFlight) return
|
|
107
|
+
const ac = new AbortController()
|
|
108
|
+
herdrInFlight = ac
|
|
109
|
+
try {
|
|
110
|
+
const agentRun = await pi.exec("herdr", ["agent", "list"], { timeout: HERDR_EXEC_TIMEOUT_MS, signal: ac.signal })
|
|
111
|
+
if (!alive) return
|
|
112
|
+
const tabRun = await pi.exec("herdr", ["tab", "list"], { timeout: HERDR_EXEC_TIMEOUT_MS, signal: ac.signal })
|
|
113
|
+
if (!alive) return
|
|
114
|
+
if (agentRun.code !== 0 || tabRun.code !== 0 || agentRun.killed || tabRun.killed) {
|
|
115
|
+
siblings = undefined
|
|
116
|
+
scheduleHerdr(HERDR_SLOW_MS)
|
|
117
|
+
return
|
|
118
|
+
}
|
|
119
|
+
siblings = listSiblings(parseAgentList(agentRun.stdout), parseTabList(tabRun.stdout), process.env.HERDR_PANE_ID, process.env.HERDR_TAB_ID)
|
|
120
|
+
scheduleHerdr(siblings ? HERDR_FAST_MS : HERDR_SLOW_MS)
|
|
121
|
+
} catch {
|
|
122
|
+
if (!alive) return
|
|
123
|
+
siblings = undefined
|
|
124
|
+
scheduleHerdr(HERDR_SLOW_MS)
|
|
125
|
+
} finally {
|
|
126
|
+
if (herdrInFlight === ac) herdrInFlight = undefined
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Install once, then re-assert shortly after: other extensions' session_start handlers may draw after ours.
|
|
131
|
+
installHeader(ctx)
|
|
132
|
+
installChromeFooter()
|
|
133
|
+
installStatusLine()
|
|
134
|
+
void gitState.refresh(true)
|
|
135
|
+
pin("session_start")
|
|
136
|
+
ctx.ui.setHiddenThinkingLabel("◆ Thought")
|
|
137
|
+
ctx.ui.setWorkingIndicator()
|
|
138
|
+
for (const delay of [0, 50]) settleTimers.push(setTimeout(() => pin("session_settle"), delay))
|
|
139
|
+
if (process.env.HERDR_ENV === "1") void probeHerdr()
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
id,
|
|
143
|
+
pin,
|
|
144
|
+
refreshGit(force) { if (alive) void gitState.refresh(force) },
|
|
145
|
+
dispose() {
|
|
146
|
+
if (!alive) return
|
|
147
|
+
alive = false
|
|
148
|
+
for (const timer of settleTimers.splice(0)) clearTimeout(timer)
|
|
149
|
+
if (herdrTimer) clearInterval(herdrTimer)
|
|
150
|
+
herdrTimer = undefined
|
|
151
|
+
herdrInFlight?.abort()
|
|
152
|
+
herdrInFlight = undefined
|
|
153
|
+
siblings = undefined
|
|
154
|
+
gitState.dispose()
|
|
155
|
+
stopFooter()
|
|
156
|
+
stopContributionEvents()
|
|
157
|
+
},
|
|
158
|
+
}
|
|
159
|
+
}
|