pi-long-task 0.3.7 → 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 +75 -12
- 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
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext, Theme } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
3
|
-
import { truncateToWidth, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
3
|
+
import { truncateToWidth, type Component, type OverlayHandle, type TUI } from "@earendil-works/pi-tui";
|
|
4
4
|
|
|
5
5
|
import { runCoordinator, type CoordinatorProgressUpdate, type CoordinatorResult } from "./coordinator.ts";
|
|
6
6
|
import { longTaskInputTransform } from "./input_router.ts";
|
|
@@ -118,20 +118,24 @@ export function createLongTaskSidebarController(ctx: UiContext | undefined): Lon
|
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
let latestUpdate: CoordinatorProgressUpdate | undefined;
|
|
121
|
-
let
|
|
122
|
-
let
|
|
121
|
+
let widgetComponent: PiLongTaskSidebarComponent | undefined;
|
|
122
|
+
let widgetTui: TUI | undefined;
|
|
123
|
+
let overlayComponent: PiLongTaskSidebarComponent | undefined;
|
|
124
|
+
let overlayTui: TUI | undefined;
|
|
125
|
+
let overlayDone: ((result: undefined) => void) | undefined;
|
|
126
|
+
let overlayHandle: OverlayHandle | undefined;
|
|
123
127
|
let closed = false;
|
|
124
128
|
|
|
125
129
|
if (supportsTuiWidget(ctx)) {
|
|
126
130
|
ctx.ui.setWidget(
|
|
127
131
|
LONG_TASK_WIDGET_KEY,
|
|
128
132
|
(tui, theme) => {
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
widgetTui = tui;
|
|
134
|
+
widgetComponent = new PiLongTaskSidebarComponent(theme, () => sidebarWidgetLineLimit(tui));
|
|
131
135
|
if (latestUpdate) {
|
|
132
|
-
|
|
136
|
+
widgetComponent.setUpdate(latestUpdate);
|
|
133
137
|
}
|
|
134
|
-
return
|
|
138
|
+
return widgetComponent;
|
|
135
139
|
},
|
|
136
140
|
{ placement: "aboveEditor" },
|
|
137
141
|
);
|
|
@@ -139,15 +143,59 @@ export function createLongTaskSidebarController(ctx: UiContext | undefined): Lon
|
|
|
139
143
|
ctx.ui.setWidget(LONG_TASK_WIDGET_KEY, ["Pi Long Task: preparing sidebar..."], { placement: "aboveEditor" });
|
|
140
144
|
}
|
|
141
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;
|
|
160
|
+
},
|
|
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
|
+
},
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
void overlayPromise.catch(() => {
|
|
179
|
+
// The widget fallback remains active if overlay registration is unavailable.
|
|
180
|
+
});
|
|
181
|
+
} catch {
|
|
182
|
+
// The widget fallback remains active if overlay registration is unavailable.
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
142
186
|
return {
|
|
143
187
|
update(update: CoordinatorProgressUpdate): void {
|
|
144
188
|
if (closed) {
|
|
145
189
|
return;
|
|
146
190
|
}
|
|
147
191
|
latestUpdate = update;
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
192
|
+
widgetComponent?.setUpdate(update);
|
|
193
|
+
overlayComponent?.setUpdate(update);
|
|
194
|
+
widgetTui?.requestRender();
|
|
195
|
+
if (overlayTui && overlayTui !== widgetTui) {
|
|
196
|
+
overlayTui.requestRender();
|
|
197
|
+
}
|
|
198
|
+
if (!widgetComponent) {
|
|
151
199
|
ctx.ui.setWidget(LONG_TASK_WIDGET_KEY, renderSidebarWidgetLines(update), { placement: "aboveEditor" });
|
|
152
200
|
}
|
|
153
201
|
},
|
|
@@ -157,8 +205,17 @@ export function createLongTaskSidebarController(ctx: UiContext | undefined): Lon
|
|
|
157
205
|
}
|
|
158
206
|
closed = true;
|
|
159
207
|
ctx.ui.setWidget(LONG_TASK_WIDGET_KEY, undefined);
|
|
160
|
-
|
|
161
|
-
|
|
208
|
+
if (overlayHandle) {
|
|
209
|
+
overlayHandle.hide();
|
|
210
|
+
} else {
|
|
211
|
+
overlayDone?.(undefined);
|
|
212
|
+
}
|
|
213
|
+
widgetComponent = undefined;
|
|
214
|
+
widgetTui = undefined;
|
|
215
|
+
overlayComponent = undefined;
|
|
216
|
+
overlayTui = undefined;
|
|
217
|
+
overlayDone = undefined;
|
|
218
|
+
overlayHandle = undefined;
|
|
162
219
|
},
|
|
163
220
|
};
|
|
164
221
|
}
|
|
@@ -168,6 +225,11 @@ function supportsTuiWidget(ctx: UiContext): boolean {
|
|
|
168
225
|
return mode === "tui" || mode === undefined;
|
|
169
226
|
}
|
|
170
227
|
|
|
228
|
+
function supportsTuiOverlay(ctx: UiContext): boolean {
|
|
229
|
+
const mode = (ctx as UiContext & { mode?: string }).mode;
|
|
230
|
+
return mode === "tui" || mode === undefined;
|
|
231
|
+
}
|
|
232
|
+
|
|
171
233
|
function sidebarWidgetLineLimit(tui: TUI): number {
|
|
172
234
|
const terminalRows = tui.terminal.rows;
|
|
173
235
|
const rows = Number.isFinite(terminalRows) ? Math.max(0, Math.floor(terminalRows)) : 24;
|
|
@@ -679,6 +741,7 @@ export default function registerPiLongTaskExtension(pi: ExtensionAPI) {
|
|
|
679
741
|
const result = await runCoordinator({
|
|
680
742
|
...params,
|
|
681
743
|
cwd: ctx?.cwd,
|
|
744
|
+
workerModel: ctx?.model,
|
|
682
745
|
abortSignal: signal,
|
|
683
746
|
onProgress: publishProgress,
|
|
684
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,
|