@ferris1225/pi-subagents 0.20.0 → 0.22.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 +9 -0
- package/package.json +53 -53
- package/src/index.ts +10 -9
- package/src/monitor.ts +10 -0
- package/src/setup.ts +119 -48
package/README.md
CHANGED
|
@@ -347,6 +347,15 @@ agent's default — its frontmatter `thinking`, else the global default). The gl
|
|
|
347
347
|
`thinkingLevel` is set first and applies as the final fallback. `notifyOnReviewPass` and
|
|
348
348
|
`maxResultLines` are edited directly in `pi-subagents.json`.
|
|
349
349
|
|
|
350
|
+
When the config already exists, re-running `/subagents-setup` opens a menu whose
|
|
351
|
+
**Configure an agent (model + thinking)** entry lets you pick one agent and set just its
|
|
352
|
+
model and thinking strength — so changing a single agent no longer walks every enabled
|
|
353
|
+
agent. After one agent's model + strength picks, the wizard returns to the agent picker
|
|
354
|
+
so several agents can be configured in one pass; Esc at any step ends the pass and keeps
|
|
355
|
+
every agent already configured. **Change default thinking strength** sets only the global
|
|
356
|
+
fallback. The rest of the menu toggles injection, scope, concurrency, fix rounds, and
|
|
357
|
+
idle timeout; **Full re-setup** re-runs the whole first-time wizard.
|
|
358
|
+
|
|
350
359
|
```json
|
|
351
360
|
{
|
|
352
361
|
"enabledAgents": ["explore", "worker", "reviewer"],
|
package/package.json
CHANGED
|
@@ -1,53 +1,53 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@ferris1225/pi-subagents",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Focused sub-agent delegation for pi: explore / worker / reviewer agents in isolated context, with proactive dispatch injection and per-agent model selection.",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"license": "MIT",
|
|
7
|
-
"publishConfig": {
|
|
8
|
-
"access": "public"
|
|
9
|
-
},
|
|
10
|
-
"keywords": [
|
|
11
|
-
"pi-package",
|
|
12
|
-
"pi-extension",
|
|
13
|
-
"subagent",
|
|
14
|
-
"sub-agent",
|
|
15
|
-
"delegation"
|
|
16
|
-
],
|
|
17
|
-
"files": [
|
|
18
|
-
"src",
|
|
19
|
-
"agents",
|
|
20
|
-
"README.md",
|
|
21
|
-
"LICENSE"
|
|
22
|
-
],
|
|
23
|
-
"pi": {
|
|
24
|
-
"extensions": [
|
|
25
|
-
"./src/index.ts"
|
|
26
|
-
]
|
|
27
|
-
},
|
|
28
|
-
"scripts": {
|
|
29
|
-
"check": "tsc --noEmit",
|
|
30
|
-
"test": "vitest run tests",
|
|
31
|
-
"prepack": "npm run check && npm test"
|
|
32
|
-
},
|
|
33
|
-
"peerDependencies": {
|
|
34
|
-
"@earendil-works/pi-agent-core": ">=0.80.6",
|
|
35
|
-
"@earendil-works/pi-ai": ">=0.80.6",
|
|
36
|
-
"@earendil-works/pi-coding-agent": ">=0.80.6",
|
|
37
|
-
"@earendil-works/pi-tui": ">=0.80.6",
|
|
38
|
-
"typebox": "*"
|
|
39
|
-
},
|
|
40
|
-
"devDependencies": {
|
|
41
|
-
"@earendil-works/pi-agent-core": "^0.83.0",
|
|
42
|
-
"@earendil-works/pi-ai": "^0.83.0",
|
|
43
|
-
"@earendil-works/pi-coding-agent": "^0.83.0",
|
|
44
|
-
"@earendil-works/pi-tui": "^0.83.0",
|
|
45
|
-
"@types/node": "^22.10.0",
|
|
46
|
-
"typebox": "^1.3.9",
|
|
47
|
-
"typescript": "^5.9.0",
|
|
48
|
-
"vitest": "^4.1.0"
|
|
49
|
-
},
|
|
50
|
-
"engines": {
|
|
51
|
-
"node": ">=22.19.0"
|
|
52
|
-
}
|
|
53
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@ferris1225/pi-subagents",
|
|
3
|
+
"version": "0.22.0",
|
|
4
|
+
"description": "Focused sub-agent delegation for pi: explore / worker / reviewer agents in isolated context, with proactive dispatch injection and per-agent model selection.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [
|
|
11
|
+
"pi-package",
|
|
12
|
+
"pi-extension",
|
|
13
|
+
"subagent",
|
|
14
|
+
"sub-agent",
|
|
15
|
+
"delegation"
|
|
16
|
+
],
|
|
17
|
+
"files": [
|
|
18
|
+
"src",
|
|
19
|
+
"agents",
|
|
20
|
+
"README.md",
|
|
21
|
+
"LICENSE"
|
|
22
|
+
],
|
|
23
|
+
"pi": {
|
|
24
|
+
"extensions": [
|
|
25
|
+
"./src/index.ts"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"check": "tsc --noEmit",
|
|
30
|
+
"test": "vitest run tests",
|
|
31
|
+
"prepack": "npm run check && npm test"
|
|
32
|
+
},
|
|
33
|
+
"peerDependencies": {
|
|
34
|
+
"@earendil-works/pi-agent-core": ">=0.80.6",
|
|
35
|
+
"@earendil-works/pi-ai": ">=0.80.6",
|
|
36
|
+
"@earendil-works/pi-coding-agent": ">=0.80.6",
|
|
37
|
+
"@earendil-works/pi-tui": ">=0.80.6",
|
|
38
|
+
"typebox": "*"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@earendil-works/pi-agent-core": "^0.83.0",
|
|
42
|
+
"@earendil-works/pi-ai": "^0.83.0",
|
|
43
|
+
"@earendil-works/pi-coding-agent": "^0.83.0",
|
|
44
|
+
"@earendil-works/pi-tui": "^0.83.0",
|
|
45
|
+
"@types/node": "^22.10.0",
|
|
46
|
+
"typebox": "^1.3.9",
|
|
47
|
+
"typescript": "^5.9.0",
|
|
48
|
+
"vitest": "^4.1.0"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=22.19.0"
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -45,7 +45,7 @@ import {
|
|
|
45
45
|
import { buildFixTaskBrief, buildReReviewBrief, shouldTriggerFixLoop } from "./fixloop.ts";
|
|
46
46
|
import {
|
|
47
47
|
activityStateLabel,
|
|
48
|
-
|
|
48
|
+
compactLine,
|
|
49
49
|
deriveActivityState,
|
|
50
50
|
formatElapsed,
|
|
51
51
|
formatTaskSummary,
|
|
@@ -795,11 +795,12 @@ export default function (pi: ExtensionAPI): void {
|
|
|
795
795
|
const title = formatTaskSummary(r.task, Math.max(16, Math.floor(width * 0.65)), true);
|
|
796
796
|
const left = `${head}${icon} ${theme.fg("dim", `#${r.id}`)} ${theme.bold(name)}`;
|
|
797
797
|
|
|
798
|
-
// Right side:
|
|
798
|
+
// Right side: full model ref (provider/model), token usage (in/out +
|
|
799
799
|
// cache read/write), tool count, elapsed, and the soft activity-state
|
|
800
|
-
// annotation (idle / long-running).
|
|
801
|
-
// the
|
|
802
|
-
|
|
800
|
+
// annotation (idle / long-running). Trailing the header with a single
|
|
801
|
+
// " · " chain keeps the row compact (no center gap); compactLine
|
|
802
|
+
// clips on overflow, never the right side on its own.
|
|
803
|
+
const model = r.model ?? "?";
|
|
803
804
|
const usage = formatUsageCompact(r.usage);
|
|
804
805
|
const tools = r.toolCount ? `${r.toolCount} tool${r.toolCount === 1 ? "" : "s"}` : "";
|
|
805
806
|
const elapsed = formatElapsed(r, now);
|
|
@@ -808,10 +809,10 @@ export default function (pi: ExtensionAPI): void {
|
|
|
808
809
|
// the other states (ready / done / stopped) so they are unambiguous.
|
|
809
810
|
if (r.status !== "running") metaParts.push(statusLabel(r.status));
|
|
810
811
|
const state = deriveActivityState(r, now);
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const right = theme.fg("dim",
|
|
814
|
-
lines.push(
|
|
812
|
+
if (state) metaParts.push(activityStateLabel(state));
|
|
813
|
+
if (r.annotation) metaParts.push(r.annotation);
|
|
814
|
+
const right = metaParts.length ? theme.fg("dim", ` · ${metaParts.join(" · ")}`) : "";
|
|
815
|
+
lines.push(compactLine(left, right, width));
|
|
815
816
|
|
|
816
817
|
// Task summary sits one indent below the header, on its own line.
|
|
817
818
|
lines.push(rightAlign(`${head} ${theme.fg("accent", title)}`, "", width));
|
package/src/monitor.ts
CHANGED
|
@@ -297,6 +297,16 @@ export function rightAlign(left: string, right: string, width: number): string {
|
|
|
297
297
|
return truncateToWidth(`${leftClipped}${" ".repeat(gap)}${right}`, width);
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
+
/** Concatenate `left` and `right` (no center padding) and clip the combined line
|
|
301
|
+
* to `width`. Unlike {@link rightAlign}, the right side trails the left side
|
|
302
|
+
* instead of being pinned to the far edge, so a short header leaves no gap in
|
|
303
|
+
* the middle. `right` should carry its own leading separator (e.g. ` \u00b7 `);
|
|
304
|
+
* pass an empty string when there is nothing to append. Styled strings are
|
|
305
|
+
* measured by display width. */
|
|
306
|
+
export function compactLine(left: string, right: string, width: number): string {
|
|
307
|
+
return truncateToWidth(`${left}${right}`, width);
|
|
308
|
+
}
|
|
309
|
+
|
|
300
310
|
/** Max length of the argument target inside a formatted activity line. */
|
|
301
311
|
export const ACTIVITY_TARGET_MAX = 60;
|
|
302
312
|
|
package/src/setup.ts
CHANGED
|
@@ -76,6 +76,33 @@ async function pickEnabledAgents(
|
|
|
76
76
|
);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/** Single-agent model pick; the inherit option drops any existing override.
|
|
80
|
+
* `escNote` describes what Esc does at this pick (whole-wizard cancel in the
|
|
81
|
+
* full setup vs. ending the per-agent loop in the menu). */
|
|
82
|
+
async function pickAgentModel(
|
|
83
|
+
ctx: ExtensionCommandContext,
|
|
84
|
+
name: string,
|
|
85
|
+
currentRef: string | undefined,
|
|
86
|
+
refs: readonly string[],
|
|
87
|
+
escNote = "cancels setup",
|
|
88
|
+
): Promise<string | typeof INHERIT | undefined> {
|
|
89
|
+
const items = [
|
|
90
|
+
{
|
|
91
|
+
value: INHERIT,
|
|
92
|
+
label: currentRef
|
|
93
|
+
? `(use main session's model — drop override "${currentRef}")`
|
|
94
|
+
: "(use main session's current model — no override)",
|
|
95
|
+
},
|
|
96
|
+
...refs.map((ref) => ({ value: ref, label: ref === currentRef ? `${ref} (current)` : ref })),
|
|
97
|
+
];
|
|
98
|
+
return promptSelectOne(
|
|
99
|
+
ctx,
|
|
100
|
+
`Model for "${name}"`,
|
|
101
|
+
`Type to filter • ↑/↓ • PgUp/PgDn • Enter selects • Esc ${escNote}`,
|
|
102
|
+
items,
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
79
106
|
async function pickAgentModelsAndStrength(
|
|
80
107
|
ctx: ExtensionCommandContext,
|
|
81
108
|
enabledAgents: readonly string[],
|
|
@@ -93,24 +120,9 @@ async function pickAgentModelsAndStrength(
|
|
|
93
120
|
const models: Record<string, string> = {};
|
|
94
121
|
const strengths: Record<string, ThinkingLevel> = {};
|
|
95
122
|
for (const name of enabledAgents) {
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
value: INHERIT,
|
|
100
|
-
label: currentRef
|
|
101
|
-
? `(use main session's model — drop override "${currentRef}")`
|
|
102
|
-
: "(use main session's current model — no override)",
|
|
103
|
-
},
|
|
104
|
-
...refs.map((ref) => ({ value: ref, label: ref === currentRef ? `${ref} (current)` : ref })),
|
|
105
|
-
];
|
|
106
|
-
const choice = await promptSelectOne(
|
|
107
|
-
ctx,
|
|
108
|
-
`Model for "${name}"`,
|
|
109
|
-
"Type to filter • ↑/↓ • PgUp/PgDn • Enter selects • Esc cancels setup",
|
|
110
|
-
items,
|
|
111
|
-
);
|
|
112
|
-
if (choice === undefined) return undefined; // Esc aborts the whole wizard
|
|
113
|
-
if (choice !== INHERIT) models[name] = choice;
|
|
123
|
+
const modelChoice = await pickAgentModel(ctx, name, currentModels[name], refs);
|
|
124
|
+
if (modelChoice === undefined) return undefined; // Esc aborts the whole wizard
|
|
125
|
+
if (modelChoice !== INHERIT) models[name] = modelChoice;
|
|
114
126
|
|
|
115
127
|
// Convenience: the model pick is immediately followed by the strength pick,
|
|
116
128
|
// so per-agent model + strength are configured in one pass.
|
|
@@ -131,13 +143,16 @@ const THINKING_LEVEL_HINTS: Record<ThinkingLevel, string> = {
|
|
|
131
143
|
max: "strongest reasoning",
|
|
132
144
|
};
|
|
133
145
|
|
|
134
|
-
/** Single strength pick for one agent; the inherit option keeps the effective default.
|
|
146
|
+
/** Single strength pick for one agent; the inherit option keeps the effective default.
|
|
147
|
+
* `escNote` describes what Esc does at this pick (whole-wizard cancel in the
|
|
148
|
+
* full setup vs. ending the per-agent loop in the menu). */
|
|
135
149
|
async function pickAgentStrength(
|
|
136
150
|
ctx: ExtensionCommandContext,
|
|
137
151
|
agentName: string,
|
|
138
152
|
current: ThinkingLevel | undefined,
|
|
139
153
|
defaultLevel: ThinkingLevel,
|
|
140
154
|
defaults: ReadonlyMap<string, ThinkingLevel>,
|
|
155
|
+
escNote = "cancels setup",
|
|
141
156
|
): Promise<ThinkingLevel | typeof INHERIT | undefined> {
|
|
142
157
|
const options = THINKING_LEVEL_VALUES.map((level) => ({
|
|
143
158
|
value: level,
|
|
@@ -150,28 +165,73 @@ async function pickAgentStrength(
|
|
|
150
165
|
const choice = await promptSelectOne(
|
|
151
166
|
ctx,
|
|
152
167
|
`Thinking strength for "${agentName}"?`,
|
|
153
|
-
|
|
168
|
+
`Type to filter • ↑/↓ • Enter selects • Esc ${escNote}`,
|
|
154
169
|
[{ value: INHERIT, label: inheritLabel }, ...options],
|
|
155
170
|
);
|
|
156
171
|
if (choice === undefined) return undefined;
|
|
157
172
|
return choice === INHERIT ? INHERIT : (choice as ThinkingLevel);
|
|
158
173
|
}
|
|
159
174
|
|
|
160
|
-
/**
|
|
161
|
-
async function
|
|
175
|
+
/** Pick one enabled agent to reconfigure. Resolves undefined on Esc. */
|
|
176
|
+
async function pickAgentToConfigure(
|
|
162
177
|
ctx: ExtensionCommandContext,
|
|
163
178
|
enabledAgents: readonly string[],
|
|
179
|
+
): Promise<string | undefined> {
|
|
180
|
+
if (enabledAgents.length === 0) {
|
|
181
|
+
ctx.ui.notify("No agents are enabled. Enable agents first.", "warning");
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const items = enabledAgents.map((name) => ({ value: name, label: moduleLabel(name) }));
|
|
185
|
+
return promptSelectOne(
|
|
186
|
+
ctx,
|
|
187
|
+
"Configure which agent?",
|
|
188
|
+
"Type to filter • ↑/↓ • PgUp/PgDn • Enter selects • Esc stops",
|
|
189
|
+
items,
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Configure a single agent picked from the enabled set: choose its model, then
|
|
195
|
+
* its thinking strength. Only that one agent is touched, so re-running the menu
|
|
196
|
+
* to tweak one agent no longer walks every enabled agent. Both picks offer an
|
|
197
|
+
* "inherit" option that drops any existing per-agent override for that field.
|
|
198
|
+
* Resolves undefined when the user presses Esc at any step; the caller keeps
|
|
199
|
+
* changes from agents already configured earlier in the same pass.
|
|
200
|
+
*/
|
|
201
|
+
async function configureOneAgent(
|
|
202
|
+
ctx: ExtensionCommandContext,
|
|
203
|
+
enabledAgents: readonly string[],
|
|
204
|
+
currentModels: Record<string, string>,
|
|
164
205
|
currentStrengths: Record<string, ThinkingLevel>,
|
|
165
206
|
defaultLevel: ThinkingLevel,
|
|
166
207
|
defaults: ReadonlyMap<string, ThinkingLevel>,
|
|
167
|
-
): Promise<
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
208
|
+
): Promise<
|
|
209
|
+
| {
|
|
210
|
+
name: string;
|
|
211
|
+
model: string | typeof INHERIT;
|
|
212
|
+
modelsAvailable: boolean;
|
|
213
|
+
strength: ThinkingLevel | typeof INHERIT;
|
|
214
|
+
}
|
|
215
|
+
| undefined
|
|
216
|
+
> {
|
|
217
|
+
const name = await pickAgentToConfigure(ctx, enabledAgents);
|
|
218
|
+
if (name === undefined) return undefined; // Esc cancels
|
|
219
|
+
|
|
220
|
+
const refs = availableModelRefs(ctx);
|
|
221
|
+
const modelsAvailable = refs.length > 0;
|
|
222
|
+
let model: string | typeof INHERIT = INHERIT;
|
|
223
|
+
if (!modelsAvailable) {
|
|
224
|
+
ctx.ui.notify("No Pi models are currently available; model override left unchanged.", "warning");
|
|
225
|
+
} else {
|
|
226
|
+
const modelChoice = await pickAgentModel(ctx, name, currentModels[name], refs, "stops — earlier agent picks are kept");
|
|
227
|
+
if (modelChoice === undefined) return undefined; // Esc ends the loop
|
|
228
|
+
model = modelChoice;
|
|
173
229
|
}
|
|
174
|
-
|
|
230
|
+
|
|
231
|
+
const strength = await pickAgentStrength(ctx, name, currentStrengths[name], defaultLevel, defaults, "stops — earlier agent picks are kept");
|
|
232
|
+
if (strength === undefined) return undefined; // Esc ends the loop
|
|
233
|
+
|
|
234
|
+
return { name, model, modelsAvailable, strength };
|
|
175
235
|
}
|
|
176
236
|
|
|
177
237
|
async function pickThinkingLevel(
|
|
@@ -330,8 +390,8 @@ async function runFullSetup(ctx: ExtensionCommandContext, configPath: string, ba
|
|
|
330
390
|
async function runMenu(ctx: ExtensionCommandContext, configPath: string, config: SubagentsConfig): Promise<void> {
|
|
331
391
|
const choice = await ctx.ui.select("pi-subagents is already configured. What would you like to change?", [
|
|
332
392
|
"Enable/disable agents",
|
|
333
|
-
"
|
|
334
|
-
"Change thinking strength",
|
|
393
|
+
"Configure an agent (model + thinking)",
|
|
394
|
+
"Change default thinking strength",
|
|
335
395
|
"Toggle proactive injection",
|
|
336
396
|
"Change agent scope",
|
|
337
397
|
"Change max concurrent sub-agents",
|
|
@@ -349,27 +409,38 @@ async function runMenu(ctx: ExtensionCommandContext, configPath: string, config:
|
|
|
349
409
|
const enabled = await pickEnabledAgents(ctx, config.enabledAgents);
|
|
350
410
|
if (enabled === undefined) return notifyCancelled(ctx);
|
|
351
411
|
next.enabledAgents = enabled;
|
|
352
|
-
} else if (choice.startsWith("
|
|
412
|
+
} else if (choice.startsWith("Configure an agent")) {
|
|
413
|
+
// Per-agent loop: pick one agent, then its model and thinking strength, then
|
|
414
|
+
// return to the agent picker so several agents can be configured in one
|
|
415
|
+
// pass. Esc at any step ends the loop; agents already configured in this
|
|
416
|
+
// pass are kept.
|
|
353
417
|
const defaults = builtinThinkingDefaults();
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
418
|
+
let configuredAny = false;
|
|
419
|
+
next.agentThinkingLevels = { ...config.agentThinkingLevels };
|
|
420
|
+
while (true) {
|
|
421
|
+
const picked = await configureOneAgent(
|
|
422
|
+
ctx,
|
|
423
|
+
next.enabledAgents,
|
|
424
|
+
next.agentModels,
|
|
425
|
+
next.agentThinkingLevels,
|
|
426
|
+
next.thinkingLevel,
|
|
427
|
+
defaults,
|
|
428
|
+
);
|
|
429
|
+
if (picked === undefined) break; // Esc ends the loop
|
|
430
|
+
configuredAny = true;
|
|
431
|
+
if (picked.modelsAvailable) {
|
|
432
|
+
if (picked.model === INHERIT) delete next.agentModels[picked.name];
|
|
433
|
+
else next.agentModels[picked.name] = picked.model;
|
|
434
|
+
}
|
|
435
|
+
if (picked.strength === INHERIT) delete next.agentThinkingLevels[picked.name];
|
|
436
|
+
else next.agentThinkingLevels[picked.name] = picked.strength;
|
|
437
|
+
}
|
|
438
|
+
if (!configuredAny) return notifyCancelled(ctx);
|
|
439
|
+
next.agentModels = repairStaleModels(ctx, next.agentModels);
|
|
440
|
+
} else if (choice.startsWith("Change default thinking")) {
|
|
367
441
|
const thinkingLevel = await pickThinkingLevel(ctx, config.thinkingLevel);
|
|
368
442
|
if (thinkingLevel === undefined) return notifyCancelled(ctx);
|
|
369
443
|
next.thinkingLevel = thinkingLevel;
|
|
370
|
-
const strengths = await pickAgentStrengths(ctx, config.enabledAgents, config.agentThinkingLevels, thinkingLevel, builtinThinkingDefaults());
|
|
371
|
-
if (strengths === undefined) return notifyCancelled(ctx);
|
|
372
|
-
next.agentThinkingLevels = strengths;
|
|
373
444
|
} else if (choice.startsWith("Toggle")) {
|
|
374
445
|
const injection = await pickInjection(ctx, config.proactiveInjection);
|
|
375
446
|
if (injection === undefined) return notifyCancelled(ctx);
|