@enderfga/claw-orchestrator 7.12.1 → 7.14.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.
Files changed (88) hide show
  1. package/README.md +14 -11
  2. package/configs/engines/README.md +20 -0
  3. package/configs/engines/dsh.json +17 -0
  4. package/configs/engines/kimi-code.json +17 -0
  5. package/dist/bin/cli.js +123 -34
  6. package/dist/bin/cli.js.map +1 -1
  7. package/dist/src/acp-server.js +4 -2
  8. package/dist/src/acp-server.js.map +1 -1
  9. package/dist/src/autoloop/dispatcher.d.ts +19 -0
  10. package/dist/src/autoloop/dispatcher.js +57 -0
  11. package/dist/src/autoloop/dispatcher.js.map +1 -1
  12. package/dist/src/autoloop/runner.d.ts +102 -2
  13. package/dist/src/autoloop/runner.js +367 -44
  14. package/dist/src/autoloop/runner.js.map +1 -1
  15. package/dist/src/autoloop/types.d.ts +17 -0
  16. package/dist/src/autoloop/types.js.map +1 -1
  17. package/dist/src/dashboard/index.html +79 -73
  18. package/dist/src/embedded-server.js +35 -7
  19. package/dist/src/embedded-server.js.map +1 -1
  20. package/dist/src/engine-presets.d.ts +7 -3
  21. package/dist/src/engine-presets.js +39 -6
  22. package/dist/src/engine-presets.js.map +1 -1
  23. package/dist/src/engine-spawn.d.ts +8 -1
  24. package/dist/src/engine-spawn.js +13 -3
  25. package/dist/src/engine-spawn.js.map +1 -1
  26. package/dist/src/index.js +78 -71
  27. package/dist/src/index.js.map +1 -1
  28. package/dist/src/kernel/agent-step.d.ts +3 -0
  29. package/dist/src/kernel/agent-step.js.map +1 -1
  30. package/dist/src/kernel/engine.d.ts +5 -2
  31. package/dist/src/kernel/engine.js +11 -12
  32. package/dist/src/kernel/engine.js.map +1 -1
  33. package/dist/src/kernel/nodes/autoloop.d.ts +32 -2
  34. package/dist/src/kernel/nodes/autoloop.js +122 -15
  35. package/dist/src/kernel/nodes/autoloop.js.map +1 -1
  36. package/dist/src/kernel/nodes/goal.d.ts +94 -0
  37. package/dist/src/kernel/nodes/goal.js +386 -0
  38. package/dist/src/kernel/nodes/goal.js.map +1 -0
  39. package/dist/src/kernel/nodes/index.d.ts +2 -1
  40. package/dist/src/kernel/nodes/index.js +3 -1
  41. package/dist/src/kernel/nodes/index.js.map +1 -1
  42. package/dist/src/kernel/templates/index.d.ts +27 -3
  43. package/dist/src/kernel/templates/index.js +78 -1
  44. package/dist/src/kernel/templates/index.js.map +1 -1
  45. package/dist/src/kernel/types.d.ts +41 -2
  46. package/dist/src/kernel/types.js.map +1 -1
  47. package/dist/src/persistent-acp-session.d.ts +76 -0
  48. package/dist/src/persistent-acp-session.js +409 -0
  49. package/dist/src/persistent-acp-session.js.map +1 -0
  50. package/dist/src/persistent-agy-session.js +1 -0
  51. package/dist/src/persistent-agy-session.js.map +1 -1
  52. package/dist/src/persistent-codex-app-session.d.ts +55 -26
  53. package/dist/src/persistent-codex-app-session.js +109 -24
  54. package/dist/src/persistent-codex-app-session.js.map +1 -1
  55. package/dist/src/persistent-codex-session.js +1 -0
  56. package/dist/src/persistent-codex-session.js.map +1 -1
  57. package/dist/src/persistent-cursor-session.js +1 -0
  58. package/dist/src/persistent-cursor-session.js.map +1 -1
  59. package/dist/src/persistent-custom-session.js +2 -0
  60. package/dist/src/persistent-custom-session.js.map +1 -1
  61. package/dist/src/persistent-gemini-session.js +1 -0
  62. package/dist/src/persistent-gemini-session.js.map +1 -1
  63. package/dist/src/persistent-grok-session.js +1 -0
  64. package/dist/src/persistent-grok-session.js.map +1 -1
  65. package/dist/src/persistent-opencode-session.js +1 -0
  66. package/dist/src/persistent-opencode-session.js.map +1 -1
  67. package/dist/src/persistent-session.js +1 -0
  68. package/dist/src/persistent-session.js.map +1 -1
  69. package/dist/src/sanitize.d.ts +10 -0
  70. package/dist/src/sanitize.js +26 -0
  71. package/dist/src/sanitize.js.map +1 -1
  72. package/dist/src/session-manager.d.ts +25 -20
  73. package/dist/src/session-manager.js +92 -43
  74. package/dist/src/session-manager.js.map +1 -1
  75. package/dist/src/types.d.ts +35 -1
  76. package/dist/src/types.js +2 -0
  77. package/dist/src/types.js.map +1 -1
  78. package/openclaw.plugin.json +1 -1
  79. package/package.json +2 -2
  80. package/skills/SKILL.md +8 -5
  81. package/skills/references/acp.md +4 -0
  82. package/skills/references/autoloop.md +27 -2
  83. package/skills/references/claude-cli-tracking.md +3 -1
  84. package/skills/references/cli.md +79 -39
  85. package/skills/references/dashboard.md +5 -1
  86. package/skills/references/multi-engine.md +39 -10
  87. package/skills/references/tools.md +77 -65
  88. package/skills/references/workflow.md +87 -13
