@raidou/pi-notify 0.3.1 → 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/README.md +33 -33
- package/package.json +13 -6
- package/src/config.ts +2 -4
- package/src/dashboard/command.ts +11 -14
- package/src/dashboard/consts.ts +6 -0
- package/src/dashboard/session-store.test.ts +438 -0
- package/src/dashboard/session-store.ts +87 -0
- package/src/dashboard/state-store.test.ts +303 -0
- package/src/dashboard/state-store.ts +124 -99
- package/src/dashboard/ui/columns.ts +102 -0
- package/src/dashboard/ui/dashboard.test.ts +118 -0
- package/src/dashboard/ui/dashboard.ts +122 -0
- package/src/focus.ts +25 -25
- package/src/index.ts +16 -18
- package/src/jobs.ts +5 -22
- package/src/notify-test.ts +12 -8
- package/src/shared/registrar.test.ts +137 -0
- package/src/shared/registrar.ts +30 -0
- package/src/{types.ts → shared/types.ts} +4 -0
- package/src/state-tracker.test.ts +360 -0
- package/src/state-tracker.ts +123 -0
- package/src/states.ts +3 -1
- package/src/tmux-title.ts +12 -10
- package/src/dashboard/state-tracker.ts +0 -174
- package/src/dashboard/ui.ts +0 -198
- package/src/events.ts +0 -46
- package/src/idle.ts +0 -52
- package/src/tool.ts +0 -18
- /package/src/{utils.ts → shared/utils.ts} +0 -0
package/src/tmux-title.ts
CHANGED
|
@@ -4,6 +4,8 @@ import { readlinkSync } from 'node:fs'
|
|
|
4
4
|
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'
|
|
5
5
|
|
|
6
6
|
import type { ResolvedNotifyConfig } from './config.js'
|
|
7
|
+
import { Registrar } from './shared/registrar.js'
|
|
8
|
+
import type { NotifyAction } from './shared/types.js'
|
|
7
9
|
|
|
8
10
|
const WINDOW_ID_FORMAT = '#{window_id}'
|
|
9
11
|
const WINDOW_NAME_FORMAT = '#{window_name}'
|
|
@@ -11,22 +13,24 @@ const LIST_FORMAT = '#{pane_tty}\t#{window_id}'
|
|
|
11
13
|
const NEWLINE = '\n'
|
|
12
14
|
const TAB = '\t'
|
|
13
15
|
|
|
14
|
-
export class TmuxTitleTracker {
|
|
16
|
+
export class TmuxTitleTracker extends Registrar {
|
|
17
|
+
private readonly config: ResolvedNotifyConfig
|
|
15
18
|
private windowId: string | undefined
|
|
16
19
|
private originalTitle: string | undefined
|
|
17
20
|
private autoRename: boolean | undefined
|
|
18
21
|
private modified = false
|
|
19
22
|
|
|
20
|
-
constructor(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
constructor(pi: ExtensionAPI, config: ResolvedNotifyConfig) {
|
|
24
|
+
super(pi)
|
|
25
|
+
this.config = config
|
|
26
|
+
}
|
|
24
27
|
|
|
25
28
|
get enabled(): boolean {
|
|
26
29
|
return this.config.tmuxSymbol.length > 0
|
|
27
30
|
}
|
|
28
31
|
|
|
29
|
-
|
|
32
|
+
protected override setup(notify: NotifyAction): void {
|
|
33
|
+
void notify
|
|
30
34
|
this.pi.on('session_start', (_event, ctx) => {
|
|
31
35
|
if (ctx.mode !== 'tui' || !this.enabled) return
|
|
32
36
|
this.restore()
|
|
@@ -36,9 +40,6 @@ export class TmuxTitleTracker {
|
|
|
36
40
|
this.originalTitle = this.queryWindowName(id)
|
|
37
41
|
this.autoRename = this.queryAutomaticRename(id)
|
|
38
42
|
})
|
|
39
|
-
this.pi.on('session_shutdown', () => {
|
|
40
|
-
this.stop()
|
|
41
|
-
})
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
mark(): void {
|
|
@@ -72,7 +73,8 @@ export class TmuxTitleTracker {
|
|
|
72
73
|
this.modified = false
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
stop(): void {
|
|
76
|
+
override stop(): void {
|
|
77
|
+
super.stop()
|
|
76
78
|
this.restore()
|
|
77
79
|
this.windowId = undefined
|
|
78
80
|
this.originalTitle = undefined
|
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import { basename } from 'node:path'
|
|
2
|
-
|
|
3
|
-
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'
|
|
4
|
-
|
|
5
|
-
import { type SessionRecord, updateState } from './state-store.js'
|
|
6
|
-
|
|
7
|
-
const IDLE_TIMEOUT_MS = 10000
|
|
8
|
-
const HEARTBEAT_INTERVAL_MS = 5000
|
|
9
|
-
|
|
10
|
-
export class StateTracker {
|
|
11
|
-
private registered = false
|
|
12
|
-
private heartbeatTimer: NodeJS.Timeout | null = null
|
|
13
|
-
private idleTimer: NodeJS.Timeout | null = null
|
|
14
|
-
private sessionId: string | undefined = undefined
|
|
15
|
-
private cwd: string | undefined = undefined
|
|
16
|
-
private projectName: string | undefined = undefined
|
|
17
|
-
private model: string | undefined = undefined
|
|
18
|
-
|
|
19
|
-
private get cwdOrCwd(): string {
|
|
20
|
-
return this.cwd ?? process.cwd()
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
private get projectNameOrCwd(): string {
|
|
24
|
-
return this.projectName ?? basename(process.cwd())
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
constructor(private readonly pi: ExtensionAPI) {}
|
|
28
|
-
|
|
29
|
-
private updateSessionRecord(updates: Partial<SessionRecord>): void {
|
|
30
|
-
if (!this.sessionId) return
|
|
31
|
-
const now = Date.now()
|
|
32
|
-
const id = this.sessionId
|
|
33
|
-
|
|
34
|
-
void updateState((state) => {
|
|
35
|
-
const existing = state.sessions[id]
|
|
36
|
-
const stateChanged =
|
|
37
|
-
updates.state !== undefined && updates.state !== existing?.state
|
|
38
|
-
|
|
39
|
-
const record: SessionRecord = {
|
|
40
|
-
pid: process.pid,
|
|
41
|
-
cwd: this.cwdOrCwd,
|
|
42
|
-
projectName: this.projectNameOrCwd,
|
|
43
|
-
lastHeartbeatAt: now,
|
|
44
|
-
startedAt: existing?.startedAt ?? now,
|
|
45
|
-
state: updates.state ?? existing?.state ?? 'running',
|
|
46
|
-
stateChangedAt: stateChanged ? now : (existing?.stateChangedAt ?? now),
|
|
47
|
-
model: this.model ?? existing?.model,
|
|
48
|
-
lastEvent: updates.lastEvent ?? existing?.lastEvent,
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
...state,
|
|
53
|
-
sessions: { ...state.sessions, [id]: record },
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private removeSession(): void {
|
|
59
|
-
if (!this.sessionId) return
|
|
60
|
-
const id = this.sessionId
|
|
61
|
-
|
|
62
|
-
void updateState((state) => {
|
|
63
|
-
const sessions: Record<string, SessionRecord | undefined> = {}
|
|
64
|
-
for (const [key, value] of Object.entries(state.sessions)) {
|
|
65
|
-
if (key !== id) sessions[key] = value
|
|
66
|
-
}
|
|
67
|
-
return { ...state, sessions }
|
|
68
|
-
})
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private clearIdleTimer(): void {
|
|
72
|
-
if (this.idleTimer) {
|
|
73
|
-
clearTimeout(this.idleTimer)
|
|
74
|
-
this.idleTimer = null
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
private startIdleTimer(): void {
|
|
79
|
-
this.clearIdleTimer()
|
|
80
|
-
this.idleTimer = setTimeout(() => {
|
|
81
|
-
this.clearIdleTimer()
|
|
82
|
-
this.updateSessionRecord({
|
|
83
|
-
state: 'idle',
|
|
84
|
-
lastEvent: { type: 'idle', summary: 'idle', at: Date.now() },
|
|
85
|
-
})
|
|
86
|
-
}, IDLE_TIMEOUT_MS)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
register(): void {
|
|
90
|
-
if (this.registered) return
|
|
91
|
-
this.registered = true
|
|
92
|
-
|
|
93
|
-
this.pi.on('session_start', (_event, ctx) => {
|
|
94
|
-
this.sessionId = ctx.sessionManager.getSessionId()
|
|
95
|
-
this.cwd = ctx.cwd
|
|
96
|
-
this.projectName = basename(ctx.cwd)
|
|
97
|
-
this.model = ctx.model?.id
|
|
98
|
-
|
|
99
|
-
this.updateSessionRecord({
|
|
100
|
-
lastEvent: {
|
|
101
|
-
type: 'session_start',
|
|
102
|
-
summary: 'started',
|
|
103
|
-
at: Date.now(),
|
|
104
|
-
},
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
this.heartbeatTimer = setInterval(() => {
|
|
108
|
-
if (!this.sessionId) return
|
|
109
|
-
const id = this.sessionId
|
|
110
|
-
|
|
111
|
-
void updateState((state) => {
|
|
112
|
-
const existing = state.sessions[id]
|
|
113
|
-
if (!existing) return state
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
...state,
|
|
117
|
-
sessions: {
|
|
118
|
-
...state.sessions,
|
|
119
|
-
[id]: {
|
|
120
|
-
...existing,
|
|
121
|
-
lastHeartbeatAt: Date.now(),
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
}
|
|
125
|
-
})
|
|
126
|
-
}, HEARTBEAT_INTERVAL_MS)
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
this.pi.on('turn_start', () => {
|
|
130
|
-
this.clearIdleTimer()
|
|
131
|
-
this.updateSessionRecord({
|
|
132
|
-
state: 'running',
|
|
133
|
-
lastEvent: { type: 'turn_start', summary: 'running', at: Date.now() },
|
|
134
|
-
})
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
this.pi.on('agent_start', () => {
|
|
138
|
-
this.clearIdleTimer()
|
|
139
|
-
this.updateSessionRecord({
|
|
140
|
-
state: 'running',
|
|
141
|
-
lastEvent: { type: 'agent_start', summary: 'running', at: Date.now() },
|
|
142
|
-
})
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
this.pi.on('agent_settled', () => {
|
|
146
|
-
this.startIdleTimer()
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
this.pi.on('tool_call', (event) => {
|
|
150
|
-
this.updateSessionRecord({
|
|
151
|
-
state: 'running',
|
|
152
|
-
lastEvent: {
|
|
153
|
-
type: 'tool_call',
|
|
154
|
-
summary: `tool:${event.toolName}`,
|
|
155
|
-
at: Date.now(),
|
|
156
|
-
},
|
|
157
|
-
})
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
this.pi.on('session_shutdown', () => {
|
|
161
|
-
this.stop()
|
|
162
|
-
this.removeSession()
|
|
163
|
-
})
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
stop(): void {
|
|
167
|
-
this.clearIdleTimer()
|
|
168
|
-
if (this.heartbeatTimer) {
|
|
169
|
-
clearInterval(this.heartbeatTimer)
|
|
170
|
-
this.heartbeatTimer = null
|
|
171
|
-
}
|
|
172
|
-
this.registered = false
|
|
173
|
-
}
|
|
174
|
-
}
|
package/src/dashboard/ui.ts
DELETED
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
import type { Theme } from '@earendil-works/pi-coding-agent'
|
|
2
|
-
import { DynamicBorder } from '@earendil-works/pi-coding-agent'
|
|
3
|
-
import {
|
|
4
|
-
Container,
|
|
5
|
-
Key,
|
|
6
|
-
matchesKey,
|
|
7
|
-
Spacer,
|
|
8
|
-
Text,
|
|
9
|
-
truncateToWidth,
|
|
10
|
-
} from '@earendil-works/pi-tui'
|
|
11
|
-
|
|
12
|
-
import type { SessionRecord } from './state-store.js'
|
|
13
|
-
|
|
14
|
-
const COLUMNS = [
|
|
15
|
-
{ name: 'PID', width: 8 },
|
|
16
|
-
{ name: 'STATE', width: 8 },
|
|
17
|
-
{ name: 'PROJECT', width: 15 },
|
|
18
|
-
{ name: 'UPTIME', width: 10 },
|
|
19
|
-
{ name: 'MODEL', width: 20 },
|
|
20
|
-
{ name: 'LAST EVENT', width: undefined },
|
|
21
|
-
] as const
|
|
22
|
-
|
|
23
|
-
const [PID_COL, STATE_COL, PROJECT_COL, UPTIME_COL, MODEL_COL] = COLUMNS
|
|
24
|
-
|
|
25
|
-
const FIXED_COLUMNS_WIDTH =
|
|
26
|
-
PID_COL.width +
|
|
27
|
-
STATE_COL.width +
|
|
28
|
-
PROJECT_COL.width +
|
|
29
|
-
UPTIME_COL.width +
|
|
30
|
-
MODEL_COL.width
|
|
31
|
-
const GAPS_WIDTH = (COLUMNS.length - 1) * 2
|
|
32
|
-
|
|
33
|
-
const MAX_ROWS = Math.max(1, (process.stdout.rows || 20) - 6)
|
|
34
|
-
|
|
35
|
-
export interface DashboardProps {
|
|
36
|
-
tui: { requestRender: () => void }
|
|
37
|
-
theme: Theme
|
|
38
|
-
initialSessions: SessionRecord[]
|
|
39
|
-
onRefresh: () => Promise<SessionRecord[]>
|
|
40
|
-
onClose: () => void
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function formatUptime(startedAt: number): string {
|
|
44
|
-
const elapsed = Date.now() - startedAt
|
|
45
|
-
const seconds = Math.floor(elapsed / 1000)
|
|
46
|
-
const minutes = Math.floor(seconds / 60)
|
|
47
|
-
const hours = Math.floor(minutes / 60)
|
|
48
|
-
|
|
49
|
-
if (hours > 0) {
|
|
50
|
-
const mins = minutes % 60
|
|
51
|
-
return `${hours}h ${mins}m`
|
|
52
|
-
}
|
|
53
|
-
if (minutes > 0) {
|
|
54
|
-
const secs = seconds % 60
|
|
55
|
-
return `${minutes}m ${secs}s`
|
|
56
|
-
}
|
|
57
|
-
return `${seconds}s`
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function createDashboard(props: DashboardProps) {
|
|
61
|
-
let sessions = [...props.initialSessions]
|
|
62
|
-
let scrollOffset = 0
|
|
63
|
-
let cachedWidth: number | null = null
|
|
64
|
-
let cachedLines: string[] = []
|
|
65
|
-
|
|
66
|
-
const dashboardContainer = new Container()
|
|
67
|
-
|
|
68
|
-
function updateChildren(width: number): void {
|
|
69
|
-
const remainingWidth = Math.max(1, width - FIXED_COLUMNS_WIDTH - GAPS_WIDTH)
|
|
70
|
-
const { theme } = props
|
|
71
|
-
|
|
72
|
-
const stats = sessions.reduce(
|
|
73
|
-
(acc, s) => {
|
|
74
|
-
acc[s.state]++
|
|
75
|
-
return acc
|
|
76
|
-
},
|
|
77
|
-
{ running: 0, idle: 0 },
|
|
78
|
-
)
|
|
79
|
-
|
|
80
|
-
const headerLine = COLUMNS.map((col) =>
|
|
81
|
-
col.width === undefined ? col.name : col.name.padEnd(col.width),
|
|
82
|
-
).join(' ')
|
|
83
|
-
|
|
84
|
-
dashboardContainer.clear()
|
|
85
|
-
dashboardContainer.addChild(
|
|
86
|
-
new Text(
|
|
87
|
-
`${theme.fg('accent', theme.bold('pi sessions dashboard'))} ${theme.fg(
|
|
88
|
-
'dim',
|
|
89
|
-
`total=${sessions.length} running=${stats.running} idle=${stats.idle}`,
|
|
90
|
-
)}`,
|
|
91
|
-
0,
|
|
92
|
-
0,
|
|
93
|
-
),
|
|
94
|
-
)
|
|
95
|
-
dashboardContainer.addChild(new Spacer(1))
|
|
96
|
-
dashboardContainer.addChild(
|
|
97
|
-
new Text(theme.fg('borderAccent', headerLine), 0, 0),
|
|
98
|
-
)
|
|
99
|
-
dashboardContainer.addChild(
|
|
100
|
-
new DynamicBorder((str) => theme.fg('borderAccent', str)),
|
|
101
|
-
)
|
|
102
|
-
|
|
103
|
-
for (const session of sessions.slice(
|
|
104
|
-
scrollOffset,
|
|
105
|
-
scrollOffset + MAX_ROWS,
|
|
106
|
-
)) {
|
|
107
|
-
const stateColor = session.state === 'running' ? 'success' : 'muted'
|
|
108
|
-
const line = [
|
|
109
|
-
theme.fg('dim', String(session.pid).padEnd(PID_COL.width)),
|
|
110
|
-
theme.fg(stateColor, session.state.padEnd(STATE_COL.width)),
|
|
111
|
-
theme.fg(
|
|
112
|
-
'text',
|
|
113
|
-
truncateToWidth(session.projectName, PROJECT_COL.width, '…', true),
|
|
114
|
-
),
|
|
115
|
-
theme.fg(
|
|
116
|
-
'dim',
|
|
117
|
-
formatUptime(session.startedAt).padEnd(UPTIME_COL.width),
|
|
118
|
-
),
|
|
119
|
-
theme.fg(
|
|
120
|
-
'muted',
|
|
121
|
-
session.model
|
|
122
|
-
? truncateToWidth(session.model, MODEL_COL.width, '…', true)
|
|
123
|
-
: '',
|
|
124
|
-
),
|
|
125
|
-
theme.fg(
|
|
126
|
-
'dim',
|
|
127
|
-
session.lastEvent
|
|
128
|
-
? truncateToWidth(
|
|
129
|
-
`${session.lastEvent.type}:${session.lastEvent.summary}`,
|
|
130
|
-
remainingWidth,
|
|
131
|
-
)
|
|
132
|
-
: '',
|
|
133
|
-
),
|
|
134
|
-
].join(' ')
|
|
135
|
-
dashboardContainer.addChild(new Text(line, 0, 0))
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
dashboardContainer.addChild(new Spacer(1))
|
|
139
|
-
dashboardContainer.addChild(
|
|
140
|
-
new Text(theme.fg('dim', '↑↓ scroll • r refresh • q or esc close'), 0, 0),
|
|
141
|
-
)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
const component = {
|
|
145
|
-
render(width: number): string[] {
|
|
146
|
-
if (cachedWidth !== width) {
|
|
147
|
-
updateChildren(width)
|
|
148
|
-
cachedLines = dashboardContainer.render(width)
|
|
149
|
-
cachedWidth = width
|
|
150
|
-
}
|
|
151
|
-
return cachedLines
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
handleInput(data: string): void {
|
|
155
|
-
if (matchesKey(data, 'r')) {
|
|
156
|
-
props
|
|
157
|
-
.onRefresh()
|
|
158
|
-
.then((newSessions) => {
|
|
159
|
-
sessions = [...newSessions]
|
|
160
|
-
scrollOffset = 0
|
|
161
|
-
cachedWidth = null
|
|
162
|
-
props.tui.requestRender()
|
|
163
|
-
})
|
|
164
|
-
.catch(() => {})
|
|
165
|
-
return
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (matchesKey(data, 'q') || matchesKey(data, Key.escape)) {
|
|
169
|
-
props.onClose()
|
|
170
|
-
return
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (matchesKey(data, Key.up) || matchesKey(data, 'k')) {
|
|
174
|
-
if (scrollOffset > 0) {
|
|
175
|
-
scrollOffset--
|
|
176
|
-
cachedWidth = null
|
|
177
|
-
props.tui.requestRender()
|
|
178
|
-
}
|
|
179
|
-
return
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if (matchesKey(data, Key.down) || matchesKey(data, 'j')) {
|
|
183
|
-
if (scrollOffset + MAX_ROWS < sessions.length) {
|
|
184
|
-
scrollOffset++
|
|
185
|
-
cachedWidth = null
|
|
186
|
-
props.tui.requestRender()
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
},
|
|
190
|
-
|
|
191
|
-
invalidate(): void {
|
|
192
|
-
cachedWidth = null
|
|
193
|
-
dashboardContainer.invalidate()
|
|
194
|
-
},
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return component
|
|
198
|
-
}
|
package/src/events.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'
|
|
2
|
-
|
|
3
|
-
import type { ResolvedNotifyConfig } from './config.js'
|
|
4
|
-
import type { NotifyAction, Unsubscribe } from './types.js'
|
|
5
|
-
|
|
6
|
-
export const PI_NOTIFY_EVENT = 'pi-notify:notify'
|
|
7
|
-
|
|
8
|
-
export class EventsNotifier {
|
|
9
|
-
private unsubscribes: Unsubscribe[] = []
|
|
10
|
-
private registered = false
|
|
11
|
-
|
|
12
|
-
constructor(
|
|
13
|
-
private readonly pi: ExtensionAPI,
|
|
14
|
-
private readonly config: ResolvedNotifyConfig,
|
|
15
|
-
) {}
|
|
16
|
-
|
|
17
|
-
register(notify: NotifyAction): void {
|
|
18
|
-
if (this.registered) return
|
|
19
|
-
this.registered = true
|
|
20
|
-
|
|
21
|
-
for (const [channel, message] of Object.entries(this.config.events)) {
|
|
22
|
-
if (!message) continue
|
|
23
|
-
const unsubscribe = this.pi.events.on(channel, () => {
|
|
24
|
-
notify(message)
|
|
25
|
-
})
|
|
26
|
-
this.unsubscribes.push(unsubscribe)
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const customEventUnsub = this.pi.events.on(PI_NOTIFY_EVENT, (payload) => {
|
|
30
|
-
notify(String(payload))
|
|
31
|
-
})
|
|
32
|
-
this.unsubscribes.push(customEventUnsub)
|
|
33
|
-
|
|
34
|
-
this.pi.on('session_shutdown', () => {
|
|
35
|
-
this.stop()
|
|
36
|
-
})
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
stop(): void {
|
|
40
|
-
this.unsubscribes.forEach((unsub) => {
|
|
41
|
-
unsub()
|
|
42
|
-
})
|
|
43
|
-
this.unsubscribes = []
|
|
44
|
-
this.registered = false
|
|
45
|
-
}
|
|
46
|
-
}
|
package/src/idle.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'
|
|
2
|
-
|
|
3
|
-
import type { ResolvedNotifyConfig } from './config.js'
|
|
4
|
-
import type { JobTracker } from './jobs.js'
|
|
5
|
-
import type { NotifyAction } from './types.js'
|
|
6
|
-
|
|
7
|
-
const IDLE_TIMEOUT_MS = 10000
|
|
8
|
-
|
|
9
|
-
export class IdleNotifier {
|
|
10
|
-
private timer: NodeJS.Timeout | null = null
|
|
11
|
-
|
|
12
|
-
constructor(
|
|
13
|
-
private readonly pi: ExtensionAPI,
|
|
14
|
-
private readonly config: ResolvedNotifyConfig,
|
|
15
|
-
private readonly jobTracker: JobTracker,
|
|
16
|
-
) {}
|
|
17
|
-
|
|
18
|
-
clearIdleTimer(): void {
|
|
19
|
-
if (this.timer) {
|
|
20
|
-
clearTimeout(this.timer)
|
|
21
|
-
this.timer = null
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
startIdleTimer(notify: NotifyAction): void {
|
|
25
|
-
this.clearIdleTimer()
|
|
26
|
-
this.timer = setTimeout(() => {
|
|
27
|
-
this.clearIdleTimer()
|
|
28
|
-
notify('Idle')
|
|
29
|
-
}, IDLE_TIMEOUT_MS)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
register(notify: NotifyAction): void {
|
|
33
|
-
if (!this.config.finished) return
|
|
34
|
-
|
|
35
|
-
this.pi.on('turn_start', () => {
|
|
36
|
-
this.clearIdleTimer()
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
this.pi.on('message_start', () => {
|
|
40
|
-
this.clearIdleTimer()
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
this.pi.on('agent_settled', () => {
|
|
44
|
-
if (this.jobTracker.hasActiveJobs) return
|
|
45
|
-
this.startIdleTimer(notify)
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
this.jobTracker.onEnd(() => {
|
|
49
|
-
this.startIdleTimer(notify)
|
|
50
|
-
})
|
|
51
|
-
}
|
|
52
|
-
}
|
package/src/tool.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import type { ExtensionAPI } from '@earendil-works/pi-coding-agent'
|
|
2
|
-
|
|
3
|
-
import type { ResolvedNotifyConfig } from './config.js'
|
|
4
|
-
import type { NotifyAction } from './types.js'
|
|
5
|
-
|
|
6
|
-
export class ToolCallNotifier {
|
|
7
|
-
constructor(
|
|
8
|
-
private readonly pi: ExtensionAPI,
|
|
9
|
-
private readonly config: ResolvedNotifyConfig,
|
|
10
|
-
) {}
|
|
11
|
-
|
|
12
|
-
register(notify: NotifyAction): void {
|
|
13
|
-
this.pi.on('tool_call', (event) => {
|
|
14
|
-
if (!this.config.notifyTools.has(event.toolName)) return
|
|
15
|
-
notify(`Tool call: ${event.toolName}`)
|
|
16
|
-
})
|
|
17
|
-
}
|
|
18
|
-
}
|
|
File without changes
|