reslop 0.3.0 → 0.3.2
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 +29 -1
- package/README.md +21 -3
- package/lib/agents/acp.js +309 -0
- package/lib/agents/agents.js +166 -370
- package/lib/agents/allow.js +141 -52
- package/lib/agents/allow.json +16 -13
- package/lib/agents/models.js +250 -376
- package/lib/agents/registry.js +437 -0
- package/lib/agents/run.js +234 -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/deps.js +29 -0
- package/lib/git/dir.js +50 -0
- package/lib/git/git.js +1 -1
- 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 +137 -31
- package/lib/input/actions.js +83 -2
- package/lib/input/editor.js +9 -0
- package/lib/input/keys.js +39 -3
- package/lib/input/line-edit.js +8 -3
- package/lib/render/agents.js +64 -36
- package/lib/render/branches.js +55 -27
- package/lib/render/field.js +19 -5
- package/lib/render/footer.js +14 -4
- package/lib/render/frame.js +16 -2
- package/lib/render/log.js +5 -4
- 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/render.js +4 -7
- package/lib/report/run.js +16 -23
- package/lib/review/agent-instructions.md +13 -8
- package/lib/review/format.js +0 -6
- package/lib/runs/process.js +3 -3
- package/lib/runs/runs.js +27 -4
- package/lib/session/agents/agents.js +227 -194
- 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 -2
- package/lib/session/compose.js +20 -6
- package/lib/session/dispatch.js +43 -3
- package/lib/session/editor-pointer.js +98 -0
- package/lib/session/files-pane.js +3 -0
- package/lib/session/import.js +12 -1
- package/lib/session/lifecycle.js +70 -10
- package/lib/session/navigation.js +5 -0
- package/lib/session/npm/history.js +3 -1
- package/lib/session/npm/npm.js +111 -13
- package/lib/session/ops.js +3 -0
- package/lib/session/pointer.js +304 -46
- package/lib/session/session.js +17 -2
- package/lib/session/tasks.js +1 -1
- 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/select.js +2 -1
- 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,41 +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
|
-
agentPlanCount: 0,
|
|
95
|
-
agentView: false,
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const NO_JOB = {
|
|
99
|
-
agentOutput: '',
|
|
100
|
-
agentCommand: '',
|
|
101
|
-
agentElapsed: '',
|
|
102
|
-
agentProgress: '',
|
|
103
|
-
agentRunning: false,
|
|
104
|
-
agentStatus: '',
|
|
105
|
-
};
|
|
106
|
-
|
|
107
71
|
class AgentsController extends RunPanel {
|
|
108
72
|
constructor(ui) {
|
|
109
73
|
super(ui);
|
|
110
74
|
this.spawn = startAgent;
|
|
111
75
|
this.listModels = listModels;
|
|
112
|
-
this.
|
|
76
|
+
this.probes = null;
|
|
113
77
|
this.reset();
|
|
114
78
|
}
|
|
115
79
|
|
|
@@ -122,8 +86,12 @@ class AgentsController extends RunPanel {
|
|
|
122
86
|
this.list = [];
|
|
123
87
|
this.choices = new Map();
|
|
124
88
|
this.loaded = new Map();
|
|
89
|
+
this.asking = new Set();
|
|
125
90
|
this.modelsEpoch = 0;
|
|
126
91
|
this.jobs = [];
|
|
92
|
+
this.away = new AwayRuns();
|
|
93
|
+
this.installs = new Map();
|
|
94
|
+
this.installTarget = '';
|
|
127
95
|
this.nextId = 1;
|
|
128
96
|
this.viewing = false;
|
|
129
97
|
this.viewId = 0;
|
|
@@ -139,31 +107,34 @@ class AgentsController extends RunPanel {
|
|
|
139
107
|
this.viewSession = '';
|
|
140
108
|
this.historyText = '';
|
|
141
109
|
this.historyAt = 0;
|
|
142
|
-
this.pendingStart = null;
|
|
143
|
-
this.authYes = new Set();
|
|
144
|
-
this.authWait = '';
|
|
145
110
|
this.env = process.env;
|
|
146
111
|
this.refresh();
|
|
147
112
|
this.loadRuns();
|
|
148
113
|
}
|
|
149
114
|
|
|
150
115
|
killAll() {
|
|
151
|
-
|
|
116
|
+
if (this.probes) this.probes.abort();
|
|
117
|
+
this.probes = null;
|
|
118
|
+
for (const job of this.everyJob()) {
|
|
152
119
|
if (job.child) job.child.kill();
|
|
153
120
|
}
|
|
154
121
|
}
|
|
155
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
|
+
|
|
156
131
|
reviewStore() {
|
|
157
132
|
return this.ui.review ? this.ui.review.store : null;
|
|
158
133
|
}
|
|
159
134
|
|
|
160
135
|
applyCached(row) {
|
|
161
136
|
const cached = this.loaded.get(cacheKey(row));
|
|
162
|
-
if (
|
|
163
|
-
row.models = cached.models;
|
|
164
|
-
row.wide = cached.wide;
|
|
165
|
-
row.listedEfforts = cached.efforts;
|
|
166
|
-
row.modelEfforts = cached.modelEfforts;
|
|
137
|
+
if (cached) row.catalog = cached;
|
|
167
138
|
}
|
|
168
139
|
|
|
169
140
|
refresh(env = this.env) {
|
|
@@ -173,7 +144,7 @@ class AgentsController extends RunPanel {
|
|
|
173
144
|
this.choices = new Map();
|
|
174
145
|
for (const row of this.list) {
|
|
175
146
|
this.applyCached(row);
|
|
176
|
-
this.choices.set(row.id, prev.get(row.id) ?? emptyChoice(
|
|
147
|
+
this.choices.set(row.id, prev.get(row.id) ?? emptyChoice());
|
|
177
148
|
}
|
|
178
149
|
this.loadChoices();
|
|
179
150
|
this.clampCursor();
|
|
@@ -184,7 +155,7 @@ class AgentsController extends RunPanel {
|
|
|
184
155
|
const saved = readPrefs(this.root).agents;
|
|
185
156
|
for (const row of this.list) {
|
|
186
157
|
const picked = this.choice(row.id);
|
|
187
|
-
restoreChoice(picked, saved[row.id]
|
|
158
|
+
restoreChoice(picked, saved[row.id]);
|
|
188
159
|
optionsOf(row).preferEffort(picked, 'medium');
|
|
189
160
|
}
|
|
190
161
|
}
|
|
@@ -195,7 +166,7 @@ class AgentsController extends RunPanel {
|
|
|
195
166
|
if (!this.root || !picked || !row) return;
|
|
196
167
|
const data = readPrefs(this.root);
|
|
197
168
|
const saved = { ...data.agents };
|
|
198
|
-
const stored = storedChoice(picked
|
|
169
|
+
const stored = storedChoice(picked);
|
|
199
170
|
if (stored) saved[id] = stored;
|
|
200
171
|
else delete saved[id];
|
|
201
172
|
writePrefs(this.root, { ...data, agents: saved });
|
|
@@ -210,22 +181,36 @@ class AgentsController extends RunPanel {
|
|
|
210
181
|
}
|
|
211
182
|
|
|
212
183
|
// The repository can change after construction: its runs and its saved
|
|
213
|
-
// 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
|
|
214
186
|
syncRuns() {
|
|
215
187
|
if (!this.root || this.root === this.runsRoot) return;
|
|
216
|
-
|
|
217
|
-
this.
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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);
|
|
223
195
|
this.viewId = 0;
|
|
224
196
|
}
|
|
225
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
|
+
|
|
226
210
|
saveRuns() {
|
|
227
211
|
if (!this.root) return;
|
|
228
|
-
saveRunFiles(this.root, this.jobs);
|
|
212
|
+
saveRunFiles(this.runsRoot || this.root, this.jobs);
|
|
213
|
+
this.away.save();
|
|
229
214
|
}
|
|
230
215
|
|
|
231
216
|
clampCursor() {
|
|
@@ -243,8 +228,8 @@ class AgentsController extends RunPanel {
|
|
|
243
228
|
|
|
244
229
|
choice(id) {
|
|
245
230
|
const row = this.cliById(id);
|
|
246
|
-
if (!row) return emptyChoice(
|
|
247
|
-
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());
|
|
248
233
|
return this.choices.get(id);
|
|
249
234
|
}
|
|
250
235
|
|
|
@@ -255,13 +240,14 @@ class AgentsController extends RunPanel {
|
|
|
255
240
|
const picked = this.choice(row.id);
|
|
256
241
|
const choices = optionsOf(row);
|
|
257
242
|
const model = choices.normalize(picked);
|
|
243
|
+
const { effort, fast, context } = choices.settings(picked);
|
|
258
244
|
return {
|
|
259
245
|
...row,
|
|
260
246
|
model,
|
|
261
|
-
effort
|
|
262
|
-
efforts: choices.efforts(
|
|
263
|
-
fast
|
|
264
|
-
context
|
|
247
|
+
effort,
|
|
248
|
+
efforts: choices.efforts(model),
|
|
249
|
+
fast,
|
|
250
|
+
context,
|
|
265
251
|
contexts: choices.contexts(model),
|
|
266
252
|
plan: row.bin ? plan : '',
|
|
267
253
|
planBusy: Boolean(row.bin && planBusy),
|
|
@@ -316,25 +302,21 @@ class AgentsController extends RunPanel {
|
|
|
316
302
|
}
|
|
317
303
|
|
|
318
304
|
jobById(id) {
|
|
319
|
-
return this.
|
|
305
|
+
return this.everyJob().find((job) => job.id === id) ?? null;
|
|
320
306
|
}
|
|
321
307
|
|
|
322
308
|
viewedJob() {
|
|
323
309
|
return this.jobById(this.viewId);
|
|
324
310
|
}
|
|
325
311
|
|
|
326
|
-
runningJob(id
|
|
327
|
-
const live = (job) =>
|
|
328
|
-
const sameCli = job.cliId === id;
|
|
329
|
-
const running = job.status === 'running';
|
|
330
|
-
const sameAction = !action || job.action === action;
|
|
331
|
-
return sameCli && running && sameAction;
|
|
332
|
-
};
|
|
312
|
+
runningJob(id) {
|
|
313
|
+
const live = (job) => job.cliId === id && job.status === 'running';
|
|
333
314
|
return this.jobs.findLast(live) ?? null;
|
|
334
315
|
}
|
|
335
316
|
|
|
336
317
|
runningCount() {
|
|
337
|
-
|
|
318
|
+
const live = (job) => job.status === 'running';
|
|
319
|
+
return this.everyJob().filter(live).length;
|
|
338
320
|
}
|
|
339
321
|
|
|
340
322
|
summary() {
|
|
@@ -378,12 +360,20 @@ class AgentsController extends RunPanel {
|
|
|
378
360
|
|
|
379
361
|
listView(onAgents) {
|
|
380
362
|
if (!onAgents) return NO_LIST;
|
|
363
|
+
const agentRuns = this.sessionRows();
|
|
364
|
+
const selected = agentRuns[this.runCursor];
|
|
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);
|
|
381
369
|
return {
|
|
382
370
|
agents: this.rows(),
|
|
383
|
-
|
|
371
|
+
agentInstallPackage: this.installPackage(),
|
|
372
|
+
agentRuns,
|
|
384
373
|
agentRunCursor: this.runCursor,
|
|
385
374
|
agentRunScroll: this.runScroll,
|
|
386
375
|
agentFocus: this.focus,
|
|
376
|
+
agentCanStart,
|
|
387
377
|
agentPlanCount: this.reviewChoices().length,
|
|
388
378
|
agentView: this.viewing,
|
|
389
379
|
};
|
|
@@ -401,7 +391,7 @@ class AgentsController extends RunPanel {
|
|
|
401
391
|
if (!job) return NO_JOB;
|
|
402
392
|
const running = job.status === 'running';
|
|
403
393
|
const name = stemOf(job.logName);
|
|
404
|
-
const agentCommand = name || job.command;
|
|
394
|
+
const agentCommand = job.title || name || job.command;
|
|
405
395
|
return {
|
|
406
396
|
agentOutput: job.output,
|
|
407
397
|
agentCommand,
|
|
@@ -457,25 +447,52 @@ class AgentsController extends RunPanel {
|
|
|
457
447
|
async loadModels() {
|
|
458
448
|
this.modelsEpoch += 1;
|
|
459
449
|
const epoch = this.modelsEpoch;
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
if (!
|
|
464
|
-
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)));
|
|
465
455
|
if (epoch === this.modelsEpoch) this.ui.paint();
|
|
466
456
|
}
|
|
467
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
|
|
468
466
|
async fetchModels(row, epoch) {
|
|
469
|
-
const
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
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 });
|
|
477
493
|
const current = this.cliById(row.id);
|
|
478
494
|
if (current && current.bin === row.bin) this.applyCached(current);
|
|
495
|
+
this.ui.paint();
|
|
479
496
|
}
|
|
480
497
|
|
|
481
498
|
logWidth() {
|
|
@@ -552,9 +569,7 @@ class AgentsController extends RunPanel {
|
|
|
552
569
|
const choices = optionsOf(row);
|
|
553
570
|
const picked = this.choice(row.id);
|
|
554
571
|
if (field === 'model') return choices.names;
|
|
555
|
-
if (field === 'context')
|
|
556
|
-
return choices.contexts(choices.shown(picked.model));
|
|
557
|
-
}
|
|
572
|
+
if (field === 'context') return choices.contexts(picked.model);
|
|
558
573
|
return choices.efforts(picked.model);
|
|
559
574
|
}
|
|
560
575
|
|
|
@@ -565,9 +580,10 @@ class AgentsController extends RunPanel {
|
|
|
565
580
|
return firstEnabled(items, Math.max(0, at), this.busyCheck());
|
|
566
581
|
}
|
|
567
582
|
const picked = this.choice(row.id);
|
|
568
|
-
const
|
|
569
|
-
const
|
|
570
|
-
|
|
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]));
|
|
571
587
|
}
|
|
572
588
|
|
|
573
589
|
menuItems() {
|
|
@@ -613,7 +629,7 @@ class AgentsController extends RunPanel {
|
|
|
613
629
|
if (!row || !row.bin) return;
|
|
614
630
|
optionsOf(row).normalize(this.choice(row.id));
|
|
615
631
|
const items = this.fieldItems(field, row);
|
|
616
|
-
if (items.length <
|
|
632
|
+
if (items.length < 2) return;
|
|
617
633
|
this.pick = new MenuPick(field, this.fieldStart(field, items, row));
|
|
618
634
|
this.ui.status = '';
|
|
619
635
|
this.ui.paint();
|
|
@@ -646,6 +662,7 @@ class AgentsController extends RunPanel {
|
|
|
646
662
|
const apply = APPLY_FIELD[field] ?? APPLY_FIELD.effort;
|
|
647
663
|
apply(optionsOf(row), picked, value);
|
|
648
664
|
this.saveChoice(row.id);
|
|
665
|
+
if (field === 'model') void this.describeModel(row, picked.model);
|
|
649
666
|
return `${field} ${picked[field]}`;
|
|
650
667
|
}
|
|
651
668
|
|
|
@@ -672,9 +689,9 @@ class AgentsController extends RunPanel {
|
|
|
672
689
|
return;
|
|
673
690
|
}
|
|
674
691
|
const picked = this.choice(row.id);
|
|
675
|
-
optionsOf(row)
|
|
676
|
-
this.saveChoice(row.id);
|
|
677
|
-
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}`;
|
|
678
695
|
this.ui.paint();
|
|
679
696
|
}
|
|
680
697
|
|
|
@@ -710,7 +727,7 @@ class AgentsController extends RunPanel {
|
|
|
710
727
|
busyPlans() {
|
|
711
728
|
const busy = new Set();
|
|
712
729
|
for (const job of this.jobs) {
|
|
713
|
-
if (job.status !== 'running'
|
|
730
|
+
if (job.status !== 'running') continue;
|
|
714
731
|
const name = path.basename(`${job.plan ?? ''}`);
|
|
715
732
|
if (name) busy.add(name);
|
|
716
733
|
}
|
|
@@ -758,12 +775,10 @@ class AgentsController extends RunPanel {
|
|
|
758
775
|
}
|
|
759
776
|
|
|
760
777
|
closeView() {
|
|
761
|
-
const job = this.viewedJob();
|
|
762
778
|
this.viewing = false;
|
|
763
779
|
this.viewSession = '';
|
|
764
780
|
this.historyText = '';
|
|
765
|
-
|
|
766
|
-
this.ui.status = waiting ? 'logging in' : '';
|
|
781
|
+
this.ui.status = '';
|
|
767
782
|
this.ui.paint();
|
|
768
783
|
}
|
|
769
784
|
|
|
@@ -782,6 +797,7 @@ class AgentsController extends RunPanel {
|
|
|
782
797
|
showOutput(job, text) {
|
|
783
798
|
job.output = text;
|
|
784
799
|
job.dirty = true;
|
|
800
|
+
job.captureStats();
|
|
785
801
|
this.keepLog(job);
|
|
786
802
|
const watched = this.viewing && this.viewId === job.id;
|
|
787
803
|
if (watched || this.ui.nav.pane === 'agents') this.ui.paint();
|
|
@@ -800,77 +816,13 @@ class AgentsController extends RunPanel {
|
|
|
800
816
|
if (job.status !== 'running') return;
|
|
801
817
|
const stopped = job.stopping;
|
|
802
818
|
this.settleJob(job, stopped ? null : result);
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
const signedIn = !stopped && isLogin && result.status === 0;
|
|
806
|
-
if (stopped && watched) this.ui.status = 'stopped';
|
|
807
|
-
else if (signedIn) this.ui.status = 'logged in';
|
|
808
|
-
this.ui.paint();
|
|
809
|
-
if (isLogin) return void this.finishLogin(job, result, stopped, signedIn);
|
|
810
|
-
if (stopped || !needsAuth(job.output)) return;
|
|
811
|
-
const cli = this.cliById(job.cliId);
|
|
812
|
-
if (canLogin(cli)) this.startLogin(cli);
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
finishLogin(job, result, stopped, signedIn) {
|
|
816
|
-
if (!signedIn) {
|
|
817
|
-
this.pendingStart = null;
|
|
818
|
-
if (!stopped && result) this.ui.status = 'not logged in';
|
|
819
|
-
this.ui.paint();
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
this.authYes.add(job.cliId);
|
|
823
|
-
const pending = this.pendingStart;
|
|
824
|
-
this.pendingStart = null;
|
|
825
|
-
if (!pending || pending.id !== job.cliId) return;
|
|
826
|
-
const cli = this.cliById(pending.id);
|
|
827
|
-
if (cli) this.launchReady(cli, pending.session || '');
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
authAnswer(row) {
|
|
831
|
-
if (!canLogin(row)) return true;
|
|
832
|
-
const args = row.spec && row.spec.status;
|
|
833
|
-
if (!args || !args.length) return true;
|
|
834
|
-
if (this.authYes.has(row.id)) return true;
|
|
835
|
-
return this.authProbe(row);
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
beginStart(row, sessionId = '') {
|
|
839
|
-
const id = row.id;
|
|
840
|
-
if (this.authWait === id) return;
|
|
841
|
-
const answer = this.authAnswer(row);
|
|
842
|
-
const go = (ok) => {
|
|
843
|
-
if (this.authWait === id) this.authWait = '';
|
|
844
|
-
const current = this.cliById(id);
|
|
845
|
-
if (!current) return;
|
|
846
|
-
this.afterAuth(current, sessionId, ok);
|
|
847
|
-
};
|
|
848
|
-
if (answer && typeof answer.then === 'function') {
|
|
849
|
-
this.authWait = id;
|
|
850
|
-
answer.then(go, () => go(null));
|
|
851
|
-
return;
|
|
819
|
+
if (stopped && this.viewing && this.viewId === job.id) {
|
|
820
|
+
this.ui.status = 'stopped';
|
|
852
821
|
}
|
|
853
|
-
this.afterAuth(row, sessionId, answer);
|
|
854
|
-
}
|
|
855
|
-
|
|
856
|
-
afterAuth(row, sessionId, ok) {
|
|
857
|
-
if (ok === true) this.authYes.add(row.id);
|
|
858
|
-
if (ok === false) return void this.holdForLogin(row, sessionId);
|
|
859
|
-
this.launchReady(row, sessionId);
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
holdForLogin(row, sessionId) {
|
|
863
|
-
this.pendingStart = { id: row.id, session: sessionId };
|
|
864
|
-
this.startLogin(row);
|
|
865
|
-
this.ui.status = 'not logged in';
|
|
866
822
|
this.ui.paint();
|
|
867
823
|
}
|
|
868
824
|
|
|
869
|
-
|
|
870
|
-
this.startJob(row, sessionId);
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
startLaunch(launch, model, showLog = true) {
|
|
825
|
+
startLaunch(launch, model) {
|
|
874
826
|
if (!launch.ok) {
|
|
875
827
|
this.ui.status = launch.error;
|
|
876
828
|
return;
|
|
@@ -885,14 +837,15 @@ class AgentsController extends RunPanel {
|
|
|
885
837
|
if (job.status === 'running') job.child = child;
|
|
886
838
|
this.saveRuns();
|
|
887
839
|
this.syncProgress();
|
|
888
|
-
if (
|
|
840
|
+
if (this.jobs.at(-1) === job) this.openView(job.id);
|
|
889
841
|
}
|
|
890
842
|
|
|
891
843
|
startJob(row, sessionId = '') {
|
|
892
844
|
const picked = this.choice(row.id);
|
|
893
845
|
const plan = this.ensurePlan();
|
|
894
846
|
const launch = buildLaunch(row, picked, plan, this.root, sessionId);
|
|
895
|
-
this.
|
|
847
|
+
const title = planCaption(this.root, launch.plan);
|
|
848
|
+
this.startLaunch({ ...launch, title }, picked.model);
|
|
896
849
|
}
|
|
897
850
|
|
|
898
851
|
prepareStart() {
|
|
@@ -914,19 +867,90 @@ class AgentsController extends RunPanel {
|
|
|
914
867
|
return row;
|
|
915
868
|
}
|
|
916
869
|
|
|
917
|
-
startLogin(row) {
|
|
918
|
-
const live = this.runningJob(row.id, 'login');
|
|
919
|
-
if (!live) this.startLaunch(buildLogin(row), '', false);
|
|
920
|
-
if (this.viewing) this.closeView();
|
|
921
|
-
this.ui.status = 'logging in';
|
|
922
|
-
this.ui.paint();
|
|
923
|
-
}
|
|
924
|
-
|
|
925
870
|
rerun() {
|
|
926
871
|
if (!this.viewing) return;
|
|
927
872
|
const job = this.viewedJob();
|
|
928
|
-
if (!job || job.status === 'running'
|
|
929
|
-
|
|
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}`;
|
|
930
954
|
}
|
|
931
955
|
|
|
932
956
|
start() {
|
|
@@ -934,8 +958,17 @@ class AgentsController extends RunPanel {
|
|
|
934
958
|
const session = this.selectedSession();
|
|
935
959
|
if (this.focus === 'runs' && this.openRunRow(session)) return;
|
|
936
960
|
const row = this.prepareStart();
|
|
937
|
-
if (
|
|
938
|
-
|
|
961
|
+
if (row) this.startJob(row, continuedId(session));
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
startSelected() {
|
|
965
|
+
if (this.viewing || this.pick) return;
|
|
966
|
+
const session = this.selectedSession();
|
|
967
|
+
if (!session || session.status === 'running') return;
|
|
968
|
+
const resume = session.fresh ? '' : continuedId(session);
|
|
969
|
+
if (!session.fresh && !resume) return;
|
|
970
|
+
const row = this.prepareStart();
|
|
971
|
+
if (row) this.startJob(row, resume);
|
|
939
972
|
}
|
|
940
973
|
|
|
941
974
|
selectedJob() {
|