copilot-operator 0.1.5 → 0.1.7
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/dist/src/api/operator.controller.js +37 -2
- package/dist/src/api/operator.service.js +110 -14
- package/dist/src/orchestrator/taskRunner.js +11 -1
- package/dist/src/plan/brief.js +52 -10
- package/dist/src/plan/script.js +13 -0
- package/dist/src/plan/systemGuide.js +1471 -190
- package/dist/src/session/compose.js +5 -1
- package/dist/src/session/exports.js +1 -0
- package/dist/src/session/store.js +4 -1
- package/dist/src/session/story.js +10 -1
- package/dist/src/vcs/git.js +71 -0
- package/dist/src/vcs/taskVcs.js +2 -1
- package/dist/web/404.html +2 -2
- package/dist/web/_next/static/chunks/549-33f0d3d53338b2f2.js +1 -0
- package/dist/web/_next/static/chunks/723-edc7b5709e5407ec.js +1 -0
- package/dist/web/_next/static/chunks/867-4aa80f5a32311bce.js +1 -0
- package/dist/web/_next/static/chunks/app/history/page-2251664c0abe3f11.js +1 -0
- package/dist/web/_next/static/chunks/app/layout-e0174d863960f109.js +1 -0
- package/dist/web/_next/static/chunks/app/sessions/view/page-9517dd74c6d00f85.js +1 -0
- package/dist/web/_next/static/css/{5a10c60adf9c5336.css → b67acb571615432b.css} +1 -1
- package/dist/web/appearance.html +2 -2
- package/dist/web/appearance.txt +25 -24
- package/dist/web/defaults.html +2 -2
- package/dist/web/defaults.txt +25 -24
- package/dist/web/history.html +2 -2
- package/dist/web/history.txt +25 -24
- package/dist/web/import.html +2 -2
- package/dist/web/import.txt +25 -24
- package/dist/web/index.html +2 -2
- package/dist/web/index.txt +25 -24
- package/dist/web/level1.html +2 -2
- package/dist/web/level1.txt +25 -24
- package/dist/web/presets.html +2 -2
- package/dist/web/presets.txt +25 -24
- package/dist/web/sessions/view.html +2 -2
- package/dist/web/sessions/view.txt +25 -24
- package/dist/web/system.html +2 -2
- package/dist/web/system.txt +25 -24
- package/package.json +4 -3
- package/dist/web/_next/static/chunks/549-2086c05135bc9753.js +0 -1
- package/dist/web/_next/static/chunks/867-0cd2c3cfd0ff0a45.js +0 -1
- package/dist/web/_next/static/chunks/89-382e9b29486d2747.js +0 -1
- package/dist/web/_next/static/chunks/app/history/page-83543937cecde5de.js +0 -1
- package/dist/web/_next/static/chunks/app/layout-92fa876afe619cf3.js +0 -1
- package/dist/web/_next/static/chunks/app/sessions/view/page-4631bdf5113a73b2.js +0 -1
- /package/dist/web/_next/static/{7TE2rEuHqGdGx14If8TEb → DCpEMjhrg9APkRwBBw5Do}/_buildManifest.js +0 -0
- /package/dist/web/_next/static/{7TE2rEuHqGdGx14If8TEb → DCpEMjhrg9APkRwBBw5Do}/_ssgManifest.js +0 -0
|
@@ -133,7 +133,8 @@ let OperatorController = class OperatorController {
|
|
|
133
133
|
return this.ops.continueTask(id, taskId).catch(fail);
|
|
134
134
|
}
|
|
135
135
|
rerunTask(id, taskId, body) {
|
|
136
|
-
|
|
136
|
+
const { buildOnFinished, ...patch } = body ?? {};
|
|
137
|
+
return this.ops.rerunTask(id, taskId, patch, { buildOnFinished: buildOnFinished === true }).catch(fail);
|
|
137
138
|
}
|
|
138
139
|
/** What going back to before this task would do. Changes nothing. */
|
|
139
140
|
restorePreview(id, taskId) {
|
|
@@ -177,6 +178,19 @@ let OperatorController = class OperatorController {
|
|
|
177
178
|
throw new NotFoundException('This task has not run yet, or that attempt is not its own.');
|
|
178
179
|
return story;
|
|
179
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* What a task changed: the files between the commit it started from and the one it produced.
|
|
183
|
+
* `run` picks an earlier attempt. See `taskChanges`.
|
|
184
|
+
*/
|
|
185
|
+
taskChanges(id, taskId, run) {
|
|
186
|
+
return this.ops.taskChanges(id, taskId, run?.trim() || undefined).catch(fail);
|
|
187
|
+
}
|
|
188
|
+
/** One of those files, before and after. Only a file the task changed can be asked for. */
|
|
189
|
+
taskChangeFile(id, taskId, path, run) {
|
|
190
|
+
if (!path)
|
|
191
|
+
throw new BadRequestException('path is required');
|
|
192
|
+
return this.ops.taskChangeFile(id, taskId, path, run?.trim() || undefined).catch(fail);
|
|
193
|
+
}
|
|
180
194
|
taskFiles(id, taskId, run) {
|
|
181
195
|
return this.ops.taskFiles(id, taskId, run);
|
|
182
196
|
}
|
|
@@ -320,13 +334,15 @@ let OperatorController = class OperatorController {
|
|
|
320
334
|
startBatch(body) {
|
|
321
335
|
if (!Array.isArray(body?.sessionIds))
|
|
322
336
|
throw new BadRequestException('sessionIds must be a list of session ids');
|
|
337
|
+
if (body?.taskIds !== undefined && !Array.isArray(body.taskIds))
|
|
338
|
+
throw new BadRequestException('taskIds must be a list of task ids');
|
|
323
339
|
return this.ops
|
|
324
340
|
.startBatch(body.sessionIds, body?.mode === 'unattended' ? 'unattended' : 'confirm', body?.onFailure === 'continue' ? 'continue' : 'stop',
|
|
325
341
|
// Absent or empty means every session keeps the model it already has, which is not the
|
|
326
342
|
// same as "no model": an empty string on a session means "leave the chat alone".
|
|
327
343
|
body?.model,
|
|
328
344
|
// The same for the review: absent means each session keeps whatever it is set to.
|
|
329
|
-
body?.reviewModel, body?.name)
|
|
345
|
+
body?.reviewModel, body?.name, body?.taskIds)
|
|
330
346
|
.catch(fail);
|
|
331
347
|
}
|
|
332
348
|
stopBatch() {
|
|
@@ -726,6 +742,25 @@ __decorate([
|
|
|
726
742
|
__metadata("design:paramtypes", [String, String, String]),
|
|
727
743
|
__metadata("design:returntype", Promise)
|
|
728
744
|
], OperatorController.prototype, "taskStory", null);
|
|
745
|
+
__decorate([
|
|
746
|
+
Get('sessions/:id/tasks/:taskId/changes'),
|
|
747
|
+
__param(0, Param('id')),
|
|
748
|
+
__param(1, Param('taskId')),
|
|
749
|
+
__param(2, Query('run')),
|
|
750
|
+
__metadata("design:type", Function),
|
|
751
|
+
__metadata("design:paramtypes", [String, String, String]),
|
|
752
|
+
__metadata("design:returntype", Promise)
|
|
753
|
+
], OperatorController.prototype, "taskChanges", null);
|
|
754
|
+
__decorate([
|
|
755
|
+
Get('sessions/:id/tasks/:taskId/changes/file'),
|
|
756
|
+
__param(0, Param('id')),
|
|
757
|
+
__param(1, Param('taskId')),
|
|
758
|
+
__param(2, Query('path')),
|
|
759
|
+
__param(3, Query('run')),
|
|
760
|
+
__metadata("design:type", Function),
|
|
761
|
+
__metadata("design:paramtypes", [String, String, String, String]),
|
|
762
|
+
__metadata("design:returntype", Promise)
|
|
763
|
+
], OperatorController.prototype, "taskChangeFile", null);
|
|
729
764
|
__decorate([
|
|
730
765
|
Get('sessions/:id/tasks/:taskId/files'),
|
|
731
766
|
__param(0, Param('id')),
|
|
@@ -20,7 +20,7 @@ import { installLayout } from '../config/layout.js';
|
|
|
20
20
|
import { SessionStore, DEFAULT_VCS, DEFAULT_REVIEW } from '../session/store.js';
|
|
21
21
|
import { EventBus } from '../session/events.js';
|
|
22
22
|
import { newId, tidyVcsPlan } from '../session/model.js';
|
|
23
|
-
import { runSession, openBrowser } from '../orchestrator/taskRunner.js';
|
|
23
|
+
import { runSession, openBrowser, queuedToRun } from '../orchestrator/taskRunner.js';
|
|
24
24
|
import { buildExport } from '../session/exportRecord.js';
|
|
25
25
|
import { buildDebugExport } from '../session/debugExport.js';
|
|
26
26
|
import { buildPlanExport, buildDomainExport, buildBotExport, buildBundleExport, exportFileName, exportMachine, taskAtAttempt, withTask, writeAttemptRecord } from '../session/exports.js';
|
|
@@ -36,8 +36,8 @@ function sameFolder(a, b) {
|
|
|
36
36
|
return a.trim() !== '' && norm(a) === norm(b);
|
|
37
37
|
}
|
|
38
38
|
import { importPlan, plannedSessionSignature, taskSignature } from '../plan/importPlan.js';
|
|
39
|
-
import { commitInterrupted, vcsPreflight, restorePreview, restoreToBase, sessionBranches } from '../vcs/taskVcs.js';
|
|
40
|
-
import { gitAvailable, repoUnusableReason } from '../vcs/git.js';
|
|
39
|
+
import { commitInterrupted, repoDirOf, vcsPreflight, restorePreview, restoreToBase, sessionBranches } from '../vcs/taskVcs.js';
|
|
40
|
+
import { changedFilesBetween, fileAt, gitAvailable, repoUnusableReason } from '../vcs/git.js';
|
|
41
41
|
import { makeAuthorizer } from '../exec/authorizer.js';
|
|
42
42
|
import { listSelectableDirs, collectFiles, findSelectionConflicts, describeConflicts, DEFAULT_IGNORE_DIRS } from '../context/projectMirror.js';
|
|
43
43
|
import { pickFolder } from './folderPicker.js';
|
|
@@ -328,16 +328,26 @@ let OperatorService = class OperatorService {
|
|
|
328
328
|
});
|
|
329
329
|
return task;
|
|
330
330
|
}
|
|
331
|
-
async rerunTask(sessionId, taskId, patch = {}
|
|
331
|
+
async rerunTask(sessionId, taskId, patch = {},
|
|
332
|
+
/**
|
|
333
|
+
* A new prompt for a task that ended done builds on what it did: the next attempt works on
|
|
334
|
+
* that attempt's branch. Only for a done task — a failed attempt's work is what is being
|
|
335
|
+
* replaced, so a re-run of one starts again from where the task first started.
|
|
336
|
+
*/
|
|
337
|
+
opts = {}) {
|
|
332
338
|
await this.init();
|
|
333
|
-
const
|
|
339
|
+
const before = (await this.store.getSession(sessionId))?.tasks.find((t) => t.id === taskId);
|
|
340
|
+
const buildsOn = opts.buildOnFinished && before?.status === 'done' ? { fromAttempt: before.attempt ?? 1 } : undefined;
|
|
341
|
+
const task = await this.store.rerunTask(sessionId, taskId, patch, undefined, buildsOn);
|
|
334
342
|
this.bus.publish({
|
|
335
343
|
sessionId,
|
|
336
344
|
taskId,
|
|
337
345
|
type: 'task-requeued',
|
|
338
346
|
level: 'info',
|
|
339
|
-
message:
|
|
340
|
-
|
|
347
|
+
message: buildsOn
|
|
348
|
+
? `"${task.title}" is queued again with a new prompt that builds on attempt ${buildsOn.fromAttempt} (attempt ${task.attempt ?? 1})`
|
|
349
|
+
: `"${task.title}" is queued again (attempt ${task.attempt ?? 1})`,
|
|
350
|
+
data: { attempt: task.attempt, ...(buildsOn ? { buildsOn } : {}) },
|
|
341
351
|
});
|
|
342
352
|
return task;
|
|
343
353
|
}
|
|
@@ -378,6 +388,78 @@ let OperatorService = class OperatorService {
|
|
|
378
388
|
return await buildStory(cfg.resolved.runsDir, chosen, view, live);
|
|
379
389
|
}
|
|
380
390
|
/** Files a task produced, so the UI can list reports and downloaded scripts. */
|
|
391
|
+
/**
|
|
392
|
+
* What a task changed in the repository: the files, between the commit it started from and the
|
|
393
|
+
* commit it produced. `runId` picks an earlier attempt. Read from git each time rather than kept
|
|
394
|
+
* on the record, so what is shown is the repository's own account; the record only says which
|
|
395
|
+
* two commits to compare.
|
|
396
|
+
*
|
|
397
|
+
* There is something to show only when version control was on and the task committed: a task
|
|
398
|
+
* that changed nothing, or ran with version control off, left no commit to compare, and the
|
|
399
|
+
* answer says which.
|
|
400
|
+
*/
|
|
401
|
+
async taskChanges(sessionId, taskId, runId) {
|
|
402
|
+
const found = await this.changesOf(sessionId, taskId, runId);
|
|
403
|
+
if ('problem' in found)
|
|
404
|
+
return { ok: false, problem: found.problem, files: [] };
|
|
405
|
+
const listed = await changedFilesBetween(found.dir, found.base, found.commit);
|
|
406
|
+
if (listed.problem)
|
|
407
|
+
return { ok: false, problem: `the repository no longer has these commits: ${listed.problem}`, files: [] };
|
|
408
|
+
return { ok: true, repoDir: found.dir, branch: found.branch, base: found.base, commit: found.commit, files: listed.files };
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* One changed file, before and after, as text. Only a file the task changed can be asked for:
|
|
412
|
+
* the path is checked against that list, so this cannot be used to read anything else in the
|
|
413
|
+
* repository's history. A binary file, or one over 2 MB on either side, is said to be so rather
|
|
414
|
+
* than returned.
|
|
415
|
+
*/
|
|
416
|
+
async taskChangeFile(sessionId, taskId, path, runId) {
|
|
417
|
+
const found = await this.changesOf(sessionId, taskId, runId);
|
|
418
|
+
if ('problem' in found)
|
|
419
|
+
throw new Error(found.problem);
|
|
420
|
+
const listed = await changedFilesBetween(found.dir, found.base, found.commit);
|
|
421
|
+
const file = listed.files.find((f) => f.path === path);
|
|
422
|
+
if (!file)
|
|
423
|
+
throw new Error('That file is not one this task changed.');
|
|
424
|
+
const MAX = 2 * 1024 * 1024;
|
|
425
|
+
const before = file.status === 'A' ? { bytes: null, tooLarge: false } : await fileAt(found.dir, found.base, file.oldPath ?? file.path, MAX);
|
|
426
|
+
const after = file.status === 'D' ? { bytes: null, tooLarge: false } : await fileAt(found.dir, found.commit, file.path, MAX);
|
|
427
|
+
// Git's own test for binary: a NUL byte in the first 8000.
|
|
428
|
+
const binary = [before.bytes, after.bytes].some((b) => !!b && b.subarray(0, 8000).includes(0));
|
|
429
|
+
const text = (b) => (b === null || binary ? null : b.toString('utf8').replace(/^\uFEFF/, ''));
|
|
430
|
+
return {
|
|
431
|
+
path: file.path,
|
|
432
|
+
...(file.oldPath ? { oldPath: file.oldPath } : {}),
|
|
433
|
+
status: file.status,
|
|
434
|
+
before: text(before.bytes),
|
|
435
|
+
after: text(after.bytes),
|
|
436
|
+
binary,
|
|
437
|
+
tooLarge: before.tooLarge || after.tooLarge,
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
/** The repository and the two commits a task's changes lie between, or why there are none. */
|
|
441
|
+
async changesOf(sessionId, taskId, runId) {
|
|
442
|
+
await this.init();
|
|
443
|
+
const session = await this.store.getSession(sessionId);
|
|
444
|
+
const task = session?.tasks.find((t) => t.id === taskId);
|
|
445
|
+
if (!session || !task)
|
|
446
|
+
return { problem: 'No such task.' };
|
|
447
|
+
const chosen = runId ? resolveRunId(task, runId) : task.runId;
|
|
448
|
+
if (runId && !chosen)
|
|
449
|
+
return { problem: 'That attempt is not one of this task\'s.' };
|
|
450
|
+
const vcs = !chosen || chosen === task.runId ? task.vcs : (task.attempts ?? []).find((a) => a.runId === chosen)?.vcs;
|
|
451
|
+
if (!vcs?.baseCommit || !vcs.commit) {
|
|
452
|
+
return {
|
|
453
|
+
problem: vcs?.branch
|
|
454
|
+
? 'This attempt committed nothing, so there are no changes to show.'
|
|
455
|
+
: 'Version control was not on for this attempt, so its changes were not recorded.',
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
const dir = repoDirOf(session);
|
|
459
|
+
if (!dir)
|
|
460
|
+
return { problem: 'The session has no repository folder set.' };
|
|
461
|
+
return { dir, base: vcs.baseCommit, commit: vcs.commit, branch: vcs.branch };
|
|
462
|
+
}
|
|
381
463
|
async taskFiles(sessionId, taskId, runId) {
|
|
382
464
|
const cfg = await this.settings.load();
|
|
383
465
|
const s = await this.store.getSession(sessionId);
|
|
@@ -643,7 +725,9 @@ let OperatorService = class OperatorService {
|
|
|
643
725
|
/** A browser that is already open. A batch passes its own; a single run opens one. */
|
|
644
726
|
transport,
|
|
645
727
|
/** The press of a start button this belongs to, stamped onto every task it reaches. */
|
|
646
|
-
runGroup
|
|
728
|
+
runGroup,
|
|
729
|
+
/** Only these queued tasks, when the operator chose some; the rest stay queued. */
|
|
730
|
+
onlyTasks) {
|
|
647
731
|
await this.init();
|
|
648
732
|
// Every project's Desktop copy brought up to date before the chat opens, when the operator
|
|
649
733
|
// asked for that. Before the run rather than before each task: a task's own mirror already
|
|
@@ -655,7 +739,7 @@ let OperatorService = class OperatorService {
|
|
|
655
739
|
const session = await this.store.getSession(sessionId);
|
|
656
740
|
if (!session)
|
|
657
741
|
return { started: false, reason: 'no such session', done: Promise.resolve(idle) };
|
|
658
|
-
const queuedIds = session
|
|
742
|
+
const queuedIds = queuedToRun(session, onlyTasks).map((t) => t.id);
|
|
659
743
|
if (queuedIds.length === 0)
|
|
660
744
|
return { started: false, reason: 'no queued tasks', done: Promise.resolve(idle) };
|
|
661
745
|
const cfg = await this.settings.load();
|
|
@@ -714,6 +798,7 @@ let OperatorService = class OperatorService {
|
|
|
714
798
|
continueOnFailure: session.onFailure === 'continue',
|
|
715
799
|
transport,
|
|
716
800
|
runGroup,
|
|
801
|
+
onlyTasks,
|
|
717
802
|
})
|
|
718
803
|
.then(async (outcome) => ({ ...(await this.tally(sessionId, queuedIds)), paused: outcome.paused }))
|
|
719
804
|
.catch(async (e) => {
|
|
@@ -816,8 +901,15 @@ let OperatorService = class OperatorService {
|
|
|
816
901
|
*/
|
|
817
902
|
reviewModel,
|
|
818
903
|
/** What to call the run. Offered from the plan's name; empty means it goes by its id. */
|
|
819
|
-
name
|
|
904
|
+
name,
|
|
905
|
+
/**
|
|
906
|
+
* The queued tasks to run, when not every queued task of those sessions: what the operator
|
|
907
|
+
* ticked in the register. A session none of whose chosen tasks is queued is skipped; the
|
|
908
|
+
* tasks nobody chose stay queued exactly as they were. Absent means every queued task.
|
|
909
|
+
*/
|
|
910
|
+
taskIds) {
|
|
820
911
|
await this.init();
|
|
912
|
+
const onlyTasks = taskIds && taskIds.length > 0 ? new Set(taskIds.map((t) => t.trim()).filter(Boolean)) : undefined;
|
|
821
913
|
if (this.batch?.running)
|
|
822
914
|
return { started: false, reason: 'a batch is already running' };
|
|
823
915
|
const wanted = [...new Set(sessionIds.map((s) => s.trim()).filter(Boolean))];
|
|
@@ -837,9 +929,9 @@ let OperatorService = class OperatorService {
|
|
|
837
929
|
sessions.push({ sessionId: id, name: id, state: 'skipped', ran: 0, failed: 0, reason: 'no such session' });
|
|
838
930
|
continue;
|
|
839
931
|
}
|
|
840
|
-
const queued = s
|
|
932
|
+
const queued = queuedToRun(s, onlyTasks).length;
|
|
841
933
|
sessions.push(queued === 0
|
|
842
|
-
? { sessionId: id, name: s.name, state: 'skipped', ran: 0, failed: 0, reason: 'nothing queued' }
|
|
934
|
+
? { sessionId: id, name: s.name, state: 'skipped', ran: 0, failed: 0, reason: onlyTasks ? 'none of its chosen tasks is queued' : 'nothing queued' }
|
|
843
935
|
: { sessionId: id, name: s.name, state: 'waiting', ran: 0, failed: 0 });
|
|
844
936
|
}
|
|
845
937
|
if (!sessions.some((s) => s.state === 'waiting')) {
|
|
@@ -899,6 +991,7 @@ let OperatorService = class OperatorService {
|
|
|
899
991
|
id: newId('b-'),
|
|
900
992
|
startedAt: new Date().toISOString(),
|
|
901
993
|
name: chosenName,
|
|
994
|
+
...(onlyTasks ? { onlyTasks: [...onlyTasks] } : {}),
|
|
902
995
|
mode,
|
|
903
996
|
onFailure,
|
|
904
997
|
stopping: false,
|
|
@@ -1045,7 +1138,8 @@ let OperatorService = class OperatorService {
|
|
|
1045
1138
|
s.runGroup = {
|
|
1046
1139
|
...runGroup,
|
|
1047
1140
|
order: at,
|
|
1048
|
-
|
|
1141
|
+
// What this run was asked to do: the chosen tasks, when some were chosen.
|
|
1142
|
+
taskIds: queuedToRun(s, batch.onlyTasks).map((t) => t.id),
|
|
1049
1143
|
mode: batch.mode,
|
|
1050
1144
|
onFailure: batch.onFailure,
|
|
1051
1145
|
};
|
|
@@ -1075,7 +1169,7 @@ let OperatorService = class OperatorService {
|
|
|
1075
1169
|
message: `starting as part of a batch of ${batch.sessions.length} session(s), in ${batch.mode} mode`,
|
|
1076
1170
|
data: { batchId: batch.id },
|
|
1077
1171
|
});
|
|
1078
|
-
const begun = await this.beginRun(entry.sessionId, batch.mode, browser, runGroup);
|
|
1172
|
+
const begun = await this.beginRun(entry.sessionId, batch.mode, browser, runGroup, batch.onlyTasks ? new Set(batch.onlyTasks) : undefined);
|
|
1079
1173
|
if (!begun.started) {
|
|
1080
1174
|
entry.state = 'skipped';
|
|
1081
1175
|
entry.reason = begun.reason;
|
|
@@ -1925,6 +2019,7 @@ let OperatorService = class OperatorService {
|
|
|
1925
2019
|
readOnly: t.readOnly || undefined,
|
|
1926
2020
|
autoRetries: t.autoRetries || undefined,
|
|
1927
2021
|
attempt: t.attempt,
|
|
2022
|
+
changedFiles: t.vcs?.commit && t.vcs.baseCommit ? (t.vcs.files?.length ?? 0) : undefined,
|
|
1928
2023
|
attempts: (t.attempts ?? []).map((a, n) => {
|
|
1929
2024
|
const from = a.startedAt ? Date.parse(a.startedAt) : undefined;
|
|
1930
2025
|
const to = a.finishedAt ? Date.parse(a.finishedAt) : undefined;
|
|
@@ -1939,6 +2034,7 @@ let OperatorService = class OperatorService {
|
|
|
1939
2034
|
reason: a.reason,
|
|
1940
2035
|
runId: a.runId,
|
|
1941
2036
|
branch: a.vcs?.branch,
|
|
2037
|
+
changedFiles: a.vcs?.commit && a.vcs.baseCommit ? (a.vcs.files?.length ?? 0) : undefined,
|
|
1942
2038
|
};
|
|
1943
2039
|
}),
|
|
1944
2040
|
});
|
|
@@ -626,6 +626,7 @@ export async function runTask(transport, session, task, deps) {
|
|
|
626
626
|
taskNumber,
|
|
627
627
|
contractAlreadySent: session.contractSent,
|
|
628
628
|
continuing: task.continuing,
|
|
629
|
+
buildsOn: task.buildsOn,
|
|
629
630
|
workDirNote: workingDirNote(work),
|
|
630
631
|
vcsNote: prepared.note,
|
|
631
632
|
readOnlyNote: task.readOnly ? READ_ONLY_NOTE : undefined,
|
|
@@ -1462,6 +1463,15 @@ export async function runTask(transport, session, task, deps) {
|
|
|
1462
1463
|
return await finish('failed', crash ? `${describeCrash(crash)}. Then: ${message}` : message);
|
|
1463
1464
|
}
|
|
1464
1465
|
}
|
|
1466
|
+
/**
|
|
1467
|
+
* The queued tasks of a session a run takes: all of them, or only the ones the operator chose.
|
|
1468
|
+
* One rule for the run itself and for every place that counts or records what a run will do, so
|
|
1469
|
+
* the batch entrance, the run's record and the run cannot disagree about it.
|
|
1470
|
+
*/
|
|
1471
|
+
export function queuedToRun(session, onlyTasks) {
|
|
1472
|
+
const only = onlyTasks ? new Set(onlyTasks) : undefined;
|
|
1473
|
+
return session.tasks.filter((t) => t.status === 'queued' && (!only || only.has(t.id)));
|
|
1474
|
+
}
|
|
1465
1475
|
/**
|
|
1466
1476
|
* Runs every queued task of a session, in order, in one conversation. Stops at the first
|
|
1467
1477
|
* task that does not end with `done` unless `continueOnFailure` is set, because a failed
|
|
@@ -1472,7 +1482,7 @@ export async function runSession(sessionId, deps) {
|
|
|
1472
1482
|
let session = await store.getSession(sessionId);
|
|
1473
1483
|
if (!session)
|
|
1474
1484
|
throw new Error(`Session ${sessionId} does not exist.`);
|
|
1475
|
-
const queued = session
|
|
1485
|
+
const queued = queuedToRun(session, deps.onlyTasks);
|
|
1476
1486
|
if (queued.length === 0) {
|
|
1477
1487
|
bus.publish({ sessionId, type: 'session-idle', level: 'info', message: 'no queued tasks' });
|
|
1478
1488
|
return { ran: 0, paused: false };
|
package/dist/src/plan/brief.js
CHANGED
|
@@ -633,6 +633,18 @@ your paraphrase means. Quote the label, and name the page by the route in the he
|
|
|
633
633
|
button in the guide, say so and ask what they can see, rather than inventing one. A label written
|
|
634
634
|
with {braces} is filled in with a number or a name at the time, so quote it with the braces and
|
|
635
635
|
say what will be in them.
|
|
636
|
+
|
|
637
|
+
**When asked what to do next, or where something is:** first be sure which page they are on and
|
|
638
|
+
what they see — ask, in one line, if it is not clear. Then answer as numbered steps, each naming the
|
|
639
|
+
page by its route, the section by its bold heading from the guide, and the control by its exact
|
|
640
|
+
label. Say the condition a control needs when the guide gives one ("only in the **"Flow"** view",
|
|
641
|
+
"only after **"Work on branches and commit what was changed"** is ticked"), because a control that is
|
|
642
|
+
not showing is otherwise a control that "does not exist".
|
|
643
|
+
|
|
644
|
+
**A run that looks stuck is usually waiting for them.** A step waiting for a decision appears at the
|
|
645
|
+
top of every page under **"A step is waiting for your decision"**, one card per step, with **"Run"**,
|
|
646
|
+
**"Run this and the rest without asking"**, **"Skip"** and **"Abort task"**; the browser tab then
|
|
647
|
+
starts with "(n) waiting for you". Send them there first.
|
|
636
648
|
`.trim();
|
|
637
649
|
const GUIDE_RULE_BG = `
|
|
638
650
|
## Говори с думите от екрана
|
|
@@ -645,6 +657,18 @@ const GUIDE_RULE_BG = `
|
|
|
645
657
|
**Никога не пращай оператора към контрол, който не е изброен там.** Ако това, което искаш да се
|
|
646
658
|
направи, няма бутон в справочника, кажи го и попитай какво вижда, вместо да си измислиш. Надпис с
|
|
647
659
|
{скоби} се попълва с число или име в момента — цитирай го със скобите и казвай какво ще има в тях.
|
|
660
|
+
|
|
661
|
+
**Когато те питат какво следва или къде е нещо:** първо се увери на коя страница е операторът и
|
|
662
|
+
какво вижда — попитай с един ред, ако не е ясно. После отговаряй с номерирани стъпки, като всяка
|
|
663
|
+
назовава страницата с маршрута, раздела с удебеленото му заглавие от справочника и контрола с точния
|
|
664
|
+
му надпис. Казвай условието, което контролът изисква, когато справочникът го дава („само в изгледа
|
|
665
|
+
**„Поток“**“, „само след отметка на **„Работи по клонове и комитвай промененото“**“), защото
|
|
666
|
+
контрол, който не се вижда, иначе е контрол, който „го няма“.
|
|
667
|
+
|
|
668
|
+
**Пускане, което изглежда заседнало, обикновено чака оператора.** Стъпка, чакаща решение, се
|
|
669
|
+
появява горе на всяка страница под **„Стъпка чака вашето решение“**, по една карта за стъпка, с
|
|
670
|
+
**„Изпълни“**, **„Изпълни без да питаш повече“**, **„Пропусни“** и **„Прекрати задачата“**; разделът
|
|
671
|
+
на браузъра тогава започва с „(n) чака вас“. Прати го там първо.
|
|
648
672
|
`.trim();
|
|
649
673
|
/**
|
|
650
674
|
* The persona's two later roles, software level: what the operator brings back when a task
|
|
@@ -699,14 +723,23 @@ in those words, every time:
|
|
|
699
723
|
- **A new prompt.** The task text was ambiguous, a path or a port was wrong, the level 2 forbade
|
|
700
724
|
what the task needed, a check asked the git index about a file the runner had not committed yet,
|
|
701
725
|
or the chat did the wrong thing and the checks caught it. Give the whole replacement text, then:
|
|
702
|
-
1. On \`/history\`, press **"Fix the prompt and queue it again"** on that row.
|
|
726
|
+
1. On \`/history\`, in the **"Flow"** view, press **"Fix the prompt and queue it again"** on that row.
|
|
703
727
|
2. Replace the text with the one above and press **"Save and queue again"**.
|
|
704
|
-
3.
|
|
705
|
-
|
|
706
|
-
|
|
728
|
+
3. Under **"What is next"**, press **"Continue: run the {n} queued task(s) in {s} session(s)"**
|
|
729
|
+
(it reads **"Continue: run {f} failed and {n} queued task(s) in {s} session(s)"** when a chain
|
|
730
|
+
has a failed task).
|
|
731
|
+
4. In **"Before it continues"** every task is ticked. To run only the fixed one, press
|
|
732
|
+
**"only this one"** beside it.
|
|
733
|
+
5. Press **"Continue without asking"** — the left-hand button — to let it run, or
|
|
734
|
+
**"Continue, asking before each command"** beside it to approve every one.
|
|
707
735
|
|
|
708
736
|
A check that is wrong rather than a prompt that is wrong is edited instead: **"Edit"** on the
|
|
709
|
-
task card, then **"What it checks"**, **"Must be"** and **"Command"** under **"Checks"
|
|
737
|
+
task card, then **"What it checks"**, **"Must be"** and **"Command"** under **"Checks"**, then
|
|
738
|
+
**"Save and queue it again"** — which, like every "queue" button, starts nothing by itself.
|
|
739
|
+
A task that ended **limit-reached** is carried on in its own chat instead: **"Continue in the
|
|
740
|
+
same chat"** on its card on the session page, then **"Run {n} task(s)"** at the top of that page.
|
|
741
|
+
When a failure shows up while the run is still going, **"Pause after this task"** holds the rest
|
|
742
|
+
while you work it out.
|
|
710
743
|
Putting the repository back to before the task is **"Restore"**; putting it back and re-running
|
|
711
744
|
that task and every task after it is **"Run again from here"**.
|
|
712
745
|
- **A change to the bot.** No wording fixes it: the shell the task needs is not on the machine,
|
|
@@ -851,14 +884,23 @@ const PHASE3_BG = `
|
|
|
851
884
|
забранявало това, което задачата иска, проверка е питала git индекса за файл, който runner-ът
|
|
852
885
|
още не е комитнал, или чатът е направил грешното нещо и проверките са го хванали. Дай целия
|
|
853
886
|
нов текст, после:
|
|
854
|
-
1. В \`/history
|
|
887
|
+
1. В \`/history\`, в изгледа **„Поток“**, натисни **„Поправи prompt-а и върни в опашката“** на този ред.
|
|
855
888
|
2. Замени текста с горния и натисни **„Запази и върни в опашката“**.
|
|
856
|
-
3.
|
|
857
|
-
|
|
858
|
-
|
|
889
|
+
3. Под **„Какво следва“** натисни **„Продължи: пусни {n} чакащи задачи в {s} сесии“** (пише
|
|
890
|
+
**„Продължи: пусни {f} провалили се и {n} чакащи задачи в {s} сесии“**, когато във верига има
|
|
891
|
+
провалена задача).
|
|
892
|
+
4. В **„Преди да продължи“** всички задачи са отметнати. За да пуснеш само поправената, натисни
|
|
893
|
+
**„само тази“** до нея.
|
|
894
|
+
5. Натисни **„Продължи без да пита“** — левият бутон — за да върви само, или **„Продължи, с
|
|
895
|
+
питане преди всяка команда“** до него, за да одобряваш всяка.
|
|
859
896
|
|
|
860
897
|
Когато е сгрешена проверката, а не prompt-ът, се редактира друго: **„Редактирай“** на картата
|
|
861
|
-
на задачата, после **„Какво проверява“**, **„Трябва“** и **„Команда“** под
|
|
898
|
+
на задачата, после **„Какво проверява“**, **„Трябва“** и **„Команда“** под **„Проверки“**, после
|
|
899
|
+
**„Запази и върни в опашката“** — който, като всеки бутон за връщане в опашката, сам не пуска нищо.
|
|
900
|
+
Задача, завършила с **limit-reached**, продължава в собствения си чат: **„Продължи в същия чат“**
|
|
901
|
+
на картата ѝ на страницата на сесията, после **„Пусни {n} задача(и)“** горе на същата страница.
|
|
902
|
+
Когато провал се покаже, докато пускането още върви, **„Пауза след тази задача“** задържа
|
|
903
|
+
останалото, докато го разбереш.
|
|
862
904
|
Връщането на хранилището отпреди задачата е **„Върни“**; връщането му плюс ново изпълнение на
|
|
863
905
|
тази задача и всички след нея е **„Пусни отново оттук“**.
|
|
864
906
|
- **Промяна по бота.** Никакви думи не го оправят: обвивката, която задачата иска, я няма на
|
package/dist/src/plan/script.js
CHANGED
|
@@ -658,6 +658,19 @@ export const SCRIPT = [
|
|
|
658
658
|
},
|
|
659
659
|
],
|
|
660
660
|
},
|
|
661
|
+
{
|
|
662
|
+
choose: { en: 'Where each session starts, if 1.1 (`startFrom`):', bg: 'Откъде тръгва всяка сесия, ако е 1.1 (`startFrom`):' },
|
|
663
|
+
options: [
|
|
664
|
+
{
|
|
665
|
+
en: 'From the local branch main — every session starts from the same clean code (branch; name another in baseBranch)',
|
|
666
|
+
bg: 'От локалния клон main — всяка сесия тръгва от един и същ чист код (branch; друг клон в baseBranch)',
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
en: "From the previous session's branch — the sessions form a chain (previous-session)",
|
|
670
|
+
bg: 'От клона на предната сесия — сесиите образуват верига (previous-session)',
|
|
671
|
+
},
|
|
672
|
+
],
|
|
673
|
+
},
|
|
661
674
|
{
|
|
662
675
|
choose: { en: 'If a task fails (`onFailure` of the session, required):', bg: 'Ако задача се провали (`onFailure` на сесията, задължително):' },
|
|
663
676
|
options: [
|