@@ -31,6 +31,32 @@ export function sanitizeSecrets(text) {
31
31
  }
32
32
  return result;
33
33
  }
34
+ /**
35
+ * A short, single-line reason for a log line, or `unknown error`.
36
+ *
37
+ * Source is `err instanceof Error ? String(err.message) : String(err)`; a throw
38
+ * during that conversion returns `unknown error`. Whitespace runs become one
39
+ * space, then trim; known secret shapes are redacted; the result is capped at
40
+ * 300 UTF-16 units (a trailing high surrogate is dropped). Empty output, or
41
+ * any Unicode Cc/Cf code point, becomes `unknown error`.
42
+ */
43
+ export function boundedErrorReason(err) {
44
+ let text;
45
+ try {
46
+ text = err instanceof Error ? String(err.message) : String(err);
47
+ }
48
+ catch {
49
+ return 'unknown error';
50
+ }
51
+ text = text.replace(/\s+/g, ' ').trim();
52
+ text = sanitizeSecrets(text);
53
+ text = text.slice(0, 300);
54
+ if (/[\uD800-\uDBFF]$/.test(text))
55
+ text = text.slice(0, -1);
56
+ if (!text || /[\p{Cc}\p{Cf}]/u.test(text))
57
+ return 'unknown error';
58
+ return text;
59
+ }
34
60
  /** Build a sanitizer from common secret patterns plus optional config patterns. */
