copilot-operator 0.1.4 → 0.1.6
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 +5 -2
- package/dist/src/api/operator.service.js +48 -12
- package/dist/src/orchestrator/taskRunner.js +12 -2
- package/dist/src/plan/brief.js +1290 -1221
- package/dist/src/plan/schema.js +12 -0
- package/dist/src/plan/script.js +13 -0
- package/dist/src/plan/systemGuide.js +1396 -172
- package/dist/src/session/compose.js +5 -1
- package/dist/src/session/exports.js +4 -0
- package/dist/src/session/store.js +4 -1
- package/dist/src/session/story.js +10 -1
- package/dist/src/vcs/taskVcs.js +122 -12
- package/dist/web/404.html +2 -2
- package/dist/web/_next/static/chunks/549-afafecd6bfbb138e.js +1 -0
- package/dist/web/_next/static/chunks/56-91cd1709158ebf99.js +1 -0
- package/dist/web/_next/static/chunks/867-fc983d08916b4e28.js +1 -0
- package/dist/web/_next/static/chunks/app/history/page-b3b4af994c868b5d.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-d4213354730a945c.js +1 -0
- package/dist/web/_next/static/css/{5a10c60adf9c5336.css → 63fea96806e6b2cb.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-f0e3e152f6f67e45.js +0 -1
- package/dist/web/_next/static/chunks/89-382e9b29486d2747.js +0 -1
- package/dist/web/_next/static/chunks/app/history/page-8d4f817e36986e3f.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-185f5fcc383e6619.js +0 -1
- /package/dist/web/_next/static/{p9lexLS0u8W5N6VzLavHO → WJ_8hRZsp4VWiDZx8NMUw}/_buildManifest.js +0 -0
- /package/dist/web/_next/static/{p9lexLS0u8W5N6VzLavHO → WJ_8hRZsp4VWiDZx8NMUw}/_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) {
|
|
@@ -320,13 +321,15 @@ let OperatorController = class OperatorController {
|
|
|
320
321
|
startBatch(body) {
|
|
321
322
|
if (!Array.isArray(body?.sessionIds))
|
|
322
323
|
throw new BadRequestException('sessionIds must be a list of session ids');
|
|
324
|
+
if (body?.taskIds !== undefined && !Array.isArray(body.taskIds))
|
|
325
|
+
throw new BadRequestException('taskIds must be a list of task ids');
|
|
323
326
|
return this.ops
|
|
324
327
|
.startBatch(body.sessionIds, body?.mode === 'unattended' ? 'unattended' : 'confirm', body?.onFailure === 'continue' ? 'continue' : 'stop',
|
|
325
328
|
// Absent or empty means every session keeps the model it already has, which is not the
|
|
326
329
|
// same as "no model": an empty string on a session means "leave the chat alone".
|
|
327
330
|
body?.model,
|
|
328
331
|
// The same for the review: absent means each session keeps whatever it is set to.
|
|
329
|
-
body?.reviewModel, body?.name)
|
|
332
|
+
body?.reviewModel, body?.name, body?.taskIds)
|
|
330
333
|
.catch(fail);
|
|
331
334
|
}
|
|
332
335
|
stopBatch() {
|
|
@@ -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';
|
|
@@ -249,6 +249,20 @@ let OperatorService = class OperatorService {
|
|
|
249
249
|
if (reason)
|
|
250
250
|
throw new Error(reason);
|
|
251
251
|
}
|
|
252
|
+
if (merged.baseBranch !== undefined)
|
|
253
|
+
merged.baseBranch = merged.baseBranch.trim();
|
|
254
|
+
/*
|
|
255
|
+
* Where the session starts is fixed at its first run. A new choice clears that record, so
|
|
256
|
+
* the next task to run is cut from what was chosen now; the tasks that already ran keep
|
|
257
|
+
* their own base commits, which is what a re-run of them goes back to.
|
|
258
|
+
*/
|
|
259
|
+
const startChanged = (merged.startFrom ?? 'head') !== (s.vcs?.startFrom ?? 'head') ||
|
|
260
|
+
(merged.baseBranch || 'main') !== (s.vcs?.baseBranch || 'main') ||
|
|
261
|
+
merged.repoDir.toLowerCase() !== (s.vcs?.repoDir ?? '').trim().toLowerCase();
|
|
262
|
+
if (startChanged) {
|
|
263
|
+
s.vcsBaseCommit = undefined;
|
|
264
|
+
s.vcsStart = undefined;
|
|
265
|
+
}
|
|
252
266
|
s.vcs = merged;
|
|
253
267
|
}
|
|
254
268
|
});
|
|
@@ -314,16 +328,26 @@ let OperatorService = class OperatorService {
|
|
|
314
328
|
});
|
|
315
329
|
return task;
|
|
316
330
|
}
|
|
317
|
-
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 = {}) {
|
|
318
338
|
await this.init();
|
|
319
|
-
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);
|
|
320
342
|
this.bus.publish({
|
|
321
343
|
sessionId,
|
|
322
344
|
taskId,
|
|
323
345
|
type: 'task-requeued',
|
|
324
346
|
level: 'info',
|
|
325
|
-
message:
|
|
326
|
-
|
|
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 } : {}) },
|
|
327
351
|
});
|
|
328
352
|
return task;
|
|
329
353
|
}
|
|
@@ -629,7 +653,9 @@ let OperatorService = class OperatorService {
|
|
|
629
653
|
/** A browser that is already open. A batch passes its own; a single run opens one. */
|
|
630
654
|
transport,
|
|
631
655
|
/** The press of a start button this belongs to, stamped onto every task it reaches. */
|
|
632
|
-
runGroup
|
|
656
|
+
runGroup,
|
|
657
|
+
/** Only these queued tasks, when the operator chose some; the rest stay queued. */
|
|
658
|
+
onlyTasks) {
|
|
633
659
|
await this.init();
|
|
634
660
|
// Every project's Desktop copy brought up to date before the chat opens, when the operator
|
|
635
661
|
// asked for that. Before the run rather than before each task: a task's own mirror already
|
|
@@ -641,7 +667,7 @@ let OperatorService = class OperatorService {
|
|
|
641
667
|
const session = await this.store.getSession(sessionId);
|
|
642
668
|
if (!session)
|
|
643
669
|
return { started: false, reason: 'no such session', done: Promise.resolve(idle) };
|
|
644
|
-
const queuedIds = session
|
|
670
|
+
const queuedIds = queuedToRun(session, onlyTasks).map((t) => t.id);
|
|
645
671
|
if (queuedIds.length === 0)
|
|
646
672
|
return { started: false, reason: 'no queued tasks', done: Promise.resolve(idle) };
|
|
647
673
|
const cfg = await this.settings.load();
|
|
@@ -700,6 +726,7 @@ let OperatorService = class OperatorService {
|
|
|
700
726
|
continueOnFailure: session.onFailure === 'continue',
|
|
701
727
|
transport,
|
|
702
728
|
runGroup,
|
|
729
|
+
onlyTasks,
|
|
703
730
|
})
|
|
704
731
|
.then(async (outcome) => ({ ...(await this.tally(sessionId, queuedIds)), paused: outcome.paused }))
|
|
705
732
|
.catch(async (e) => {
|
|
@@ -802,8 +829,15 @@ let OperatorService = class OperatorService {
|
|
|
802
829
|
*/
|
|
803
830
|
reviewModel,
|
|
804
831
|
/** What to call the run. Offered from the plan's name; empty means it goes by its id. */
|
|
805
|
-
name
|
|
832
|
+
name,
|
|
833
|
+
/**
|
|
834
|
+
* The queued tasks to run, when not every queued task of those sessions: what the operator
|
|
835
|
+
* ticked in the register. A session none of whose chosen tasks is queued is skipped; the
|
|
836
|
+
* tasks nobody chose stay queued exactly as they were. Absent means every queued task.
|
|
837
|
+
*/
|
|
838
|
+
taskIds) {
|
|
806
839
|
await this.init();
|
|
840
|
+
const onlyTasks = taskIds && taskIds.length > 0 ? new Set(taskIds.map((t) => t.trim()).filter(Boolean)) : undefined;
|
|
807
841
|
if (this.batch?.running)
|
|
808
842
|
return { started: false, reason: 'a batch is already running' };
|
|
809
843
|
const wanted = [...new Set(sessionIds.map((s) => s.trim()).filter(Boolean))];
|
|
@@ -823,9 +857,9 @@ let OperatorService = class OperatorService {
|
|
|
823
857
|
sessions.push({ sessionId: id, name: id, state: 'skipped', ran: 0, failed: 0, reason: 'no such session' });
|
|
824
858
|
continue;
|
|
825
859
|
}
|
|
826
|
-
const queued = s
|
|
860
|
+
const queued = queuedToRun(s, onlyTasks).length;
|
|
827
861
|
sessions.push(queued === 0
|
|
828
|
-
? { sessionId: id, name: s.name, state: 'skipped', ran: 0, failed: 0, reason: 'nothing queued' }
|
|
862
|
+
? { sessionId: id, name: s.name, state: 'skipped', ran: 0, failed: 0, reason: onlyTasks ? 'none of its chosen tasks is queued' : 'nothing queued' }
|
|
829
863
|
: { sessionId: id, name: s.name, state: 'waiting', ran: 0, failed: 0 });
|
|
830
864
|
}
|
|
831
865
|
if (!sessions.some((s) => s.state === 'waiting')) {
|
|
@@ -885,6 +919,7 @@ let OperatorService = class OperatorService {
|
|
|
885
919
|
id: newId('b-'),
|
|
886
920
|
startedAt: new Date().toISOString(),
|
|
887
921
|
name: chosenName,
|
|
922
|
+
...(onlyTasks ? { onlyTasks: [...onlyTasks] } : {}),
|
|
888
923
|
mode,
|
|
889
924
|
onFailure,
|
|
890
925
|
stopping: false,
|
|
@@ -1031,7 +1066,8 @@ let OperatorService = class OperatorService {
|
|
|
1031
1066
|
s.runGroup = {
|
|
1032
1067
|
...runGroup,
|
|
1033
1068
|
order: at,
|
|
1034
|
-
|
|
1069
|
+
// What this run was asked to do: the chosen tasks, when some were chosen.
|
|
1070
|
+
taskIds: queuedToRun(s, batch.onlyTasks).map((t) => t.id),
|
|
1035
1071
|
mode: batch.mode,
|
|
1036
1072
|
onFailure: batch.onFailure,
|
|
1037
1073
|
};
|
|
@@ -1061,7 +1097,7 @@ let OperatorService = class OperatorService {
|
|
|
1061
1097
|
message: `starting as part of a batch of ${batch.sessions.length} session(s), in ${batch.mode} mode`,
|
|
1062
1098
|
data: { batchId: batch.id },
|
|
1063
1099
|
});
|
|
1064
|
-
const begun = await this.beginRun(entry.sessionId, batch.mode, browser, runGroup);
|
|
1100
|
+
const begun = await this.beginRun(entry.sessionId, batch.mode, browser, runGroup, batch.onlyTasks ? new Set(batch.onlyTasks) : undefined);
|
|
1065
1101
|
if (!begun.started) {
|
|
1066
1102
|
entry.state = 'skipped';
|
|
1067
1103
|
entry.reason = begun.reason;
|
|
@@ -567,7 +567,7 @@ export async function runTask(transport, session, task, deps) {
|
|
|
567
567
|
// --- version control: a branch of this task's own, before anything is touched -------
|
|
568
568
|
const prepared = await prepareForTask(session, task, bus, async (mutate) => {
|
|
569
569
|
await store.updateSession(session.id, mutate);
|
|
570
|
-
});
|
|
570
|
+
}, () => store.listSessions());
|
|
571
571
|
if (prepared.vcs.branch || prepared.vcs.problem) {
|
|
572
572
|
await setTask((t) => {
|
|
573
573
|
t.vcs = prepared.vcs;
|
|
@@ -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 };
|