copilot-operator 0.1.40 → 0.1.42
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 +28 -6
- package/dist/src/api/operator.service.js +82 -7
- package/dist/src/config/schema.js +7 -0
- package/dist/src/orchestrator/planMode.js +102 -0
- package/dist/src/orchestrator/review.js +8 -4
- package/dist/src/orchestrator/taskRunner.js +137 -2
- package/dist/src/plan/brief.js +12 -0
- package/dist/src/plan/importPlan.js +2 -0
- package/dist/src/plan/schema.js +10 -0
- package/dist/src/plan/systemGuide.js +193 -2
- package/dist/src/protocol/parser.js +26 -2
- package/dist/src/protocol/replySchema.js +28 -4
- package/dist/src/session/compose.js +62 -7
- package/dist/src/session/exports.js +1 -0
- package/dist/src/session/model.js +38 -0
- package/dist/src/session/store.js +30 -3
- package/dist/src/transport/copilotTransport.js +28 -10
- package/dist/src/transport/locators.js +7 -1
- package/dist/src/vcs/startChoice.js +80 -8
- package/dist/src/vcs/taskVcs.js +16 -7
- package/dist/web/404.html +2 -2
- package/dist/web/_next/static/chunks/{584-578be94a0786334b.js → 204-db82ca47ff211e21.js} +1 -1
- package/dist/web/_next/static/chunks/499-39b2c00bda3656f0.js +1 -0
- package/dist/web/_next/static/chunks/549-348b0a1404407e5a.js +1 -0
- package/dist/web/_next/static/chunks/867-7d86a692260006f1.js +1 -0
- package/dist/web/_next/static/chunks/app/defaults/page-a25c40436fc312db.js +1 -0
- package/dist/web/_next/static/chunks/app/history/page-47973539ceff81d6.js +1 -0
- package/dist/web/_next/static/chunks/app/layout-6ee612d25e78b246.js +1 -0
- package/dist/web/_next/static/chunks/app/sessions/view/page-872d8b9fe8eedbbf.js +1 -0
- package/dist/web/_next/static/css/{cea7913ba416dbb7.css → 0e55721108c300f2.css} +1 -1
- package/dist/web/appearance.html +2 -2
- package/dist/web/appearance.txt +13 -13
- package/dist/web/defaults.html +2 -2
- package/dist/web/defaults.txt +13 -13
- package/dist/web/history.html +2 -2
- package/dist/web/history.txt +13 -13
- package/dist/web/import.html +2 -2
- package/dist/web/import.txt +13 -13
- package/dist/web/index.html +2 -2
- package/dist/web/index.txt +13 -13
- package/dist/web/level1.html +2 -2
- package/dist/web/level1.txt +13 -13
- package/dist/web/presets.html +2 -2
- package/dist/web/presets.txt +13 -13
- package/dist/web/sessions/view.html +2 -2
- package/dist/web/sessions/view.txt +13 -13
- package/dist/web/system.html +2 -2
- package/dist/web/system.txt +13 -13
- package/package.json +159 -158
- package/dist/web/_next/static/chunks/499-28e118e33ce07935.js +0 -1
- package/dist/web/_next/static/chunks/549-f295c8429e83b25b.js +0 -1
- package/dist/web/_next/static/chunks/867-8dc5bbb092ca2c5c.js +0 -1
- package/dist/web/_next/static/chunks/app/defaults/page-879158e9e550eea7.js +0 -1
- package/dist/web/_next/static/chunks/app/history/page-9f69ccde726c9be2.js +0 -1
- package/dist/web/_next/static/chunks/app/layout-73f28bc065a46ed3.js +0 -1
- package/dist/web/_next/static/chunks/app/sessions/view/page-0485ee7f781156ec.js +0 -1
- /package/dist/web/_next/static/{Y9tuV5LV-O0XyCREkeGc5 → lDmqS7pWU_B_Ac5idPvdS}/_buildManifest.js +0 -0
- /package/dist/web/_next/static/{Y9tuV5LV-O0XyCREkeGc5 → lDmqS7pWU_B_Ac5idPvdS}/_ssgManifest.js +0 -0
|
@@ -190,6 +190,46 @@ export const SYSTEM_GUIDE = [
|
|
|
190
190
|
doesEn: 'Only for a session whose start was prepared before the run — a starting snapshot approved on the run panel — and chosen first then: the run starts from it as approved. Any other choice below drops it.',
|
|
191
191
|
doesBg: 'Само за сесия, чието начало е подготвено преди пускането — стартова снимка, одобрена в панела за пускане — и тогава е избрано първо: пускането тръгва от нея, както е одобрена. Всеки друг избор по-долу я отменя.',
|
|
192
192
|
},
|
|
193
|
+
{
|
|
194
|
+
key: 'startdlg.refresh',
|
|
195
|
+
kind: 'button',
|
|
196
|
+
en: 'Read the branches again',
|
|
197
|
+
bg: 'Прочети клоновете отново',
|
|
198
|
+
doesEn: 'Beside the window\'s first line: reads the repository again — the branch checked out, the branches and their commits — after a checkout or a pull made outside the bot; what was chosen stays chosen.',
|
|
199
|
+
doesBg: 'До първия ред на прозореца: прочита хранилището отново — избрания клон, клоновете и комитите им — след checkout или pull, направени извън бота; избраното остава избрано.',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
key: 'startdlg.keepStarted',
|
|
203
|
+
kind: 'radio',
|
|
204
|
+
en: 'Keep: carry on {branch}',
|
|
205
|
+
bg: 'Запази: продължи на {branch}',
|
|
206
|
+
doesEn: 'For a session that has run already, chosen first: it carries on its own branch (named in the label). Above it the window says where that branch is now, where the session started, the branch on the remote, and warns when the repository is on another branch.',
|
|
207
|
+
doesBg: 'За сесия, която вече е вървяла, избрано първо: продължава на собствения си клон (името е в надписа). Над него прозорецът казва къде е клонът сега, откъде е тръгнала сесията, клона в remote, и предупреждава, когато хранилището е на друг клон.',
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
key: 'startdlg.useCurrent',
|
|
211
|
+
kind: 'radio',
|
|
212
|
+
en: 'The branch checked out now: {branch} ({commit})',
|
|
213
|
+
bg: 'Клонът, избран в момента: {branch} ({commit})',
|
|
214
|
+
doesEn: 'For a session that has run, when the repository is on another branch: the work starts from the branch checked out now. Like any other branch chosen for it, the tasks that run next start from it — a task run again too — and what already ran stays on its branches.',
|
|
215
|
+
doesBg: 'За сесия, която вече е вървяла, когато хранилището е на друг клон: работата тръгва от клона, избран в момента. Както при всеки друг избран клон, задачите, които тръгнат оттук нататък, тръгват от него — и пуснатата отново задача — а изпълненото остава в своите клонове.',
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
key: 'startdlg.goUnattendedOn',
|
|
219
|
+
kind: 'button',
|
|
220
|
+
en: 'Start without asking — on {where}',
|
|
221
|
+
bg: 'Започни без питане — на {where}',
|
|
222
|
+
doesEn: 'The window\'s start button when the run has one session: says the branch and commit the work starts on.',
|
|
223
|
+
doesBg: 'Бутонът за старт на прозореца, когато пускането е на една сесия: казва клона и комита, от които тръгва работата.',
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
key: 'startdlg.goStepOn',
|
|
227
|
+
kind: 'button',
|
|
228
|
+
en: 'Start, asking before each command — on {where}',
|
|
229
|
+
bg: 'Започни, с питане преди всяка команда — на {where}',
|
|
230
|
+
doesEn: 'The same, for a run that asks before each command.',
|
|
231
|
+
doesBg: 'Същото, за пускане с питане преди всяка команда.',
|
|
232
|
+
},
|
|
193
233
|
{
|
|
194
234
|
key: 'startdlg.local',
|
|
195
235
|
kind: 'radio',
|
|
@@ -404,6 +444,38 @@ export const SYSTEM_GUIDE = [
|
|
|
404
444
|
doesEn: 'Nothing is sent: the session stops, its tasks stay queued.',
|
|
405
445
|
doesBg: 'Нищо не се изпраща: сесията спира, задачите ѝ остават на опашката.',
|
|
406
446
|
},
|
|
447
|
+
{
|
|
448
|
+
key: 'pm.note',
|
|
449
|
+
kind: 'field',
|
|
450
|
+
en: 'Note for the chat (optional when approving; says what to change when asking for another plan)',
|
|
451
|
+
bg: 'Бележка за чата (по желание при одобрение; казва какво да се промени, когато искаш друг план)',
|
|
452
|
+
doesEn: 'On a plan waiting for approval (plan first): sent to the chat with the answer — a condition for the work when approving, what to change when asking for another plan.',
|
|
453
|
+
doesBg: 'На план, който чака одобрение („Първо план“): изпраща се на чата с отговора — условие за работата при одобрение, какво да се промени, когато искаш друг план.',
|
|
454
|
+
},
|
|
455
|
+
{
|
|
456
|
+
key: 'pm.approve',
|
|
457
|
+
kind: 'button',
|
|
458
|
+
en: 'Approve and start the work',
|
|
459
|
+
bg: 'Одобри и започни работата',
|
|
460
|
+
doesEn: 'The plan is recorded as approved by the operator and the chat starts changing the code, following it.',
|
|
461
|
+
doesBg: 'Планът се записва като одобрен от оператора и чатът започва да променя кода, като го следва.',
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
key: 'pm.revise',
|
|
465
|
+
kind: 'button',
|
|
466
|
+
en: 'Ask for another plan',
|
|
467
|
+
bg: 'Поискай друг план',
|
|
468
|
+
doesEn: 'Needs the note: the chat makes a new version of the plan with it, still without changing anything, and the card comes back.',
|
|
469
|
+
doesBg: 'Иска бележка: чатът прави нова версия на плана с нея, пак без да променя нищо, и картата се връща.',
|
|
470
|
+
},
|
|
471
|
+
{
|
|
472
|
+
key: 'pm.stop',
|
|
473
|
+
kind: 'button',
|
|
474
|
+
en: 'Stop the task',
|
|
475
|
+
bg: 'Спри задачата',
|
|
476
|
+
doesEn: 'After a confirmation the task ends as stopped, with nothing changed; it can be run again.',
|
|
477
|
+
doesBg: 'След потвърждение задачата приключва като спряна, без нищо променено; може да се пусне отново.',
|
|
478
|
+
},
|
|
407
479
|
],
|
|
408
480
|
},
|
|
409
481
|
],
|
|
@@ -1193,6 +1265,22 @@ export const SYSTEM_GUIDE = [
|
|
|
1193
1265
|
doesEn: 'Sessions with the same name here share one Copilot conversation; empty gives this one its own.',
|
|
1194
1266
|
doesBg: 'Сесиите с едно и също име тук споделят един разговор в Copilot; празно дава на тази собствен.',
|
|
1195
1267
|
},
|
|
1268
|
+
{
|
|
1269
|
+
key: 'pm.sessionSetting',
|
|
1270
|
+
kind: 'select',
|
|
1271
|
+
en: 'Plan first, for this session',
|
|
1272
|
+
bg: 'Първо план, за тази сесия',
|
|
1273
|
+
doesEn: 'Plan first for this session\'s tasks: "As in Settings", off, approved without asking, or approved by the operator; overrides Settings → Execution → Plan first.',
|
|
1274
|
+
doesBg: '„Първо план“ за задачите на тази сесия: „Както е в Настройки“, изключено, одобрява се без питане или от оператора; отменя Настройки → Изпълнение → Първо план.',
|
|
1275
|
+
},
|
|
1276
|
+
{
|
|
1277
|
+
key: 'pm.followSettings',
|
|
1278
|
+
kind: 'select',
|
|
1279
|
+
en: 'As in Settings',
|
|
1280
|
+
bg: 'Както е в Настройки',
|
|
1281
|
+
doesEn: 'The session follows Settings → Execution → Plan first.',
|
|
1282
|
+
doesBg: 'Сесията следва Настройки → Изпълнение → Първо план.',
|
|
1283
|
+
},
|
|
1196
1284
|
{
|
|
1197
1285
|
key: 'session.run',
|
|
1198
1286
|
kind: 'button',
|
|
@@ -1708,6 +1796,14 @@ export const SYSTEM_GUIDE = [
|
|
|
1708
1796
|
doesEn: 'In the edit, with version control on: the commit message\'s subject and body.',
|
|
1709
1797
|
doesBg: 'В редакцията, с включен контрол на версиите: заглавието и тялото на комита.',
|
|
1710
1798
|
},
|
|
1799
|
+
{
|
|
1800
|
+
key: 'fup.button',
|
|
1801
|
+
kind: 'button',
|
|
1802
|
+
en: 'Continue with a new prompt',
|
|
1803
|
+
bg: 'Продължи с нов prompt',
|
|
1804
|
+
doesEn: 'On a task card that failed or blocked on its work: the same dialog as on the register\'s row — how it ended, a new prompt, and the task carries on from where it got to in its chat and on its branch.',
|
|
1805
|
+
doesBg: 'На карта на задача, провалена или блокирана по работата: същият прозорец като на реда в регистъра — как е завършила, нов prompt, и задачата продължава оттам, докъдето е стигнала, в чата и на клона си.',
|
|
1806
|
+
},
|
|
1711
1807
|
{
|
|
1712
1808
|
key: 'task.continue',
|
|
1713
1809
|
kind: 'button',
|
|
@@ -1804,6 +1900,22 @@ export const SYSTEM_GUIDE = [
|
|
|
1804
1900
|
doesEn: 'The exact first message that opened the task.',
|
|
1805
1901
|
doesBg: 'Точното първо съобщение, с което е започнала задачата.',
|
|
1806
1902
|
},
|
|
1903
|
+
{
|
|
1904
|
+
key: 'pm.taskApproved',
|
|
1905
|
+
kind: 'disclosure',
|
|
1906
|
+
en: 'Plan (version {n}, approved)',
|
|
1907
|
+
bg: 'План (версия {n}, одобрен)',
|
|
1908
|
+
doesEn: 'The plan the work follows (plan first): what was already there, the approach, steps, files, how it shows the work is done, risks; who approved it and the comments that asked for earlier versions to change.',
|
|
1909
|
+
doesBg: 'Планът, който работата следва („Първо план“): какво вече има, подход, стъпки, файлове, как ще покаже, че е готово, рискове; кой го е одобрил и коментарите, поискали промени в по-ранни версии.',
|
|
1910
|
+
},
|
|
1911
|
+
{
|
|
1912
|
+
key: 'pm.taskWaiting',
|
|
1913
|
+
kind: 'disclosure',
|
|
1914
|
+
en: 'Plan (version {n}, waiting for approval)',
|
|
1915
|
+
bg: 'План (версия {n}, чака одобрение)',
|
|
1916
|
+
doesEn: 'The same, open while the plan waits on its approval card at the top of every page.',
|
|
1917
|
+
doesBg: 'Същото, отворено, докато планът чака на картата си за одобрение най-горе на всяка страница.',
|
|
1918
|
+
},
|
|
1807
1919
|
{
|
|
1808
1920
|
key: 'task.finalReply',
|
|
1809
1921
|
kind: 'disclosure',
|
|
@@ -2312,6 +2424,37 @@ export const SYSTEM_GUIDE = [
|
|
|
2312
2424
|
},
|
|
2313
2425
|
],
|
|
2314
2426
|
},
|
|
2427
|
+
{
|
|
2428
|
+
headingKey: 'fup.title',
|
|
2429
|
+
en: 'Continue “{title}” with a new prompt',
|
|
2430
|
+
bg: 'Продължи „{title}“ с нов prompt',
|
|
2431
|
+
controls: [
|
|
2432
|
+
{
|
|
2433
|
+
key: 'fup.original',
|
|
2434
|
+
kind: 'disclosure',
|
|
2435
|
+
en: 'The task’s prompt as it stands',
|
|
2436
|
+
bg: 'Prompt-ът на задачата, какъвто е',
|
|
2437
|
+
doesEn: 'Shows the task\'s own prompt, which stays as it is: the new one is added to it.',
|
|
2438
|
+
doesBg: 'Показва собствения prompt на задачата, който остава какъвто е: новият се добавя към него.',
|
|
2439
|
+
},
|
|
2440
|
+
{
|
|
2441
|
+
key: 'fup.prompt',
|
|
2442
|
+
kind: 'field',
|
|
2443
|
+
en: 'New prompt',
|
|
2444
|
+
bg: 'Нов prompt',
|
|
2445
|
+
doesEn: 'What to do now, in the operator\'s words: what was wrong and what to do instead. Required. It wins where it and the task\'s prompt disagree.',
|
|
2446
|
+
doesBg: 'Какво да направи сега, с думите на оператора: какво е било грешно и какво да направи вместо това. Задължително. Надделява, където то и prompt-ът на задачата си противоречат.',
|
|
2447
|
+
},
|
|
2448
|
+
{
|
|
2449
|
+
key: 'fup.save',
|
|
2450
|
+
kind: 'button',
|
|
2451
|
+
en: 'Queue it to continue with this prompt',
|
|
2452
|
+
bg: 'Върни в опашката да продължи с този prompt',
|
|
2453
|
+
doesEn: 'Queues the task to carry on with the prompt, in its chat and on its branch; the dialog then offers "Run this task".',
|
|
2454
|
+
doesBg: 'Връща задачата в опашката да продължи с prompt-а, в чата и на клона ѝ; после прозорецът предлага „Пусни тази задача“.',
|
|
2455
|
+
},
|
|
2456
|
+
],
|
|
2457
|
+
},
|
|
2315
2458
|
{
|
|
2316
2459
|
headingKey: null,
|
|
2317
2460
|
en: 'On every task row, in every view',
|
|
@@ -2421,6 +2564,14 @@ export const SYSTEM_GUIDE = [
|
|
|
2421
2564
|
doesEn: 'On a row that failed or blocked on its work: says what happens to the chat, branch and record, then starts a new attempt of that task alone; the rest of the queue stays.',
|
|
2422
2565
|
doesBg: 'На ред, провален или блокиран по работата: казва какво става с чата, клона и записа, после пуска нов опит само за тази задача; останалите в опашката остават.',
|
|
2423
2566
|
},
|
|
2567
|
+
{
|
|
2568
|
+
key: 'fup.button',
|
|
2569
|
+
kind: 'button',
|
|
2570
|
+
en: 'Continue with a new prompt',
|
|
2571
|
+
bg: 'Продължи с нов prompt',
|
|
2572
|
+
doesEn: 'The way on after a task failed or blocked on its work: opens a dialog that shows how it ended (the reason, the checks that did not pass, what the review found) and takes a new prompt. Queued with it, the task carries on from where it got to — its own chat and branch, the failed attempt\'s work in the tree — and the chat is told how it ended and why, then given the prompt, which is added to the task\'s own. Saving does not start it; the dialog then offers "Run this task". Recommend this when the operator wants to keep the work and correct it.',
|
|
2573
|
+
doesBg: 'Пътят напред, когато задача се е провалила или блокирала по работата: отваря прозорец, който показва как е завършила (причината, проверките, които не минаха, какво откри рецензията) и приема нов prompt. Върната с него в опашката, задачата продължава оттам, докъдето е стигнала — собственият ѝ чат и клон, с работата на неуспешния опит в дървото — а чатът научава как и защо е завършила и получава prompt-а, който се добавя към собствения на задачата. Запазването не я пуска; после прозорецът предлага „Пусни тази задача“. Препоръчвай го, когато операторът иска да запази работата и да я поправи.',
|
|
2574
|
+
},
|
|
2424
2575
|
{
|
|
2425
2576
|
key: 'reg.continueHere',
|
|
2426
2577
|
kind: 'button',
|
|
@@ -2429,13 +2580,21 @@ export const SYSTEM_GUIDE = [
|
|
|
2429
2580
|
doesEn: 'On a row stopped by a limit from the settings (minutes, messages, reply wait, rounds of fixing), by the bot stopping or by the operator: says what happens to the chat, branch and files, then starts that task alone in its chat.',
|
|
2430
2581
|
doesBg: 'На ред, спрян от лимит в настройките (минути, съобщения, чакане на отговор, кръгове поправки), от спрял бот или от оператора: казва какво става с чата, клона и файловете, после пуска само тази задача в чата ѝ.',
|
|
2431
2582
|
},
|
|
2583
|
+
{
|
|
2584
|
+
key: 'reg.continueHerePrompt',
|
|
2585
|
+
kind: 'field',
|
|
2586
|
+
en: 'New prompt (optional)',
|
|
2587
|
+
bg: 'Нов prompt (по желание)',
|
|
2588
|
+
doesEn: 'In the "Continue where it stopped" panel: a new prompt to carry on with, optional — added to the task, and the chat does it as it carries on.',
|
|
2589
|
+
doesBg: 'В панела „Продължи откъдето спря“: нов prompt, с който да продължи, по желание — добавя се към задачата и чатът го изпълнява, докато продължава.',
|
|
2590
|
+
},
|
|
2432
2591
|
{
|
|
2433
2592
|
key: 'reg.fixPrompt',
|
|
2434
2593
|
kind: 'button',
|
|
2435
2594
|
en: 'Fix the prompt and queue it again',
|
|
2436
2595
|
bg: 'Поправи prompt-а и върни в опашката',
|
|
2437
|
-
doesEn: 'On a row that did not end done, its session idle: rewrite the task\'s text and queue it again. Saving does not start it; the same dialog then asks "Run it now?" with the "Run this task" choices and buttons.',
|
|
2438
|
-
doesBg: 'На ред, който не е завършил готов, при спряна сесия: пренапишете текста на задачата и я върнете в
|
|
2596
|
+
doesEn: 'On a row that did not end done, its session idle: rewrite the task\'s text and queue it again — it starts over from where the task first began, the failed attempt\'s work set aside on its branch (to keep that work and go on from it: "Continue with a new prompt"). Saving does not start it; the same dialog then asks "Run it now?" with the "Run this task" choices and buttons.',
|
|
2597
|
+
doesBg: 'На ред, който не е завършил готов, при спряна сесия: пренапишете текста на задачата и я върнете в опашката — тя започва отначало оттам, откъдето е тръгнала първия път, а работата на неуспешния опит остава в неговия клон (за да я запазите и да продължите от нея: „Продължи с нов prompt“). Запазването не я пуска; после същият прозорец пита „Да я пусна ли сега?“ с изборите и бутоните на „Пусни тази задача“.',
|
|
2439
2598
|
},
|
|
2440
2599
|
{
|
|
2441
2600
|
key: 'runq.later',
|
|
@@ -3354,6 +3513,38 @@ export const SYSTEM_GUIDE = [
|
|
|
3354
3513
|
doesEn: 'It runs without anyone reading it. Only on a machine where that is acceptable.',
|
|
3355
3514
|
doesBg: 'Изпълнява се, без някой да я е прочел. Само на машина, където това е приемливо.',
|
|
3356
3515
|
},
|
|
3516
|
+
{
|
|
3517
|
+
key: 'pm.setting',
|
|
3518
|
+
kind: 'select',
|
|
3519
|
+
en: 'Plan first',
|
|
3520
|
+
bg: 'Първо план',
|
|
3521
|
+
doesEn: 'Each task first looks at the code without changing anything and makes a plan; the work starts once the plan is approved, and the review checks the work followed it. A run that does not ask approves the plan itself, so it never waits.',
|
|
3522
|
+
doesBg: 'Всяка задача първо разглежда кода, без да променя нищо, и прави план; работата започва, когато планът е одобрен, а прегледът проверява дали работата го следва. Пускане, което не пита, одобрява плана само, така че никога не чака.',
|
|
3523
|
+
},
|
|
3524
|
+
{
|
|
3525
|
+
key: 'pm.off',
|
|
3526
|
+
kind: 'select',
|
|
3527
|
+
en: 'Off — the work starts at once',
|
|
3528
|
+
bg: 'Изключено — работата започва веднага',
|
|
3529
|
+
doesEn: 'No plan: the task starts changing the code in its first reply, as before.',
|
|
3530
|
+
doesBg: 'Без план: задачата започва да променя кода от първия си отговор, както досега.',
|
|
3531
|
+
},
|
|
3532
|
+
{
|
|
3533
|
+
key: 'pm.auto',
|
|
3534
|
+
kind: 'select',
|
|
3535
|
+
en: 'On — the plan is approved without asking',
|
|
3536
|
+
bg: 'Включено — планът се одобрява без питане',
|
|
3537
|
+
doesEn: 'The runner approves the plan as soon as it is complete and records that it did.',
|
|
3538
|
+
doesBg: 'Runner-ът одобрява плана веднага щом е пълен и записва, че го е направил.',
|
|
3539
|
+
},
|
|
3540
|
+
{
|
|
3541
|
+
key: 'pm.approveMode',
|
|
3542
|
+
kind: 'select',
|
|
3543
|
+
en: 'On — I approve the plan when the run asks before each command',
|
|
3544
|
+
bg: 'Включено — аз одобрявам плана, когато пускането пита преди всяка команда',
|
|
3545
|
+
doesEn: 'In a run that asks before each command, the plan waits on a card at the top of every page; an unattended run approves it itself and says why.',
|
|
3546
|
+
doesBg: 'При пускане с питане преди всяка команда планът чака на карта най-горе на всяка страница; пускане без надзор го одобрява само и казва защо.',
|
|
3547
|
+
},
|
|
3357
3548
|
],
|
|
3358
3549
|
},
|
|
3359
3550
|
],
|
|
@@ -142,11 +142,33 @@ export function normaliseProse(value) {
|
|
|
142
142
|
});
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
+
// A plan's prose, in plan mode: read as text whatever shape it came in, as the fields above are.
|
|
146
|
+
if (out.plan && typeof out.plan === 'object' && !Array.isArray(out.plan)) {
|
|
147
|
+
const plan = { ...out.plan };
|
|
148
|
+
for (const key of ['existing', 'approach', 'verification', 'risks']) {
|
|
149
|
+
if (key in plan && plan[key] !== undefined && typeof plan[key] !== 'string') {
|
|
150
|
+
plan[key] = proseOf(plan[key]);
|
|
151
|
+
coerced.push(`plan.${key}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
for (const key of ['steps', 'files']) {
|
|
155
|
+
if (typeof plan[key] === 'string') {
|
|
156
|
+
// One item per line, its numbering or bullet taken off: "1. add the writer\n2. add the route".
|
|
157
|
+
plan[key] = plan[key]
|
|
158
|
+
.split(/\r?\n/)
|
|
159
|
+
.map((l) => l.replace(/^\s*(?:\d+[.)]|[-*•])\s+/, '').trim())
|
|
160
|
+
.filter(Boolean);
|
|
161
|
+
coerced.push(`plan.${key}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
out.plan = plan;
|
|
165
|
+
}
|
|
145
166
|
return { value: out, coerced };
|
|
146
167
|
}
|
|
147
168
|
/** What each field must look like, said in the message that asks for a reformatted reply. */
|
|
148
169
|
const FIELD_SHAPES = [
|
|
149
|
-
[/^status$/, '"status" is one of "continue", "done", "blocked"'],
|
|
170
|
+
[/^status$/, '"status" is one of "continue", "done", "blocked" — and "plan", in plan mode only'],
|
|
171
|
+
[/^plan(\.|$)/, '"plan" is {"existing": "…", "approach": "…", "steps": ["…"], "files": ["…"], "verification": "…", "risks": "…"} with plain strings'],
|
|
150
172
|
[/^steps(\.|$)/, '"steps" is a list of {"id": 1, "type": "command", "cmd": "the command"} ("shell", "expect", "timeoutSec" optional)'],
|
|
151
173
|
[/^tried(\.|$)/, '"tried" is a list of plain strings, one sentence per approach'],
|
|
152
174
|
[/^deviations(\.|$)/, '"deviations" is a list of {"instruction": "…", "did": "…", "why": "…"} with plain strings'],
|
|
@@ -210,10 +232,12 @@ export function parseReply(markdown, opts) {
|
|
|
210
232
|
// model that writes "Край" under an explanation of why it cannot finish has ended the
|
|
211
233
|
// task, not completed it, and reading that as `done` would file a failure as a success.
|
|
212
234
|
const blocked = reply.status === 'blocked';
|
|
235
|
+
// A plan waits for its approval: the stop word in it does not end the task.
|
|
236
|
+
const plan = reply.status === 'plan';
|
|
213
237
|
return {
|
|
214
238
|
ok: true,
|
|
215
239
|
reply: { ...reply, steps },
|
|
216
|
-
done: reply.status === 'done' || (markerHit && hasSummary && !blocked),
|
|
240
|
+
done: reply.status === 'done' || (markerHit && hasSummary && !blocked && !plan),
|
|
217
241
|
blocked,
|
|
218
242
|
json: text.trim(),
|
|
219
243
|
...(prose.coerced.length > 0 ? { coerced: prose.coerced } : {}),
|
|
@@ -90,6 +90,21 @@ export const DisputeSchema = z.object({
|
|
|
90
90
|
/** What shows it: the command run and what came back, quoted. */
|
|
91
91
|
evidence: z.string().trim().min(1),
|
|
92
92
|
});
|
|
93
|
+
/** A list item that arrived as an object or a number: read as its text, since nothing in a plan is run. */
|
|
94
|
+
const asText = (v) => typeof v === 'string' ? v : v && typeof v === 'object' ? Object.values(v).map((x) => (typeof x === 'string' ? x : JSON.stringify(x))).join(' — ') : String(v ?? '');
|
|
95
|
+
/**
|
|
96
|
+
* The plan of a task in plan mode (see `PlanMode` in session/model.ts): what is already there, how the
|
|
97
|
+
* task will be done, its stages, the files it touches, how the result will be shown to work, and the
|
|
98
|
+
* risks. Prose, never run; a list item in another shape is read as its text.
|
|
99
|
+
*/
|
|
100
|
+
export const PlanSchema = z.object({
|
|
101
|
+
existing: z.string().trim().min(1, 'plan.existing must say what is already in the project that the task touches: the files, functions and behaviour you found.'),
|
|
102
|
+
approach: z.string().trim().min(1, 'plan.approach must say how you mean to do the task.'),
|
|
103
|
+
steps: z.array(z.preprocess(asText, z.string().trim().min(1))).min(1, 'plan.steps must list the stages, in order.'),
|
|
104
|
+
files: z.array(z.preprocess(asText, z.string().trim().min(1))).default([]),
|
|
105
|
+
verification: z.string().trim().min(1, 'plan.verification must say how you will show the result works.'),
|
|
106
|
+
risks: z.string().optional(),
|
|
107
|
+
});
|
|
93
108
|
export const ReplySchema = z
|
|
94
109
|
.object({
|
|
95
110
|
/**
|
|
@@ -105,8 +120,13 @@ export const ReplySchema = z
|
|
|
105
120
|
* costing a message, a reply and a report, and ending in a timeout that says nothing about
|
|
106
121
|
* why. A task that stops with "I tried these three things and this is in the way" is a
|
|
107
122
|
* result. A task that runs out of iterations is not.
|
|
123
|
+
*
|
|
124
|
+
* `plan` — in plan mode only: here is the plan, made from what I read; no steps with it, and no change
|
|
125
|
+
* made yet. See `PlanSchema`.
|
|
108
126
|
*/
|
|
109
|
-
status: z.enum(['continue', 'done', 'blocked']),
|
|
127
|
+
status: z.enum(['continue', 'done', 'blocked', 'plan']),
|
|
128
|
+
/** Required when `status` is `plan`. */
|
|
129
|
+
plan: PlanSchema.optional(),
|
|
110
130
|
steps: z.array(StepSchema).default([]),
|
|
111
131
|
notes: z.string().optional(),
|
|
112
132
|
/**
|
|
@@ -138,8 +158,12 @@ export const ReplySchema = z
|
|
|
138
158
|
'Send at least one step, or set status to "done" or "blocked".',
|
|
139
159
|
})
|
|
140
160
|
.refine((r) => r.status === 'continue' || r.steps.length === 0, {
|
|
141
|
-
message: 'status "done" and
|
|
142
|
-
'Send the steps with status "continue"
|
|
161
|
+
message: 'status "done", "blocked" and "plan" cannot carry steps: "done" and "blocked" end the task, and "plan" waits for the plan to be approved. ' +
|
|
162
|
+
'Send the steps with status "continue".',
|
|
163
|
+
})
|
|
164
|
+
.refine((r) => r.status !== 'plan' || !!r.plan, {
|
|
165
|
+
message: 'status "plan" requires a "plan" object: { "existing": what is already there, "approach": how, "steps": [the stages], ' +
|
|
166
|
+
'"files": [the files you will create or change], "verification": how you will show it works, "risks": optional }.',
|
|
143
167
|
})
|
|
144
168
|
.refine((r) => r.status !== 'done' || (r.summary ?? '').trim().length >= MIN_SUMMARY_CHARS, {
|
|
145
169
|
message: 'status "done" requires a real summary: several sentences explaining what was done, ' +
|
|
@@ -190,7 +214,7 @@ export function describeDeviations(deviations) {
|
|
|
190
214
|
* because forgetting to repeat is more common than meaning to retract.
|
|
191
215
|
*/
|
|
192
216
|
export function resolveDeviations(existing, status, incoming) {
|
|
193
|
-
if (status !== 'continue' && incoming.length > 0)
|
|
217
|
+
if (status !== 'continue' && status !== 'plan' && incoming.length > 0)
|
|
194
218
|
return mergeDeviations([], incoming);
|
|
195
219
|
return mergeDeviations(existing, incoming);
|
|
196
220
|
}
|
|
@@ -11,7 +11,7 @@ const TASK_HEADER = '## Task';
|
|
|
11
11
|
* and a note that is empty simply leaves nothing behind.
|
|
12
12
|
*/
|
|
13
13
|
function runnerBlock(input) {
|
|
14
|
-
const notes = [input.workDirNote, input.readOnlyNote, input.scopeNote, input.approachesNote, input.vcsNote, input.shellNote].map((n) => (n ?? '').trim()).filter((n) => n.length > 0);
|
|
14
|
+
const notes = [input.workDirNote, input.readOnlyNote, input.scopeNote, input.approachesNote, input.vcsNote, input.shellNote, input.planNote].map((n) => (n ?? '').trim()).filter((n) => n.length > 0);
|
|
15
15
|
return notes.length > 0 ? `${notes.join('\n\n')}\n\n` : '';
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
@@ -52,6 +52,8 @@ function level2Block(level2) {
|
|
|
52
52
|
* the steps it had just asked for actually ran.
|
|
53
53
|
*/
|
|
54
54
|
function whyItStopped(c) {
|
|
55
|
+
if (c.how === 'failed')
|
|
56
|
+
return describeEnding(c.fromAttempt, c.ended, c.stoppedBecause);
|
|
55
57
|
if (c.how === 'interrupted') {
|
|
56
58
|
const where = c.interruption ? `\n\n${describeInterruption(c.interruption)}\n\n` : ' ';
|
|
57
59
|
return ('The runner stopped unexpectedly while this task was in progress — the program was closed, the machine went off or it ' +
|
|
@@ -71,6 +73,33 @@ function whyItStopped(c) {
|
|
|
71
73
|
const which = c.limit ? ` of ${LIMIT_WORDS[c.limit.setting](c.limit.value)}` : '';
|
|
72
74
|
return `It stopped because the runner's limit${which} was reached${why} — not because anything failed. The files are as you left them.`;
|
|
73
75
|
}
|
|
76
|
+
/**
|
|
77
|
+
* How an attempt ended without the work being done, in the words the chat is sent: its status and the
|
|
78
|
+
* runner's reason, the checks that did not pass, what the independent review found, and the attempt's
|
|
79
|
+
* own summary. The chat that carries it on needs the why as much as the what (2026-10-13).
|
|
80
|
+
*/
|
|
81
|
+
export function describeEnding(fromAttempt, ended, reason) {
|
|
82
|
+
const status = ended?.status ?? 'failed';
|
|
83
|
+
const why = (ended?.reason ?? reason ?? '').trim();
|
|
84
|
+
const lines = [`The previous attempt (attempt ${fromAttempt}) did not succeed: it ended ${status}${why ? ` — ${why}` : '.'}`];
|
|
85
|
+
if (ended?.failedChecks?.length) {
|
|
86
|
+
lines.push('', 'The checks that did not pass:');
|
|
87
|
+
for (const c of ended.failedChecks)
|
|
88
|
+
lines.push(`- ${c.name}: ${c.detail}`);
|
|
89
|
+
}
|
|
90
|
+
if (ended?.findings?.length) {
|
|
91
|
+
lines.push('', 'What the independent review found:');
|
|
92
|
+
for (const f of ended.findings)
|
|
93
|
+
lines.push(`- ${f.what}${f.where ? ` (${f.where})` : ''}: ${f.evidence}`);
|
|
94
|
+
}
|
|
95
|
+
if (ended?.summary)
|
|
96
|
+
lines.push('', `That attempt's own summary: ${ended.summary}`);
|
|
97
|
+
return lines.join('\n');
|
|
98
|
+
}
|
|
99
|
+
/** The operator's new prompt, as its own section of the message. */
|
|
100
|
+
function instructionSection(text) {
|
|
101
|
+
return `## New instruction from the operator\n\nAdded to the task above, not in place of it; where the two disagree, this wins.\n\n${text.trim()}`;
|
|
102
|
+
}
|
|
74
103
|
/** Each limit from the settings, as the chat is told about it. See `TaskLimit`. */
|
|
75
104
|
const LIMIT_WORDS = {
|
|
76
105
|
maxRunMinutes: (n) => `${n} minutes per task`,
|
|
@@ -87,23 +116,49 @@ export function composeOpening(input) {
|
|
|
87
116
|
* again — sending it would read as a new task and start the work over. Only when the
|
|
88
117
|
* conversation was lost does the task go out in full, with a line saying it is a continuation.
|
|
89
118
|
*/
|
|
119
|
+
const instruction = input.instruction?.text.trim() ? input.instruction : undefined;
|
|
90
120
|
if (input.continuing && input.contractAlreadySent) {
|
|
91
|
-
const
|
|
121
|
+
const title = input.taskTitle.trim() || 'untitled';
|
|
122
|
+
/*
|
|
123
|
+
* After a failure, with the operator's new prompt: told how it ended and why, that its files are
|
|
124
|
+
* there, and what to do now — not "carry on from your plan", which is the plan that failed.
|
|
125
|
+
*/
|
|
126
|
+
if (input.continuing.how === 'failed' && instruction) {
|
|
127
|
+
const message = `Continue task ${input.taskNumber}: ${title}, in this same conversation, with a new instruction from the operator.\n\n` +
|
|
128
|
+
`${whyItStopped(input.continuing)}\n\n` +
|
|
129
|
+
`The files are as that attempt left them, and what it changed is in your working tree. Do not start over: look at ` +
|
|
130
|
+
`what is there first, keep what is right, and do what the instruction below says. The assignment is the one you ` +
|
|
131
|
+
`were given above, with the instruction added to it. Verify the result as for any task. Step numbering restarts at 1.\n\n` +
|
|
132
|
+
`${instructionSection(instruction.text)}\n\n` +
|
|
133
|
+
`${runnerBlock(input)}`.trimEnd();
|
|
134
|
+
return { messages: [message], firstMessage: message };
|
|
135
|
+
}
|
|
136
|
+
const message = `Continue task ${input.taskNumber}: ${title}, in this same conversation. ` +
|
|
92
137
|
`${whyItStopped(input.continuing)} ` +
|
|
93
138
|
`Do not start over and do not repeat work that is done: start from ` +
|
|
94
139
|
`your plan, say in \`notes\` which stages are done and which one you are on, and carry on. ` +
|
|
95
|
-
|
|
140
|
+
(instruction
|
|
141
|
+
? `The assignment is the one you were given above, with the operator's instruction below added to it. Step numbering restarts at 1.\n\n${instructionSection(instruction.text)}\n\n`
|
|
142
|
+
: `The assignment is the one you were given above; it has not changed. Step numbering restarts at 1.\n\n`) +
|
|
96
143
|
`${runnerBlock(input)}`.trimEnd();
|
|
97
144
|
return { messages: [message], firstMessage: message };
|
|
98
145
|
}
|
|
99
146
|
const continuationLine = input.continuing
|
|
100
|
-
? `\n\nThis continues an earlier attempt at this task. ${whyItStopped(input.continuing)}
|
|
101
|
-
|
|
147
|
+
? `\n\nThis continues an earlier attempt at this task. ${whyItStopped(input.continuing)}` +
|
|
148
|
+
(input.continuing.how === 'failed'
|
|
149
|
+
? `\n\nWhat that attempt changed is in your working tree. Look at it before changing anything, keep what is right, and do what the instruction below says.`
|
|
150
|
+
: ` Look at what is already done before changing anything, and carry on from there.`) +
|
|
151
|
+
(instruction ? `\n\n${instructionSection(instruction.text)}` : '')
|
|
102
152
|
: input.buildsOn
|
|
103
153
|
? `\n\nThis task was done once already (attempt ${input.buildsOn.fromAttempt}), and that work is in your ` +
|
|
104
154
|
`working tree. The text above is a new instruction for it: look at what is there first, build on it, and ` +
|
|
105
|
-
`change or undo it only where the new instruction asks. Verify the result as for any task.`
|
|
106
|
-
|
|
155
|
+
`change or undo it only where the new instruction asks. Verify the result as for any task.` +
|
|
156
|
+
(instruction ? `\n\n${describeEnding(instruction.fromAttempt, instruction.ended)}\n\n${instructionSection(instruction.text)}` : '')
|
|
157
|
+
: instruction
|
|
158
|
+
? // The attempt it follows never reached a conversation: what is known of its ending, then the prompt.
|
|
159
|
+
`\n\nAn earlier attempt at this task never reached this conversation. ${describeEnding(instruction.fromAttempt, instruction.ended)}` +
|
|
160
|
+
`\n\n${instructionSection(instruction.text)}`
|
|
161
|
+
: '';
|
|
107
162
|
if (!input.contractAlreadySent) {
|
|
108
163
|
const taskMessage = `${runnerBlock(input)}${level2Block(input.level2)}\n\n${taskBlock}${continuationLine}`;
|
|
109
164
|
return {
|
|
@@ -137,6 +137,7 @@ export function buildPlanExport(scope) {
|
|
|
137
137
|
onFailure: s.onFailure ?? 'stop',
|
|
138
138
|
level2: split.level2,
|
|
139
139
|
conversationGroup: shared ? '' : (s.conversationGroup ?? ''),
|
|
140
|
+
...(s.planMode ? { planMode: s.planMode } : {}),
|
|
140
141
|
vcs: {
|
|
141
142
|
enabled: s.vcs?.enabled ?? false,
|
|
142
143
|
repoDir: s.vcs?.repoDir ?? '',
|
|
@@ -12,6 +12,44 @@ export function tidyVcsPlan(plan) {
|
|
|
12
12
|
return undefined;
|
|
13
13
|
return { ...(branch ? { branch } : {}), ...(commitMessage ? { commitMessage } : {}) };
|
|
14
14
|
}
|
|
15
|
+
const clipText = (s, n) => {
|
|
16
|
+
const t = (s ?? '').trim();
|
|
17
|
+
if (!t)
|
|
18
|
+
return undefined;
|
|
19
|
+
return t.length > n ? `${t.slice(0, n)}…` : t;
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* How a task's latest attempt ended, for the next one to be told: its status and reason, its own
|
|
23
|
+
* summary, the checks that did not pass and what the independent review found — clipped, since it
|
|
24
|
+
* goes into a chat message.
|
|
25
|
+
*/
|
|
26
|
+
export function endingOf(t) {
|
|
27
|
+
const failedChecks = (t.checkResults ?? []).filter((c) => !c.passed).slice(0, 10).map((c) => ({ name: c.name, detail: clipText(c.detail, 400) ?? '' }));
|
|
28
|
+
const findings = t.review?.verdict === 'fail'
|
|
29
|
+
? (t.review.findings ?? []).slice(0, 10).map((f) => ({ what: clipText(f.what, 400) ?? '', ...(f.where ? { where: clipText(f.where, 200) } : {}), evidence: clipText(f.evidence, 400) ?? '' }))
|
|
30
|
+
: [];
|
|
31
|
+
const reason = clipText(t.reason, 2000);
|
|
32
|
+
const summary = clipText(t.summary, 2000);
|
|
33
|
+
return {
|
|
34
|
+
status: t.status,
|
|
35
|
+
...(reason ? { reason } : {}),
|
|
36
|
+
...(summary && summary !== reason ? { summary } : {}),
|
|
37
|
+
...(t.stopCode ? { stopCode: t.stopCode } : {}),
|
|
38
|
+
...(failedChecks.length > 0 ? { failedChecks } : {}),
|
|
39
|
+
...(findings.length > 0 ? { findings } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether a task can be carried on with a new prompt after it failed: it ended failed or blocked on its
|
|
44
|
+
* work. Not one that contradicted itself — it never started its work, and its way on is the fix of the
|
|
45
|
+
* contradiction. A task that stopped at a limit or was stopped is continued anyway (`isContinuable`),
|
|
46
|
+
* with a new prompt or without.
|
|
47
|
+
*/
|
|
48
|
+
export function canFollowUp(t) {
|
|
49
|
+
return (t.status === 'failed' || t.status === 'blocked') && t.stopCode !== 'contract-conflict';
|
|
50
|
+
}
|
|
51
|
+
/** The longest new prompt taken: it goes into one chat message beside the task. */
|
|
52
|
+
export const MAX_INSTRUCTION_CHARS = 20_000;
|
|
15
53
|
/**
|
|
16
54
|
* Whether "Continue" may carry a task on where it stopped, in its chat and on its branch.
|
|
17
55
|
*
|
|
@@ -11,7 +11,7 @@ import { mkdir, readFile, writeFile, readdir, rename, rm } from 'node:fs/promise
|
|
|
11
11
|
import { existsSync } from 'node:fs';
|
|
12
12
|
import { randomUUID } from 'node:crypto';
|
|
13
13
|
import { join, resolve } from 'node:path';
|
|
14
|
-
import { isContinuable, newId, tidyVcsPlan } from './model.js';
|
|
14
|
+
import { endingOf, isContinuable, MAX_INSTRUCTION_CHARS, newId, tidyVcsPlan } from './model.js';
|
|
15
15
|
/** A task the runner is inside of. It cannot be edited, deleted, or left behind at startup. */
|
|
16
16
|
const ACTIVE_STATUSES = ['running', 'waiting-approval'];
|
|
17
17
|
/**
|
|
@@ -107,6 +107,7 @@ export const TASK_FIELD_ON_RERUN = {
|
|
|
107
107
|
leftovers: 'archived',
|
|
108
108
|
environment: 'archived',
|
|
109
109
|
handoff: 'archived',
|
|
110
|
+
plan: 'archived',
|
|
110
111
|
interruption: 'cleared',
|
|
111
112
|
finalReply: 'cleared',
|
|
112
113
|
firstMessage: 'cleared',
|
|
@@ -525,12 +526,35 @@ export class SessionStore {
|
|
|
525
526
|
*/
|
|
526
527
|
async continueTask(sessionId, taskId,
|
|
527
528
|
/** Where an attempt the operator stopped had got to, read from its run folder by the caller. */
|
|
528
|
-
stoppedAt
|
|
529
|
+
stoppedAt,
|
|
530
|
+
/**
|
|
531
|
+
* The operator's new prompt (2026-10-13): what carries a task that failed or blocked on from where
|
|
532
|
+
* it got to, and may come with any other Continue. See `TaskContinuation.instruction`.
|
|
533
|
+
*/
|
|
534
|
+
instruction) {
|
|
529
535
|
const current = (await this.getSession(sessionId))?.tasks.find((x) => x.id === taskId);
|
|
530
536
|
if (!current)
|
|
531
537
|
throw new Error(`Task ${taskId} does not exist in session ${sessionId}.`);
|
|
538
|
+
const text = instruction?.trim() || undefined;
|
|
539
|
+
if (text && text.length > MAX_INSTRUCTION_CHARS)
|
|
540
|
+
throw new Error(`The new prompt is ${text.length} characters; it can be at most ${MAX_INSTRUCTION_CHARS}.`);
|
|
532
541
|
if (!isContinuable(current)) {
|
|
533
|
-
|
|
542
|
+
if (current.status === 'failed' || current.status === 'blocked') {
|
|
543
|
+
if (current.stopCode === 'contract-conflict') {
|
|
544
|
+
throw new Error('This task never started its work: it contradicts itself. Fix the contradiction ("Fix the contradiction"), then run it.');
|
|
545
|
+
}
|
|
546
|
+
if (!text)
|
|
547
|
+
throw new Error('A task that failed is carried on with a new prompt: say what to do now. To run it again as it is, use "Run again".');
|
|
548
|
+
// The task as it ended, before the attempt is archived: what the chat is told about it.
|
|
549
|
+
return await this.rerunTask(sessionId, taskId, {}, {
|
|
550
|
+
fromAttempt: current.attempt ?? 1,
|
|
551
|
+
stoppedBecause: current.reason,
|
|
552
|
+
how: 'failed',
|
|
553
|
+
instruction: text,
|
|
554
|
+
ended: endingOf(current),
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
throw new Error('Only a task that stopped before it finished, at a limit from the settings, or that failed can be continued; run this one again instead.');
|
|
534
558
|
}
|
|
535
559
|
const how = current.status === 'limit-reached' || current.limit ? 'limit' : current.interruption ? 'interrupted' : 'stopped';
|
|
536
560
|
return await this.rerunTask(sessionId, taskId, {}, {
|
|
@@ -539,6 +563,7 @@ export class SessionStore {
|
|
|
539
563
|
how,
|
|
540
564
|
...(current.limit ? { limit: current.limit } : {}),
|
|
541
565
|
...(current.interruption ? { interruption: current.interruption } : how === 'stopped' && stoppedAt ? { interruption: stoppedAt } : {}),
|
|
566
|
+
...(text ? { instruction: text } : {}),
|
|
542
567
|
});
|
|
543
568
|
}
|
|
544
569
|
/**
|
|
@@ -595,6 +620,7 @@ export class SessionStore {
|
|
|
595
620
|
inputsRestored: t.inputsRestored,
|
|
596
621
|
artifactsKept: t.artifactsKept,
|
|
597
622
|
vcs: t.vcs,
|
|
623
|
+
plan: t.plan,
|
|
598
624
|
},
|
|
599
625
|
];
|
|
600
626
|
// An edit is applied only after the old attempt has been put beyond its reach.
|
|
@@ -627,6 +653,7 @@ export class SessionStore {
|
|
|
627
653
|
// marks itself again after this, in the runner.
|
|
628
654
|
t.stats = undefined;
|
|
629
655
|
t.handoff = undefined;
|
|
656
|
+
t.plan = undefined;
|
|
630
657
|
t.scopeReverted = undefined;
|
|
631
658
|
t.treeChanged = undefined;
|
|
632
659
|
t.inputsRestored = undefined;
|