@open-mercato/cezar 0.9.0-pr646.708 → 0.9.0
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/README.md +1 -17
- package/dist/config.d.ts +0 -14
- package/dist/config.js +0 -39
- package/dist/config.js.map +1 -1
- package/dist/core/claude-ui-mapper.d.ts +0 -9
- package/dist/core/claude-ui-mapper.js +2 -31
- package/dist/core/claude-ui-mapper.js.map +1 -1
- package/dist/core/codex-app-server-runner.js +18 -19
- package/dist/core/codex-app-server-runner.js.map +1 -1
- package/dist/core/opencode-server-runner.js +5 -25
- package/dist/core/opencode-server-runner.js.map +1 -1
- package/dist/core/tool-display.d.ts +1 -2
- package/dist/core/tool-display.js +1 -6
- package/dist/core/tool-display.js.map +1 -1
- package/dist/index.js +4 -40
- package/dist/index.js.map +1 -1
- package/dist/runs/auto-name.js +0 -6
- package/dist/runs/auto-name.js.map +1 -1
- package/dist/runs/store.js +9 -34
- package/dist/runs/store.js.map +1 -1
- package/dist/server/capabilities.d.ts +0 -6
- package/dist/server/capabilities.js +0 -6
- package/dist/server/capabilities.js.map +1 -1
- package/dist/server/server.d.ts +0 -8
- package/dist/server/server.js +7 -146
- package/dist/server/server.js.map +1 -1
- package/dist/server-install/engine.d.ts +0 -5
- package/dist/server-install/engine.js +0 -6
- package/dist/server-install/engine.js.map +1 -1
- package/dist/server-install/platforms/ubuntu-vps.d.ts +2 -11
- package/dist/server-install/platforms/ubuntu-vps.js +10 -123
- package/dist/server-install/platforms/ubuntu-vps.js.map +1 -1
- package/dist/server-install/types.d.ts +0 -39
- package/dist/server-install/types.js +0 -13
- package/dist/server-install/types.js.map +1 -1
- package/dist/skills-remote.d.ts +0 -11
- package/dist/skills-remote.js +2 -31
- package/dist/skills-remote.js.map +1 -1
- package/dist/skills.d.ts +0 -29
- package/dist/skills.js +14 -72
- package/dist/skills.js.map +1 -1
- package/dist/workflows/run.d.ts +10 -50
- package/dist/workflows/run.js +103 -216
- package/dist/workflows/run.js.map +1 -1
- package/dist/workspace/config.d.ts +0 -30
- package/dist/workspace/config.js +0 -5
- package/dist/workspace/config.js.map +1 -1
- package/dist/workspace/projects-cli.d.ts +0 -2
- package/dist/workspace/projects-cli.js +4 -21
- package/dist/workspace/projects-cli.js.map +1 -1
- package/dist/workspace/projects.d.ts +1 -5
- package/dist/workspace/projects.js +8 -5
- package/dist/workspace/projects.js.map +1 -1
- package/dist/workspace/semaphore.d.ts +2 -53
- package/dist/workspace/semaphore.js +5 -93
- package/dist/workspace/semaphore.js.map +1 -1
- package/package.json +1 -1
- package/web/dist/assets/centered-state-zzWBfO_1.js +43 -0
- package/web/dist/assets/command-ylf8-fG0.js +1 -0
- package/web/dist/assets/compare-variants-BcR3zdtB.js +1 -0
- package/web/dist/assets/dialog-BYr_BAil.js +1 -0
- package/web/dist/assets/diff-view-DPCR7gon.js +4 -0
- package/web/dist/assets/{dist-CqH67S8q.js → dist-Cw_CDvC3.js} +1 -1
- package/web/dist/assets/dropdown-menu-C3GD8eLA.js +1 -0
- package/web/dist/assets/git-toolbar-CiMaCi03.js +1 -0
- package/web/dist/assets/github-BJEZbEOp.js +1 -0
- package/web/dist/assets/index-DmW_TYjO.js +16 -0
- package/web/dist/assets/index-Qbs-JRWQ.css +2 -0
- package/web/dist/assets/loader-circle-CGwIVNTv.js +1 -0
- package/web/dist/assets/{project-router-BaWVyqc8.js → project-router-CT8Rt5Eu.js} +1 -1
- package/web/dist/assets/prompt-templates-DW7_d8Ys.js +15 -0
- package/web/dist/assets/repo-git-BkmyKcvN.js +1 -0
- package/web/dist/assets/{run-diff-Cksr4Dk5.js → run-diff-CxIDdRwy.js} +2 -2
- package/web/dist/assets/run-header-8nwDIWWO.js +1 -0
- package/web/dist/assets/skill-empty-hint-Cl3ESoPu.js +1 -0
- package/web/dist/assets/skills-BIpb0mqO.js +1 -0
- package/web/dist/assets/{tab-link-vtgjgL_M.js → tab-link-C_bk_Kvn.js} +1 -1
- package/web/dist/assets/task-changes-D1bJx2uW.js +1 -0
- package/web/dist/assets/task-commits-DlhB21mR.js +1 -0
- package/web/dist/assets/task-files-ChJDOFg5.js +2 -0
- package/web/dist/assets/task-thread-J7ckxJJh.js +9 -0
- package/web/dist/assets/tasks-table--V1pKwuK.js +1 -0
- package/web/dist/assets/use-desktop-CuDnWju1.js +3 -0
- package/web/dist/assets/workflows-BksgdYAQ.js +11 -0
- package/web/dist/assets/x-DgoqYQOJ.js +1 -0
- package/web/dist/index.html +12 -6
- package/dist/core/v1-text-coalescer.d.ts +0 -36
- package/dist/core/v1-text-coalescer.js +0 -72
- package/dist/core/v1-text-coalescer.js.map +0 -1
- package/web/dist/assets/centered-state-CLmJ67EV.js +0 -43
- package/web/dist/assets/commit-list-CSaWuw-x.js +0 -1
- package/web/dist/assets/compare-variants-B5BlnpUb.js +0 -1
- package/web/dist/assets/diff-view-CYKPGL2w.js +0 -6
- package/web/dist/assets/git-toolbar-CDII-3Uv.js +0 -1
- package/web/dist/assets/github-C7SNDPf3.js +0 -1
- package/web/dist/assets/index-CcfkF2Ey.js +0 -30
- package/web/dist/assets/index-DzroxqBG.css +0 -2
- package/web/dist/assets/lib-DPEZDBUN.js +0 -1
- package/web/dist/assets/repo-git-4l-2tm7d.js +0 -1
- package/web/dist/assets/run-header-B3kW2sWC.js +0 -1
- package/web/dist/assets/skill-empty-hint-DTUSp6Vl.js +0 -1
- package/web/dist/assets/skills-D7hPgl4y.js +0 -1
- package/web/dist/assets/task-changes-DvIUGL9a.js +0 -1
- package/web/dist/assets/task-commits-QD2JI3zj.js +0 -1
- package/web/dist/assets/task-files-BE7JDEwK.js +0 -2
- package/web/dist/assets/task-thread-CRjuPhsl.js +0 -9
- package/web/dist/assets/textarea-Bj_LCreg.js +0 -1
- package/web/dist/assets/use-desktop-4e8LLqMG.js +0 -3
- package/web/dist/assets/workflows-bPYx0nbO.js +0 -11
package/dist/workflows/run.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { spawn } from 'node:child_process';
|
|
3
3
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import {
|
|
4
|
+
import { join } from 'node:path';
|
|
5
5
|
import { parseAskMarker, stripAskMarker } from '../core/ask.js';
|
|
6
6
|
import { onUsage, registerRunProcess, unregisterRunProcess } from '../core/process-usage.js';
|
|
7
7
|
import { createRunner } from '../core/runner-factory.js';
|
|
@@ -46,19 +46,6 @@ const DONE_MARKER_RE = /CEZ:DONE\s*$/;
|
|
|
46
46
|
* backends can't split the marker across text events.
|
|
47
47
|
*/
|
|
48
48
|
const MONITORING_MARKER_RE = /CEZ:MONITORING\s*$/;
|
|
49
|
-
/**
|
|
50
|
-
* Preserve boundaries between complete assistant text blocks while a turn is
|
|
51
|
-
* accumulated for marker parsing. The runners join these same v1 blocks with
|
|
52
|
-
* newlines in `AgentRunResult`; matching that contract here prevents a
|
|
53
|
-
* trailing `CEZ:TITLE=` block from absorbing later commentary (#623).
|
|
54
|
-
*/
|
|
55
|
-
export function appendTurnText(current, next) {
|
|
56
|
-
if (!current)
|
|
57
|
-
return next;
|
|
58
|
-
if (!next)
|
|
59
|
-
return current;
|
|
60
|
-
return `${current}\n${next}`;
|
|
61
|
-
}
|
|
62
49
|
/** Strip a trailing marker from one text event so transcripts stay free of
|
|
63
50
|
* protocol noise. Delta backends may split the marker across events — then
|
|
64
51
|
* it stays visible; detection above is unaffected. */
|
|
@@ -209,9 +196,6 @@ export class RunManager {
|
|
|
209
196
|
* ordinary follow-up turns the moment the session opens. In-memory only. */
|
|
210
197
|
deferredMessages = new Map();
|
|
211
198
|
pumping = false;
|
|
212
|
-
/** A pump that arrived while one was in flight — replayed by `pump()`'s own
|
|
213
|
-
* loop so a slot freed mid-sweep is never a lost wakeup. */
|
|
214
|
-
pumpAgain = false;
|
|
215
199
|
/**
|
|
216
200
|
* Runs normally isolate in worktrees and may execute in parallel. When that
|
|
217
201
|
* isolation is unavailable (or explicitly disabled), serialize access to
|
|
@@ -240,8 +224,7 @@ export class RunManager {
|
|
|
240
224
|
this.semaphore = options.semaphore ?? new WorkspaceSemaphore();
|
|
241
225
|
this.offSemaphore = this.semaphore.register({
|
|
242
226
|
busySlots: () => this.busySlots(),
|
|
243
|
-
pump: () => this.pump(),
|
|
244
|
-
oldestQueuedAt: () => this.oldestQueuedAt(),
|
|
227
|
+
pump: () => void this.pump(),
|
|
245
228
|
});
|
|
246
229
|
// Memory guard (#memory-guard): the shared process-tree sampler already ticks ~every 2 s for
|
|
247
230
|
// the runs table; piggyback on it to enforce the per-task memory ceiling.
|
|
@@ -361,19 +344,6 @@ export class RunManager {
|
|
|
361
344
|
// Persist the full definition so a queued run survives a restart (#367) —
|
|
362
345
|
// ad-hoc "(planned)" chains exist nowhere else to re-resolve from.
|
|
363
346
|
this.store.updateRun(run.id, { workflowDef: workflow });
|
|
364
|
-
// Initial pasted images must be visible while the run is still queued (#612),
|
|
365
|
-
// and must survive a restart before a slot opens. Persist them before the job
|
|
366
|
-
// enters `pendingJobs`; `hydrateQueuedInput` reconstructs their content blocks
|
|
367
|
-
// from these URLs when a recovered run eventually starts.
|
|
368
|
-
if (input.images?.length) {
|
|
369
|
-
const persisted = input.images
|
|
370
|
-
.filter((b) => b.type === 'image')
|
|
371
|
-
.map((b) => this.persistImage(run.id, b.source.media_type, b.source.data, 'pasted'))
|
|
372
|
-
.filter((saved) => saved !== null);
|
|
373
|
-
if (persisted.length) {
|
|
374
|
-
this.store.updateRun(run.id, { taskImages: persisted.map((saved) => saved.url) });
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
347
|
// Step-0 reference extraction (task auto-naming spec): the regex layer's
|
|
378
348
|
// numbers persist immediately; the namer may add the kind it verified later.
|
|
379
349
|
const skillHint = workflow.steps.find((s) => stepKind(s) === 'agent' && s.skill)?.skill?.trim();
|
|
@@ -420,95 +390,54 @@ export class RunManager {
|
|
|
420
390
|
busySlots() {
|
|
421
391
|
return this.active.size + this.starting.size - this.waiting.size;
|
|
422
392
|
}
|
|
423
|
-
/** Epoch ms of this manager's oldest queued run (the semaphore's fairness
|
|
424
|
-
* key when a freed slot is broadcast), or null when nothing is queued.
|
|
425
|
-
* `queue` is FIFO — `startRun` pushes and `recover()` re-queues by
|
|
426
|
-
* `createdAt` — so the head is the oldest. */
|
|
427
|
-
oldestQueuedAt() {
|
|
428
|
-
const head = this.queue[0];
|
|
429
|
-
if (!head)
|
|
430
|
-
return null;
|
|
431
|
-
const createdAt = this.store.getRun(head)?.createdAt;
|
|
432
|
-
const ms = createdAt ? Date.parse(createdAt) : Number.NaN;
|
|
433
|
-
return Number.isNaN(ms) ? null : ms;
|
|
434
|
-
}
|
|
435
393
|
/**
|
|
436
|
-
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
* and went. `release()` pumps this manager too, so it replaces the local
|
|
442
|
-
* `pump()` at every slot-freeing transition.
|
|
443
|
-
*/
|
|
444
|
-
releaseSlot() {
|
|
445
|
-
void this.semaphore.release();
|
|
446
|
-
}
|
|
447
|
-
/**
|
|
448
|
-
* Start queued runs while parallel slots are free. A run starts only under
|
|
449
|
-
* BOTH ceilings: the WORKSPACE `resources.maxParallel` (default 2, counted
|
|
450
|
-
* across every manager — spec 2026-07-20, step 2.5) AND this project's own
|
|
451
|
-
* per-project `maxParallel` when the registry sets one (spec 2026-07-22,
|
|
452
|
-
* inherits the workspace cap when unset). Legacy per-repo `maxParallel` keys
|
|
453
|
-
* are ignored. A non-git directory degrades to 1 sequential run in the repo
|
|
454
|
-
* root (spec 006 degradation rule), which is always the tighter bound.
|
|
394
|
+
* Start queued runs while parallel slots are free. The cap is the WORKSPACE
|
|
395
|
+
* `resources.maxParallel` (default 2), cached in the shared semaphore and
|
|
396
|
+
* counted across every manager (spec 2026-07-20, step 2.5) — legacy per-repo
|
|
397
|
+
* `maxParallel` keys are ignored. A non-git directory degrades to 1
|
|
398
|
+
* sequential run in the repo root (spec 006 degradation rule).
|
|
455
399
|
*/
|
|
456
400
|
async pump() {
|
|
457
|
-
|
|
458
|
-
// in-flight pass may already have read capacity (it awaits `getRepoInfo`
|
|
459
|
-
// before the first check), so a slot freed in that window would be lost
|
|
460
|
-
// until the next unrelated event. Re-run the sweep instead.
|
|
461
|
-
if (this.pumping) {
|
|
462
|
-
this.pumpAgain = true;
|
|
401
|
+
if (this.pumping)
|
|
463
402
|
return;
|
|
464
|
-
}
|
|
465
403
|
this.pumping = true;
|
|
466
404
|
try {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
// in the same synchronous tick as the `pendingJobs.delete` above, so no
|
|
493
|
-
// handler can observe a half-dequeued run.
|
|
494
|
-
const input = this.hydrateQueuedInput(runId, job.input);
|
|
495
|
-
void this.execute(runId, job.workflow, input).catch((err) => {
|
|
496
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
497
|
-
this.store.updateRun(runId, {
|
|
498
|
-
status: 'failed',
|
|
499
|
-
error: `engine crashed: ${message}`,
|
|
500
|
-
finishedAt: new Date().toISOString(),
|
|
501
|
-
});
|
|
502
|
-
const state = this.active.get(runId);
|
|
503
|
-
if (state) {
|
|
504
|
-
this.clearIdleTimer(state);
|
|
505
|
-
this.clearAutosaveTimer(state);
|
|
506
|
-
}
|
|
507
|
-
this.starting.delete(runId);
|
|
508
|
-
this.dropActive(runId);
|
|
405
|
+
const repo = await getRepoInfo(this.repoRoot);
|
|
406
|
+
const maxParallel = this.semaphore.maxParallel();
|
|
407
|
+
// `waiting` runs don't hold a slot (#347) — see busySlots(). The check
|
|
408
|
+
// below is the only slot gate: resumes never pass through it.
|
|
409
|
+
const capacity = () => this.semaphore.busy() < maxParallel && (repo !== null || this.busySlots() < 1);
|
|
410
|
+
while (this.queue.length > 0 && capacity()) {
|
|
411
|
+
const runId = this.queue.shift();
|
|
412
|
+
if (!runId)
|
|
413
|
+
break;
|
|
414
|
+
const job = this.pendingJobs.get(runId);
|
|
415
|
+
this.pendingJobs.delete(runId);
|
|
416
|
+
if (!job)
|
|
417
|
+
continue;
|
|
418
|
+
this.starting.add(runId);
|
|
419
|
+
// Rebuild the prompt from the store at the last instant (#472), so an edit
|
|
420
|
+
// or a stacked message that landed while the run waited is honored. Entered
|
|
421
|
+
// in the same synchronous tick as the `pendingJobs.delete` above, so no
|
|
422
|
+
// handler can observe a half-dequeued run.
|
|
423
|
+
const input = this.hydrateQueuedInput(runId, job.input);
|
|
424
|
+
void this.execute(runId, job.workflow, input).catch((err) => {
|
|
425
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
426
|
+
this.store.updateRun(runId, {
|
|
427
|
+
status: 'failed',
|
|
428
|
+
error: `engine crashed: ${message}`,
|
|
429
|
+
finishedAt: new Date().toISOString(),
|
|
509
430
|
});
|
|
510
|
-
|
|
511
|
-
|
|
431
|
+
const state = this.active.get(runId);
|
|
432
|
+
if (state) {
|
|
433
|
+
this.clearIdleTimer(state);
|
|
434
|
+
this.clearAutosaveTimer(state);
|
|
435
|
+
}
|
|
436
|
+
this.starting.delete(runId);
|
|
437
|
+
this.dropActive(runId);
|
|
438
|
+
void this.pump();
|
|
439
|
+
});
|
|
440
|
+
}
|
|
512
441
|
}
|
|
513
442
|
finally {
|
|
514
443
|
this.pumping = false;
|
|
@@ -635,10 +564,6 @@ export class RunManager {
|
|
|
635
564
|
this.active.delete(runId);
|
|
636
565
|
this.memoryPausing.delete(runId);
|
|
637
566
|
this.lastNamerKey.delete(runId);
|
|
638
|
-
// The run's slot is gone from busySlots() as of the deletes above — hand it
|
|
639
|
-
// to the workspace's oldest queued run, in ANY project. Every terminal path
|
|
640
|
-
// funnels through here, so this one call covers them all.
|
|
641
|
-
this.releaseSlot();
|
|
642
567
|
// A run leaving the active registry is a terminal transition (done/review/
|
|
643
568
|
// failed/cancelled) — the one moment the finished-worktree count can grow.
|
|
644
569
|
// Enforce count-based retention (#483) here so a single hook covers every
|
|
@@ -704,7 +629,7 @@ export class RunManager {
|
|
|
704
629
|
abort();
|
|
705
630
|
};
|
|
706
631
|
this.waiting.add(runId);
|
|
707
|
-
this.
|
|
632
|
+
void this.pump();
|
|
708
633
|
try {
|
|
709
634
|
await Promise.race([previous, cancelled]);
|
|
710
635
|
}
|
|
@@ -759,45 +684,34 @@ export class RunManager {
|
|
|
759
684
|
if (!run)
|
|
760
685
|
return input;
|
|
761
686
|
const stack = run.queuedMessages ?? [];
|
|
687
|
+
if (!stack.length)
|
|
688
|
+
return { ...input, task: run.task };
|
|
762
689
|
const task = [run.task, ...stack.map((m) => m.text)]
|
|
763
690
|
.map((part) => part.trim())
|
|
764
691
|
.filter((part) => part.length > 0)
|
|
765
692
|
.join('\n\n');
|
|
766
|
-
const
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
});
|
|
778
|
-
}
|
|
779
|
-
catch {
|
|
780
|
-
// Degrade, never fail the boot (AGENTS.md): the user deleted `.ai/cezar/`
|
|
781
|
-
// or the file is unreadable — start with the text and say which image went.
|
|
782
|
-
this.store.appendEvent(runId, {
|
|
783
|
-
type: 'note',
|
|
784
|
-
message: `${kind} attachment ${name} could not be read — starting without it`,
|
|
785
|
-
});
|
|
786
|
-
}
|
|
693
|
+
const stackedImages = [];
|
|
694
|
+
for (const url of stack.flatMap((m) => m.images ?? [])) {
|
|
695
|
+
const name = url.split('/').pop();
|
|
696
|
+
if (!name || name.includes('..') || name.includes('/') || name.includes('\\'))
|
|
697
|
+
continue;
|
|
698
|
+
try {
|
|
699
|
+
const data = readFileSync(join(this.dataDir, 'runs', `${runId}-images`, name));
|
|
700
|
+
stackedImages.push({
|
|
701
|
+
type: 'image',
|
|
702
|
+
source: { type: 'base64', media_type: mediaTypeFor(name), data: data.toString('base64') },
|
|
703
|
+
});
|
|
787
704
|
}
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
...(images.length ? { images } : { images: undefined }),
|
|
799
|
-
...(stackedImages.length ? { stackedImages } : { stackedImages: undefined }),
|
|
800
|
-
};
|
|
705
|
+
catch {
|
|
706
|
+
// Degrade, never fail the boot (AGENTS.md): the user deleted `.ai/cezar/`
|
|
707
|
+
// or the file is unreadable — start with the text and say which image went.
|
|
708
|
+
this.store.appendEvent(runId, {
|
|
709
|
+
type: 'note',
|
|
710
|
+
message: `queued attachment ${name} could not be read — starting without it`,
|
|
711
|
+
});
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
return { ...input, task, ...(stackedImages.length ? { stackedImages } : {}) };
|
|
801
715
|
}
|
|
802
716
|
/**
|
|
803
717
|
* Still waiting for a slot? Checked against the engine's own queue rather than
|
|
@@ -1108,7 +1022,7 @@ export class RunManager {
|
|
|
1108
1022
|
const continuations = run.steps.filter((s) => s.id.startsWith('continue-')).length;
|
|
1109
1023
|
const stepId = `continue-${continuations + 1}`;
|
|
1110
1024
|
this.store.addStep(runId, { id: stepId, name: 'Continue', kind: 'agent' });
|
|
1111
|
-
void this.runContinuation(runId, stepId, resume ? sessionStep.sessionId : undefined, targetRunner, opts.text?.trim() || 'Continue.'
|
|
1025
|
+
void this.runContinuation(runId, stepId, resume ? sessionStep.sessionId : undefined, targetRunner, opts.text?.trim() || 'Continue.').catch((err) => {
|
|
1112
1026
|
const message = err instanceof Error ? err.message : String(err);
|
|
1113
1027
|
this.store.updateRun(runId, {
|
|
1114
1028
|
status: 'failed',
|
|
@@ -1116,14 +1030,11 @@ export class RunManager {
|
|
|
1116
1030
|
finishedAt: new Date().toISOString(),
|
|
1117
1031
|
});
|
|
1118
1032
|
this.dropActive(runId);
|
|
1033
|
+
void this.pump();
|
|
1119
1034
|
});
|
|
1120
1035
|
return { ok: true };
|
|
1121
1036
|
}
|
|
1122
|
-
async runContinuation(runId, stepId, sessionId, backend, prompt
|
|
1123
|
-
/** Screenshots pasted into the follow-up composer — delivered with the
|
|
1124
|
-
* reopened session's opening message, exactly like a live-session
|
|
1125
|
-
* message's attachments. */
|
|
1126
|
-
images = []) {
|
|
1037
|
+
async runContinuation(runId, stepId, sessionId, backend, prompt) {
|
|
1127
1038
|
// Continuation runs in the task's worktree when it still exists (spec
|
|
1128
1039
|
// 006) — the resumed session sees exactly what the original run left.
|
|
1129
1040
|
// Retention (#483) may have reclaimed this run's worktree directory while
|
|
@@ -1154,6 +1065,7 @@ export class RunManager {
|
|
|
1154
1065
|
});
|
|
1155
1066
|
this.store.appendEvent(runId, { type: 'lifecycle', message: 'run cancelled' });
|
|
1156
1067
|
this.dropActive(runId);
|
|
1068
|
+
void this.pump();
|
|
1157
1069
|
return;
|
|
1158
1070
|
}
|
|
1159
1071
|
}
|
|
@@ -1175,22 +1087,7 @@ export class RunManager {
|
|
|
1175
1087
|
backend,
|
|
1176
1088
|
});
|
|
1177
1089
|
this.store.appendEvent(runId, { type: 'step-start', stepId, name: 'Continue', kind: 'agent', iteration: 1 });
|
|
1178
|
-
|
|
1179
|
-
// message (#357): persisted to the run's own image store so the thread renders the bubble's
|
|
1180
|
-
// images rather than a bare count, and handed to the agent BOTH as base64 blocks (so it can
|
|
1181
|
-
// view them) and as absolute paths appended to the prompt (so it can operate on them — and
|
|
1182
|
-
// because codex/opencode drop image blocks before they reach the model).
|
|
1183
|
-
const attachments = images
|
|
1184
|
-
.filter((b) => b.type === 'image')
|
|
1185
|
-
.map((b) => this.persistImage(runId, b.source.media_type, b.source.data, 'pasted'))
|
|
1186
|
-
.filter((saved) => saved !== null);
|
|
1187
|
-
this.store.appendEvent(runId, {
|
|
1188
|
-
type: 'user-message',
|
|
1189
|
-
stepId,
|
|
1190
|
-
text: prompt,
|
|
1191
|
-
imageCount: images.filter((b) => b.type === 'image').length,
|
|
1192
|
-
...(attachments.length ? { images: attachments.map((saved) => saved.url) } : {}),
|
|
1193
|
-
});
|
|
1090
|
+
this.store.appendEvent(runId, { type: 'user-message', stepId, text: prompt, imageCount: 0 });
|
|
1194
1091
|
let stepCost = 0;
|
|
1195
1092
|
let turnText = '';
|
|
1196
1093
|
const sink = this.makeUiSink(runId, stepId);
|
|
@@ -1202,7 +1099,7 @@ export class RunManager {
|
|
|
1202
1099
|
return;
|
|
1203
1100
|
}
|
|
1204
1101
|
if (event.type === 'text') {
|
|
1205
|
-
turnText
|
|
1102
|
+
turnText += event.text;
|
|
1206
1103
|
const text = stripAskMarker(stripTaskMarkers(stripMonitoringMarker(stripDoneMarker(event.text))));
|
|
1207
1104
|
if (text)
|
|
1208
1105
|
this.store.appendEvent(runId, { type: 'text', text, stepId });
|
|
@@ -1274,7 +1171,7 @@ export class RunManager {
|
|
|
1274
1171
|
}
|
|
1275
1172
|
this.waiting.add(runId);
|
|
1276
1173
|
this.armIdleTimer(runId, state);
|
|
1277
|
-
this.
|
|
1174
|
+
void this.pump();
|
|
1278
1175
|
}
|
|
1279
1176
|
}
|
|
1280
1177
|
appendHandoffHeartbeat(this.dataDir, runId, `turn complete — status=${monitoring ? 'monitoring' : sessionOpen ? 'waiting' : 'running'}`);
|
|
@@ -1318,6 +1215,7 @@ export class RunManager {
|
|
|
1318
1215
|
message: `continue failed — ${err.message}`,
|
|
1319
1216
|
});
|
|
1320
1217
|
this.dropActive(runId);
|
|
1218
|
+
void this.pump();
|
|
1321
1219
|
return;
|
|
1322
1220
|
}
|
|
1323
1221
|
const runner = createRunner(continueBackend);
|
|
@@ -1326,8 +1224,7 @@ export class RunManager {
|
|
|
1326
1224
|
// run's extra system prompt (already resolved at execute time and
|
|
1327
1225
|
// echoed on the record) rides along with the handoff contract.
|
|
1328
1226
|
systemPrompt: composeSystemPrompt(record?.systemPrompt, generateFollowups ? HANDOFF_INSTRUCTIONS : HANDOFF_ONLY_INSTRUCTIONS),
|
|
1329
|
-
userPrompt:
|
|
1330
|
-
...(images.length ? { images } : {}),
|
|
1227
|
+
userPrompt: prompt,
|
|
1331
1228
|
cwd: state.cwd,
|
|
1332
1229
|
allowedTools: DEFAULT_ALLOWED_TOOLS,
|
|
1333
1230
|
additionalDirectories: [join(this.dataDir, 'runs')],
|
|
@@ -1381,6 +1278,7 @@ export class RunManager {
|
|
|
1381
1278
|
if (state.cwd !== this.repoRoot)
|
|
1382
1279
|
await autosaveCommit(state.cwd, 'turn end');
|
|
1383
1280
|
this.dropActive(runId);
|
|
1281
|
+
void this.pump();
|
|
1384
1282
|
}
|
|
1385
1283
|
}
|
|
1386
1284
|
// ---- execution -----------------------------------------------------------
|
|
@@ -1486,6 +1384,7 @@ export class RunManager {
|
|
|
1486
1384
|
});
|
|
1487
1385
|
emit({ type: 'lifecycle', message: `run failed — ${error}` });
|
|
1488
1386
|
this.dropActive(runId);
|
|
1387
|
+
void this.pump();
|
|
1489
1388
|
return;
|
|
1490
1389
|
}
|
|
1491
1390
|
}
|
|
@@ -1511,18 +1410,21 @@ export class RunManager {
|
|
|
1511
1410
|
const retriesUsed = new Map();
|
|
1512
1411
|
let checkFailure = null;
|
|
1513
1412
|
let runError = null;
|
|
1514
|
-
//
|
|
1515
|
-
// (#
|
|
1516
|
-
//
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1413
|
+
// Persist the task's attached images so the thread's initial bubble can render them
|
|
1414
|
+
// (#image-display); they still ride the first agent step's opening message below.
|
|
1415
|
+
// `pasted` prefix (#357) marks these as user attachments on disk and keeps their
|
|
1416
|
+
// absolute paths so runAgentStep can tell the agent where to find the real files.
|
|
1417
|
+
let startAttachments = [];
|
|
1418
|
+
if (input.images?.length) {
|
|
1419
|
+
const persisted = input.images
|
|
1420
|
+
.filter((b) => b.type === 'image')
|
|
1421
|
+
.map((b) => this.persistImage(runId, b.source.media_type, b.source.data, 'pasted'))
|
|
1422
|
+
.filter((saved) => saved !== null);
|
|
1423
|
+
if (persisted.length) {
|
|
1424
|
+
this.store.updateRun(runId, { taskImages: persisted.map((p) => p.url) });
|
|
1425
|
+
startAttachments = persisted;
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1526
1428
|
// Task screenshots go with the FIRST agent step's opening message only —
|
|
1527
1429
|
// later steps and retry loops run in fresh sessions without them. Stacked
|
|
1528
1430
|
// attachments (#472) ride along too, but are NOT re-persisted above: they
|
|
@@ -1620,6 +1522,7 @@ export class RunManager {
|
|
|
1620
1522
|
}
|
|
1621
1523
|
this.clearIdleTimer(state);
|
|
1622
1524
|
this.dropActive(runId);
|
|
1525
|
+
void this.pump();
|
|
1623
1526
|
}
|
|
1624
1527
|
/** Returns an error message, or null on success. */
|
|
1625
1528
|
async runAgentStep(runId, state, step, input, skills, checkFailure, interactive, emit, images, taskBackend, extraSystemPrompt,
|
|
@@ -1696,7 +1599,7 @@ export class RunManager {
|
|
|
1696
1599
|
return;
|
|
1697
1600
|
}
|
|
1698
1601
|
if (event.type === 'text') {
|
|
1699
|
-
turnText
|
|
1602
|
+
turnText += event.text;
|
|
1700
1603
|
const text = stripAskMarker(stripTaskMarkers(stripMonitoringMarker(stripDoneMarker(event.text))));
|
|
1701
1604
|
if (text)
|
|
1702
1605
|
emit({ type: 'text', text, stepId: step.id });
|
|
@@ -1759,7 +1662,7 @@ export class RunManager {
|
|
|
1759
1662
|
}
|
|
1760
1663
|
this.waiting.add(runId);
|
|
1761
1664
|
this.armIdleTimer(runId, state);
|
|
1762
|
-
this.
|
|
1665
|
+
void this.pump(); // the freed slot can start a queued run right away
|
|
1763
1666
|
}
|
|
1764
1667
|
// Cez's own heartbeat — the handoff stays current even when the
|
|
1765
1668
|
// agent forgets to write (spec 007).
|
|
@@ -1871,7 +1774,7 @@ export class RunManager {
|
|
|
1871
1774
|
this.store.updateRun(runId, { status: 'waiting', activity: undefined });
|
|
1872
1775
|
if (state.currentStepId)
|
|
1873
1776
|
this.store.updateStep(runId, state.currentStepId, { status: 'waiting' });
|
|
1874
|
-
this.
|
|
1777
|
+
void this.pump();
|
|
1875
1778
|
}
|
|
1876
1779
|
/**
|
|
1877
1780
|
* Turn-end bookkeeping (#389), shared by `runAgentStep` and
|
|
@@ -2218,30 +2121,14 @@ export function makeRunTitle(task, workflow) {
|
|
|
2218
2121
|
const chars = [...(prefixed || '(untitled task)')];
|
|
2219
2122
|
return chars.length > 80 ? `${chars.slice(0, 79).join('').trimEnd()}…` : chars.join('');
|
|
2220
2123
|
}
|
|
2221
|
-
/**
|
|
2222
|
-
* Skill identity is context, while the Markdown body remains instructions.
|
|
2223
|
-
*
|
|
2224
|
-
* For an on-disk skill we also hand the agent the ABSOLUTE directory of the
|
|
2225
|
-
* installed copy. A run executes in an isolated worktree that has no local
|
|
2226
|
-
* `.agents/skills` (gitignored, absent in a fresh checkout), so without this
|
|
2227
|
-
* the agent cannot read the skill's companion files (`references/*.md`) — or,
|
|
2228
|
-
* worse, reads a stale copy materialized from the team-repo cache. The path
|
|
2229
|
-
* resolves against the MAIN project root (`discoverSkills(repoRoot)`), i.e. the
|
|
2230
|
-
* current `npx skills`-installed copy, so a worktree agent and the main
|
|
2231
|
-
* checkout read the exact same, up-to-date files. Team skills are omitted here:
|
|
2232
|
-
* they are materialized into the worktree separately (see the call site).
|
|
2233
|
-
*/
|
|
2124
|
+
/** Skill identity is context, while the Markdown body remains instructions. */
|
|
2234
2125
|
export function skillSystemPrompt(skill) {
|
|
2235
|
-
|
|
2126
|
+
return [
|
|
2236
2127
|
`Selected skill: /${skill.name}`,
|
|
2237
2128
|
...(skill.description ? [`Description: ${skill.description}`] : []),
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
`It is the current installed copy — use it even though your working directory is a separate worktree that does not contain the skill.`);
|
|
2243
|
-
}
|
|
2244
|
-
lines.push('', 'Skill instructions:', skill.body.trim());
|
|
2245
|
-
return lines.join('\n');
|
|
2129
|
+
'',
|
|
2130
|
+
'Skill instructions:',
|
|
2131
|
+
skill.body.trim(),
|
|
2132
|
+
].join('\n');
|
|
2246
2133
|
}
|
|
2247
2134
|
//# sourceMappingURL=run.js.map
|