@enderfga/claw-orchestrator 7.12.0 → 7.13.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 +6 -4
- package/dist/bin/cli.js +66 -6
- package/dist/bin/cli.js.map +1 -1
- package/dist/src/autoloop/dispatcher.d.ts +19 -0
- package/dist/src/autoloop/dispatcher.js +57 -0
- package/dist/src/autoloop/dispatcher.js.map +1 -1
- package/dist/src/autoloop/runner.d.ts +102 -2
- package/dist/src/autoloop/runner.js +367 -44
- package/dist/src/autoloop/runner.js.map +1 -1
- package/dist/src/autoloop/types.d.ts +17 -0
- package/dist/src/autoloop/types.js.map +1 -1
- package/dist/src/dashboard/index.html +79 -73
- package/dist/src/embedded-server.js +28 -5
- package/dist/src/embedded-server.js.map +1 -1
- package/dist/src/engine-spawn.d.ts +8 -1
- package/dist/src/engine-spawn.js +13 -3
- package/dist/src/engine-spawn.js.map +1 -1
- package/dist/src/index.js +58 -69
- package/dist/src/index.js.map +1 -1
- package/dist/src/kernel/agent-step.d.ts +3 -0
- package/dist/src/kernel/agent-step.js.map +1 -1
- package/dist/src/kernel/engine.d.ts +5 -2
- package/dist/src/kernel/engine.js +11 -12
- package/dist/src/kernel/engine.js.map +1 -1
- package/dist/src/kernel/nodes/autoloop.d.ts +32 -2
- package/dist/src/kernel/nodes/autoloop.js +122 -15
- package/dist/src/kernel/nodes/autoloop.js.map +1 -1
- package/dist/src/kernel/nodes/goal.d.ts +94 -0
- package/dist/src/kernel/nodes/goal.js +386 -0
- package/dist/src/kernel/nodes/goal.js.map +1 -0
- package/dist/src/kernel/nodes/index.d.ts +2 -1
- package/dist/src/kernel/nodes/index.js +3 -1
- package/dist/src/kernel/nodes/index.js.map +1 -1
- package/dist/src/kernel/templates/index.d.ts +27 -3
- package/dist/src/kernel/templates/index.js +78 -1
- package/dist/src/kernel/templates/index.js.map +1 -1
- package/dist/src/kernel/types.d.ts +41 -2
- package/dist/src/kernel/types.js.map +1 -1
- package/dist/src/models.js +10 -2
- package/dist/src/models.js.map +1 -1
- package/dist/src/persistent-agy-session.js +1 -0
- package/dist/src/persistent-agy-session.js.map +1 -1
- package/dist/src/persistent-codex-app-session.d.ts +55 -26
- package/dist/src/persistent-codex-app-session.js +109 -24
- package/dist/src/persistent-codex-app-session.js.map +1 -1
- package/dist/src/persistent-codex-session.js +1 -0
- package/dist/src/persistent-codex-session.js.map +1 -1
- package/dist/src/persistent-cursor-session.js +1 -0
- package/dist/src/persistent-cursor-session.js.map +1 -1
- package/dist/src/persistent-custom-session.js +2 -0
- package/dist/src/persistent-custom-session.js.map +1 -1
- package/dist/src/persistent-gemini-session.js +1 -0
- package/dist/src/persistent-gemini-session.js.map +1 -1
- package/dist/src/persistent-grok-session.d.ts +4 -0
- package/dist/src/persistent-grok-session.js +12 -2
- package/dist/src/persistent-grok-session.js.map +1 -1
- package/dist/src/persistent-opencode-session.js +1 -0
- package/dist/src/persistent-opencode-session.js.map +1 -1
- package/dist/src/persistent-session.js +1 -0
- package/dist/src/persistent-session.js.map +1 -1
- package/dist/src/sanitize.d.ts +10 -0
- package/dist/src/sanitize.js +26 -0
- package/dist/src/sanitize.js.map +1 -1
- package/dist/src/session-manager.d.ts +25 -20
- package/dist/src/session-manager.js +217 -44
- package/dist/src/session-manager.js.map +1 -1
- package/dist/src/types.d.ts +6 -0
- package/package.json +2 -2
- package/skills/SKILL.md +8 -5
- package/skills/references/autoloop.md +52 -8
- package/skills/references/claude-cli-tracking.md +3 -1
- package/skills/references/cli.md +32 -1
- package/skills/references/dashboard.md +5 -1
- package/skills/references/multi-engine.md +26 -15
- package/skills/references/tools.md +25 -14
- package/skills/references/workflow.md +87 -13
|
@@ -142,9 +142,10 @@ import { resolveAlias, isClaudeModel, lookupModel } from './models.js';
|
|
|
142
142
|
import { isAgyConversationId } from './agy-conversation.js';
|
|
143
143
|
import { Council } from './council.js';
|
|
144
144
|
import { AutoloopRunner } from './autoloop/runner.js';
|
|
145
|
+
import { boundedErrorReason } from './sanitize.js';
|
|
145
146
|
import { ClaudeAgentDispatcher } from './autoloop/dispatcher.js';
|
|
146
|
-
import { DEFAULT_PUSH_POLICY, DEFAULT_SEND_TIMEOUT_MS, validateAutoloopTimeoutConfig } from './autoloop/types.js';
|
|
147
|
-
import { Msg as AutoloopMsg } from './autoloop/messages.js';
|
|
147
|
+
import { DEFAULT_PUSH_POLICY, DEFAULT_SEND_TIMEOUT_MS, MAX_METRIC_HISTORY, validateAutoloopTimeoutConfig, } from './autoloop/types.js';
|
|
148
|
+
import { Msg as AutoloopMsg, } from './autoloop/messages.js';
|
|
148
149
|
import { appendPushLog, notifyUserFallbackChain } from './autoloop/notify.js';
|
|
149
150
|
import { UltraappManager } from './ultraapp/manager.js';
|
|
150
151
|
import { UltraappStore, defaultStoreRoot } from './ultraapp/store.js';
|
|
@@ -646,6 +647,128 @@ function readPermissionDenials(evt) {
|
|
|
646
647
|
}
|
|
647
648
|
return out;
|
|
648
649
|
}
|
|
650
|
+
/** Fixed refusal when a non-fresh boot cannot trust the closed-ledger prefix. */
|
|
651
|
+
const AUTOLOOP_CLOSED_LEDGER_REFUSAL = 'Autoloop resume refused: ledger closed-prefix is incomplete or malformed';
|
|
652
|
+
const REVIEWER_DECISIONS = new Set(['advance', 'hold', 'rollback']);
|
|
653
|
+
/**
|
|
654
|
+
* One-reader closed-ledger restore for non-fresh `_bootAutoloop`.
|
|
655
|
+
*
|
|
656
|
+
* Derives typed `iter` / `metric_history` from the contiguous valid closed
|
|
657
|
+
* prefix starting at 0. Checkpoint bytes are never progress authority.
|
|
658
|
+
* Returns null when there is no valid prefix (legacy lone verdict, empty).
|
|
659
|
+
* Throws {@link AUTOLOOP_CLOSED_LEDGER_REFUSAL} on gap / malformed tail.
|
|
660
|
+
*/
|
|
661
|
+
/** Canonical numeric iter directory names — no leading zeros. */
|
|
662
|
+
const CLOSED_LEDGER_ITER_DIR = /^(0|[1-9][0-9]*)$/;
|
|
663
|
+
function readClosedLedgerRestore(ledgerDir) {
|
|
664
|
+
const iterRoot = path.join(ledgerDir, 'iter');
|
|
665
|
+
if (!fs.existsSync(iterRoot))
|
|
666
|
+
return null;
|
|
667
|
+
let numericDirs;
|
|
668
|
+
try {
|
|
669
|
+
numericDirs = fs
|
|
670
|
+
.readdirSync(iterRoot)
|
|
671
|
+
.filter((name) => CLOSED_LEDGER_ITER_DIR.test(name))
|
|
672
|
+
.map((name) => Number(name))
|
|
673
|
+
.sort((a, b) => a - b);
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
if (numericDirs.length === 0)
|
|
679
|
+
return null;
|
|
680
|
+
const present = new Set(numericDirs);
|
|
681
|
+
const metrics = [];
|
|
682
|
+
let i = 0;
|
|
683
|
+
for (;;) {
|
|
684
|
+
const iterDir = path.join(iterRoot, String(i));
|
|
685
|
+
if (!fs.existsSync(iterDir)) {
|
|
686
|
+
// K=-1 / no valid closed prefix: never refuse on higher dirs (base Planner-only).
|
|
687
|
+
if (i === 0)
|
|
688
|
+
return null;
|
|
689
|
+
for (const j of present) {
|
|
690
|
+
if (j > i)
|
|
691
|
+
throw new Error(AUTOLOOP_CLOSED_LEDGER_REFUSAL);
|
|
692
|
+
}
|
|
693
|
+
break;
|
|
694
|
+
}
|
|
695
|
+
const directivePath = path.join(iterDir, 'directive.json');
|
|
696
|
+
const verdictPath = path.join(iterDir, 'verdict.json');
|
|
697
|
+
const hasDirective = fs.existsSync(directivePath);
|
|
698
|
+
const hasVerdict = fs.existsSync(verdictPath);
|
|
699
|
+
if (!hasDirective) {
|
|
700
|
+
if (hasVerdict) {
|
|
701
|
+
// Legacy lone verdict (or directive-less "closed" appearance).
|
|
702
|
+
if (i === 0)
|
|
703
|
+
return null;
|
|
704
|
+
throw new Error(AUTOLOOP_CLOSED_LEDGER_REFUSAL);
|
|
705
|
+
}
|
|
706
|
+
// Empty/non-closed at 0: prefixless compat before any gap check.
|
|
707
|
+
if (i === 0)
|
|
708
|
+
return null;
|
|
709
|
+
for (const j of present) {
|
|
710
|
+
if (j > i)
|
|
711
|
+
throw new Error(AUTOLOOP_CLOSED_LEDGER_REFUSAL);
|
|
712
|
+
}
|
|
713
|
+
break;
|
|
714
|
+
}
|
|
715
|
+
if (!hasVerdict) {
|
|
716
|
+
// Open / directive-only / committed-without-review tail at i.
|
|
717
|
+
// No closed prefix yet: return null before gap checks (open0+lateriter1 compat).
|
|
718
|
+
if (i === 0)
|
|
719
|
+
return null;
|
|
720
|
+
for (const j of present) {
|
|
721
|
+
if (j > i)
|
|
722
|
+
throw new Error(AUTOLOOP_CLOSED_LEDGER_REFUSAL);
|
|
723
|
+
}
|
|
724
|
+
const history = metrics.length > MAX_METRIC_HISTORY ? metrics.slice(metrics.length - MAX_METRIC_HISTORY) : metrics.slice();
|
|
725
|
+
return { iter: i, metric_history: history };
|
|
726
|
+
}
|
|
727
|
+
let raw;
|
|
728
|
+
try {
|
|
729
|
+
raw = fs.readFileSync(verdictPath, 'utf8');
|
|
730
|
+
}
|
|
731
|
+
catch {
|
|
732
|
+
// Prefixless unreadable verdict at 0: no-restore / no-refusal (base compat).
|
|
733
|
+
if (i === 0)
|
|
734
|
+
return null;
|
|
735
|
+
throw new Error(AUTOLOOP_CLOSED_LEDGER_REFUSAL);
|
|
736
|
+
}
|
|
737
|
+
let parsed;
|
|
738
|
+
try {
|
|
739
|
+
parsed = JSON.parse(raw);
|
|
740
|
+
}
|
|
741
|
+
catch {
|
|
742
|
+
if (i === 0)
|
|
743
|
+
return null;
|
|
744
|
+
throw new Error(AUTOLOOP_CLOSED_LEDGER_REFUSAL);
|
|
745
|
+
}
|
|
746
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
747
|
+
if (i === 0)
|
|
748
|
+
return null;
|
|
749
|
+
throw new Error(AUTOLOOP_CLOSED_LEDGER_REFUSAL);
|
|
750
|
+
}
|
|
751
|
+
const verdict = parsed;
|
|
752
|
+
if (verdict.iter !== i) {
|
|
753
|
+
if (i === 0)
|
|
754
|
+
return null;
|
|
755
|
+
throw new Error(AUTOLOOP_CLOSED_LEDGER_REFUSAL);
|
|
756
|
+
}
|
|
757
|
+
if (typeof verdict.decision !== 'string' || !REVIEWER_DECISIONS.has(verdict.decision)) {
|
|
758
|
+
if (i === 0)
|
|
759
|
+
return null;
|
|
760
|
+
throw new Error(AUTOLOOP_CLOSED_LEDGER_REFUSAL);
|
|
761
|
+
}
|
|
762
|
+
if (typeof verdict.metric === 'number' && Number.isFinite(verdict.metric)) {
|
|
763
|
+
metrics.push(verdict.metric);
|
|
764
|
+
}
|
|
765
|
+
i += 1;
|
|
766
|
+
}
|
|
767
|
+
if (i === 0)
|
|
768
|
+
return null;
|
|
769
|
+
const history = metrics.length > MAX_METRIC_HISTORY ? metrics.slice(metrics.length - MAX_METRIC_HISTORY) : metrics.slice();
|
|
770
|
+
return { iter: i, metric_history: history };
|
|
771
|
+
}
|
|
649
772
|
export class SessionManager {
|
|
650
773
|
sessions = new Map();
|
|
651
774
|
_pendingSessions = new Map();
|
|
@@ -1299,7 +1422,7 @@ export class SessionManager {
|
|
|
1299
1422
|
// its executor is registered here with a builder closed over `this`
|
|
1300
1423
|
// rather than living in the kernel.
|
|
1301
1424
|
kernel.setExecutor('autoloop', makeAutoloopExecutor({
|
|
1302
|
-
boot: (config, secrets, { freshStart }) => this._bootAutoloop({
|
|
1425
|
+
boot: (config, secrets, { freshStart, restoredInput }) => this._bootAutoloop({
|
|
1303
1426
|
...config,
|
|
1304
1427
|
// Custom-engine configs never reach the spec, so they come from
|
|
1305
1428
|
// the run's in-memory secret bag — supplied at start, and
|
|
@@ -1310,6 +1433,7 @@ export class SessionManager {
|
|
|
1310
1433
|
// Always set (including false on resume) — omitting false left a
|
|
1311
1434
|
// `_freshStart: true` from config/secrets intact.
|
|
1312
1435
|
_freshStart: freshStart,
|
|
1436
|
+
_restoredInput: restoredInput,
|
|
1313
1437
|
}),
|
|
1314
1438
|
ready: (key, value) => {
|
|
1315
1439
|
const deferred = this._autoloopReady.get(key);
|
|
@@ -1846,46 +1970,35 @@ export class SessionManager {
|
|
|
1846
1970
|
// Persist final state (TTL-expired sessions already removed by cleanup)
|
|
1847
1971
|
this._savePersisted();
|
|
1848
1972
|
}
|
|
1849
|
-
// ─── Codex
|
|
1850
|
-
/**
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
* caches.
|
|
1855
|
-
*
|
|
1856
|
-
* Errors clearly when called against a non-`codex-app` session — those
|
|
1857
|
-
* sessions cannot interpret `/goal` (the `codex exec` path has no slash
|
|
1858
|
-
* command surface).
|
|
1859
|
-
*/
|
|
1860
|
-
async codexGoalCommand(name, slashArgs, timeoutMs) {
|
|
1861
|
-
const managed = this.sessions.get(name);
|
|
1862
|
-
if (!managed)
|
|
1863
|
-
throw new Error(`Session not found: ${name}`);
|
|
1864
|
-
const session = managed.session;
|
|
1865
|
-
if (typeof session.sendGoalCommand !== 'function') {
|
|
1866
|
-
const engine = managed.config.engine || 'claude';
|
|
1867
|
-
throw new Error(`Session "${name}" uses engine "${engine}" which does not support /goal. ` +
|
|
1868
|
-
`Start a session with engine: "codex-app" to use the goal tools.`);
|
|
1869
|
-
}
|
|
1870
|
-
const result = await session.sendGoalCommand(slashArgs, timeoutMs);
|
|
1871
|
-
return { ok: true, text: result.text, goal: result.goal };
|
|
1973
|
+
// ─── Codex goals (codex-app engine only) ─────────────────────────────
|
|
1974
|
+
/** The live `codex-app` session behind `name`, for the kernel's `goal` node. */
|
|
1975
|
+
codexGoalSession(name) {
|
|
1976
|
+
const session = this.sessions.get(name)?.session;
|
|
1977
|
+
return session instanceof PersistentCodexAppServerSession ? session : undefined;
|
|
1872
1978
|
}
|
|
1873
1979
|
/**
|
|
1874
|
-
*
|
|
1875
|
-
*
|
|
1876
|
-
*
|
|
1980
|
+
* Act on a `codex-app` session's goal through the app-server's
|
|
1981
|
+
* `thread/goal/*` RPCs. Used by the `codex_goal_*` tools; `get` asks the
|
|
1982
|
+
* server rather than a cache.
|
|
1983
|
+
*
|
|
1984
|
+
* Errors clearly when called against a non-`codex-app` session — `codex
|
|
1985
|
+
* exec` has no goal surface.
|
|
1877
1986
|
*/
|
|
1878
|
-
|
|
1879
|
-
const
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1987
|
+
async codexGoal(name, op) {
|
|
1988
|
+
const session = this._getCodexAppSession(name, '/goal');
|
|
1989
|
+
switch (op.action) {
|
|
1990
|
+
case 'set':
|
|
1991
|
+
return { ok: true, goal: await session.setGoal({ objective: op.objective, tokenBudget: op.tokenBudget }) };
|
|
1992
|
+
case 'pause':
|
|
1993
|
+
return { ok: true, goal: await session.setGoal({ status: 'paused' }) };
|
|
1994
|
+
case 'resume':
|
|
1995
|
+
return { ok: true, goal: await session.setGoal({ status: 'active' }) };
|
|
1996
|
+
case 'clear':
|
|
1997
|
+
await session.clearGoal();
|
|
1998
|
+
return { ok: true, goal: null };
|
|
1999
|
+
case 'get':
|
|
2000
|
+
return { ok: true, goal: await session.getGoal() };
|
|
1887
2001
|
}
|
|
1888
|
-
return { ok: true, goal: session.goal ?? null };
|
|
1889
2002
|
}
|
|
1890
2003
|
// ─── Codex app-server v2 RPCs (codex-app engine only, Codex 0.137) ────────
|
|
1891
2004
|
//
|
|
@@ -2557,6 +2670,12 @@ export class SessionManager {
|
|
|
2557
2670
|
if (engine === 'codex') {
|
|
2558
2671
|
return session.threadId;
|
|
2559
2672
|
}
|
|
2673
|
+
// `sessionId` on a codex-app session is a label of ours; `thread/resume`
|
|
2674
|
+
// needs the app-server's own thread id, and silently opened a new thread
|
|
2675
|
+
// when handed the label.
|
|
2676
|
+
if (engine === 'codex-app') {
|
|
2677
|
+
return session.codexThreadId;
|
|
2678
|
+
}
|
|
2560
2679
|
return session.sessionId;
|
|
2561
2680
|
}
|
|
2562
2681
|
_storedResumeId(engine, id) {
|
|
@@ -2566,6 +2685,8 @@ export class SessionManager {
|
|
|
2566
2685
|
return id && !/^codex-\d+-/.test(id) ? id : undefined;
|
|
2567
2686
|
if (engine === 'grok')
|
|
2568
2687
|
return id && !/^grok-\d+-/.test(id) ? id : undefined;
|
|
2688
|
+
if (engine === 'codex-app')
|
|
2689
|
+
return id && !id.startsWith('codex-app-') ? id : undefined;
|
|
2569
2690
|
return id;
|
|
2570
2691
|
}
|
|
2571
2692
|
_listMdFiles(dir) {
|
|
@@ -3086,6 +3207,13 @@ export class SessionManager {
|
|
|
3086
3207
|
if (!fs.existsSync(ledgerDir)) {
|
|
3087
3208
|
fs.mkdirSync(ledgerDir, { recursive: true });
|
|
3088
3209
|
}
|
|
3210
|
+
// Non-fresh boots: restore typed progress from the contiguous closed ledger
|
|
3211
|
+
// prefix before any dispatcher/runner/session work. Refusal throws here so
|
|
3212
|
+
// no Planner start or checkpoint publish occurs.
|
|
3213
|
+
let closedLedgerRestore = null;
|
|
3214
|
+
if (opts._freshStart !== true) {
|
|
3215
|
+
closedLedgerRestore = readClosedLedgerRestore(ledgerDir);
|
|
3216
|
+
}
|
|
3089
3217
|
// Per-run policy object — mutable so Planner's update_push_policy is visible
|
|
3090
3218
|
// to the runner without re-wiring.
|
|
3091
3219
|
const pushPolicy = JSON.parse(JSON.stringify(DEFAULT_PUSH_POLICY));
|
|
@@ -3132,6 +3260,10 @@ export class SessionManager {
|
|
|
3132
3260
|
run_id: opts.runId,
|
|
3133
3261
|
workspace: opts.workspace,
|
|
3134
3262
|
ledger_dir: ledgerDir,
|
|
3263
|
+
persistCheckpoint: () => {
|
|
3264
|
+
const publish = this._autoloopPublishers.get(runId);
|
|
3265
|
+
return publish ? publish() : false;
|
|
3266
|
+
},
|
|
3135
3267
|
push_policy: pushPolicy,
|
|
3136
3268
|
notifyUser: async (level, summary, detail, channel) => {
|
|
3137
3269
|
const result = await notifyUserFallbackChain({
|
|
@@ -3156,7 +3288,18 @@ export class SessionManager {
|
|
|
3156
3288
|
activityLeaseMs: opts.activityLeaseMs,
|
|
3157
3289
|
autoloopHardTimeoutMs: opts.autoloopHardTimeoutMs,
|
|
3158
3290
|
});
|
|
3291
|
+
// Background resume/deadline/stall paths emit 'error' after a void catch.
|
|
3292
|
+
// Without a listener, Node turns that into an unhandled rejection.
|
|
3293
|
+
runner.on('error', (err) => {
|
|
3294
|
+
this.logger.error?.(`[autoloop/${runId}] background runner task failed: ${boundedErrorReason(err)}`);
|
|
3295
|
+
});
|
|
3159
3296
|
runnerRef = runner;
|
|
3297
|
+
if (closedLedgerRestore) {
|
|
3298
|
+
runner.state.iter = closedLedgerRestore.iter;
|
|
3299
|
+
runner.state.metric_history = closedLedgerRestore.metric_history.slice();
|
|
3300
|
+
}
|
|
3301
|
+
if (opts._restoredInput)
|
|
3302
|
+
runner.loadRestoredInput(opts._restoredInput);
|
|
3160
3303
|
try {
|
|
3161
3304
|
await runner.start();
|
|
3162
3305
|
// A reconstructed migration becomes externally visible only after its
|
|
@@ -3164,7 +3307,6 @@ export class SessionManager {
|
|
|
3164
3307
|
// this inside the startup try makes an append failure follow the same
|
|
3165
3308
|
// cleanup path as any other failed resume.
|
|
3166
3309
|
opts._commitTimeoutMigration?.();
|
|
3167
|
-
await this._recoverCommittedCoderIteration(opts.workspace, ledgerDir, runId, runner, dispatcher);
|
|
3168
3310
|
}
|
|
3169
3311
|
catch (err) {
|
|
3170
3312
|
try {
|
|
@@ -3178,7 +3320,17 @@ export class SessionManager {
|
|
|
3178
3320
|
runner.stop();
|
|
3179
3321
|
throw err;
|
|
3180
3322
|
}
|
|
3181
|
-
|
|
3323
|
+
// Committed-iteration recovery runs after publisher registration, the
|
|
3324
|
+
// initial-publish check and notice reconciliation (see afterPublishReconcile
|
|
3325
|
+
// in makeAutoloopExecutor) — never inside boot before that gate.
|
|
3326
|
+
return {
|
|
3327
|
+
runner,
|
|
3328
|
+
dispatcher,
|
|
3329
|
+
ledgerDir,
|
|
3330
|
+
pushPolicy,
|
|
3331
|
+
abort: () => runner.markCancelledByKernel(),
|
|
3332
|
+
afterPublishReconcile: () => this._recoverCommittedCoderIteration(opts.workspace, ledgerDir, runId, runner, dispatcher),
|
|
3333
|
+
};
|
|
3182
3334
|
}
|
|
3183
3335
|
/**
|
|
3184
3336
|
* If the durable ledger has a committed Coder iteration with no Reviewer
|
|
@@ -3219,8 +3371,12 @@ export class SessionManager {
|
|
|
3219
3371
|
return;
|
|
3220
3372
|
this.logger.info?.(`[autoloop/${runId}] recovering committed iter ${n} into Reviewer`);
|
|
3221
3373
|
await dispatcher.ensureReviewerForCommittedRecovery();
|
|
3222
|
-
|
|
3374
|
+
// Keep a reconciled chat_notice_pending pause so Planner-bound follow-ups
|
|
3375
|
+
// park until the notice is recorded; live spawn_subagents stays unchanged.
|
|
3376
|
+
runner.markSubagentsSpawned({ preservePause: true });
|
|
3223
3377
|
runner.state.iter = n;
|
|
3378
|
+
// Queue only — never await the physical Reviewer turn. Readiness and the
|
|
3379
|
+
// cancellation observer must install before any model transport settles.
|
|
3224
3380
|
void runner
|
|
3225
3381
|
.send(AutoloopMsg.reviewRequest(n, {
|
|
3226
3382
|
iter: n,
|
|
@@ -3308,12 +3464,21 @@ export class SessionManager {
|
|
|
3308
3464
|
this._autoloopStarting.delete(tag);
|
|
3309
3465
|
}
|
|
3310
3466
|
}
|
|
3467
|
+
/**
|
|
3468
|
+
* Durably accept a chat message for a live run's Planner and return the
|
|
3469
|
+
* envelope to deliver. Throws, with nothing accepted, when the run cannot
|
|
3470
|
+
* checkpoint it. Internal: callers pass the result to `runner.send`.
|
|
3471
|
+
*/
|
|
3472
|
+
acceptAutoloopChat(runId, text) {
|
|
3473
|
+
return this._liveAutoloop(runId).runner.acceptChat(text);
|
|
3474
|
+
}
|
|
3311
3475
|
/**
|
|
3312
3476
|
* Inject a user chat message into a v2 run's Planner. Returns the Planner's
|
|
3313
3477
|
* natural-language reply.
|
|
3314
3478
|
*/
|
|
3315
3479
|
async autoloopChat(runId, text) {
|
|
3316
3480
|
const ctx = this._liveAutoloop(runId);
|
|
3481
|
+
const env = this.acceptAutoloopChat(runId, text);
|
|
3317
3482
|
let reply = '';
|
|
3318
3483
|
const onReply = (...args) => {
|
|
3319
3484
|
const t = args[0];
|
|
@@ -3322,7 +3487,7 @@ export class SessionManager {
|
|
|
3322
3487
|
};
|
|
3323
3488
|
ctx.dispatcher.on('planner_reply', onReply);
|
|
3324
3489
|
try {
|
|
3325
|
-
await ctx.runner.send(
|
|
3490
|
+
await ctx.runner.send(env);
|
|
3326
3491
|
}
|
|
3327
3492
|
finally {
|
|
3328
3493
|
ctx.dispatcher.off('planner_reply', onReply);
|
|
@@ -3433,8 +3598,16 @@ export class SessionManager {
|
|
|
3433
3598
|
}
|
|
3434
3599
|
const live = this.kernel.handle(runId, LEGACY_NODE);
|
|
3435
3600
|
if (live) {
|
|
3436
|
-
if (!hasTimeoutIncrease)
|
|
3601
|
+
if (!hasTimeoutIncrease) {
|
|
3602
|
+
// Ordinary and activity-lease pauses have no uncertain in-flight
|
|
3603
|
+
// dispatch. A pending send timeout stays the historical no-op until
|
|
3604
|
+
// an explicit timeout increase names that dispatch.
|
|
3605
|
+
if (live.runner.state.status === 'paused' && live.runner.state.pending_dispatch === null) {
|
|
3606
|
+
live.runner.resumePaused();
|
|
3607
|
+
this._autoloopPublishers.get(runId)?.();
|
|
3608
|
+
}
|
|
3437
3609
|
return live.runner.state;
|
|
3610
|
+
}
|
|
3438
3611
|
const pending = live.runner.state.pending_dispatch;
|
|
3439
3612
|
if (live.runner.state.status !== 'paused' || !pending) {
|
|
3440
3613
|
throw new Error(`Autoloop run '${runId}' is not awaiting a recoverable send timeout`);
|