reslop 0.3.1 → 0.3.3
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/CHANGELOG.md +28 -1
- package/README.md +30 -4
- package/lib/agents/acp.js +309 -0
- package/lib/agents/agents.js +274 -438
- package/lib/agents/allow.js +141 -52
- package/lib/agents/allow.json +16 -14
- package/lib/agents/models.js +250 -383
- package/lib/agents/registry.js +437 -0
- package/lib/agents/run.js +234 -0
- package/lib/agents/script.js +93 -0
- package/lib/agents/transcript.js +143 -0
- package/lib/cli.js +2 -1
- package/lib/common/process.js +26 -1
- package/lib/dashboard/git.js +11 -1
- package/lib/dashboard/reports.js +80 -0
- package/lib/dashboard/watch.js +43 -8
- package/lib/dashboard/workspace.js +19 -20
- package/lib/dashboard/worktrees.js +145 -0
- package/lib/git/dir.js +50 -0
- package/lib/git/linked.js +152 -0
- package/lib/git/local.js +179 -0
- package/lib/git/status.js +126 -0
- package/lib/git/worktree.js +113 -36
- package/lib/input/actions.js +83 -5
- package/lib/input/keys.js +30 -1
- package/lib/input/line-edit.js +5 -1
- package/lib/render/agents.js +84 -34
- package/lib/render/branches.js +55 -27
- package/lib/render/field.js +19 -5
- package/lib/render/footer.js +14 -6
- package/lib/render/frame.js +16 -2
- package/lib/render/menu.js +103 -6
- package/lib/render/name-edit.js +31 -0
- package/lib/render/repos.js +52 -8
- package/lib/render/status.js +22 -4
- package/lib/report/run.js +16 -23
- package/lib/review/agent-instructions.md +2 -2
- package/lib/runs/process.js +3 -3
- package/lib/runs/runs.js +27 -4
- package/lib/session/agents/agents.js +220 -205
- package/lib/session/agents/away.js +48 -0
- package/lib/session/agents/job.js +23 -86
- package/lib/session/agents/jobs.js +14 -27
- package/lib/session/agents/options.js +45 -101
- package/lib/session/agents/plans.js +53 -0
- package/lib/session/agents/view.js +34 -0
- package/lib/session/branches.js +18 -1
- package/lib/session/compose.js +13 -5
- package/lib/session/dispatch.js +43 -4
- package/lib/session/editor-pointer.js +98 -0
- package/lib/session/files-pane.js +3 -0
- package/lib/session/lifecycle.js +67 -10
- package/lib/session/navigation.js +5 -0
- package/lib/session/npm/history.js +2 -1
- package/lib/session/npm/npm.js +89 -12
- package/lib/session/ops.js +3 -0
- package/lib/session/pointer.js +304 -46
- package/lib/session/session.js +15 -2
- package/lib/session/terminal.js +2 -2
- package/lib/session/text-menu.js +50 -0
- package/lib/session/view.js +19 -2
- package/lib/session/workspace.js +179 -38
- package/lib/session/worktrees.js +280 -0
- package/lib/term/themes/dark.json +4 -0
- package/lib/term/themes/light.json +4 -0
- package/package.json +2 -2
|
@@ -4,19 +4,8 @@ const fs = require('node:fs');
|
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
const { clamp } = require('../../common/utilities.js');
|
|
6
6
|
const agents = require('../../agents/agents.js');
|
|
7
|
-
const {
|
|
8
|
-
|
|
9
|
-
canLogin,
|
|
10
|
-
listedOf,
|
|
11
|
-
listModels,
|
|
12
|
-
startAgent,
|
|
13
|
-
buildLaunch,
|
|
14
|
-
buildLogin,
|
|
15
|
-
needsAuth,
|
|
16
|
-
probeAuth,
|
|
17
|
-
emptyChoice,
|
|
18
|
-
mergeModels,
|
|
19
|
-
} = agents;
|
|
7
|
+
const { detectAgents, listModels, startAgent, buildLaunch } = agents;
|
|
8
|
+
const { emptyChoice, catalogOf, buildInstall } = agents;
|
|
20
9
|
const review = require('../../review/review.js');
|
|
21
10
|
const { latestReviewName, listReviewNames, planFileNames } = review;
|
|
22
11
|
const { REVIEW_DIR } = require('../../common/files.js');
|
|
@@ -24,16 +13,18 @@ const render = require('../../render/log.js');
|
|
|
24
13
|
const { logRowCount } = render;
|
|
25
14
|
const { RunPanel } = require('../run-panel.js');
|
|
26
15
|
const options = require('./options.js');
|
|
27
|
-
const { AgentOptions,
|
|
16
|
+
const { AgentOptions, restoreChoice, storedChoice } = options;
|
|
28
17
|
const jobs = require('./jobs.js');
|
|
18
|
+
const { AwayRuns } = require('./away.js');
|
|
29
19
|
const { readPrefs, writePrefs } = require('../prefs.js');
|
|
30
20
|
const { elapsedLabel, launchJob, assignLog, stemOf } = jobs;
|
|
31
21
|
const { restoreJobs, readRunList, saveRunFiles } = jobs;
|
|
32
22
|
const { panelSessions, orderedSessions, runBlock } = jobs;
|
|
33
23
|
const { runningJobs, withLiveRuns } = jobs;
|
|
34
|
-
const { replayLaunch } = jobs;
|
|
35
24
|
const plans = require('./plans.js');
|
|
36
25
|
const { planKey, planProgress, planPart, planLabel, alignPlanLabels } = plans;
|
|
26
|
+
const { planCaption } = plans;
|
|
27
|
+
const { NO_PICK, NO_LIST, NO_JOB } = require('./view.js');
|
|
37
28
|
const { MenuPick, menuRows, pickDelta } = require('../../input/menu-pick.js');
|
|
38
29
|
const { lineKey } = require('../../input/line-edit.js');
|
|
39
30
|
|
|
@@ -41,7 +32,9 @@ const optionsOf = (row) => new AgentOptions(row);
|
|
|
41
32
|
|
|
42
33
|
const APPLY_FIELD = {
|
|
43
34
|
model: (choices, picked, value) => choices.pickModel(picked, value),
|
|
44
|
-
context: (choices, picked, value) =>
|
|
35
|
+
context: (choices, picked, value) => {
|
|
36
|
+
picked.context = value;
|
|
37
|
+
},
|
|
45
38
|
effort: (choices, picked, value) => {
|
|
46
39
|
picked.effort = value;
|
|
47
40
|
},
|
|
@@ -61,9 +54,9 @@ const continuedId = (session) => {
|
|
|
61
54
|
|
|
62
55
|
const cacheKey = (row) => `${row.id}:${row.bin}`;
|
|
63
56
|
|
|
64
|
-
const listQuietly = async (list, row,
|
|
57
|
+
const listQuietly = async (list, row, options) => {
|
|
65
58
|
try {
|
|
66
|
-
return await list(row
|
|
59
|
+
return await list(row, options);
|
|
67
60
|
} catch {
|
|
68
61
|
return null;
|
|
69
62
|
}
|
|
@@ -75,42 +68,12 @@ const firstEnabled = (items, at, isOff) => {
|
|
|
75
68
|
return free < 0 ? at : free;
|
|
76
69
|
};
|
|
77
70
|
|
|
78
|
-
const NO_PICK = {
|
|
79
|
-
agentPick: '',
|
|
80
|
-
agentMenu: null,
|
|
81
|
-
agentQuery: '',
|
|
82
|
-
agentEditor: null,
|
|
83
|
-
agentMenuOff: [],
|
|
84
|
-
agentPickCursor: 0,
|
|
85
|
-
agentMenuScroll: 0,
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const NO_LIST = {
|
|
89
|
-
agents: null,
|
|
90
|
-
agentRuns: [],
|
|
91
|
-
agentRunCursor: 0,
|
|
92
|
-
agentRunScroll: 0,
|
|
93
|
-
agentFocus: 'cli',
|
|
94
|
-
agentCanContinue: false,
|
|
95
|
-
agentPlanCount: 0,
|
|
96
|
-
agentView: false,
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const NO_JOB = {
|
|
100
|
-
agentOutput: '',
|
|
101
|
-
agentCommand: '',
|
|
102
|
-
agentElapsed: '',
|
|
103
|
-
agentProgress: '',
|
|
104
|
-
agentRunning: false,
|
|
105
|
-
agentStatus: '',
|
|
106
|
-
};
|
|
107
|
-
|
|
108
71
|
class AgentsController extends RunPanel {
|
|
109
72
|
constructor(ui) {
|
|
110
73
|
super(ui);
|
|
111
74
|
this.spawn = startAgent;
|
|
112
75
|
this.listModels = listModels;
|
|
113
|
-
this.
|
|
76
|
+
this.probes = null;
|
|
114
77
|
this.reset();
|
|
115
78
|
}
|
|
116
79
|
|
|
@@ -123,8 +86,12 @@ class AgentsController extends RunPanel {
|
|
|
123
86
|
this.list = [];
|
|
124
87
|
this.choices = new Map();
|
|
125
88
|
this.loaded = new Map();
|
|
89
|
+
this.asking = new Set();
|
|
126
90
|
this.modelsEpoch = 0;
|
|
127
91
|
this.jobs = [];
|
|
92
|
+
this.away = new AwayRuns();
|
|
93
|
+
this.installs = new Map();
|
|
94
|
+
this.installTarget = '';
|
|
128
95
|
this.nextId = 1;
|
|
129
96
|
this.viewing = false;
|
|
130
97
|
this.viewId = 0;
|
|
@@ -140,31 +107,34 @@ class AgentsController extends RunPanel {
|
|
|
140
107
|
this.viewSession = '';
|
|
141
108
|
this.historyText = '';
|
|
142
109
|
this.historyAt = 0;
|
|
143
|
-
this.pendingStart = null;
|
|
144
|
-
this.authYes = new Set();
|
|
145
|
-
this.authWait = '';
|
|
146
110
|
this.env = process.env;
|
|
147
111
|
this.refresh();
|
|
148
112
|
this.loadRuns();
|
|
149
113
|
}
|
|
150
114
|
|
|
151
115
|
killAll() {
|
|
152
|
-
|
|
116
|
+
if (this.probes) this.probes.abort();
|
|
117
|
+
this.probes = null;
|
|
118
|
+
for (const job of this.everyJob()) {
|
|
153
119
|
if (job.child) job.child.kill();
|
|
154
120
|
}
|
|
155
121
|
}
|
|
156
122
|
|
|
123
|
+
// The install of an adapter is watched like a run but is no session
|
|
124
|
+
everyJob() {
|
|
125
|
+
const installs = this.installs ? this.installs.values() : [];
|
|
126
|
+
const runs = this.jobs ?? [];
|
|
127
|
+
const away = this.away ? this.away.jobs() : [];
|
|
128
|
+
return [...runs, ...away, ...installs];
|
|
129
|
+
}
|
|
130
|
+
|
|
157
131
|
reviewStore() {
|
|
158
132
|
return this.ui.review ? this.ui.review.store : null;
|
|
159
133
|
}
|
|
160
134
|
|
|
161
135
|
applyCached(row) {
|
|
162
136
|
const cached = this.loaded.get(cacheKey(row));
|
|
163
|
-
if (
|
|
164
|
-
row.models = cached.models;
|
|
165
|
-
row.wide = cached.wide;
|
|
166
|
-
row.listedEfforts = cached.efforts;
|
|
167
|
-
row.modelEfforts = cached.modelEfforts;
|
|
137
|
+
if (cached) row.catalog = cached;
|
|
168
138
|
}
|
|
169
139
|
|
|
170
140
|
refresh(env = this.env) {
|
|
@@ -174,7 +144,7 @@ class AgentsController extends RunPanel {
|
|
|
174
144
|
this.choices = new Map();
|
|
175
145
|
for (const row of this.list) {
|
|
176
146
|
this.applyCached(row);
|
|
177
|
-
this.choices.set(row.id, prev.get(row.id) ?? emptyChoice(
|
|
147
|
+
this.choices.set(row.id, prev.get(row.id) ?? emptyChoice());
|
|
178
148
|
}
|
|
179
149
|
this.loadChoices();
|
|
180
150
|
this.clampCursor();
|
|
@@ -185,7 +155,7 @@ class AgentsController extends RunPanel {
|
|
|
185
155
|
const saved = readPrefs(this.root).agents;
|
|
186
156
|
for (const row of this.list) {
|
|
187
157
|
const picked = this.choice(row.id);
|
|
188
|
-
restoreChoice(picked, saved[row.id]
|
|
158
|
+
restoreChoice(picked, saved[row.id]);
|
|
189
159
|
optionsOf(row).preferEffort(picked, 'medium');
|
|
190
160
|
}
|
|
191
161
|
}
|
|
@@ -196,7 +166,7 @@ class AgentsController extends RunPanel {
|
|
|
196
166
|
if (!this.root || !picked || !row) return;
|
|
197
167
|
const data = readPrefs(this.root);
|
|
198
168
|
const saved = { ...data.agents };
|
|
199
|
-
const stored = storedChoice(picked
|
|
169
|
+
const stored = storedChoice(picked);
|
|
200
170
|
if (stored) saved[id] = stored;
|
|
201
171
|
else delete saved[id];
|
|
202
172
|
writePrefs(this.root, { ...data, agents: saved });
|
|
@@ -211,22 +181,36 @@ class AgentsController extends RunPanel {
|
|
|
211
181
|
}
|
|
212
182
|
|
|
213
183
|
// The repository can change after construction: its runs and its saved
|
|
214
|
-
// sessions must come from the same directory
|
|
184
|
+
// sessions must come from the same directory. A run that goes on in the
|
|
185
|
+
// previous one stays with it
|
|
215
186
|
syncRuns() {
|
|
216
187
|
if (!this.root || this.root === this.runsRoot) return;
|
|
217
|
-
|
|
218
|
-
this.
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
188
|
+
this.away.park(this.runsRoot, this.jobs);
|
|
189
|
+
const parked = this.away.take(this.root);
|
|
190
|
+
if (parked) this.jobs = parked;
|
|
191
|
+
else this.loadRuns();
|
|
192
|
+
this.runsRoot = this.root;
|
|
193
|
+
const last = this.jobs.reduce((max, job) => Math.max(max, job.id), 0);
|
|
194
|
+
this.nextId = Math.max(this.nextId, last + 1);
|
|
224
195
|
this.viewId = 0;
|
|
225
196
|
}
|
|
226
197
|
|
|
198
|
+
retarget() {
|
|
199
|
+
this.viewing = false;
|
|
200
|
+
this.viewId = 0;
|
|
201
|
+
this.pick = null;
|
|
202
|
+
this.viewSession = '';
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// The runs in work by directory, for the screen of worktrees
|
|
206
|
+
liveByRoot() {
|
|
207
|
+
return this.away.counts(this.runsRoot, this.jobs);
|
|
208
|
+
}
|
|
209
|
+
|
|
227
210
|
saveRuns() {
|
|
228
211
|
if (!this.root) return;
|
|
229
|
-
saveRunFiles(this.root, this.jobs);
|
|
212
|
+
saveRunFiles(this.runsRoot || this.root, this.jobs);
|
|
213
|
+
this.away.save();
|
|
230
214
|
}
|
|
231
215
|
|
|
232
216
|
clampCursor() {
|
|
@@ -244,8 +228,8 @@ class AgentsController extends RunPanel {
|
|
|
244
228
|
|
|
245
229
|
choice(id) {
|
|
246
230
|
const row = this.cliById(id);
|
|
247
|
-
if (!row) return emptyChoice(
|
|
248
|
-
if (!this.choices.has(id)) this.choices.set(id, emptyChoice(
|
|
231
|
+
if (!row) return emptyChoice();
|
|
232
|
+
if (!this.choices.has(id)) this.choices.set(id, emptyChoice());
|
|
249
233
|
return this.choices.get(id);
|
|
250
234
|
}
|
|
251
235
|
|
|
@@ -256,13 +240,14 @@ class AgentsController extends RunPanel {
|
|
|
256
240
|
const picked = this.choice(row.id);
|
|
257
241
|
const choices = optionsOf(row);
|
|
258
242
|
const model = choices.normalize(picked);
|
|
243
|
+
const { effort, fast, context } = choices.settings(picked);
|
|
259
244
|
return {
|
|
260
245
|
...row,
|
|
261
246
|
model,
|
|
262
|
-
effort
|
|
263
|
-
efforts: choices.efforts(
|
|
264
|
-
fast
|
|
265
|
-
context
|
|
247
|
+
effort,
|
|
248
|
+
efforts: choices.efforts(model),
|
|
249
|
+
fast,
|
|
250
|
+
context,
|
|
266
251
|
contexts: choices.contexts(model),
|
|
267
252
|
plan: row.bin ? plan : '',
|
|
268
253
|
planBusy: Boolean(row.bin && planBusy),
|
|
@@ -317,25 +302,21 @@ class AgentsController extends RunPanel {
|
|
|
317
302
|
}
|
|
318
303
|
|
|
319
304
|
jobById(id) {
|
|
320
|
-
return this.
|
|
305
|
+
return this.everyJob().find((job) => job.id === id) ?? null;
|
|
321
306
|
}
|
|
322
307
|
|
|
323
308
|
viewedJob() {
|
|
324
309
|
return this.jobById(this.viewId);
|
|
325
310
|
}
|
|
326
311
|
|
|
327
|
-
runningJob(id
|
|
328
|
-
const live = (job) =>
|
|
329
|
-
const sameCli = job.cliId === id;
|
|
330
|
-
const running = job.status === 'running';
|
|
331
|
-
const sameAction = !action || job.action === action;
|
|
332
|
-
return sameCli && running && sameAction;
|
|
333
|
-
};
|
|
312
|
+
runningJob(id) {
|
|
313
|
+
const live = (job) => job.cliId === id && job.status === 'running';
|
|
334
314
|
return this.jobs.findLast(live) ?? null;
|
|
335
315
|
}
|
|
336
316
|
|
|
337
317
|
runningCount() {
|
|
338
|
-
|
|
318
|
+
const live = (job) => job.status === 'running';
|
|
319
|
+
return this.everyJob().filter(live).length;
|
|
339
320
|
}
|
|
340
321
|
|
|
341
322
|
summary() {
|
|
@@ -381,17 +362,18 @@ class AgentsController extends RunPanel {
|
|
|
381
362
|
if (!onAgents) return NO_LIST;
|
|
382
363
|
const agentRuns = this.sessionRows();
|
|
383
364
|
const selected = agentRuns[this.runCursor];
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
365
|
+
const running = Boolean(selected && selected.status === 'running');
|
|
366
|
+
const fresh = Boolean(selected && selected.fresh);
|
|
367
|
+
const resume = Boolean(continuedId(selected));
|
|
368
|
+
const agentCanStart = !running && (fresh || resume);
|
|
388
369
|
return {
|
|
389
370
|
agents: this.rows(),
|
|
371
|
+
agentInstallPackage: this.installPackage(),
|
|
390
372
|
agentRuns,
|
|
391
373
|
agentRunCursor: this.runCursor,
|
|
392
374
|
agentRunScroll: this.runScroll,
|
|
393
375
|
agentFocus: this.focus,
|
|
394
|
-
|
|
376
|
+
agentCanStart,
|
|
395
377
|
agentPlanCount: this.reviewChoices().length,
|
|
396
378
|
agentView: this.viewing,
|
|
397
379
|
};
|
|
@@ -409,7 +391,7 @@ class AgentsController extends RunPanel {
|
|
|
409
391
|
if (!job) return NO_JOB;
|
|
410
392
|
const running = job.status === 'running';
|
|
411
393
|
const name = stemOf(job.logName);
|
|
412
|
-
const agentCommand = name || job.command;
|
|
394
|
+
const agentCommand = job.title || name || job.command;
|
|
413
395
|
return {
|
|
414
396
|
agentOutput: job.output,
|
|
415
397
|
agentCommand,
|
|
@@ -465,25 +447,52 @@ class AgentsController extends RunPanel {
|
|
|
465
447
|
async loadModels() {
|
|
466
448
|
this.modelsEpoch += 1;
|
|
467
449
|
const epoch = this.modelsEpoch;
|
|
468
|
-
const
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if (!
|
|
472
|
-
await Promise.all(
|
|
450
|
+
const waiting = this.list.filter(
|
|
451
|
+
(row) => row.bin && !this.loaded.has(cacheKey(row)),
|
|
452
|
+
);
|
|
453
|
+
if (!waiting.length) return;
|
|
454
|
+
await Promise.all(waiting.map((row) => this.fetchModels(row, epoch)));
|
|
473
455
|
if (epoch === this.modelsEpoch) this.ui.paint();
|
|
474
456
|
}
|
|
475
457
|
|
|
458
|
+
probeOptions(model) {
|
|
459
|
+
this.probes ??= new AbortController();
|
|
460
|
+
const { signal } = this.probes;
|
|
461
|
+
const models = model ? [model] : [];
|
|
462
|
+
return { env: this.env, cwd: this.root, signal, models };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// The agent lists its models only to a session, so one is opened to ask
|
|
476
466
|
async fetchModels(row, epoch) {
|
|
477
|
-
const
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
const
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
this.
|
|
467
|
+
const options = this.probeOptions(this.choice(row.id).model);
|
|
468
|
+
const catalog = await listQuietly(this.listModels, row, options);
|
|
469
|
+
if (epoch !== this.modelsEpoch || !catalog) return;
|
|
470
|
+
this.loaded.set(cacheKey(row), catalog);
|
|
471
|
+
const current = this.cliById(row.id);
|
|
472
|
+
if (!current || current.bin !== row.bin) return;
|
|
473
|
+
this.applyCached(current);
|
|
474
|
+
optionsOf(current).preferEffort(this.choice(current.id), 'medium');
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// The settings of a model come from asking the agent about that model
|
|
478
|
+
async describeModel(row, model) {
|
|
479
|
+
const key = cacheKey(row);
|
|
480
|
+
const mark = `${key}:${model}`;
|
|
481
|
+
const known = this.loaded.get(key);
|
|
482
|
+
if (!known || !catalogOf(known).unknown(model)) return;
|
|
483
|
+
if (this.asking.has(mark)) return;
|
|
484
|
+
this.asking.add(mark);
|
|
485
|
+
const options = this.probeOptions(model);
|
|
486
|
+
const found = await listQuietly(this.listModels, row, options);
|
|
487
|
+
this.asking.delete(mark);
|
|
488
|
+
const cached = this.loaded.get(key);
|
|
489
|
+
if (!cached || !found || !(found.byModel instanceof Map)) return;
|
|
490
|
+
const prior = cached.byModel ?? [];
|
|
491
|
+
const byModel = new Map([...prior, ...found.byModel]);
|
|
492
|
+
this.loaded.set(key, { ...cached, byModel });
|
|
485
493
|
const current = this.cliById(row.id);
|
|
486
494
|
if (current && current.bin === row.bin) this.applyCached(current);
|
|
495
|
+
this.ui.paint();
|
|
487
496
|
}
|
|
488
497
|
|
|
489
498
|
logWidth() {
|
|
@@ -560,9 +569,7 @@ class AgentsController extends RunPanel {
|
|
|
560
569
|
const choices = optionsOf(row);
|
|
561
570
|
const picked = this.choice(row.id);
|
|
562
571
|
if (field === 'model') return choices.names;
|
|
563
|
-
if (field === 'context')
|
|
564
|
-
return choices.contexts(choices.shown(picked.model));
|
|
565
|
-
}
|
|
572
|
+
if (field === 'context') return choices.contexts(picked.model);
|
|
566
573
|
return choices.efforts(picked.model);
|
|
567
574
|
}
|
|
568
575
|
|
|
@@ -573,9 +580,10 @@ class AgentsController extends RunPanel {
|
|
|
573
580
|
return firstEnabled(items, Math.max(0, at), this.busyCheck());
|
|
574
581
|
}
|
|
575
582
|
const picked = this.choice(row.id);
|
|
576
|
-
const
|
|
577
|
-
const
|
|
578
|
-
|
|
583
|
+
const choices = optionsOf(row);
|
|
584
|
+
const shown = { ...choices.settings(picked) };
|
|
585
|
+
shown.model = choices.shown(picked.model);
|
|
586
|
+
return Math.max(0, items.indexOf(shown[field]));
|
|
579
587
|
}
|
|
580
588
|
|
|
581
589
|
menuItems() {
|
|
@@ -621,7 +629,7 @@ class AgentsController extends RunPanel {
|
|
|
621
629
|
if (!row || !row.bin) return;
|
|
622
630
|
optionsOf(row).normalize(this.choice(row.id));
|
|
623
631
|
const items = this.fieldItems(field, row);
|
|
624
|
-
if (items.length <
|
|
632
|
+
if (items.length < 2) return;
|
|
625
633
|
this.pick = new MenuPick(field, this.fieldStart(field, items, row));
|
|
626
634
|
this.ui.status = '';
|
|
627
635
|
this.ui.paint();
|
|
@@ -654,6 +662,7 @@ class AgentsController extends RunPanel {
|
|
|
654
662
|
const apply = APPLY_FIELD[field] ?? APPLY_FIELD.effort;
|
|
655
663
|
apply(optionsOf(row), picked, value);
|
|
656
664
|
this.saveChoice(row.id);
|
|
665
|
+
if (field === 'model') void this.describeModel(row, picked.model);
|
|
657
666
|
return `${field} ${picked[field]}`;
|
|
658
667
|
}
|
|
659
668
|
|
|
@@ -680,9 +689,9 @@ class AgentsController extends RunPanel {
|
|
|
680
689
|
return;
|
|
681
690
|
}
|
|
682
691
|
const picked = this.choice(row.id);
|
|
683
|
-
optionsOf(row)
|
|
684
|
-
this.saveChoice(row.id);
|
|
685
|
-
this.ui.status = picked.fast
|
|
692
|
+
const choices = optionsOf(row);
|
|
693
|
+
if (choices.toggleFast(picked)) this.saveChoice(row.id);
|
|
694
|
+
this.ui.status = `fast ${choices.settings(picked).fast}`;
|
|
686
695
|
this.ui.paint();
|
|
687
696
|
}
|
|
688
697
|
|
|
@@ -718,7 +727,7 @@ class AgentsController extends RunPanel {
|
|
|
718
727
|
busyPlans() {
|
|
719
728
|
const busy = new Set();
|
|
720
729
|
for (const job of this.jobs) {
|
|
721
|
-
if (job.status !== 'running'
|
|
730
|
+
if (job.status !== 'running') continue;
|
|
722
731
|
const name = path.basename(`${job.plan ?? ''}`);
|
|
723
732
|
if (name) busy.add(name);
|
|
724
733
|
}
|
|
@@ -766,12 +775,10 @@ class AgentsController extends RunPanel {
|
|
|
766
775
|
}
|
|
767
776
|
|
|
768
777
|
closeView() {
|
|
769
|
-
const job = this.viewedJob();
|
|
770
778
|
this.viewing = false;
|
|
771
779
|
this.viewSession = '';
|
|
772
780
|
this.historyText = '';
|
|
773
|
-
|
|
774
|
-
this.ui.status = waiting ? 'logging in' : '';
|
|
781
|
+
this.ui.status = '';
|
|
775
782
|
this.ui.paint();
|
|
776
783
|
}
|
|
777
784
|
|
|
@@ -790,6 +797,7 @@ class AgentsController extends RunPanel {
|
|
|
790
797
|
showOutput(job, text) {
|
|
791
798
|
job.output = text;
|
|
792
799
|
job.dirty = true;
|
|
800
|
+
job.captureStats();
|
|
793
801
|
this.keepLog(job);
|
|
794
802
|
const watched = this.viewing && this.viewId === job.id;
|
|
795
803
|
if (watched || this.ui.nav.pane === 'agents') this.ui.paint();
|
|
@@ -808,77 +816,13 @@ class AgentsController extends RunPanel {
|
|
|
808
816
|
if (job.status !== 'running') return;
|
|
809
817
|
const stopped = job.stopping;
|
|
810
818
|
this.settleJob(job, stopped ? null : result);
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
const signedIn = !stopped && isLogin && result.status === 0;
|
|
814
|
-
if (stopped && watched) this.ui.status = 'stopped';
|
|
815
|
-
else if (signedIn) this.ui.status = 'logged in';
|
|
816
|
-
this.ui.paint();
|
|
817
|
-
if (isLogin) return void this.finishLogin(job, result, stopped, signedIn);
|
|
818
|
-
if (stopped || !needsAuth(job.output)) return;
|
|
819
|
-
const cli = this.cliById(job.cliId);
|
|
820
|
-
if (canLogin(cli)) this.startLogin(cli);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
finishLogin(job, result, stopped, signedIn) {
|
|
824
|
-
if (!signedIn) {
|
|
825
|
-
this.pendingStart = null;
|
|
826
|
-
if (!stopped && result) this.ui.status = 'not logged in';
|
|
827
|
-
this.ui.paint();
|
|
828
|
-
return;
|
|
819
|
+
if (stopped && this.viewing && this.viewId === job.id) {
|
|
820
|
+
this.ui.status = 'stopped';
|
|
829
821
|
}
|
|
830
|
-
this.authYes.add(job.cliId);
|
|
831
|
-
const pending = this.pendingStart;
|
|
832
|
-
this.pendingStart = null;
|
|
833
|
-
if (!pending || pending.id !== job.cliId) return;
|
|
834
|
-
const cli = this.cliById(pending.id);
|
|
835
|
-
if (cli) this.launchReady(cli, pending.session || '');
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
authAnswer(row) {
|
|
839
|
-
if (!canLogin(row)) return true;
|
|
840
|
-
const args = row.spec && row.spec.status;
|
|
841
|
-
if (!args || !args.length) return true;
|
|
842
|
-
if (this.authYes.has(row.id)) return true;
|
|
843
|
-
return this.authProbe(row);
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
beginStart(row, sessionId = '') {
|
|
847
|
-
const id = row.id;
|
|
848
|
-
if (this.authWait === id) return;
|
|
849
|
-
const answer = this.authAnswer(row);
|
|
850
|
-
const go = (ok) => {
|
|
851
|
-
if (this.authWait === id) this.authWait = '';
|
|
852
|
-
const current = this.cliById(id);
|
|
853
|
-
if (!current) return;
|
|
854
|
-
this.afterAuth(current, sessionId, ok);
|
|
855
|
-
};
|
|
856
|
-
if (answer && typeof answer.then === 'function') {
|
|
857
|
-
this.authWait = id;
|
|
858
|
-
answer.then(go, () => go(null));
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
|
-
this.afterAuth(row, sessionId, answer);
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
afterAuth(row, sessionId, ok) {
|
|
865
|
-
if (ok === true) this.authYes.add(row.id);
|
|
866
|
-
if (ok === false) return void this.holdForLogin(row, sessionId);
|
|
867
|
-
this.launchReady(row, sessionId);
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
holdForLogin(row, sessionId) {
|
|
871
|
-
this.pendingStart = { id: row.id, session: sessionId };
|
|
872
|
-
this.startLogin(row);
|
|
873
|
-
this.ui.status = 'not logged in';
|
|
874
822
|
this.ui.paint();
|
|
875
823
|
}
|
|
876
824
|
|
|
877
|
-
|
|
878
|
-
this.startJob(row, sessionId);
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
startLaunch(launch, model, showLog = true) {
|
|
825
|
+
startLaunch(launch, model) {
|
|
882
826
|
if (!launch.ok) {
|
|
883
827
|
this.ui.status = launch.error;
|
|
884
828
|
return;
|
|
@@ -893,14 +837,15 @@ class AgentsController extends RunPanel {
|
|
|
893
837
|
if (job.status === 'running') job.child = child;
|
|
894
838
|
this.saveRuns();
|
|
895
839
|
this.syncProgress();
|
|
896
|
-
if (
|
|
840
|
+
if (this.jobs.at(-1) === job) this.openView(job.id);
|
|
897
841
|
}
|
|
898
842
|
|
|
899
843
|
startJob(row, sessionId = '') {
|
|
900
844
|
const picked = this.choice(row.id);
|
|
901
845
|
const plan = this.ensurePlan();
|
|
902
846
|
const launch = buildLaunch(row, picked, plan, this.root, sessionId);
|
|
903
|
-
this.
|
|
847
|
+
const title = planCaption(this.root, launch.plan);
|
|
848
|
+
this.startLaunch({ ...launch, title }, picked.model);
|
|
904
849
|
}
|
|
905
850
|
|
|
906
851
|
prepareStart() {
|
|
@@ -922,19 +867,90 @@ class AgentsController extends RunPanel {
|
|
|
922
867
|
return row;
|
|
923
868
|
}
|
|
924
869
|
|
|
925
|
-
startLogin(row) {
|
|
926
|
-
const live = this.runningJob(row.id, 'login');
|
|
927
|
-
if (!live) this.startLaunch(buildLogin(row), '', false);
|
|
928
|
-
if (this.viewing) this.closeView();
|
|
929
|
-
this.ui.status = 'logging in';
|
|
930
|
-
this.ui.paint();
|
|
931
|
-
}
|
|
932
|
-
|
|
933
870
|
rerun() {
|
|
934
871
|
if (!this.viewing) return;
|
|
935
872
|
const job = this.viewedJob();
|
|
936
|
-
if (!job || job.status === 'running'
|
|
937
|
-
|
|
873
|
+
if (!job || job.status === 'running') return;
|
|
874
|
+
if (job.action === 'install') {
|
|
875
|
+
this.closeView();
|
|
876
|
+
return void this.install(job.cliId);
|
|
877
|
+
}
|
|
878
|
+
const row = this.cliById(job.cliId);
|
|
879
|
+
const plan = job.plan ? path.resolve(this.root, job.plan) : '';
|
|
880
|
+
const launch = buildLaunch(row, job.choice, plan, this.root, job.resume);
|
|
881
|
+
const title = planCaption(this.root, launch.plan) || job.title;
|
|
882
|
+
this.startLaunch({ ...launch, title }, job.model);
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
installPackage() {
|
|
886
|
+
const row = this.cliById(this.installTarget);
|
|
887
|
+
return row ? row.package || row.installLabel : '';
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
proposeInstall() {
|
|
891
|
+
if (this.viewing || this.pick) return;
|
|
892
|
+
const row = this.selected();
|
|
893
|
+
const live = row ? this.installs.get(row.id) : null;
|
|
894
|
+
if (live && live.status === 'running') return void this.openView(live.id);
|
|
895
|
+
const launch = buildInstall(row);
|
|
896
|
+
if (!launch.ok) {
|
|
897
|
+
this.ui.status = launch.error;
|
|
898
|
+
return void this.ui.paint();
|
|
899
|
+
}
|
|
900
|
+
this.installTarget = row.id;
|
|
901
|
+
this.ui.status = '';
|
|
902
|
+
this.ui.mode = 'confirmAgentInstall';
|
|
903
|
+
this.ui.paint();
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
cancelInstall() {
|
|
907
|
+
this.installTarget = '';
|
|
908
|
+
this.ui.mode = 'review';
|
|
909
|
+
this.ui.paint();
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
confirmInstall() {
|
|
913
|
+
const id = this.installTarget;
|
|
914
|
+
this.installTarget = '';
|
|
915
|
+
this.ui.mode = 'review';
|
|
916
|
+
this.install(id);
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
install(id = '') {
|
|
920
|
+
if (this.viewing || this.pick) return;
|
|
921
|
+
const row = id ? this.cliById(id) : this.selected();
|
|
922
|
+
const launch = buildInstall(row);
|
|
923
|
+
if (!launch.ok) {
|
|
924
|
+
this.ui.status = launch.error;
|
|
925
|
+
return void this.ui.paint();
|
|
926
|
+
}
|
|
927
|
+
const job = launchJob(this.nextId, launch, '');
|
|
928
|
+
this.nextId += 1;
|
|
929
|
+
this.installs.set(row.id, job);
|
|
930
|
+
const onData = (text) => this.showOutput(job, text);
|
|
931
|
+
const onClose = (result) => this.finishInstall(job, result);
|
|
932
|
+
const child = this.spawn(this.root, launch, onData, onClose);
|
|
933
|
+
if (job.status === 'running') job.child = child;
|
|
934
|
+
this.syncProgress();
|
|
935
|
+
this.openView(job.id);
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// Refresh PATH and managed registry locations after the installer exits.
|
|
939
|
+
finishInstall(job, result) {
|
|
940
|
+
if (job.status !== 'running') return;
|
|
941
|
+
const stopped = job.stopping;
|
|
942
|
+
this.settleJob(job, stopped ? null : result);
|
|
943
|
+
this.ui.status = stopped ? 'stopped' : this.installOutcome(job, result);
|
|
944
|
+
this.ui.paint();
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
installOutcome(job, result) {
|
|
948
|
+
if (result.status !== 0) return 'install failed';
|
|
949
|
+
this.refresh();
|
|
950
|
+
const row = this.cliById(job.cliId);
|
|
951
|
+
if (!row || !row.bin) return 'installed, but not found';
|
|
952
|
+
void this.loadModels();
|
|
953
|
+
return `installed ${job.name}`;
|
|
938
954
|
}
|
|
939
955
|
|
|
940
956
|
start() {
|
|
@@ -942,18 +958,17 @@ class AgentsController extends RunPanel {
|
|
|
942
958
|
const session = this.selectedSession();
|
|
943
959
|
if (this.focus === 'runs' && this.openRunRow(session)) return;
|
|
944
960
|
const row = this.prepareStart();
|
|
945
|
-
if (
|
|
946
|
-
this.beginStart(row, continuedId(session));
|
|
961
|
+
if (row) this.startJob(row, continuedId(session));
|
|
947
962
|
}
|
|
948
963
|
|
|
949
|
-
|
|
950
|
-
if (this.viewing || this.pick
|
|
964
|
+
startSelected() {
|
|
965
|
+
if (this.viewing || this.pick) return;
|
|
951
966
|
const session = this.selectedSession();
|
|
952
|
-
|
|
953
|
-
|
|
967
|
+
if (!session || session.status === 'running') return;
|
|
968
|
+
const resume = session.fresh ? '' : continuedId(session);
|
|
969
|
+
if (!session.fresh && !resume) return;
|
|
954
970
|
const row = this.prepareStart();
|
|
955
|
-
if (
|
|
956
|
-
this.beginStart(row, id);
|
|
971
|
+
if (row) this.startJob(row, resume);
|
|
957
972
|
}
|
|
958
973
|
|
|
959
974
|
selectedJob() {
|