@felan-ai/ext-background-bash 0.5.0 → 0.6.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 +25 -14
- package/dist/coordinator.d.ts +24 -0
- package/dist/coordinator.d.ts.map +1 -0
- package/dist/coordinator.js +212 -0
- package/dist/coordinator.js.map +1 -0
- package/dist/index.d.ts +9 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +496 -410
- package/dist/index.js.map +1 -1
- package/dist/job-store.d.ts +17 -2
- package/dist/job-store.d.ts.map +1 -1
- package/dist/job-store.js +113 -51
- package/dist/job-store.js.map +1 -1
- package/dist/logs.d.ts +2 -3
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +38 -23
- package/dist/logs.js.map +1 -1
- package/dist/process-manager.d.ts +12 -1
- package/dist/process-manager.d.ts.map +1 -1
- package/dist/process-manager.js +170 -29
- package/dist/process-manager.js.map +1 -1
- package/dist/runtime-support.d.ts +1 -0
- package/dist/runtime-support.d.ts.map +1 -1
- package/dist/runtime-support.js +5 -1
- package/dist/runtime-support.js.map +1 -1
- package/dist/ui/background-bash-view.js +2 -2
- package/dist/ui/background-bash-view.js.map +1 -1
- package/dist/ui/completion-message.js +2 -2
- package/dist/ui/completion-message.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,34 +1,39 @@
|
|
|
1
|
-
import { StringEnum, createRuntimeCodingTools, } from '@felan-ai/agent-core';
|
|
1
|
+
import { StringEnum, associateExtensionConfig, configField, createRuntimeCodingTools, defineExtensionConfig, } from '@felan-ai/agent-core';
|
|
2
2
|
import { Key, Text } from '@earendil-works/pi-tui';
|
|
3
3
|
import { Type } from 'typebox';
|
|
4
4
|
import { normalizeBackgroundCommand } from './command-normalizer.js';
|
|
5
5
|
import { isTerminalStatus, } from './job-store.js';
|
|
6
6
|
import { BackgroundBashManager } from './process-manager.js';
|
|
7
|
+
import { BackgroundBashCoordinator } from './coordinator.js';
|
|
7
8
|
import { inspectBackgroundBashRuntime } from './runtime-support.js';
|
|
8
9
|
import { BackgroundBashView } from './ui/background-bash-view.js';
|
|
9
10
|
import { BACKGROUND_BASH_COMPLETION_MESSAGE_TYPE, registerBackgroundBashCompletionRenderer, } from './ui/completion-message.js';
|
|
10
11
|
const STATUS_VALUES = ['running', 'completed', 'failed', 'killed', 'unknown', 'all'];
|
|
11
12
|
const SIGNAL_VALUES = ['SIGTERM', 'SIGKILL'];
|
|
12
|
-
const OPENAI_PROVIDER_IDS = new Set(['openai', 'openai-codex']);
|
|
13
13
|
const COMPLETION_POLL_MS = 500;
|
|
14
14
|
const BACKGROUND_TOOL_NAMES = [
|
|
15
15
|
'list_background_bash',
|
|
16
16
|
'read_background_bash',
|
|
17
17
|
'wait_background_bash',
|
|
18
18
|
'stop_background_bash',
|
|
19
|
+
'write_background_bash',
|
|
19
20
|
];
|
|
21
|
+
export function supportsBackgroundBashModel(model) {
|
|
22
|
+
return model !== undefined;
|
|
23
|
+
}
|
|
20
24
|
const BashParams = Type.Object({
|
|
21
25
|
command: Type.String({ description: 'Bash command to execute' }),
|
|
22
|
-
timeout: Type.Optional(Type.Number({ description: '
|
|
26
|
+
timeout: Type.Optional(Type.Number({ minimum: 0, description: 'Seconds to wait before promoting the same process to the background. Defaults to 120; 0 backgrounds immediately.' })),
|
|
23
27
|
background: Type.Optional(Type.Boolean({
|
|
24
|
-
description: 'Start a
|
|
28
|
+
description: 'Start a background process and return immediately',
|
|
25
29
|
})),
|
|
30
|
+
tty: Type.Optional(Type.Boolean({ description: 'Start with a PTY so write_background_bash can send stdin and control bytes' })),
|
|
26
31
|
}, { additionalProperties: false });
|
|
27
32
|
const ListBackgroundBashParams = Type.Object({
|
|
28
33
|
status: Type.Optional(StringEnum(STATUS_VALUES, { description: 'Filter processes by status' })),
|
|
29
34
|
}, { additionalProperties: false });
|
|
30
35
|
const ReadBackgroundBashParams = Type.Object({
|
|
31
|
-
id: Type.String({ description: 'Background
|
|
36
|
+
id: Type.String({ description: 'Background process id returned by bash' }),
|
|
32
37
|
lines: Type.Optional(Type.Integer({
|
|
33
38
|
minimum: 1,
|
|
34
39
|
maximum: 1_000,
|
|
@@ -36,427 +41,502 @@ const ReadBackgroundBashParams = Type.Object({
|
|
|
36
41
|
})),
|
|
37
42
|
}, { additionalProperties: false });
|
|
38
43
|
const WaitBackgroundBashParams = Type.Object({
|
|
39
|
-
id: Type.String({ description: 'Background
|
|
44
|
+
id: Type.String({ description: 'Background process id returned by bash' }),
|
|
40
45
|
timeout: Type.Optional(Type.Number({ description: 'Maximum seconds to wait before returning current status' })),
|
|
41
46
|
}, { additionalProperties: false });
|
|
42
47
|
const StopBackgroundBashParams = Type.Object({
|
|
43
|
-
id: Type.String({ description: 'Background
|
|
48
|
+
id: Type.String({ description: 'Background process id returned by bash' }),
|
|
44
49
|
signal: Type.Optional(StringEnum(SIGNAL_VALUES, { description: 'Signal to send. Default: SIGTERM.' })),
|
|
45
50
|
}, { additionalProperties: false });
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
51
|
+
const WriteBackgroundBashParams = Type.Object({
|
|
52
|
+
id: Type.String({ description: 'Background process id returned by bash' }),
|
|
53
|
+
chars: Type.String({ description: 'Exact text or control bytes to write to a PTY process' }),
|
|
54
|
+
}, { additionalProperties: false });
|
|
55
|
+
export const BACKGROUND_BASH_CONFIG = defineExtensionConfig({
|
|
56
|
+
id: 'backgroundBash',
|
|
57
|
+
title: 'Background processes',
|
|
58
|
+
fields: {
|
|
59
|
+
foregroundTimeoutSeconds: configField.number({
|
|
60
|
+
default: 120,
|
|
61
|
+
description: 'Seconds before a foreground Bash command is promoted to the background',
|
|
62
|
+
validate: (value) => typeof value === 'number' && Number.isFinite(value) && value >= 0
|
|
63
|
+
? undefined
|
|
64
|
+
: 'must be a finite non-negative number',
|
|
65
|
+
}),
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
export function createBackgroundBashExtension(coordinator, ownsCoordinator = coordinator === undefined) {
|
|
69
|
+
const extension = (pi) => {
|
|
70
|
+
const config = {
|
|
71
|
+
foregroundTimeoutSeconds: Number(pi.config?.foregroundTimeoutSeconds ?? 120),
|
|
72
|
+
};
|
|
73
|
+
registerBackgroundBashCompletionRenderer(pi);
|
|
74
|
+
const manager = new BackgroundBashManager(pi.runtime, coordinator);
|
|
75
|
+
const foregroundBash = createRuntimeCodingTools(pi.runtime, { shellFlavor: 'posix' })
|
|
76
|
+
.find((tool) => tool.name === 'bash');
|
|
77
|
+
let helperToolsRegistered = false;
|
|
78
|
+
let backgroundBashActive = false;
|
|
79
|
+
let controlsRegistered = false;
|
|
80
|
+
let statusTarget;
|
|
81
|
+
let statusPollTimer;
|
|
82
|
+
let statusUpdateRunning = false;
|
|
83
|
+
let statusGeneration = 0;
|
|
84
|
+
let completionPollingEnabled = false;
|
|
85
|
+
let completionPollTimer;
|
|
86
|
+
let completionPollGeneration = 0;
|
|
87
|
+
let completionPollRunningGeneration;
|
|
88
|
+
let runtimeAvailable;
|
|
89
|
+
let runtimeCheck;
|
|
90
|
+
const watchedJobIds = new Set();
|
|
91
|
+
const createStatusTarget = (ctx) => {
|
|
92
|
+
if (ctx.mode !== 'tui')
|
|
93
|
+
return undefined;
|
|
94
|
+
return { ui: ctx.ui, generation: statusGeneration };
|
|
95
|
+
};
|
|
96
|
+
const updateStatus = async (target) => {
|
|
97
|
+
if (!target || target.generation !== statusGeneration || statusUpdateRunning)
|
|
83
98
|
return;
|
|
99
|
+
statusUpdateRunning = true;
|
|
100
|
+
try {
|
|
101
|
+
const running = await manager.list('running');
|
|
102
|
+
if (target.generation !== statusGeneration)
|
|
103
|
+
return;
|
|
104
|
+
if (running.length === 0) {
|
|
105
|
+
target.ui.setStatus('background-bash', undefined);
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
const label = running.length === 1 ? '1 process' : `${running.length} processes`;
|
|
109
|
+
const icon = target.ui.theme.fg('accent', '●');
|
|
110
|
+
const text = target.ui.theme.fg('accent', label);
|
|
111
|
+
target.ui.setStatus('background-bash', `${icon} ${text}`);
|
|
84
112
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
113
|
+
catch {
|
|
114
|
+
if (target.generation !== statusGeneration)
|
|
115
|
+
return;
|
|
116
|
+
target.ui.setStatus('background-bash', target.ui.theme.fg('warning', 'bash ?'));
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
statusUpdateRunning = false;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const startStatusPolling = (ctx) => {
|
|
123
|
+
statusGeneration += 1;
|
|
124
|
+
if (statusPollTimer)
|
|
125
|
+
clearInterval(statusPollTimer);
|
|
126
|
+
const target = createStatusTarget(ctx);
|
|
127
|
+
statusTarget = target;
|
|
128
|
+
void updateStatus(target);
|
|
129
|
+
statusPollTimer = target ? setInterval(() => void updateStatus(target), 5_000) : undefined;
|
|
130
|
+
};
|
|
131
|
+
const stopStatusPolling = () => {
|
|
132
|
+
statusGeneration += 1;
|
|
133
|
+
if (statusPollTimer) {
|
|
134
|
+
clearInterval(statusPollTimer);
|
|
135
|
+
statusPollTimer = undefined;
|
|
136
|
+
}
|
|
137
|
+
statusTarget?.ui.setStatus('background-bash', undefined);
|
|
138
|
+
statusTarget = undefined;
|
|
139
|
+
};
|
|
140
|
+
const clearCompletionPollTimer = () => {
|
|
141
|
+
if (!completionPollTimer)
|
|
92
142
|
return;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
triggerTurn: true,
|
|
131
|
-
deliverAs: 'steer',
|
|
132
|
-
});
|
|
133
|
-
};
|
|
134
|
-
const pollCompletions = async (generation) => {
|
|
135
|
-
if (generation !== completionPollGeneration
|
|
136
|
-
|| completionPollRunningGeneration === generation)
|
|
137
|
-
return;
|
|
138
|
-
completionPollRunningGeneration = generation;
|
|
139
|
-
try {
|
|
140
|
-
for (const id of [...watchedJobIds]) {
|
|
141
|
-
let job;
|
|
142
|
-
try {
|
|
143
|
-
job = await manager.get(id);
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
continue;
|
|
143
|
+
clearInterval(completionPollTimer);
|
|
144
|
+
completionPollTimer = undefined;
|
|
145
|
+
};
|
|
146
|
+
const deliverCompletion = (job) => {
|
|
147
|
+
pi.sendMessage({
|
|
148
|
+
customType: BACKGROUND_BASH_COMPLETION_MESSAGE_TYPE,
|
|
149
|
+
content: formatCompletionNotice(job),
|
|
150
|
+
display: true,
|
|
151
|
+
details: { job: completionDetails(job) },
|
|
152
|
+
}, {
|
|
153
|
+
triggerTurn: true,
|
|
154
|
+
deliverAs: 'steer',
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
const pollCompletions = async (generation) => {
|
|
158
|
+
if (generation !== completionPollGeneration
|
|
159
|
+
|| completionPollRunningGeneration === generation)
|
|
160
|
+
return;
|
|
161
|
+
completionPollRunningGeneration = generation;
|
|
162
|
+
try {
|
|
163
|
+
for (const id of [...watchedJobIds]) {
|
|
164
|
+
let job;
|
|
165
|
+
try {
|
|
166
|
+
job = await manager.get(id);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
if (generation !== completionPollGeneration
|
|
172
|
+
|| !completionPollingEnabled
|
|
173
|
+
|| !watchedJobIds.has(id))
|
|
174
|
+
return;
|
|
175
|
+
if (!isTerminalStatus(job.status.status))
|
|
176
|
+
continue;
|
|
177
|
+
watchedJobIds.delete(id);
|
|
178
|
+
deliverCompletion(job);
|
|
179
|
+
void updateStatus(statusTarget);
|
|
147
180
|
}
|
|
148
|
-
if (generation !== completionPollGeneration
|
|
149
|
-
|| !completionPollingEnabled
|
|
150
|
-
|| !watchedJobIds.has(id))
|
|
151
|
-
return;
|
|
152
|
-
if (!isTerminalStatus(job.status.status))
|
|
153
|
-
continue;
|
|
154
|
-
watchedJobIds.delete(id);
|
|
155
|
-
deliverCompletion(job);
|
|
156
|
-
void updateStatus(statusTarget);
|
|
157
181
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
182
|
+
finally {
|
|
183
|
+
if (completionPollRunningGeneration === generation) {
|
|
184
|
+
completionPollRunningGeneration = undefined;
|
|
185
|
+
}
|
|
186
|
+
if (watchedJobIds.size === 0)
|
|
187
|
+
clearCompletionPollTimer();
|
|
162
188
|
}
|
|
189
|
+
};
|
|
190
|
+
const ensureCompletionPolling = () => {
|
|
191
|
+
if (!completionPollingEnabled || watchedJobIds.size === 0 || completionPollTimer)
|
|
192
|
+
return;
|
|
193
|
+
const generation = completionPollGeneration;
|
|
194
|
+
completionPollTimer = setInterval(() => void pollCompletions(generation), COMPLETION_POLL_MS);
|
|
195
|
+
completionPollTimer.unref?.();
|
|
196
|
+
void pollCompletions(generation);
|
|
197
|
+
};
|
|
198
|
+
const watchCompletion = (id) => {
|
|
199
|
+
watchedJobIds.add(id);
|
|
200
|
+
ensureCompletionPolling();
|
|
201
|
+
};
|
|
202
|
+
const suppressCompletion = (id) => {
|
|
203
|
+
const removed = watchedJobIds.delete(id);
|
|
163
204
|
if (watchedJobIds.size === 0)
|
|
164
205
|
clearCompletionPollTimer();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const watchCompletion = (id) => {
|
|
176
|
-
watchedJobIds.add(id);
|
|
177
|
-
ensureCompletionPolling();
|
|
178
|
-
};
|
|
179
|
-
const suppressCompletion = (id) => {
|
|
180
|
-
const removed = watchedJobIds.delete(id);
|
|
181
|
-
if (watchedJobIds.size === 0)
|
|
206
|
+
return removed;
|
|
207
|
+
};
|
|
208
|
+
const resumeCompletionPolling = () => {
|
|
209
|
+
completionPollingEnabled = true;
|
|
210
|
+
ensureCompletionPolling();
|
|
211
|
+
};
|
|
212
|
+
const pauseCompletionPolling = () => {
|
|
213
|
+
completionPollingEnabled = false;
|
|
214
|
+
completionPollGeneration += 1;
|
|
215
|
+
completionPollRunningGeneration = undefined;
|
|
182
216
|
clearCompletionPollTimer();
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
217
|
+
};
|
|
218
|
+
const clearCompletionWatches = () => {
|
|
219
|
+
pauseCompletionPolling();
|
|
220
|
+
watchedJobIds.clear();
|
|
221
|
+
};
|
|
222
|
+
const openProcessView = async (ctx) => {
|
|
223
|
+
if (!backgroundBashActive) {
|
|
224
|
+
if (ctx.mode === 'tui')
|
|
225
|
+
ctx.ui.notify('Background processes are unavailable in this runtime.', 'info');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const target = createStatusTarget(ctx);
|
|
229
|
+
if (!target)
|
|
230
|
+
return;
|
|
231
|
+
await target.ui.custom((tui, theme, _keybindings, done) => new BackgroundBashView(manager, theme, () => done(undefined), () => tui.requestRender()));
|
|
232
|
+
await updateStatus(target);
|
|
233
|
+
};
|
|
234
|
+
const registerBackgroundBash = () => {
|
|
235
|
+
if (backgroundBashActive)
|
|
236
|
+
return;
|
|
237
|
+
pi.registerTool({
|
|
238
|
+
name: 'bash',
|
|
239
|
+
label: 'bash',
|
|
240
|
+
description: 'Execute a Bash command in the current working directory. Set background: true for long-running commands and inspect output with read_background_bash.',
|
|
241
|
+
promptSnippet: 'Execute Bash commands, optionally as background processes with background: true',
|
|
242
|
+
promptGuidelines: [
|
|
243
|
+
'Use bash with background: true for long-running commands such as dev servers, watchers, and scripts the agent should not block on.',
|
|
244
|
+
'Use tty: true when a command needs interactive stdin or control bytes; write_background_bash sends exact input to it.',
|
|
245
|
+
'Foreground commands are promoted to the background after their timeout without restarting; timeout: 0 promotes immediately.',
|
|
246
|
+
'Background process completion messages arrive automatically; continue useful work instead of polling when the current task does not need to block.',
|
|
247
|
+
'After starting a background process, inspect output with read_background_bash; do not use wait_background_bash just to read output.',
|
|
248
|
+
'Use wait_background_bash only when you need to wait for the process to finish or check whether it has finished.',
|
|
249
|
+
'Use list_background_bash to discover processes started in this root session and workspace, including its subagents.',
|
|
250
|
+
],
|
|
251
|
+
parameters: BashParams,
|
|
252
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
253
|
+
if (signal?.aborted)
|
|
254
|
+
throw new Error('Command cancelled');
|
|
255
|
+
const timeoutSeconds = params.timeout ?? config.foregroundTimeoutSeconds;
|
|
256
|
+
const interactive = params.tty === true;
|
|
257
|
+
const normalized = interactive
|
|
258
|
+
? { command: params.command, rtkRewriteRemoved: false }
|
|
259
|
+
: normalizeBackgroundCommand(params.command);
|
|
260
|
+
const target = createStatusTarget(ctx);
|
|
261
|
+
const started = interactive
|
|
262
|
+
? await manager.startInteractive(normalized.command)
|
|
263
|
+
: { job: await manager.start(normalized.command) };
|
|
264
|
+
const job = started.job;
|
|
265
|
+
if (signal?.aborted) {
|
|
266
|
+
if (interactive)
|
|
267
|
+
await manager.stopInteractive(job.meta.id).catch(() => { });
|
|
268
|
+
else
|
|
269
|
+
await manager.stop(job.meta.id).catch(() => { });
|
|
270
|
+
throw new Error('Command cancelled');
|
|
271
|
+
}
|
|
272
|
+
if (!params.background && timeoutSeconds > 0) {
|
|
273
|
+
try {
|
|
274
|
+
if (interactive) {
|
|
275
|
+
const deadline = Date.now() + timeoutSeconds * 1_000;
|
|
276
|
+
let result = await manager.readInteractive(job.meta.id, Math.max(0, deadline - Date.now()), signal);
|
|
277
|
+
if (signal?.aborted)
|
|
278
|
+
throw new Error('Command cancelled');
|
|
279
|
+
while (result.running && Date.now() < deadline) {
|
|
280
|
+
result = await manager.readInteractive(job.meta.id, Math.max(0, deadline - Date.now()), signal);
|
|
281
|
+
if (signal?.aborted)
|
|
282
|
+
throw new Error('Command cancelled');
|
|
283
|
+
}
|
|
284
|
+
if (!result.running) {
|
|
285
|
+
const completed = await manager.get(job.meta.id);
|
|
286
|
+
return {
|
|
287
|
+
content: [{ type: 'text', text: formatForegroundOutput(completed, result.output) }],
|
|
288
|
+
details: { background: false, id: job.meta.id, status: completed.status.status, output: result.output },
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
const result = await manager.wait(job.meta.id, timeoutSeconds, signal);
|
|
294
|
+
if (!result.timedOut) {
|
|
295
|
+
const output = await manager.tail(job.meta.id);
|
|
296
|
+
return {
|
|
297
|
+
content: [{ type: 'text', text: formatForegroundOutput(result.job, output) }],
|
|
298
|
+
details: { background: false, id: job.meta.id, status: result.job.status.status, output },
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
if (signal?.aborted) {
|
|
305
|
+
if (interactive)
|
|
306
|
+
await manager.stopInteractive(job.meta.id, 'SIGTERM').catch(() => { });
|
|
307
|
+
else
|
|
308
|
+
await manager.stop(job.meta.id, 'SIGTERM').catch(() => { });
|
|
309
|
+
throw new Error('Command cancelled', { cause: error });
|
|
310
|
+
}
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (!params.background)
|
|
315
|
+
await manager.markPromoted(job.meta.id, interactive ? 'pty' : 'detached');
|
|
316
|
+
watchCompletion(job.meta.id);
|
|
317
|
+
await updateStatus(target);
|
|
318
|
+
const notice = normalized.rtkRewriteRemoved
|
|
319
|
+
? '\n\nRTK rewrite was removed for this background process so output streams directly to the log file.'
|
|
320
|
+
: '';
|
|
321
|
+
const details = {
|
|
322
|
+
background: true,
|
|
323
|
+
id: job.meta.id,
|
|
324
|
+
status: job.status.status,
|
|
325
|
+
logPath: job.meta.logPath,
|
|
326
|
+
infoPath: job.meta.infoPath,
|
|
327
|
+
jobDir: job.meta.jobDir,
|
|
328
|
+
command: job.meta.command,
|
|
329
|
+
cwd: job.meta.cwd,
|
|
330
|
+
startedAt: job.meta.startedAt,
|
|
331
|
+
...(job.status.pid ?? job.meta.pid) === undefined
|
|
332
|
+
? {}
|
|
333
|
+
: { pid: job.status.pid ?? job.meta.pid },
|
|
334
|
+
...(normalized.originalCommand === undefined
|
|
335
|
+
? {}
|
|
336
|
+
: { originalCommand: normalized.originalCommand }),
|
|
337
|
+
...(normalized.rtkRewriteRemoved ? { rtkRewriteRemoved: true } : {}),
|
|
338
|
+
...(interactive ? { tty: true } : {}),
|
|
339
|
+
};
|
|
340
|
+
return {
|
|
341
|
+
content: [{ type: 'text', text: `${formatStarted(job)}${notice}` }],
|
|
342
|
+
details,
|
|
343
|
+
};
|
|
344
|
+
},
|
|
345
|
+
renderCall(args, theme) {
|
|
346
|
+
const suffix = args.background || args.tty ? theme.fg('muted', args.tty ? ' (pty)' : ' (background)') : '';
|
|
347
|
+
return new Text(theme.fg('toolTitle', theme.bold(`$ ${args.command}`)) + suffix, 0, 0);
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
backgroundBashActive = true;
|
|
351
|
+
};
|
|
352
|
+
const registerHelperTools = () => {
|
|
353
|
+
if (helperToolsRegistered)
|
|
354
|
+
return;
|
|
355
|
+
helperToolsRegistered = true;
|
|
356
|
+
pi.registerTool({
|
|
357
|
+
name: 'list_background_bash',
|
|
358
|
+
label: 'List background processes',
|
|
359
|
+
description: 'List processes started in this root session and workspace, including processes started by its subagents.',
|
|
360
|
+
promptSnippet: 'List workspace background processes and their status',
|
|
361
|
+
parameters: ListBackgroundBashParams,
|
|
362
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
363
|
+
const target = createStatusTarget(ctx);
|
|
364
|
+
const jobs = await manager.list((params.status ?? 'all'));
|
|
365
|
+
for (const job of jobs) {
|
|
366
|
+
if (isTerminalStatus(job.status.status))
|
|
367
|
+
suppressCompletion(job.meta.id);
|
|
368
|
+
}
|
|
369
|
+
await updateStatus(target);
|
|
370
|
+
return {
|
|
371
|
+
content: [{ type: 'text', text: formatJobList(jobs) }],
|
|
372
|
+
details: { jobs: jobs.map((job) => ({ meta: job.meta, status: job.status })) },
|
|
373
|
+
};
|
|
374
|
+
},
|
|
375
|
+
});
|
|
376
|
+
pi.registerTool({
|
|
377
|
+
name: 'read_background_bash',
|
|
378
|
+
label: 'Read background process',
|
|
379
|
+
description: 'Read the trailing output of a background process by id.',
|
|
380
|
+
promptSnippet: 'Read output from a background process by id',
|
|
381
|
+
parameters: ReadBackgroundBashParams,
|
|
382
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
383
|
+
const output = await manager.tail(params.id, params.lines ?? 80);
|
|
384
|
+
const job = await manager.get(params.id);
|
|
298
385
|
if (isTerminalStatus(job.status.status))
|
|
299
386
|
suppressCompletion(job.meta.id);
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
});
|
|
326
|
-
pi.registerTool({
|
|
327
|
-
name: 'wait_background_bash',
|
|
328
|
-
label: 'Wait Background Bash',
|
|
329
|
-
description: 'Wait for a detached Bash process to finish, or return current status after a timeout. Use read_background_bash for output.',
|
|
330
|
-
promptSnippet: 'Wait for a Background Bash process and return its status',
|
|
331
|
-
parameters: WaitBackgroundBashParams,
|
|
332
|
-
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
333
|
-
assertSupportedModel(ctx);
|
|
334
|
-
const target = createStatusTarget(ctx);
|
|
335
|
-
const wasWatched = suppressCompletion(params.id);
|
|
336
|
-
let result;
|
|
337
|
-
try {
|
|
338
|
-
result = await manager.wait(params.id, params.timeout, signal);
|
|
339
|
-
}
|
|
340
|
-
catch (error) {
|
|
341
|
-
if (wasWatched)
|
|
387
|
+
return {
|
|
388
|
+
content: [{ type: 'text', text: output }],
|
|
389
|
+
details: { id: params.id, status: job.status.status, lines: params.lines ?? 80 },
|
|
390
|
+
};
|
|
391
|
+
},
|
|
392
|
+
});
|
|
393
|
+
pi.registerTool({
|
|
394
|
+
name: 'wait_background_bash',
|
|
395
|
+
label: 'Wait for background process',
|
|
396
|
+
description: 'Wait for a background process to finish, or return current status after a timeout. Use read_background_bash for output.',
|
|
397
|
+
promptSnippet: 'Wait for a background process and return its status',
|
|
398
|
+
parameters: WaitBackgroundBashParams,
|
|
399
|
+
async execute(_toolCallId, params, signal, _onUpdate, ctx) {
|
|
400
|
+
const target = createStatusTarget(ctx);
|
|
401
|
+
const wasWatched = suppressCompletion(params.id);
|
|
402
|
+
let result;
|
|
403
|
+
try {
|
|
404
|
+
result = await manager.wait(params.id, params.timeout, signal);
|
|
405
|
+
}
|
|
406
|
+
catch (error) {
|
|
407
|
+
if (wasWatched)
|
|
408
|
+
watchCompletion(params.id);
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
411
|
+
if (wasWatched && result.timedOut)
|
|
342
412
|
watchCompletion(params.id);
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
413
|
+
await updateStatus(target);
|
|
414
|
+
return {
|
|
415
|
+
content: [{ type: 'text', text: formatWaitResult(result.job, result.timedOut) }],
|
|
416
|
+
details: { job: result.job, timedOut: result.timedOut },
|
|
417
|
+
};
|
|
418
|
+
},
|
|
419
|
+
});
|
|
420
|
+
pi.registerTool({
|
|
421
|
+
name: 'stop_background_bash',
|
|
422
|
+
label: 'Stop background process',
|
|
423
|
+
description: 'Stop a running background process by id and mark it as killed.',
|
|
424
|
+
promptSnippet: 'Stop a running background process by id',
|
|
425
|
+
parameters: StopBackgroundBashParams,
|
|
426
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
427
|
+
const target = createStatusTarget(ctx);
|
|
428
|
+
const wasWatched = suppressCompletion(params.id);
|
|
429
|
+
let job;
|
|
430
|
+
try {
|
|
431
|
+
job = await manager.stop(params.id, params.signal ?? 'SIGTERM');
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
if (wasWatched)
|
|
435
|
+
watchCompletion(params.id);
|
|
436
|
+
throw error;
|
|
437
|
+
}
|
|
438
|
+
await updateStatus(target);
|
|
439
|
+
return {
|
|
440
|
+
content: [{ type: 'text', text: `Background process stop result.\n\n${formatJobDetails(job)}` }],
|
|
441
|
+
details: { job },
|
|
442
|
+
};
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
pi.registerTool({
|
|
446
|
+
name: 'write_background_bash',
|
|
447
|
+
label: 'Write to background process',
|
|
448
|
+
description: 'Write exact text or control bytes to a running background process with a PTY.',
|
|
449
|
+
promptSnippet: 'Send stdin or control bytes to a background process with a PTY',
|
|
450
|
+
parameters: WriteBackgroundBashParams,
|
|
451
|
+
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
452
|
+
const result = await manager.writeInteractive(params.id, params.chars);
|
|
453
|
+
const job = await manager.get(params.id);
|
|
454
|
+
if (!result.running)
|
|
455
|
+
suppressCompletion(job.meta.id);
|
|
456
|
+
await updateStatus(createStatusTarget(ctx));
|
|
457
|
+
return {
|
|
458
|
+
content: [{ type: 'text', text: result.output || '(no output)' }],
|
|
459
|
+
details: { id: params.id, status: job.status.status, tty: true },
|
|
460
|
+
};
|
|
461
|
+
},
|
|
462
|
+
});
|
|
463
|
+
};
|
|
464
|
+
const registerControls = () => {
|
|
465
|
+
if (controlsRegistered)
|
|
466
|
+
return;
|
|
467
|
+
controlsRegistered = true;
|
|
468
|
+
pi.registerCommand('processes', {
|
|
469
|
+
description: 'View processes and logs',
|
|
470
|
+
handler: async (_args, ctx) => openProcessView(ctx),
|
|
471
|
+
});
|
|
472
|
+
pi.registerShortcut(Key.ctrlShift('j'), {
|
|
473
|
+
description: 'View processes and logs',
|
|
474
|
+
handler: openProcessView,
|
|
475
|
+
});
|
|
476
|
+
};
|
|
477
|
+
const activateRegisteredTools = () => {
|
|
478
|
+
if (!helperToolsRegistered || !backgroundBashActive)
|
|
479
|
+
return;
|
|
480
|
+
pi.setActiveTools([...new Set([...pi.getActiveTools(), 'bash', ...BACKGROUND_TOOL_NAMES])]);
|
|
481
|
+
};
|
|
482
|
+
const ensureRuntimeAvailable = async () => {
|
|
483
|
+
if (runtimeAvailable !== undefined)
|
|
484
|
+
return runtimeAvailable;
|
|
485
|
+
runtimeCheck ??= inspectBackgroundBashRuntime(pi.runtime)
|
|
486
|
+
.then((status) => {
|
|
487
|
+
runtimeAvailable = status.available;
|
|
488
|
+
return status.available;
|
|
489
|
+
})
|
|
490
|
+
.finally(() => {
|
|
491
|
+
runtimeCheck = undefined;
|
|
492
|
+
});
|
|
493
|
+
return runtimeCheck;
|
|
494
|
+
};
|
|
495
|
+
const enableExtension = async (ctx) => {
|
|
496
|
+
if (!await ensureRuntimeAvailable()) {
|
|
497
|
+
disableExtension();
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
registerBackgroundBash();
|
|
501
|
+
registerHelperTools();
|
|
502
|
+
activateRegisteredTools();
|
|
503
|
+
resumeCompletionPolling();
|
|
504
|
+
if (ctx.mode === 'tui')
|
|
505
|
+
startStatusPolling(ctx);
|
|
506
|
+
};
|
|
507
|
+
const disableExtension = () => {
|
|
508
|
+
stopStatusPolling();
|
|
509
|
+
pauseCompletionPolling();
|
|
510
|
+
if (!helperToolsRegistered || !backgroundBashActive)
|
|
511
|
+
return;
|
|
512
|
+
pi.registerTool({ ...foregroundBash });
|
|
513
|
+
backgroundBashActive = false;
|
|
514
|
+
const backgroundNames = new Set(BACKGROUND_TOOL_NAMES);
|
|
515
|
+
pi.setActiveTools([
|
|
516
|
+
...new Set([
|
|
517
|
+
...pi.getActiveTools().filter((name) => !backgroundNames.has(name)),
|
|
518
|
+
'bash',
|
|
519
|
+
]),
|
|
520
|
+
]);
|
|
521
|
+
};
|
|
522
|
+
pi.on('session_start', async (_event, ctx) => {
|
|
523
|
+
if (ctx.mode === 'tui')
|
|
524
|
+
registerControls();
|
|
525
|
+
await enableExtension(ctx);
|
|
392
526
|
});
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
if (!helperToolsRegistered || !backgroundBashActive)
|
|
396
|
-
return;
|
|
397
|
-
pi.setActiveTools([...new Set([...pi.getActiveTools(), 'bash', ...BACKGROUND_TOOL_NAMES])]);
|
|
398
|
-
};
|
|
399
|
-
const ensureRuntimeAvailable = async () => {
|
|
400
|
-
if (runtimeAvailable !== undefined)
|
|
401
|
-
return runtimeAvailable;
|
|
402
|
-
runtimeCheck ??= inspectBackgroundBashRuntime(pi.runtime)
|
|
403
|
-
.then((status) => {
|
|
404
|
-
runtimeAvailable = status.available;
|
|
405
|
-
return status.available;
|
|
406
|
-
})
|
|
407
|
-
.finally(() => {
|
|
408
|
-
runtimeCheck = undefined;
|
|
527
|
+
pi.on('model_select', async (_event, ctx) => {
|
|
528
|
+
await enableExtension(ctx);
|
|
409
529
|
});
|
|
410
|
-
|
|
411
|
-
};
|
|
412
|
-
const enableExtension = async (ctx) => {
|
|
413
|
-
if (!supportsBackgroundBashModel(ctx.model)) {
|
|
530
|
+
pi.on('session_shutdown', (event) => {
|
|
414
531
|
stopStatusPolling();
|
|
415
|
-
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
if (!await ensureRuntimeAvailable()) {
|
|
419
|
-
disableExtension();
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
registerBackgroundBash();
|
|
423
|
-
registerHelperTools();
|
|
424
|
-
activateRegisteredTools();
|
|
425
|
-
resumeCompletionPolling();
|
|
426
|
-
if (ctx.mode === 'tui')
|
|
427
|
-
startStatusPolling(ctx);
|
|
428
|
-
};
|
|
429
|
-
const disableExtension = () => {
|
|
430
|
-
stopStatusPolling();
|
|
431
|
-
pauseCompletionPolling();
|
|
432
|
-
if (!helperToolsRegistered || !backgroundBashActive)
|
|
433
|
-
return;
|
|
434
|
-
pi.registerTool({ ...foregroundBash });
|
|
435
|
-
backgroundBashActive = false;
|
|
436
|
-
const backgroundNames = new Set(BACKGROUND_TOOL_NAMES);
|
|
437
|
-
pi.setActiveTools([
|
|
438
|
-
...new Set([
|
|
439
|
-
...pi.getActiveTools().filter((name) => !backgroundNames.has(name)),
|
|
440
|
-
'bash',
|
|
441
|
-
]),
|
|
442
|
-
]);
|
|
532
|
+
clearCompletionWatches();
|
|
533
|
+
return ownsCoordinator && event.reason !== 'reload' ? manager.shutdownInteractive() : undefined;
|
|
534
|
+
});
|
|
443
535
|
};
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
});
|
|
449
|
-
pi.on('model_select', async (event, ctx) => {
|
|
450
|
-
if (supportsBackgroundBashModel(event.model))
|
|
451
|
-
await enableExtension(ctx);
|
|
452
|
-
else
|
|
453
|
-
disableExtension();
|
|
454
|
-
});
|
|
455
|
-
pi.on('session_shutdown', () => {
|
|
456
|
-
stopStatusPolling();
|
|
457
|
-
clearCompletionWatches();
|
|
458
|
-
});
|
|
459
|
-
};
|
|
536
|
+
associateExtensionConfig(extension, BACKGROUND_BASH_CONFIG);
|
|
537
|
+
return extension;
|
|
538
|
+
}
|
|
539
|
+
const backgroundBashExtension = createBackgroundBashExtension();
|
|
460
540
|
export { inspectBackgroundBashRuntime } from './runtime-support.js';
|
|
461
541
|
function formatDate(ms) {
|
|
462
542
|
return ms ? new Date(ms).toISOString() : '-';
|
|
@@ -491,10 +571,10 @@ function formatJobDetails(job) {
|
|
|
491
571
|
].join('\n');
|
|
492
572
|
}
|
|
493
573
|
function formatStarted(job) {
|
|
494
|
-
return `Started
|
|
574
|
+
return `Started background process.\n\n${formatJobDetails(job)}\n\nCompletion will be delivered automatically. Use list_background_bash to see processes, read_background_bash with id "${job.meta.id}" to inspect output, wait_background_bash when the current task must block, or stop_background_bash to stop it.`;
|
|
495
575
|
}
|
|
496
576
|
function formatCompletionNotice(job) {
|
|
497
|
-
return `Background
|
|
577
|
+
return `Background process reached terminal status: ${job.status.status}.\n\n${formatJobDetails(job)}\n\nUse read_background_bash with id "${job.meta.id}" if its output is needed.`;
|
|
498
578
|
}
|
|
499
579
|
function completionDetails(job) {
|
|
500
580
|
return {
|
|
@@ -513,7 +593,7 @@ function completionDetails(job) {
|
|
|
513
593
|
}
|
|
514
594
|
function formatJobList(jobs) {
|
|
515
595
|
if (jobs.length === 0)
|
|
516
|
-
return 'No
|
|
596
|
+
return 'No background processes found for this workspace.';
|
|
517
597
|
return jobs.map((job) => {
|
|
518
598
|
const pid = String(job.status.pid ?? job.meta.pid ?? '-');
|
|
519
599
|
const exit = job.status.exitCode ?? job.status.signal ?? '-';
|
|
@@ -526,10 +606,16 @@ function formatJobList(jobs) {
|
|
|
526
606
|
}
|
|
527
607
|
function formatWaitResult(job, timedOut) {
|
|
528
608
|
const heading = timedOut
|
|
529
|
-
? 'Background
|
|
530
|
-
: 'Background
|
|
609
|
+
? 'Background process is still running.'
|
|
610
|
+
: 'Background process finished.';
|
|
531
611
|
return `${heading}\n\n${formatJobDetails(job)}\n\nUse read_background_bash with id "${job.meta.id}" to inspect output.`;
|
|
532
612
|
}
|
|
613
|
+
function formatForegroundOutput(job, output) {
|
|
614
|
+
const exit = job.status.exitCode ?? job.status.signal ?? '-';
|
|
615
|
+
return `Exit: ${exit}\n\n${output || '(no output)'}`;
|
|
616
|
+
}
|
|
533
617
|
export { BackgroundBashManager } from './process-manager.js';
|
|
618
|
+
export { BackgroundBashCoordinator } from './coordinator.js';
|
|
534
619
|
export default backgroundBashExtension;
|
|
620
|
+
associateExtensionConfig(backgroundBashExtension, BACKGROUND_BASH_CONFIG);
|
|
535
621
|
//# sourceMappingURL=index.js.map
|