pi-long-task 0.3.6 → 0.3.8
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/package.json +1 -1
- package/src/coordinator.ts +21 -3
- package/src/index.ts +109 -37
- package/src/worker_config.ts +291 -0
- package/src/worker_session.ts +2 -2
package/package.json
CHANGED
package/src/coordinator.ts
CHANGED
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
import { commitAfterSession, gitDirtyPaths, shouldCommitOutcome, type CommitAfterSessionResult } from "./git.ts";
|
|
12
12
|
import { formatCoordinatorResultMessage } from "./render.ts";
|
|
13
13
|
import { extractResultSummary } from "./result_writer.ts";
|
|
14
|
+
import { parseWorkerRuntimeConfig } from "./worker_config.ts";
|
|
14
15
|
import { buildTaskProgressModel, type TaskProgressModel, type TaskProgressStatus } from "./task_progress.ts";
|
|
15
16
|
import {
|
|
16
17
|
buildTodoCreationPrompt,
|
|
@@ -97,6 +98,8 @@ export interface RunCoordinatorOptions extends PiLongTaskInput {
|
|
|
97
98
|
todoPlanner?: TodoPlanner;
|
|
98
99
|
workerSessionFactory?: WorkerSessionFactory;
|
|
99
100
|
todoSessionFactory?: WorkerSessionFactory;
|
|
101
|
+
workerModel?: unknown;
|
|
102
|
+
workerModelName?: string;
|
|
100
103
|
maxAttemptsPerTask?: number;
|
|
101
104
|
taskTimeoutMs?: number;
|
|
102
105
|
maxBashTimeoutMs?: number;
|
|
@@ -111,6 +114,7 @@ export interface TodoPlannerOptions {
|
|
|
111
114
|
cwd: string;
|
|
112
115
|
runDir: string;
|
|
113
116
|
thinkingLevel: string;
|
|
117
|
+
model?: unknown;
|
|
114
118
|
abortSignal?: AbortSignal;
|
|
115
119
|
sessionFactory?: WorkerSessionFactory;
|
|
116
120
|
}
|
|
@@ -167,6 +171,8 @@ interface RuntimeOptions {
|
|
|
167
171
|
maxAttemptsPerTask: number;
|
|
168
172
|
taskTimeoutSeconds: number;
|
|
169
173
|
maxBashTimeoutSeconds: number;
|
|
174
|
+
workerModel?: unknown;
|
|
175
|
+
workerModelName?: string;
|
|
170
176
|
taskThinking: string;
|
|
171
177
|
todoThinking: string;
|
|
172
178
|
workerRunner: WorkerRunner;
|
|
@@ -240,6 +246,8 @@ export async function runCoordinator(options: RunCoordinatorOptions): Promise<Co
|
|
|
240
246
|
globalInstructions: todoGlobalInstructions(todoMarkdown),
|
|
241
247
|
maxBashTimeoutSeconds: runtime.maxBashTimeoutSeconds,
|
|
242
248
|
taskTimeoutSeconds: runtime.taskTimeoutSeconds,
|
|
249
|
+
model: runtime.workerModel,
|
|
250
|
+
modelName: runtime.workerModelName,
|
|
243
251
|
thinkingLevel: runtime.taskThinking,
|
|
244
252
|
abortSignal: runtime.abortSignal,
|
|
245
253
|
sessionFactory: runtime.workerSessionFactory,
|
|
@@ -422,6 +430,7 @@ async function generateOrNormalizeTodoMarkdown(inputText: string, runtime: Runti
|
|
|
422
430
|
cwd: runtime.cwd,
|
|
423
431
|
runDir: runtime.runDir,
|
|
424
432
|
thinkingLevel: runtime.todoThinking,
|
|
433
|
+
model: runtime.workerModel,
|
|
425
434
|
abortSignal: runtime.abortSignal,
|
|
426
435
|
sessionFactory: runtime.todoSessionFactory,
|
|
427
436
|
});
|
|
@@ -435,6 +444,7 @@ export async function runTodoPlanner(options: TodoPlannerOptions): Promise<strin
|
|
|
435
444
|
const result = await sessionFactory({
|
|
436
445
|
cwd: options.cwd,
|
|
437
446
|
tools: [],
|
|
447
|
+
model: options.model,
|
|
438
448
|
thinkingLevel: options.thinkingLevel,
|
|
439
449
|
});
|
|
440
450
|
session = result.session;
|
|
@@ -460,6 +470,12 @@ function buildRuntimeOptions(options: RunCoordinatorOptions): RuntimeOptions {
|
|
|
460
470
|
const cwd = path.resolve(options.cwd ?? process.cwd());
|
|
461
471
|
const runId = sanitizeRunId(options.runId ?? defaultRunId(options.now?.() ?? new Date()));
|
|
462
472
|
const runDir = path.join(cwd, "tmp", "pi-long-task", runId);
|
|
473
|
+
const parsedWorkerConfig = parseWorkerRuntimeConfig(options.inputText);
|
|
474
|
+
const configuredAttempts = options.maxAttemptsPerTask ?? parsedWorkerConfig.maxAttemptsPerTask;
|
|
475
|
+
const configuredTaskTimeoutMs = options.taskTimeoutMs ?? parsedWorkerConfig.taskTimeoutMs;
|
|
476
|
+
const configuredMaxBashTimeoutMs = options.maxBashTimeoutMs ?? parsedWorkerConfig.maxBashTimeoutMs;
|
|
477
|
+
const workerModelName = options.workerModelName ?? parsedWorkerConfig.modelName;
|
|
478
|
+
const workerModel = workerModelName ? undefined : options.workerModel;
|
|
463
479
|
|
|
464
480
|
return {
|
|
465
481
|
cwd,
|
|
@@ -467,10 +483,12 @@ function buildRuntimeOptions(options: RunCoordinatorOptions): RuntimeOptions {
|
|
|
467
483
|
runDir,
|
|
468
484
|
todoPath: path.join(runDir, "TODO.md"),
|
|
469
485
|
taskResultPath: path.join(runDir, "TASK_RESULT.md"),
|
|
470
|
-
maxAttemptsPerTask: positiveInteger(
|
|
471
|
-
taskTimeoutSeconds: positiveMilliseconds(
|
|
486
|
+
maxAttemptsPerTask: positiveInteger(configuredAttempts, DEFAULT_COORDINATOR_OPTIONS.maxAttemptsPerTask),
|
|
487
|
+
taskTimeoutSeconds: positiveMilliseconds(configuredTaskTimeoutMs, DEFAULT_COORDINATOR_OPTIONS.taskTimeoutMs) / 1000,
|
|
472
488
|
maxBashTimeoutSeconds:
|
|
473
|
-
positiveMilliseconds(
|
|
489
|
+
positiveMilliseconds(configuredMaxBashTimeoutMs, DEFAULT_COORDINATOR_OPTIONS.maxBashTimeoutMs) / 1000,
|
|
490
|
+
workerModel,
|
|
491
|
+
workerModelName,
|
|
474
492
|
taskThinking: options.taskThinking ?? DEFAULT_COORDINATOR_OPTIONS.taskThinking,
|
|
475
493
|
todoThinking: options.todoThinking ?? DEFAULT_COORDINATOR_OPTIONS.todoThinking,
|
|
476
494
|
workerRunner: options.workerRunner ?? runWorkerTask,
|
package/src/index.ts
CHANGED
|
@@ -76,6 +76,9 @@ function toolDetails(result: CoordinatorResult) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
const LONG_TASK_WIDGET_KEY = "pi-long-task-sidebar";
|
|
79
|
+
const SIDEBAR_WIDGET_RESERVED_INPUT_ROWS = 8;
|
|
80
|
+
const SIDEBAR_WIDGET_MIN_ROWS = 4;
|
|
81
|
+
const SIDEBAR_WIDGET_MAX_ROWS = 24;
|
|
79
82
|
|
|
80
83
|
type UiContext = ExtensionContext;
|
|
81
84
|
|
|
@@ -86,10 +89,12 @@ export interface LongTaskSidebarController {
|
|
|
86
89
|
|
|
87
90
|
class PiLongTaskSidebarComponent implements Component {
|
|
88
91
|
private readonly theme: Theme;
|
|
92
|
+
private readonly maxRows: (() => number | undefined) | undefined;
|
|
89
93
|
private update: CoordinatorProgressUpdate | undefined;
|
|
90
94
|
|
|
91
|
-
constructor(theme: Theme) {
|
|
95
|
+
constructor(theme: Theme, maxRows?: () => number | undefined) {
|
|
92
96
|
this.theme = theme;
|
|
97
|
+
this.maxRows = maxRows;
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
setUpdate(update: CoordinatorProgressUpdate): void {
|
|
@@ -98,7 +103,8 @@ class PiLongTaskSidebarComponent implements Component {
|
|
|
98
103
|
}
|
|
99
104
|
|
|
100
105
|
render(width: number): string[] {
|
|
101
|
-
|
|
106
|
+
const lines = renderSidebarOverlayLines(this.update, this.theme, width);
|
|
107
|
+
return limitSidebarPanelLines(lines, this.theme, width, this.maxRows?.());
|
|
102
108
|
}
|
|
103
109
|
|
|
104
110
|
invalidate(): void {
|
|
@@ -112,48 +118,69 @@ export function createLongTaskSidebarController(ctx: UiContext | undefined): Lon
|
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
let latestUpdate: CoordinatorProgressUpdate | undefined;
|
|
121
|
+
let widgetComponent: PiLongTaskSidebarComponent | undefined;
|
|
122
|
+
let widgetTui: TUI | undefined;
|
|
115
123
|
let overlayComponent: PiLongTaskSidebarComponent | undefined;
|
|
116
124
|
let overlayTui: TUI | undefined;
|
|
117
125
|
let overlayDone: ((result: undefined) => void) | undefined;
|
|
118
126
|
let overlayHandle: OverlayHandle | undefined;
|
|
119
127
|
let closed = false;
|
|
120
128
|
|
|
121
|
-
ctx
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
overlayDone = done;
|
|
128
|
-
overlayComponent = new PiLongTaskSidebarComponent(theme);
|
|
129
|
+
if (supportsTuiWidget(ctx)) {
|
|
130
|
+
ctx.ui.setWidget(
|
|
131
|
+
LONG_TASK_WIDGET_KEY,
|
|
132
|
+
(tui, theme) => {
|
|
133
|
+
widgetTui = tui;
|
|
134
|
+
widgetComponent = new PiLongTaskSidebarComponent(theme, () => sidebarWidgetLineLimit(tui));
|
|
129
135
|
if (latestUpdate) {
|
|
130
|
-
|
|
136
|
+
widgetComponent.setUpdate(latestUpdate);
|
|
131
137
|
}
|
|
132
|
-
|
|
133
|
-
done(undefined);
|
|
134
|
-
}
|
|
135
|
-
return overlayComponent;
|
|
138
|
+
return widgetComponent;
|
|
136
139
|
},
|
|
137
|
-
{
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
140
|
+
{ placement: "aboveEditor" },
|
|
141
|
+
);
|
|
142
|
+
} else {
|
|
143
|
+
ctx.ui.setWidget(LONG_TASK_WIDGET_KEY, ["Pi Long Task: preparing sidebar..."], { placement: "aboveEditor" });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (supportsTuiOverlay(ctx)) {
|
|
147
|
+
try {
|
|
148
|
+
const overlayPromise = ctx.ui.custom<undefined>(
|
|
149
|
+
(tui, theme, _keybindings, done) => {
|
|
150
|
+
overlayTui = tui;
|
|
151
|
+
overlayDone = done;
|
|
152
|
+
overlayComponent = new PiLongTaskSidebarComponent(theme);
|
|
153
|
+
if (latestUpdate) {
|
|
154
|
+
overlayComponent.setUpdate(latestUpdate);
|
|
155
|
+
}
|
|
156
|
+
if (closed) {
|
|
157
|
+
done(undefined);
|
|
158
|
+
}
|
|
159
|
+
return overlayComponent;
|
|
147
160
|
},
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
161
|
+
{
|
|
162
|
+
overlay: true,
|
|
163
|
+
overlayOptions: {
|
|
164
|
+
anchor: "right-center",
|
|
165
|
+
width: "34%",
|
|
166
|
+
minWidth: 32,
|
|
167
|
+
maxHeight: "85%",
|
|
168
|
+
margin: 1,
|
|
169
|
+
nonCapturing: true,
|
|
170
|
+
visible: (termWidth, termHeight) => termWidth >= 96 && termHeight >= 16,
|
|
171
|
+
},
|
|
172
|
+
onHandle: (handle) => {
|
|
173
|
+
overlayHandle = handle;
|
|
174
|
+
handle.unfocus();
|
|
175
|
+
},
|
|
151
176
|
},
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
177
|
+
);
|
|
178
|
+
void overlayPromise.catch(() => {
|
|
179
|
+
// The widget fallback remains active if overlay registration is unavailable.
|
|
180
|
+
});
|
|
181
|
+
} catch {
|
|
155
182
|
// The widget fallback remains active if overlay registration is unavailable.
|
|
156
|
-
}
|
|
183
|
+
}
|
|
157
184
|
}
|
|
158
185
|
|
|
159
186
|
return {
|
|
@@ -162,9 +189,15 @@ export function createLongTaskSidebarController(ctx: UiContext | undefined): Lon
|
|
|
162
189
|
return;
|
|
163
190
|
}
|
|
164
191
|
latestUpdate = update;
|
|
192
|
+
widgetComponent?.setUpdate(update);
|
|
165
193
|
overlayComponent?.setUpdate(update);
|
|
166
|
-
|
|
167
|
-
|
|
194
|
+
widgetTui?.requestRender();
|
|
195
|
+
if (overlayTui && overlayTui !== widgetTui) {
|
|
196
|
+
overlayTui.requestRender();
|
|
197
|
+
}
|
|
198
|
+
if (!widgetComponent) {
|
|
199
|
+
ctx.ui.setWidget(LONG_TASK_WIDGET_KEY, renderSidebarWidgetLines(update), { placement: "aboveEditor" });
|
|
200
|
+
}
|
|
168
201
|
},
|
|
169
202
|
close(): void {
|
|
170
203
|
if (closed) {
|
|
@@ -172,11 +205,13 @@ export function createLongTaskSidebarController(ctx: UiContext | undefined): Lon
|
|
|
172
205
|
}
|
|
173
206
|
closed = true;
|
|
174
207
|
ctx.ui.setWidget(LONG_TASK_WIDGET_KEY, undefined);
|
|
175
|
-
if (
|
|
176
|
-
|
|
208
|
+
if (overlayHandle) {
|
|
209
|
+
overlayHandle.hide();
|
|
177
210
|
} else {
|
|
178
|
-
|
|
211
|
+
overlayDone?.(undefined);
|
|
179
212
|
}
|
|
213
|
+
widgetComponent = undefined;
|
|
214
|
+
widgetTui = undefined;
|
|
180
215
|
overlayComponent = undefined;
|
|
181
216
|
overlayTui = undefined;
|
|
182
217
|
overlayDone = undefined;
|
|
@@ -185,11 +220,23 @@ export function createLongTaskSidebarController(ctx: UiContext | undefined): Lon
|
|
|
185
220
|
};
|
|
186
221
|
}
|
|
187
222
|
|
|
223
|
+
function supportsTuiWidget(ctx: UiContext): boolean {
|
|
224
|
+
const mode = (ctx as UiContext & { mode?: string }).mode;
|
|
225
|
+
return mode === "tui" || mode === undefined;
|
|
226
|
+
}
|
|
227
|
+
|
|
188
228
|
function supportsTuiOverlay(ctx: UiContext): boolean {
|
|
189
229
|
const mode = (ctx as UiContext & { mode?: string }).mode;
|
|
190
230
|
return mode === "tui" || mode === undefined;
|
|
191
231
|
}
|
|
192
232
|
|
|
233
|
+
function sidebarWidgetLineLimit(tui: TUI): number {
|
|
234
|
+
const terminalRows = tui.terminal.rows;
|
|
235
|
+
const rows = Number.isFinite(terminalRows) ? Math.max(0, Math.floor(terminalRows)) : 24;
|
|
236
|
+
const availableRows = rows - SIDEBAR_WIDGET_RESERVED_INPUT_ROWS;
|
|
237
|
+
return Math.max(SIDEBAR_WIDGET_MIN_ROWS, Math.min(SIDEBAR_WIDGET_MAX_ROWS, availableRows));
|
|
238
|
+
}
|
|
239
|
+
|
|
193
240
|
function renderSidebarWidgetLines(update: CoordinatorProgressUpdate): string[] {
|
|
194
241
|
const progress = update.taskProgress;
|
|
195
242
|
const summary = progress?.summary;
|
|
@@ -229,6 +276,30 @@ function renderSidebarOverlayLines(
|
|
|
229
276
|
return rows.map((row) => sidebarPanelRow(row, contentWidth, theme));
|
|
230
277
|
}
|
|
231
278
|
|
|
279
|
+
function limitSidebarPanelLines(lines: string[], theme: Theme, width: number, maxRows: number | undefined): string[] {
|
|
280
|
+
if (maxRows === undefined || !Number.isFinite(maxRows)) {
|
|
281
|
+
return lines;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const limit = Math.max(0, Math.floor(maxRows));
|
|
285
|
+
if (lines.length <= limit) {
|
|
286
|
+
return lines;
|
|
287
|
+
}
|
|
288
|
+
if (limit === 0) {
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const contentWidth = Math.max(8, Math.max(28, width) - 4);
|
|
293
|
+
if (limit === 1) {
|
|
294
|
+
return [sidebarPanelRow(theme.fg("dim", "…"), contentWidth, theme)];
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const visibleLines = lines.slice(0, limit - 1);
|
|
298
|
+
const omitted = lines.length - visibleLines.length;
|
|
299
|
+
visibleLines.push(sidebarPanelRow(theme.fg("dim", `… ${omitted} more`), contentWidth, theme));
|
|
300
|
+
return visibleLines;
|
|
301
|
+
}
|
|
302
|
+
|
|
232
303
|
function renderSidebarRows(update: CoordinatorProgressUpdate | undefined, theme: Theme, width: number): string[] {
|
|
233
304
|
if (!update) {
|
|
234
305
|
return ["", sidebarHeading("Pi Long Task", theme), "", theme.fg("muted", "Preparing long-task sidebar...")];
|
|
@@ -670,6 +741,7 @@ export default function registerPiLongTaskExtension(pi: ExtensionAPI) {
|
|
|
670
741
|
const result = await runCoordinator({
|
|
671
742
|
...params,
|
|
672
743
|
cwd: ctx?.cwd,
|
|
744
|
+
workerModel: ctx?.model,
|
|
673
745
|
abortSignal: signal,
|
|
674
746
|
onProgress: publishProgress,
|
|
675
747
|
});
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
export interface ParsedWorkerRuntimeConfig {
|
|
2
|
+
modelName?: string;
|
|
3
|
+
maxAttemptsPerTask?: number;
|
|
4
|
+
taskTimeoutMs?: number;
|
|
5
|
+
maxBashTimeoutMs?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const MODEL_TOKEN_RE = /[A-Za-z0-9][A-Za-z0-9._~:+/@-]*/;
|
|
9
|
+
const STOP_WORDS = new Set([
|
|
10
|
+
"and",
|
|
11
|
+
"as",
|
|
12
|
+
"for",
|
|
13
|
+
"from",
|
|
14
|
+
"in",
|
|
15
|
+
"is",
|
|
16
|
+
"of",
|
|
17
|
+
"on",
|
|
18
|
+
"per",
|
|
19
|
+
"please",
|
|
20
|
+
"task",
|
|
21
|
+
"tasks",
|
|
22
|
+
"the",
|
|
23
|
+
"to",
|
|
24
|
+
"use",
|
|
25
|
+
"with",
|
|
26
|
+
"worker",
|
|
27
|
+
"workers",
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
export function parseWorkerRuntimeConfig(text: string): ParsedWorkerRuntimeConfig {
|
|
31
|
+
const state: ParsedWorkerRuntimeConfig & { provider?: string; model?: string } = {};
|
|
32
|
+
|
|
33
|
+
parseLineDirectives(text, state);
|
|
34
|
+
parseNaturalLanguageDirectives(text, state);
|
|
35
|
+
|
|
36
|
+
const modelName = combineProviderAndModel(state.provider, state.model);
|
|
37
|
+
return {
|
|
38
|
+
...(modelName ? { modelName } : {}),
|
|
39
|
+
...(state.maxAttemptsPerTask !== undefined ? { maxAttemptsPerTask: state.maxAttemptsPerTask } : {}),
|
|
40
|
+
...(state.taskTimeoutMs !== undefined ? { taskTimeoutMs: state.taskTimeoutMs } : {}),
|
|
41
|
+
...(state.maxBashTimeoutMs !== undefined ? { maxBashTimeoutMs: state.maxBashTimeoutMs } : {}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseLineDirectives(
|
|
46
|
+
text: string,
|
|
47
|
+
state: ParsedWorkerRuntimeConfig & { provider?: string; model?: string },
|
|
48
|
+
): void {
|
|
49
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
50
|
+
const line = rawLine.replace(/^\s{0,3}>+\s?/, "").trim();
|
|
51
|
+
const match = line.match(
|
|
52
|
+
/^(?:[-*+]\s*)?(?:(pi\s+long\s+task|long\s+task|worker|workers?|task)\s+)?([a-z][a-z\s-]{0,40})\s*(?::|=)\s*(.+)$/i,
|
|
53
|
+
);
|
|
54
|
+
if (!match) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const prefix = normalizeWords(match[1] ?? "");
|
|
59
|
+
const key = normalizeWords(match[2] ?? "");
|
|
60
|
+
const fullKey = normalizeWords(`${prefix} ${key}`);
|
|
61
|
+
const value = match[3] ?? "";
|
|
62
|
+
|
|
63
|
+
applyDirective(fullKey, value, state);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function parseNaturalLanguageDirectives(
|
|
68
|
+
text: string,
|
|
69
|
+
state: ParsedWorkerRuntimeConfig & { provider?: string; model?: string },
|
|
70
|
+
): void {
|
|
71
|
+
captureTokens(
|
|
72
|
+
text,
|
|
73
|
+
/\bworker\s+(?:model|provider\/model)\s*(?:is|=|:|to|as)?\s*[`'"]?([A-Za-z0-9][A-Za-z0-9._~:+/@-]*)/gi,
|
|
74
|
+
(token) => {
|
|
75
|
+
state.model = token;
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
captureTokens(
|
|
79
|
+
text,
|
|
80
|
+
/\b(?:use|using|with|set|run(?:ning)?)\s+(?:the\s+)?(?:worker\s+)?model\s*(?:to|as|is|=|:)?\s*[`'"]?([A-Za-z0-9][A-Za-z0-9._~:+/@-]*)/gi,
|
|
81
|
+
(token) => {
|
|
82
|
+
state.model = token;
|
|
83
|
+
},
|
|
84
|
+
);
|
|
85
|
+
captureTokens(text, /\bmodel\s*(?:=|:)\s*[`'"]?([A-Za-z0-9][A-Za-z0-9._~:+/@-]*)/gi, (token) => {
|
|
86
|
+
state.model = token;
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
captureTokens(
|
|
90
|
+
text,
|
|
91
|
+
/\bworker\s+provider\s*(?:is|=|:|to|as)?\s*[`'"]?([A-Za-z0-9][A-Za-z0-9._~:+/@-]*)/gi,
|
|
92
|
+
(token) => {
|
|
93
|
+
state.provider = token;
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
captureTokens(text, /\bprovider\s*(?:=|:)\s*[`'"]?([A-Za-z0-9][A-Za-z0-9._~:+/@-]*)/gi, (token) => {
|
|
97
|
+
state.provider = token;
|
|
98
|
+
});
|
|
99
|
+
captureTokens(
|
|
100
|
+
text,
|
|
101
|
+
/\b(?:use|using|with|set)\s+(?:the\s+)?(?:worker\s+)?provider\s*(?:to|as|is|=|:)?\s*[`'"]?([A-Za-z0-9][A-Za-z0-9._~:+/@-]*)/gi,
|
|
102
|
+
(token) => {
|
|
103
|
+
state.provider = token;
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
captureNumbers(
|
|
108
|
+
text,
|
|
109
|
+
/\b(?:max(?:imum)?\s+)?(?:worker\s+|task\s+)?attempts?\s*(?:per\s+task)?\s*(?:is|=|:|to|at)?\s*(\d+)/gi,
|
|
110
|
+
(value) => {
|
|
111
|
+
state.maxAttemptsPerTask = value;
|
|
112
|
+
},
|
|
113
|
+
);
|
|
114
|
+
captureNumbers(text, /\b(\d+)\s+(?:worker\s+|task\s+)?attempts?\b/gi, (value) => {
|
|
115
|
+
state.maxAttemptsPerTask = value;
|
|
116
|
+
});
|
|
117
|
+
captureNumbers(text, /\btry\s+(?:each\s+task\s+)?(?:up\s+to\s+)?(\d+)\s+times\b/gi, (value) => {
|
|
118
|
+
state.maxAttemptsPerTask = value;
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
captureDurations(
|
|
122
|
+
text,
|
|
123
|
+
/\b(?<!bash\s)(?<!max\s)(?:worker\s+|task\s+)?timeout\s*(?:is|=|:|to|of)?\s*(\d+(?:\.\d+)?\s*(?:milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)?)/gi,
|
|
124
|
+
(value) => {
|
|
125
|
+
state.taskTimeoutMs = value;
|
|
126
|
+
},
|
|
127
|
+
);
|
|
128
|
+
captureDurations(
|
|
129
|
+
text,
|
|
130
|
+
/\b(\d+(?:\.\d+)?\s*(?:milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h))\s+(?:worker\s+|task\s+)?timeout\b/gi,
|
|
131
|
+
(value) => {
|
|
132
|
+
state.taskTimeoutMs = value;
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
captureDurations(
|
|
136
|
+
text,
|
|
137
|
+
/\b(?:max\s+)?bash\s+timeout\s*(?:is|=|:|to|of)?\s*(\d+(?:\.\d+)?\s*(?:milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)?)/gi,
|
|
138
|
+
(value) => {
|
|
139
|
+
state.maxBashTimeoutMs = value;
|
|
140
|
+
},
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function applyDirective(
|
|
145
|
+
key: string,
|
|
146
|
+
value: string,
|
|
147
|
+
state: ParsedWorkerRuntimeConfig & { provider?: string; model?: string },
|
|
148
|
+
): void {
|
|
149
|
+
if (/\bprovider\b/.test(key)) {
|
|
150
|
+
const token = modelToken(value);
|
|
151
|
+
if (token) {
|
|
152
|
+
state.provider = token;
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (/\bmodel\b/.test(key)) {
|
|
158
|
+
const token = modelToken(value);
|
|
159
|
+
if (token) {
|
|
160
|
+
state.model = token;
|
|
161
|
+
}
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (/\b(?:attempt|attempts|retry|retries)\b/.test(key)) {
|
|
166
|
+
const attempts = positiveIntegerFromText(value);
|
|
167
|
+
if (attempts !== undefined) {
|
|
168
|
+
state.maxAttemptsPerTask = attempts;
|
|
169
|
+
}
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (/\bbash\b/.test(key) && /\btimeout\b/.test(key)) {
|
|
174
|
+
const timeout = durationMsFromText(value, { allowBareSeconds: true });
|
|
175
|
+
if (timeout !== undefined) {
|
|
176
|
+
state.maxBashTimeoutMs = timeout;
|
|
177
|
+
}
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (/\btimeout\b/.test(key)) {
|
|
182
|
+
const timeout = durationMsFromText(value, { allowBareSeconds: true });
|
|
183
|
+
if (timeout !== undefined) {
|
|
184
|
+
state.taskTimeoutMs = timeout;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function captureTokens(text: string, pattern: RegExp, apply: (token: string) => void): void {
|
|
190
|
+
for (const match of text.matchAll(pattern)) {
|
|
191
|
+
const token = modelToken(match[1] ?? "");
|
|
192
|
+
if (token) {
|
|
193
|
+
apply(token);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function captureNumbers(text: string, pattern: RegExp, apply: (value: number) => void): void {
|
|
199
|
+
for (const match of text.matchAll(pattern)) {
|
|
200
|
+
const value = positiveIntegerFromText(match[1] ?? "");
|
|
201
|
+
if (value !== undefined) {
|
|
202
|
+
apply(value);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function captureDurations(text: string, pattern: RegExp, apply: (value: number) => void): void {
|
|
208
|
+
for (const match of text.matchAll(pattern)) {
|
|
209
|
+
const value = durationMsFromText(match[1] ?? "", { allowBareSeconds: true });
|
|
210
|
+
if (value !== undefined) {
|
|
211
|
+
apply(value);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function combineProviderAndModel(provider: string | undefined, model: string | undefined): string | undefined {
|
|
217
|
+
if (!model) {
|
|
218
|
+
return undefined;
|
|
219
|
+
}
|
|
220
|
+
if (model.includes("/") || !provider) {
|
|
221
|
+
return model;
|
|
222
|
+
}
|
|
223
|
+
return `${provider}/${model}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function modelToken(value: string): string | undefined {
|
|
227
|
+
const trimmed = trimDirectiveValue(value);
|
|
228
|
+
const match = MODEL_TOKEN_RE.exec(trimmed);
|
|
229
|
+
if (!match) {
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
const token = match[0].replace(/[.,;:]+$/g, "");
|
|
233
|
+
return token && !STOP_WORDS.has(token.toLowerCase()) ? token : undefined;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function trimDirectiveValue(value: string): string {
|
|
237
|
+
return value
|
|
238
|
+
.trim()
|
|
239
|
+
.replace(/^['"`]+/, "")
|
|
240
|
+
.replace(/['"`]+$/, "")
|
|
241
|
+
.trim();
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function positiveIntegerFromText(value: string): number | undefined {
|
|
245
|
+
const match = /\d+/.exec(value);
|
|
246
|
+
if (!match) {
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
249
|
+
const parsed = Number.parseInt(match[0], 10);
|
|
250
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : undefined;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function durationMsFromText(value: string, options: { allowBareSeconds: boolean }): number | undefined {
|
|
254
|
+
const match = /(\d+(?:\.\d+)?)\s*(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h)?\b/i.exec(
|
|
255
|
+
value,
|
|
256
|
+
);
|
|
257
|
+
if (!match) {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const amount = Number.parseFloat(match[1] ?? "");
|
|
262
|
+
if (!Number.isFinite(amount) || amount <= 0) {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const unit = (match[2] ?? "").toLowerCase();
|
|
267
|
+
if (!unit && !options.allowBareSeconds) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const multiplier = durationMultiplier(unit || "seconds");
|
|
272
|
+
const milliseconds = Math.round(amount * multiplier);
|
|
273
|
+
return Number.isSafeInteger(milliseconds) && milliseconds > 0 ? milliseconds : undefined;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function durationMultiplier(unit: string): number {
|
|
277
|
+
if (unit === "ms" || unit.startsWith("millisecond") || unit.startsWith("msec")) {
|
|
278
|
+
return 1;
|
|
279
|
+
}
|
|
280
|
+
if (unit === "h" || unit.startsWith("hour") || unit.startsWith("hr")) {
|
|
281
|
+
return 60 * 60 * 1000;
|
|
282
|
+
}
|
|
283
|
+
if (unit === "m" || unit.startsWith("minute") || unit.startsWith("min")) {
|
|
284
|
+
return 60 * 1000;
|
|
285
|
+
}
|
|
286
|
+
return 1000;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function normalizeWords(value: string): string {
|
|
290
|
+
return value.toLowerCase().replace(/[-_]+/g, " ").replace(/\s+/g, " ").trim();
|
|
291
|
+
}
|
package/src/worker_session.ts
CHANGED
|
@@ -186,7 +186,6 @@ export const extractAssistantTextFromEvent = assistantTextFromEvent;
|
|
|
186
186
|
export const extractLastAssistantTextFromEvents = lastAssistantTextFromEvents;
|
|
187
187
|
|
|
188
188
|
export const DEFAULT_WORKER_TOOLS = ["read", "bash", "edit", "write", "grep", "find", "ls"] as const;
|
|
189
|
-
export const DEFAULT_WORKER_MODEL = "openai-codex/gpt-5.5";
|
|
190
189
|
export const DEFAULT_WORKER_THINKING_LEVEL = "high";
|
|
191
190
|
export const DEFAULT_TASK_TIMEOUT_SECONDS = 60 * 60;
|
|
192
191
|
export const DEFAULT_GRACEFUL_SHUTDOWN_SECONDS = 60;
|
|
@@ -309,7 +308,8 @@ export async function createIsolatedWorkerSession(
|
|
|
309
308
|
const resourceLoader = disableExtensionsForWorker(discoveredResourceLoader, () => pi.createExtensionRuntime());
|
|
310
309
|
await resourceLoader.reload();
|
|
311
310
|
|
|
312
|
-
const model =
|
|
311
|
+
const model =
|
|
312
|
+
options.model ?? (options.modelName ? await resolveWorkerModel(modelRegistry, options.modelName) : undefined);
|
|
313
313
|
const createOptions: Record<string, unknown> = {
|
|
314
314
|
cwd,
|
|
315
315
|
agentDir,
|