pi-harness-delegate 0.1.0 → 0.1.1
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/extensions/activity.ts +225 -201
- package/extensions/command.ts +61 -61
- package/extensions/config.ts +137 -131
- package/extensions/harnesses/amp.ts +122 -115
- package/extensions/harnesses/claude.ts +136 -111
- package/extensions/harnesses/codex.ts +191 -147
- package/extensions/harnesses/opencode.ts +133 -116
- package/extensions/harnesses/registry.ts +22 -22
- package/extensions/harnesses/types.ts +60 -60
- package/extensions/hint.ts +22 -19
- package/extensions/index.ts +1011 -741
- package/extensions/progress.ts +104 -104
- package/extensions/run-claude.ts +44 -35
- package/extensions/runner.ts +111 -99
- package/extensions/stream-parse.ts +32 -24
- package/extensions/templates.ts +134 -117
- package/extensions/usage.ts +24 -24
- package/package.json +69 -56
package/extensions/activity.ts
CHANGED
|
@@ -3,252 +3,276 @@ import { join } from 'node:path';
|
|
|
3
3
|
import type { ActivityEvent } from './harnesses/types.ts';
|
|
4
4
|
|
|
5
5
|
function truncate(s: string, max: number): string {
|
|
6
|
-
|
|
6
|
+
return s.length > max ? `${s.slice(0, max - 1)}…` : s;
|
|
7
7
|
}
|
|
8
8
|
|
|
9
9
|
/** Make a template/mode name safe for use in a filename. */
|
|
10
10
|
export function safeSegmentName(name: string): string {
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
const safe = name.replace(/[^a-zA-Z0-9_-]+/g, '_').replace(/^_+|_+$/g, '');
|
|
12
|
+
return safe.length > 0 ? safe : 'delegate';
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export interface MetricsInput {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
numTurns: number;
|
|
17
|
+
totalCostUsd: number;
|
|
18
|
+
promptTokens: number;
|
|
19
|
+
contextPercent: number | null;
|
|
20
|
+
durationMs: number | null;
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/** Compact run summary: `3 turn(s) · $0.54 · 62k tok · 6.2% ctx · 12s`. */
|
|
24
24
|
export function formatMetrics(m: MetricsInput): string {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
const parts: Array<string | null> = [
|
|
26
|
+
`${m.numTurns} turn(s)`,
|
|
27
|
+
`$${m.totalCostUsd.toFixed(3)}`,
|
|
28
|
+
m.promptTokens > 0 ? `${Math.round(m.promptTokens / 1000)}k tok` : null,
|
|
29
|
+
typeof m.contextPercent === 'number' ? `${m.contextPercent.toFixed(1)}% ctx` : null,
|
|
30
|
+
typeof m.durationMs === 'number' && m.durationMs !== null ? `${(m.durationMs / 1000).toFixed(0)}s` : null,
|
|
31
|
+
];
|
|
32
|
+
return parts.filter((p): p is string => Boolean(p)).join(' · ');
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/** Parse the metadata header of a transcript file (without loading the whole body). */
|
|
36
36
|
export function parseTranscriptMeta(head: string): {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
mode: string;
|
|
38
|
+
cost: number;
|
|
39
|
+
sessionId: string | null;
|
|
40
|
+
harness: string | null;
|
|
41
41
|
} {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
42
|
+
let mode = 'delegate';
|
|
43
|
+
let cost = 0;
|
|
44
|
+
let sessionId: string | null = null;
|
|
45
|
+
let harness: string | null = null;
|
|
46
|
+
const mm = /^# Delegated (?:Claude|Harness) run — (.+)$/m.exec(head);
|
|
47
|
+
if (mm) mode = mm[1];
|
|
48
|
+
// Also match new header: # Delegated <harness> run — <mode>
|
|
49
|
+
const hm = /^# Delegated (\w+) run —/m.exec(head);
|
|
50
|
+
if (hm) harness = hm[1].toLowerCase();
|
|
51
|
+
const cm = /\bcost: \$([\d.]+)/.exec(head);
|
|
52
|
+
if (cm) cost = Number(cm[1]);
|
|
53
|
+
const sm = /\bsession: ([0-9a-f-]+)/.exec(head);
|
|
54
|
+
if (sm) sessionId = sm[1];
|
|
55
|
+
// harness explicit field
|
|
56
|
+
const hfm = /^-\s*harness:\s*(\w+)/m.exec(head);
|
|
57
|
+
if (hfm) harness = hfm[1];
|
|
58
|
+
return { mode, cost, sessionId, harness };
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
/** Build the markdown report content injected into the session on the next turn. */
|
|
62
62
|
export function buildReportContent(opts: {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
harness?: string;
|
|
64
|
+
mode: string;
|
|
65
|
+
metrics: string;
|
|
66
|
+
body: string;
|
|
67
|
+
file?: string;
|
|
68
|
+
sessionId?: string;
|
|
69
69
|
}): string {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
70
|
+
const harness = opts.harness ?? 'claude';
|
|
71
|
+
const header = `## ${harness} ${opts.mode} (${opts.metrics})`;
|
|
72
|
+
const foot: string[] = [];
|
|
73
|
+
if (opts.file) foot.push(`transcript: ${opts.file}`);
|
|
74
|
+
if (opts.sessionId)
|
|
75
|
+
foot.push(
|
|
76
|
+
`resume: \`/delegate --harness=${opts.harness} --resume=${opts.sessionId} <prompt>\` (or /${opts.harness} --resume=${opts.sessionId})`,
|
|
77
|
+
);
|
|
78
|
+
return [header, '', opts.body, foot.length > 0 ? `\n_${foot.join(' · ')}_` : ''].join('\n');
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
/** Legacy wrapper for compat */
|
|
79
82
|
export function buildClaudeReportContent(opts: {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
mode: string;
|
|
84
|
+
metrics: string;
|
|
85
|
+
body: string;
|
|
86
|
+
file?: string;
|
|
87
|
+
sessionId?: string;
|
|
85
88
|
}): string {
|
|
86
|
-
|
|
89
|
+
return buildReportContent({ harness: 'claude', ...opts });
|
|
87
90
|
}
|
|
88
91
|
|
|
89
92
|
/** Delete oldest transcript files beyond `maxCount` (0 = keep everything). */
|
|
90
93
|
export function pruneOutputs(dir: string, maxCount: number): void {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
94
|
+
if (maxCount <= 0) return;
|
|
95
|
+
let files: string[];
|
|
96
|
+
try {
|
|
97
|
+
files = readdirSync(dir);
|
|
98
|
+
} catch {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const byMtime = files
|
|
102
|
+
.filter(f => f.endsWith('.md'))
|
|
103
|
+
.map(f => ({ f, mtime: statSync(join(dir, f), { throwIfNoEntry: false })?.mtimeMs ?? 0 }))
|
|
104
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
105
|
+
for (const { f } of byMtime.slice(maxCount)) {
|
|
106
|
+
try {
|
|
107
|
+
rmSync(join(dir, f));
|
|
108
|
+
} catch {
|
|
109
|
+
// best-effort
|
|
110
|
+
}
|
|
111
|
+
}
|
|
109
112
|
}
|
|
110
113
|
|
|
111
114
|
/** Human-readable one-liner for a tool call (uses Claude's `description` when present). */
|
|
112
115
|
export function formatToolUse(name: string, input: Record<string, unknown>): string {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
if (typeof input.description === 'string' && input.description) {
|
|
117
|
+
return `${name}: ${truncate(input.description, 90)}`;
|
|
118
|
+
}
|
|
119
|
+
if (typeof input.command === 'string') return `${name}: ${truncate(input.command.split('\n')[0], 90)}`;
|
|
120
|
+
if (typeof input.file_path === 'string') return `${name}: ${input.file_path}`;
|
|
121
|
+
if (typeof input.pattern === 'string') return `${name}: ${input.pattern}`;
|
|
122
|
+
if (typeof input.url === 'string') return `${name}: ${input.url}`;
|
|
123
|
+
const first = Object.values(input).find((v): v is string => typeof v === 'string' && v.length > 0);
|
|
124
|
+
return first ? `${name}: ${truncate(first, 90)}` : name;
|
|
122
125
|
}
|
|
123
126
|
|
|
124
127
|
/** Compact per-line activity log for the transcript (tool_input + results only). */
|
|
125
128
|
export function collectActivityLog(events: ActivityEvent[]): string[] {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
129
|
+
const log: string[] = [];
|
|
130
|
+
for (const ev of events) {
|
|
131
|
+
if (ev.kind === 'tool_input') {
|
|
132
|
+
log.push(`▶ ${formatToolUse(ev.name, ev.input)}`);
|
|
133
|
+
} else if (ev.kind === 'tool_result') {
|
|
134
|
+
const last = log.length - 1;
|
|
135
|
+
if (last >= 0 && log[last].startsWith('▶')) {
|
|
136
|
+
log[last] += ev.isError ? ' ✗ error' : ' ✓';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return log;
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
/** Full transcript written to the outputs dir: metadata + activity + output. */
|
|
141
|
-
export function buildTranscript(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
144
|
+
export function buildTranscript(
|
|
145
|
+
opts: {
|
|
146
|
+
harness?: string;
|
|
147
|
+
mode: string;
|
|
148
|
+
permission?: string;
|
|
149
|
+
permissionMode?: string;
|
|
150
|
+
nativePermission?: string;
|
|
151
|
+
model: string | null;
|
|
152
|
+
cwd: string;
|
|
153
|
+
sessionId: string | null;
|
|
154
|
+
resumed: boolean;
|
|
155
|
+
numTurns: number;
|
|
156
|
+
totalCostUsd: number;
|
|
157
|
+
isError: boolean;
|
|
158
|
+
stopReason: string | null;
|
|
159
|
+
durationMs: number | null;
|
|
160
|
+
usage: {
|
|
161
|
+
inputTokens: number;
|
|
162
|
+
outputTokens: number;
|
|
163
|
+
cacheCreationInputTokens: number;
|
|
164
|
+
cacheReadInputTokens: number;
|
|
165
|
+
} | null;
|
|
166
|
+
contextPercent: number | null;
|
|
167
|
+
contextWindow: number | null;
|
|
168
|
+
activityLog: string[];
|
|
169
|
+
output: string;
|
|
170
|
+
} & Record<string, unknown>,
|
|
171
|
+
): string {
|
|
172
|
+
const harness = (opts.harness as string | undefined) ?? 'claude';
|
|
173
|
+
const permissionRaw =
|
|
174
|
+
(opts.permission as string | undefined) ?? (opts.permissionMode as string | undefined) ?? 'edit';
|
|
175
|
+
let permission = permissionRaw;
|
|
176
|
+
let nativePermission = opts.nativePermission as string | undefined;
|
|
177
|
+
// map legacy permissionMode to normalized if needed
|
|
178
|
+
if ((opts as Record<string, unknown>).permissionMode && !opts.permission) {
|
|
179
|
+
const pm = (opts as Record<string, unknown>).permissionMode as string;
|
|
180
|
+
if (pm === 'plan') {
|
|
181
|
+
permission = 'readonly';
|
|
182
|
+
nativePermission = pm;
|
|
183
|
+
} else if (pm === 'bypassPermissions') {
|
|
184
|
+
permission = 'danger';
|
|
185
|
+
nativePermission = pm;
|
|
186
|
+
} else {
|
|
187
|
+
permission = 'edit';
|
|
188
|
+
nativePermission = pm;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const u = opts.usage;
|
|
192
|
+
const tokens = u
|
|
193
|
+
? [
|
|
194
|
+
`input ${u.inputTokens}`,
|
|
195
|
+
`output ${u.outputTokens}`,
|
|
196
|
+
`cache+${u.cacheCreationInputTokens}`,
|
|
197
|
+
`cache ${u.cacheReadInputTokens}`,
|
|
198
|
+
].join(' · ')
|
|
199
|
+
: null;
|
|
200
|
+
const context =
|
|
201
|
+
opts.contextPercent !== null && opts.contextWindow
|
|
202
|
+
? `${opts.contextPercent.toFixed(1)}% of ${opts.contextWindow.toLocaleString()} window`
|
|
203
|
+
: null;
|
|
204
|
+
const duration = opts.durationMs !== null ? `${(opts.durationMs / 1000).toFixed(1)}s` : null;
|
|
205
|
+
const permLine = nativePermission
|
|
206
|
+
? `- permission: ${permission} (${nativePermission})`
|
|
207
|
+
: `- permission: ${permission}`;
|
|
190
208
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
209
|
+
return [
|
|
210
|
+
`# Delegated ${harness.charAt(0).toUpperCase() + harness.slice(1)} run — ${opts.mode}`,
|
|
211
|
+
'',
|
|
212
|
+
`- harness: ${harness}`,
|
|
213
|
+
`- mode: ${opts.mode}`,
|
|
214
|
+
permLine,
|
|
215
|
+
`- model: ${opts.model ?? 'default'}`,
|
|
216
|
+
`- cwd: ${opts.cwd}`,
|
|
217
|
+
`- session: ${opts.sessionId ?? 'n/a'}${opts.resumed ? ' (resumed)' : ''}`,
|
|
218
|
+
`- turns: ${opts.numTurns} · cost: $${opts.totalCostUsd.toFixed(4)} · isError: ${opts.isError}`,
|
|
219
|
+
`- tokens: ${tokens ?? 'n/a'}`,
|
|
220
|
+
`- context: ${context ?? 'n/a'}`,
|
|
221
|
+
`- duration: ${duration ?? 'n/a'}`,
|
|
222
|
+
`- stop reason: ${opts.stopReason ?? 'n/a'}`,
|
|
223
|
+
'',
|
|
224
|
+
'## Activity',
|
|
225
|
+
opts.activityLog.length > 0 ? opts.activityLog.join('\n') : '(no tool activity)',
|
|
226
|
+
'',
|
|
227
|
+
'## Output',
|
|
228
|
+
opts.output || '(empty)',
|
|
229
|
+
'',
|
|
230
|
+
].join('\n');
|
|
213
231
|
}
|
|
214
232
|
|
|
215
233
|
/** Legacy wrapper */
|
|
216
234
|
export function buildClaudeTranscript(opts: {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
235
|
+
mode: string;
|
|
236
|
+
permissionMode: string;
|
|
237
|
+
model: string | null;
|
|
238
|
+
cwd: string;
|
|
239
|
+
sessionId: string | null;
|
|
240
|
+
resumed: boolean;
|
|
241
|
+
numTurns: number;
|
|
242
|
+
totalCostUsd: number;
|
|
243
|
+
isError: boolean;
|
|
244
|
+
stopReason: string | null;
|
|
245
|
+
durationMs: number | null;
|
|
246
|
+
usage: {
|
|
247
|
+
inputTokens: number;
|
|
248
|
+
outputTokens: number;
|
|
249
|
+
cacheCreationInputTokens: number;
|
|
250
|
+
cacheReadInputTokens: number;
|
|
251
|
+
} | null;
|
|
252
|
+
contextPercent: number | null;
|
|
253
|
+
contextWindow: number | null;
|
|
254
|
+
activityLog: string[];
|
|
255
|
+
output: string;
|
|
233
256
|
}): string {
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
257
|
+
return buildTranscript({
|
|
258
|
+
harness: 'claude',
|
|
259
|
+
mode: opts.mode,
|
|
260
|
+
permission:
|
|
261
|
+
opts.permissionMode === 'plan' ? 'readonly' : opts.permissionMode === 'bypassPermissions' ? 'danger' : 'edit',
|
|
262
|
+
nativePermission: opts.permissionMode,
|
|
263
|
+
model: opts.model,
|
|
264
|
+
cwd: opts.cwd,
|
|
265
|
+
sessionId: opts.sessionId,
|
|
266
|
+
resumed: opts.resumed,
|
|
267
|
+
numTurns: opts.numTurns,
|
|
268
|
+
totalCostUsd: opts.totalCostUsd,
|
|
269
|
+
isError: opts.isError,
|
|
270
|
+
stopReason: opts.stopReason,
|
|
271
|
+
durationMs: opts.durationMs,
|
|
272
|
+
usage: opts.usage,
|
|
273
|
+
contextPercent: opts.contextPercent,
|
|
274
|
+
contextWindow: opts.contextWindow,
|
|
275
|
+
activityLog: opts.activityLog,
|
|
276
|
+
output: opts.output,
|
|
277
|
+
});
|
|
254
278
|
}
|
package/extensions/command.ts
CHANGED
|
@@ -6,16 +6,16 @@ import type { DelegateTemplate } from './templates.ts';
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
export interface DelegateCommandArgs {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
9
|
+
task: string;
|
|
10
|
+
harness?: string;
|
|
11
|
+
mode?: string;
|
|
12
|
+
model?: string;
|
|
13
|
+
scope?: string;
|
|
14
|
+
budget?: number;
|
|
15
|
+
/** Resume an existing delegated session (--resume=<id>). */
|
|
16
|
+
sessionId?: string;
|
|
17
|
+
/** GitHub PR number/URL to review (--pr=). */
|
|
18
|
+
pr?: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export type ClaudeCommandArgs = DelegateCommandArgs;
|
|
@@ -23,69 +23,69 @@ export type ClaudeCommandArgs = DelegateCommandArgs;
|
|
|
23
23
|
const KNOWN_HARNESSES = new Set(['claude', 'codex', 'opencode', 'amp', 'omp']);
|
|
24
24
|
|
|
25
25
|
export function parseDelegateCommand(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
raw: string,
|
|
27
|
+
knownModes: ReadonlySet<string>,
|
|
28
|
+
knownHarnesses: ReadonlySet<string> = KNOWN_HARNESSES,
|
|
29
29
|
): DelegateCommandArgs {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
const flags: Record<string, string> = {};
|
|
31
|
+
const rest = raw.replace(/--([a-zA-Z-]+)=(\S+)/g, (_m, k: string, v: string) => {
|
|
32
|
+
flags[k] = v;
|
|
33
|
+
return '';
|
|
34
|
+
});
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
let harness = flags.harness?.toLowerCase();
|
|
37
|
+
let mode = flags.mode;
|
|
38
|
+
let task = rest.trim();
|
|
39
39
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
40
|
+
// First word handling: harness, mode, or both
|
|
41
|
+
const words = task.split(/\s+/).filter(Boolean);
|
|
42
|
+
let idx = 0;
|
|
43
|
+
if (!harness && words[idx] && knownHarnesses.has(words[idx].toLowerCase())) {
|
|
44
|
+
harness = words[idx].toLowerCase();
|
|
45
|
+
if (harness === 'omp') harness = 'amp';
|
|
46
|
+
idx++;
|
|
47
|
+
}
|
|
48
|
+
if (!mode && words[idx] && knownModes.has(words[idx])) {
|
|
49
|
+
mode = words[idx];
|
|
50
|
+
idx++;
|
|
51
|
+
}
|
|
52
|
+
if (idx > 0) task = words.slice(idx).join(' ').trim();
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
54
|
+
const out: DelegateCommandArgs = { task };
|
|
55
|
+
if (harness) out.harness = harness;
|
|
56
|
+
if (mode) out.mode = mode;
|
|
57
|
+
if (flags.model) out.model = flags.model;
|
|
58
|
+
if (flags.scope) out.scope = flags.scope;
|
|
59
|
+
if (flags.budget !== undefined) {
|
|
60
|
+
const budget = Number(flags.budget);
|
|
61
|
+
if (Number.isFinite(budget) && budget > 0) out.budget = budget;
|
|
62
|
+
}
|
|
63
|
+
if (flags.resume) out.sessionId = flags.resume;
|
|
64
|
+
if (flags.pr) out.pr = flags.pr;
|
|
65
|
+
return out;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
export function parseClaudeCommand(raw: string, knownModes: ReadonlySet<string>): ClaudeCommandArgs {
|
|
69
|
-
|
|
69
|
+
return parseDelegateCommand(raw, knownModes);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* Apply template defaults when the prompt is empty.
|
|
74
74
|
*/
|
|
75
75
|
export function resolveDefaults(
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
args: DelegateCommandArgs,
|
|
77
|
+
templates: ReadonlyMap<string, DelegateTemplate>,
|
|
78
78
|
): { task: string; scope?: string } | null {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
79
|
+
if (args.task) {
|
|
80
|
+
return args.scope ? { task: args.task, scope: args.scope } : { task: args.task };
|
|
81
|
+
}
|
|
82
|
+
if (args.mode) {
|
|
83
|
+
const t = templates.get(args.mode);
|
|
84
|
+
if (t?.defaultTask) {
|
|
85
|
+
const scope = args.scope ?? t.defaultScope;
|
|
86
|
+
return scope ? { task: t.defaultTask, scope } : { task: t.defaultTask };
|
|
87
|
+
}
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
91
|
}
|