35
61
  export function buildSanitizer(options = {}) {
36
62
  const patterns = [...SECRET_PATTERNS];
@@ -1 +1 @@
1
- {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../src/sanitize.ts"],"names":[],"mappings":"AAAA,OAAO,GAAG,MAAM,KAAK,CAAC;AAEtB;;;;;;;;;;GAUG;AAEH,MAAM,CAAC,MAAM,eAAe,GAAuD;IACjF,mEAAmE;IACnE,+DAA+D;IAC/D,EAAE,EAAE,EAAE,yBAAyB,EAAE,WAAW,EAAE,YAAY,EAAE;IAC5D,8DAA8D;IAC9D,EAAE,EAAE,EAAE,wBAAwB,EAAE,WAAW,EAAE,QAAQ,EAAE;IACvD,4DAA4D;IAC5D,EAAE,EAAE,EAAE,wCAAwC,EAAE,WAAW,EAAE,OAAO,EAAE;IACtE,kDAAkD;IAClD,mFAAmF;IACnF,EAAE,EAAE,EAAE,iDAAiD,EAAE,WAAW,EAAE,QAAQ,EAAE;CACjF,CAAC;AAEF,6DAA6D;AAC7D,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,eAAe,EAAE,CAAC;QAClD,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;QACjB,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;IAC3C,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAOD,mFAAmF;AACnF,MAAM,UAAU,cAAc,CAAC,UAA4B,EAAE;IAC3D,MAAM,QAAQ,GAA+C,CAAC,GAAG,eAAe,CAAC,CAAC;IAClF,KAAK,MAAM,OAAO,IAAI,OAAO,CAAC,aAAa,IAAI,EAAE,EAAE,CAAC;QAClD,IAAI,CAAC;YACH,0EAA0E;YAC1E,+DAA+D;YAC/D,QAAQ,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,GAAG,CAAC,OAAO,EAAE,GAAG,CAAsB,EAAE,WAAW,EAAE,KAAK,EAAE,CAAC,CAAC;QACxF,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;YACzD,OAAO,CAAC,IAAI,CAAC,GAAG,KAAK,oCAAoC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACjH,CAAC;IACH,CAAC;IAED,OAAO,CAAC,IAAY,EAAU,EAAE;QAC9B,IAAI,MAAM,GAAG,IAAI,CAAC;QAClB,KAAK,MAAM,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC3C,EAAE,CAAC,SAAS,GAAG,CAAC,CAAC;YACjB,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,EAAE,EAAE,WAAW,CAAC,CAAC;QAC3C,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../../src/sanitize.ts"],"names":[],"mappings":"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"}
@@ -22,12 +22,15 @@ import { type RunSummary } from './kernel/store.js';
22
22
  import type { KernelEvent, RunRecord, RunState, WorkflowSpec } from './kernel/types.js';
23
23
  import { type EvidenceBundle } from './verify/evidence.js';
24
24
  import { type RunLedgerQuery, type RunLedgerRow, type RunLedgerSummary } from './run-ledger.js';
25
+ import { type ThreadGoal } from './persistent-codex-app-session.js';
26
+ import type { GoalSession } from './kernel/nodes/goal.js';
25
27
  import { type SessionConfig, type SessionInfo, type SendOptions, type SendResult, type PluginConfig, type EffortLevel, type EngineType, type CustomEngineConfig, type AgentInfo, type SkillInfo, type RuleInfo, type ISession, type CouncilConfig, type CouncilSession, type CouncilReviewResult, type CouncilAcceptResult, type CouncilRejectResult, type InboxMessage, type UltraplanResult, type UltrareviewResult } from './types.js';
26
28
  import { Council } from './council.js';
27
29
  import { type FanoutConfig, type FanoutSession } from './fanout.js';
28
30
  import { AutoloopRunner } from './autoloop/runner.js';
29
31
  import { ClaudeAgentDispatcher } from './autoloop/dispatcher.js';
30
32
  import type { AutoloopState } from './autoloop/types.js';
33
+ import { type AnyAutoloopMessage } from './autoloop/messages.js';
31
34
  import { UltraappManager } from './ultraapp/manager.js';
32
35
  import type { UltraappRouter } from './ultraapp/router.js';
33
36
  type AutoloopRoleName = 'planner' | 'coder' | 'reviewer';
@@ -327,30 +330,26 @@ export declare class SessionManager {
327
330
  * After shutdown(), no new sessions can be started. Idempotent.
328
331
  */
329
332
  shutdown(): Promise<void>;
333
+ /** The live `codex-app` session behind `name`, for the kernel's `goal` node. */
334
+ codexGoalSession(name: string): GoalSession | undefined;
330
335
  /**
331
- * Send a `/goal <args>` slash command to a `codex-app` session. Used by
332
- * the `codex_goal_*` tools. The server-side parser interprets the slash
333
- * command and emits goal-related notifications which the session class
334
- * caches.
336
+ * Act on a `codex-app` session's goal through the app-server's
337
+ * `thread/goal/*` RPCs. Used by the `codex_goal_*` tools; `get` asks the
338
+ * server rather than a cache.
335
339
  *
336
- * Errors clearly when called against a non-`codex-app` session — those
337
- * sessions cannot interpret `/goal` (the `codex exec` path has no slash
338
- * command surface).
339
- */
340
- codexGoalCommand(name: string, slashArgs: string, timeoutMs?: number): Promise<{
340
+ * Errors clearly when called against a non-`codex-app` session — `codex
341
+ * exec` has no goal surface.
342
+ */
343
+ codexGoal(name: string, op: {
344
+ action: 'set';
345
+ objective: string;
346
+ tokenBudget?: number | null;
347
+ } | {
348
+ action: 'pause' | 'resume' | 'clear' | 'get';
349
+ }): Promise<{
341
350
  ok: true;
342
- text: string;
343
- goal: unknown;
351
+ goal: ThreadGoal | null;
344
352
  }>;
345
- /**
346
- * Read the cached goal state from a `codex-app` session without sending
347
- * any command. Returns null if no goal is set or the session has not yet
348
- * received a `thread/goal/updated` notification.
349
- */
350
- codexGoalGet(name: string): {
351
- ok: true;
352
- goal: unknown;
353
- };
354
353
  private _getCodexAppSession;
355
354
  codexInterrupt(name: string): Promise<{
356
355
  ok: true;
@@ -679,6 +678,12 @@ export declare class SessionManager {
679
678
  plannerSession: string;
680
679
  state: AutoloopState;
681
680
  }>;
681
+ /**
682
+ * Durably accept a chat message for a live run's Planner and return the
683
+ * envelope to deliver. Throws, with nothing accepted, when the run cannot
684
+ * checkpoint it. Internal: callers pass the result to `runner.send`.
685
+ */
686
+ acceptAutoloopChat(runId: string, text: string): AnyAutoloopMessage;
682
687
  /**
683
688
  * Inject a user chat message into a v2 run's Planner. Returns the Planner's
684
689
  * natural-language reply.
@@ -136,15 +136,17 @@ import { PersistentGrokSession } from './persistent-grok-session.js';
136
136
  import { PersistentOpencodeSession } from './persistent-opencode-session.js';
137
137
  import { PersistentAgySession } from './persistent-agy-session.js';
138
138
  import { PersistentCustomSession } from './persistent-custom-session.js';
139
+ import { PersistentAcpSession } from './persistent-acp-session.js';
139
140
  import { resolveCustomEngine } from './engine-presets.js';
140
141
  import { EFFORT_LEVELS, ENGINE_TYPES, overrideModelPricing, ENGINE_BINARY_NAMES, } from './types.js';
141
142
  import { resolveAlias, isClaudeModel, lookupModel } from './models.js';
142
143
  import { isAgyConversationId } from './agy-conversation.js';
143
144
  import { Council } from './council.js';
144
145
  import { AutoloopRunner } from './autoloop/runner.js';
146
+ import { boundedErrorReason } from './sanitize.js';
145
147
  import { ClaudeAgentDispatcher } from './autoloop/dispatcher.js';
146
148
  import { DEFAULT_PUSH_POLICY, DEFAULT_SEND_TIMEOUT_MS, MAX_METRIC_HISTORY, validateAutoloopTimeoutConfig, } from './autoloop/types.js';
147
- import { Msg as AutoloopMsg } from './autoloop/messages.js';
149
+ import { Msg as AutoloopMsg, } from './autoloop/messages.js';
148
150
  import { appendPushLog, notifyUserFallbackChain } from './autoloop/notify.js';
149
151
  import { UltraappManager } from './ultraapp/manager.js';
150
152
  import { UltraappStore, defaultStoreRoot } from './ultraapp/store.js';
@@ -1421,7 +1423,7 @@ export class SessionManager {
1421
1423
  // its executor is registered here with a builder closed over `this`
1422
1424
  // rather than living in the kernel.
1423
1425
  kernel.setExecutor('autoloop', makeAutoloopExecutor({
1424
- boot: (config, secrets, { freshStart }) => this._bootAutoloop({
1426
+ boot: (config, secrets, { freshStart, restoredInput }) => this._bootAutoloop({
1425
1427
  ...config,
1426
1428
  // Custom-engine configs never reach the spec, so they come from
1427
1429
  // the run's in-memory secret bag — supplied at start, and
@@ -1432,6 +1434,7 @@ export class SessionManager {
1432
1434
  // Always set (including false on resume) — omitting false left a
1433
1435
  // `_freshStart: true` from config/secrets intact.
1434
1436
  _freshStart: freshStart,
1437
+ _restoredInput: restoredInput,
1435
1438
  }),
1436
1439
  ready: (key, value) => {
1437
1440
  const deferred = this._autoloopReady.get(key);
@@ -1968,46 +1971,35 @@ export class SessionManager {
1968
1971
  // Persist final state (TTL-expired sessions already removed by cleanup)
1969
1972
  this._savePersisted();
1970
1973
  }
1971
- // ─── Codex /goal helpers (codex-app engine only) ─────────────────────
1974
+ // ─── Codex goals (codex-app engine only) ─────────────────────────────
1975
+ /** The live `codex-app` session behind `name`, for the kernel's `goal` node. */
1976
+ codexGoalSession(name) {
1977
+ const session = this.sessions.get(name)?.session;
1978
+ return session instanceof PersistentCodexAppServerSession ? session : undefined;
1979
+ }
1972
1980
  /**
1973
- * Send a `/goal <args>` slash command to a `codex-app` session. Used by
1974
- * the `codex_goal_*` tools. The server-side parser interprets the slash
1975
- * command and emits goal-related notifications which the session class
1976
- * caches.
1981
+ * Act on a `codex-app` session's goal through the app-server's
1982
+ * `thread/goal/*` RPCs. Used by the `codex_goal_*` tools; `get` asks the
1983
+ * server rather than a cache.
1977
1984
  *
1978
- * Errors clearly when called against a non-`codex-app` session — those
1979
- * sessions cannot interpret `/goal` (the `codex exec` path has no slash
1980
- * command surface).
1985
+ * Errors clearly when called against a non-`codex-app` session — `codex
1986
+ * exec` has no goal surface.
1981
1987
  */
1982
- async codexGoalCommand(name, slashArgs, timeoutMs) {
1983
- const managed = this.sessions.get(name);
1984
- if (!managed)
1985
- throw new Error(`Session not found: ${name}`);
1986
- const session = managed.session;
1987
- if (typeof session.sendGoalCommand !== 'function') {
1988
- const engine = managed.config.engine || 'claude';
1989
- throw new Error(`Session "${name}" uses engine "${engine}" which does not support /goal. ` +
1990
- `Start a session with engine: "codex-app" to use the goal tools.`);
1988
+ async codexGoal(name, op) {
1989
+ const session = this._getCodexAppSession(name, '/goal');
1990
+ switch (op.action) {
1991
+ case 'set':
1992
+ return { ok: true, goal: await session.setGoal({ objective: op.objective, tokenBudget: op.tokenBudget }) };
1993
+ case 'pause':
1994
+ return { ok: true, goal: await session.setGoal({ status: 'paused' }) };
1995
+ case 'resume':
1996
+ return { ok: true, goal: await session.setGoal({ status: 'active' }) };
1997
+ case 'clear':
1998
+ await session.clearGoal();
1999
+ return { ok: true, goal: null };
2000
+ case 'get':
2001
+ return { ok: true, goal: await session.getGoal() };
1991
2002
  }
1992
- const result = await session.sendGoalCommand(slashArgs, timeoutMs);
1993
- return { ok: true, text: result.text, goal: result.goal };
1994
- }
1995
- /**
1996
- * Read the cached goal state from a `codex-app` session without sending
1997
- * any command. Returns null if no goal is set or the session has not yet
1998
- * received a `thread/goal/updated` notification.
1999
- */
2000
- codexGoalGet(name) {
2001
- const managed = this.sessions.get(name);
2002
- if (!managed)
2003
- throw new Error(`Session not found: ${name}`);
2004
- const session = managed.session;
2005
- if (!('goal' in session)) {
2006
- const engine = managed.config.engine || 'claude';
2007
- throw new Error(`Session "${name}" uses engine "${engine}" which does not track goal state. ` +
2008
- `Start a session with engine: "codex-app" to use the goal tools.`);
2009
- }
2010
- return { ok: true, goal: session.goal ?? null };
2011
2003
  }
2012
2004
  // ─── Codex app-server v2 RPCs (codex-app engine only, Codex 0.137) ────────
2013
2005
  //
@@ -2679,6 +2671,16 @@ export class SessionManager {
2679
2671
  if (engine === 'codex') {
2680
2672
  return session.threadId;
2681
2673
  }
2674
+ // `sessionId` on a codex-app session is a label of ours; `thread/resume`
2675
+ // needs the app-server's own thread id, and silently opened a new thread
2676
+ // when handed the label.
2677
+ if (engine === 'codex-app') {
2678
+ return session.codexThreadId;
2679
+ }
2680
+ // Likewise an ACP session's `sessionId` is ours; the agent resumes its own.
2681
+ if (engine === 'acp') {
2682
+ return session.agentSessionId;
2683
+ }
2682
2684
  return session.sessionId;
2683
2685
  }
2684
2686
  _storedResumeId(engine, id) {
@@ -2688,6 +2690,10 @@ export class SessionManager {
2688
2690
  return id && !/^codex-\d+-/.test(id) ? id : undefined;
2689
2691
  if (engine === 'grok')
2690
2692
  return id && !/^grok-\d+-/.test(id) ? id : undefined;
2693
+ if (engine === 'codex-app')
2694
+ return id && !id.startsWith('codex-app-') ? id : undefined;
2695
+ if (engine === 'acp')
2696
+ return id && !id.startsWith('acp-') ? id : undefined;
2691
2697
  return id;
2692
2698
  }
2693
2699
  _listMdFiles(dir) {
@@ -2729,6 +2735,8 @@ export class SessionManager {
2729
2735
  if (!config.customEngine)
2730
2736
  throw new Error('customEngine config is required for engine type "custom"');
2731
2737
  return new PersistentCustomSession(config);
2738
+ case 'acp':
2739
+ return new PersistentAcpSession(config);
2732
2740
  case 'claude':
2733
2741
  default:
2734
2742
  return new PersistentClaudeSession(config, this.pluginConfig.claudeBin);
@@ -3261,6 +3269,10 @@ export class SessionManager {
3261
3269
  run_id: opts.runId,
3262
3270
  workspace: opts.workspace,
3263
3271
  ledger_dir: ledgerDir,
3272
+ persistCheckpoint: () => {
3273
+ const publish = this._autoloopPublishers.get(runId);
3274
+ return publish ? publish() : false;
3275
+ },
3264
3276
  push_policy: pushPolicy,
3265
3277
  notifyUser: async (level, summary, detail, channel) => {
3266
3278
  const result = await notifyUserFallbackChain({
@@ -3285,11 +3297,18 @@ export class SessionManager {
3285
3297
  activityLeaseMs: opts.activityLeaseMs,
3286
3298
  autoloopHardTimeoutMs: opts.autoloopHardTimeoutMs,
3287
3299
  });
3300
+ // Background resume/deadline/stall paths emit 'error' after a void catch.
3301
+ // Without a listener, Node turns that into an unhandled rejection.
3302
+ runner.on('error', (err) => {
3303
+ this.logger.error?.(`[autoloop/${runId}] background runner task failed: ${boundedErrorReason(err)}`);
3304
+ });
3288
3305
  runnerRef = runner;
3289
3306
  if (closedLedgerRestore) {
3290
3307
  runner.state.iter = closedLedgerRestore.iter;
3291
3308
  runner.state.metric_history = closedLedgerRestore.metric_history.slice();
3292
3309
  }
3310
+ if (opts._restoredInput)
3311
+ runner.loadRestoredInput(opts._restoredInput);
3293
3312
  try {
3294
3313
  await runner.start();
3295
3314
  // A reconstructed migration becomes externally visible only after its
@@ -3297,7 +3316,6 @@ export class SessionManager {
3297
3316
  // this inside the startup try makes an append failure follow the same
3298
3317
  // cleanup path as any other failed resume.
3299
3318
  opts._commitTimeoutMigration?.();
3300
- await this._recoverCommittedCoderIteration(opts.workspace, ledgerDir, runId, runner, dispatcher);
3301
3319
  }
3302
3320
  catch (err) {
3303
3321
  try {
@@ -3311,7 +3329,17 @@ export class SessionManager {
3311
3329
  runner.stop();
3312
3330
  throw err;
3313
3331
  }
3314
- return { runner, dispatcher, ledgerDir, pushPolicy };
3332
+ // Committed-iteration recovery runs after publisher registration, the
3333
+ // initial-publish check and notice reconciliation (see afterPublishReconcile
3334
+ // in makeAutoloopExecutor) — never inside boot before that gate.
3335
+ return {
3336
+ runner,
3337
+ dispatcher,
3338
+ ledgerDir,
3339
+ pushPolicy,
3340
+ abort: () => runner.markCancelledByKernel(),
3341
+ afterPublishReconcile: () => this._recoverCommittedCoderIteration(opts.workspace, ledgerDir, runId, runner, dispatcher),
3342
+ };
3315
3343
  }
3316
3344
  /**
3317
3345
  * If the durable ledger has a committed Coder iteration with no Reviewer
@@ -3352,8 +3380,12 @@ export class SessionManager {
3352
3380
  return;
3353
3381
  this.logger.info?.(`[autoloop/${runId}] recovering committed iter ${n} into Reviewer`);
3354
3382
  await dispatcher.ensureReviewerForCommittedRecovery();
3355
- runner.markSubagentsSpawned();
3383
+ // Keep a reconciled chat_notice_pending pause so Planner-bound follow-ups
3384
+ // park until the notice is recorded; live spawn_subagents stays unchanged.
3385
+ runner.markSubagentsSpawned({ preservePause: true });
3356
3386
  runner.state.iter = n;
3387
+ // Queue only — never await the physical Reviewer turn. Readiness and the
3388
+ // cancellation observer must install before any model transport settles.
3357
3389
  void runner
3358
3390
  .send(AutoloopMsg.reviewRequest(n, {
3359
3391
  iter: n,
@@ -3441,12 +3473,21 @@ export class SessionManager {
3441
3473
  this._autoloopStarting.delete(tag);
3442
3474
  }
3443
3475
  }
3476
+ /**
3477
+ * Durably accept a chat message for a live run's Planner and return the
3478
+ * envelope to deliver. Throws, with nothing accepted, when the run cannot
3479
+ * checkpoint it. Internal: callers pass the result to `runner.send`.
3480
+ */
3481
+ acceptAutoloopChat(runId, text) {
3482
+ return this._liveAutoloop(runId).runner.acceptChat(text);
3483
+ }
3444
3484
  /**
3445
3485
  * Inject a user chat message into a v2 run's Planner. Returns the Planner's
3446
3486
  * natural-language reply.
3447
3487
  */
3448
3488
  async autoloopChat(runId, text) {
3449
3489
  const ctx = this._liveAutoloop(runId);
3490
+ const env = this.acceptAutoloopChat(runId, text);
3450
3491
  let reply = '';
3451
3492
  const onReply = (...args) => {
3452
3493
  const t = args[0];
@@ -3455,7 +3496,7 @@ export class SessionManager {
3455
3496
  };
3456
3497
  ctx.dispatcher.on('planner_reply', onReply);
3457
3498
  try {
3458
- await ctx.runner.send(AutoloopMsg.chat(ctx.runner.state.iter, { text }));
3499
+ await ctx.runner.send(env);
3459
3500
  }
3460
3501
  finally {
3461
3502
  ctx.dispatcher.off('planner_reply', onReply);
@@ -3566,8 +3607,16 @@ export class SessionManager {
3566
3607
  }
3567
3608
  const live = this.kernel.handle(runId, LEGACY_NODE);
3568
3609
  if (live) {
3569
- if (!hasTimeoutIncrease)
3610
+ if (!hasTimeoutIncrease) {
3611
+ // Ordinary and activity-lease pauses have no uncertain in-flight
3612
+ // dispatch. A pending send timeout stays the historical no-op until
3613
+ // an explicit timeout increase names that dispatch.
3614
+ if (live.runner.state.status === 'paused' && live.runner.state.pending_dispatch === null) {
3615
+ live.runner.resumePaused();
3616
+ this._autoloopPublishers.get(runId)?.();
3617
+ }
3570
3618
  return live.runner.state;
3619
+ }
3571
3620
  const pending = live.runner.state.pending_dispatch;
3572
3621
  if (live.runner.state.status !== 'paused' || !pending) {
3573
3622
  throw new Error(`Autoloop run '${runId}' is not awaiting a recoverable send timeout`);