feat(plugins): add session followup turn API and gateway-restart extension#63330
feat(plugins): add session followup turn API and gateway-restart extension#63330corbin-breton wants to merge 17 commits into
Conversation
Greptile SummaryThis PR adds
Confidence Score: 4/5Safe to merge after addressing the missing execSync timeout; the P1 is limited to the opt-in gateway-restart extension and does not affect the core followup API. The core followup API (runtime-followup.ts, build-followup-run.ts, types-core.ts) is well-designed and has solid test coverage. The one P1 finding is in the bundled gateway-restart extension: execSync with no timeout can permanently stall the gateway process on a slow or hung pre-command. This is a real reliability defect on the changed path, warranting a 4 rather than 5. extensions/gateway-restart/index.ts — the execSync timeout gap and error-propagation style.
|
| for (const command of commands) { | ||
| const output = execSync(command, { | ||
| encoding: "utf8", | ||
| stdio: ["ignore", "pipe", "pipe"], | ||
| }); | ||
| commandOutputs.push(output); |
There was a problem hiding this comment.
No timeout on
execSync can hang the gateway indefinitely
execSync has no timeout option, so if a pre-command (e.g. openclaw gateway install --force during a slow network operation) stalls, the gateway process blocks forever — preventing both the restart and all active sessions from making progress. Since the gateway is single-process, one hung pre-command is a complete outage.
| for (const command of commands) { | |
| const output = execSync(command, { | |
| encoding: "utf8", | |
| stdio: ["ignore", "pipe", "pipe"], | |
| }); | |
| commandOutputs.push(output); | |
| const output = execSync(command, { | |
| encoding: "utf8", | |
| timeout: 5 * 60 * 1000, // 5-minute hard cap per command | |
| stdio: ["ignore", "pipe", "pipe"], | |
| }); |
Consider making the timeout configurable via PluginConfig if operators need longer windows for custom commands.
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/gateway-restart/index.ts
Line: 139-144
Comment:
**No timeout on `execSync` can hang the gateway indefinitely**
`execSync` has no `timeout` option, so if a pre-command (e.g. `openclaw gateway install --force` during a slow network operation) stalls, the gateway process blocks forever — preventing both the restart and all active sessions from making progress. Since the gateway is single-process, one hung pre-command is a complete outage.
```suggestion
const output = execSync(command, {
encoding: "utf8",
timeout: 5 * 60 * 1000, // 5-minute hard cap per command
stdio: ["ignore", "pipe", "pipe"],
});
```
Consider making the timeout configurable via `PluginConfig` if operators need longer windows for custom commands.
How can I resolve this? If you propose a fix, please make it concise.| // Fix 1: Execute pre-commands BEFORE writing marker. | ||
| // If execSync throws, we never write the marker, preventing stale markers. | ||
| const commandOutputs: string[] = []; | ||
| for (const command of commands) { | ||
| const output = execSync(command, { | ||
| encoding: "utf8", | ||
| stdio: ["ignore", "pipe", "pipe"], | ||
| }); | ||
| commandOutputs.push(output); | ||
| } |
There was a problem hiding this comment.
execSync failure throws from execute() instead of returning an error result
When a pre-command fails, execSync throws and the exception propagates out of execute() unhandled (confirmed by the test's rejects.toThrow). If the plugin runtime doesn't wrap tool callbacks in a try/catch, the agent receives an opaque framework error rather than a readable diagnostic. The existing allowlist-violation path already returns a structured error object — the command-failure path should match that pattern.
| // Fix 1: Execute pre-commands BEFORE writing marker. | |
| // If execSync throws, we never write the marker, preventing stale markers. | |
| const commandOutputs: string[] = []; | |
| for (const command of commands) { | |
| const output = execSync(command, { | |
| encoding: "utf8", | |
| stdio: ["ignore", "pipe", "pipe"], | |
| }); | |
| commandOutputs.push(output); | |
| } | |
| let commandOutputs: string[] = []; | |
| try { | |
| for (const command of commands) { | |
| const output = execSync(command, { | |
| encoding: "utf8", | |
| stdio: ["ignore", "pipe", "pipe"], | |
| }); | |
| commandOutputs.push(output); | |
| } | |
| } catch (err) { | |
| return { | |
| content: [ | |
| { | |
| type: "text" as const, | |
| text: `ERROR: Pre-restart command failed: ${String(err instanceof Error ? err.message : err)}`, | |
| }, | |
| ], | |
| details: null, | |
| }; | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/gateway-restart/index.ts
Line: 136-145
Comment:
**`execSync` failure throws from `execute()` instead of returning an error result**
When a pre-command fails, `execSync` throws and the exception propagates out of `execute()` unhandled (confirmed by the test's `rejects.toThrow`). If the plugin runtime doesn't wrap tool callbacks in a try/catch, the agent receives an opaque framework error rather than a readable diagnostic. The existing allowlist-violation path already returns a structured error object — the command-failure path should match that pattern.
```suggestion
let commandOutputs: string[] = [];
try {
for (const command of commands) {
const output = execSync(command, {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
commandOutputs.push(output);
}
} catch (err) {
return {
content: [
{
type: "text" as const,
text: `ERROR: Pre-restart command failed: ${String(err instanceof Error ? err.message : err)}`,
},
],
details: null,
};
}
```
How can I resolve this? If you propose a fix, please make it concise.| let runnerStorePath: string | undefined; | ||
| let runnerSessionStore: ReturnType<typeof loadSessionStore> | undefined; | ||
| let runnerSessionEntry: ReturnType<typeof loadSessionStore>[string] | undefined; | ||
| try { | ||
| runnerStorePath = resolveStorePath(followupRun.run.config.session?.store, { | ||
| agentId: followupRun.run.agentId, | ||
| }); | ||
| runnerSessionStore = loadSessionStore(runnerStorePath); | ||
| runnerSessionEntry = runnerSessionStore[params.sessionKey]; | ||
| } catch (err) { | ||
| logVerbose( | ||
| `[runtime.followup] failed to preload session metadata for "${params.sessionKey}": ${String(err)}`, | ||
| ); | ||
| } |
There was a problem hiding this comment.
Session store is loaded twice for every cold-session enqueue
buildFollowupRunForSession() (called on line 19) already calls loadSessionStore internally to resolve the session entry. The try/catch block here reloads the same store a second time from the same path just to pass sessionEntry to the cold runner. This is two synchronous file-system reads for identical data. Consider returning the pre-loaded store/entry from buildFollowupRunForSession or accepting them as optional parameters so callers can pass through what they already have.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/plugins/runtime/runtime-followup.ts
Line: 24-37
Comment:
**Session store is loaded twice for every cold-session enqueue**
`buildFollowupRunForSession()` (called on line 19) already calls `loadSessionStore` internally to resolve the session entry. The try/catch block here reloads the same store a second time from the same path just to pass `sessionEntry` to the cold runner. This is two synchronous file-system reads for identical data. Consider returning the pre-loaded store/entry from `buildFollowupRunForSession` or accepting them as optional parameters so callers can pass through what they already have.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7e401fa1fc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| '@aws-sdk/client-bedrock': | ||
| specifier: 3.1024.0 | ||
| version: 3.1024.0 | ||
| '@aws-sdk/client-bedrock-runtime': | ||
| specifier: 3.1024.0 | ||
| version: 3.1024.0 | ||
| '@aws-sdk/credential-provider-node': | ||
| specifier: 3.972.29 | ||
| version: 3.972.29 | ||
| '@aws/bedrock-token-generator': | ||
| specifier: ^1.1.0 | ||
| version: 1.1.0 | ||
| '@buape/carbon': | ||
| specifier: 0.14.0 | ||
| version: 0.14.0(@discordjs/[email protected])([email protected])([email protected]) | ||
| specifier: 3.1023.0 | ||
| version: 3.1023.0 |
There was a problem hiding this comment.
Regenerate lockfile from current manifests
Update pnpm-lock.yaml so its root importer matches package.json again: this hunk downgrades locked specs (for example @aws-sdk/client-bedrock to 3.1023.0 while package.json still declares 3.1024.0), and the commit does not include corresponding manifest changes. That makes the lockfile stale relative to the manifests, which can fail pnpm install --frozen-lockfile in CI and/or install unintended downgraded runtime dependencies.
Useful? React with 👍 / 👎.
…errors, session store dedup - Add 5-minute timeout to execSync in gateway-restart (prevents hung commands from blocking gateway) - Wrap execSync failures in structured error result instead of throwing (matches allowlist-rejection pattern) - Eliminate double session store load in runtime-followup by returning metadata from buildFollowupRunForSession via _sessionMeta - Fix pnpm-lock.yaml to match current overrides config Addresses Greptile review feedback on PR openclaw#63330. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 65db810896
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| name: "gateway_restart", | ||
| label: "Gateway Restart", | ||
| description: | ||
| "Restart the current OpenClaw gateway process, optionally run allowlisted commands first, and deliver a completion callback to the provided session.", |
There was a problem hiding this comment.
Mark gateway_restart as owner-only
This tool registration omits ownerOnly, so non-owner senders can still invoke it when the plugin is enabled and plugin tools are otherwise allowed. In src/agents/tool-policy.ts, owner gating only auto-applies to known core names (for example gateway) or tools explicitly marked ownerOnly: true; gateway_restart matches neither, so untrusted users can trigger gateway-wide restarts and run allowlisted pre-commands.
Useful? React with 👍 / 👎.
| async execute(_toolCallId, params) { | ||
| const parsed = params as GatewayRestartParams; | ||
| const commands = parsed.commands ?? []; | ||
| const allowedCommands = new Set(config.allowedCommands); | ||
|
|
There was a problem hiding this comment.
Enforce commands.restart policy in plugin restart path
The execution path performs a restart without checking commands.restart, which bypasses the global restart-disable policy once this plugin is enabled. The core gateway tool explicitly blocks restart when commands.restart=false (src/agents/tools/gateway-tool.ts), so this new tool introduces inconsistent behavior where operators who disabled restart actions can still be restarted through gateway_restart.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6d038df492
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const agentId = params.agentId?.trim() || resolveAgentIdFromSessionKey(params.sessionKey); | ||
| const cfg = loadConfig(); | ||
| const agentDir = resolveAgentDir(cfg, agentId); | ||
| const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId); |
There was a problem hiding this comment.
Reuse session workspace for followup turns
buildFollowupRunForSession always derives workspaceDir from resolveAgentWorkspaceDir(cfg, agentId), but followup sessions can carry a persisted spawnedWorkspaceDir (for spawned/subagent sessions). That means a resumed followup can execute in the agent’s default workspace instead of the session’s actual workspace, so post-restart or deferred turns may run commands against the wrong files/repo and produce incorrect side effects. The run builder should prefer the session entry’s workspace when present before falling back to the agent default.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 97bfb5549e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const enqueued = enqueueFollowupRun( | ||
| params.sessionKey, | ||
| followupRun, | ||
| { mode: existingMode ?? "followup" }, |
There was a problem hiding this comment.
Strip _sessionMeta before queueing followup runs
buildFollowupRunForSession attaches _sessionMeta.sessionStore (the full loaded session store), and this code enqueues that same object directly. As a result, each queued followup item keeps a full store snapshot alive until drain finishes; when plugins enqueue multiple followups for a large sessions file, memory usage scales with queue depth and can cause avoidable RSS spikes or OOM pressure. Keep _sessionMeta only for cold-runner setup, and enqueue a plain FollowupRun payload without the extra metadata.
Useful? React with 👍 / 👎.
| const parsed = params as GatewayRestartParams; | ||
| const commands = parsed.commands ?? []; | ||
| const allowedCommands = new Set(config.allowedCommands); | ||
|
|
There was a problem hiding this comment.
Validate sessionKey before executing gateway restart
The tool proceeds to pre-commands, marker write, and process.exit(0) without checking that sessionKey is non-empty. Since the schema accepts empty strings, a malformed tool call can restart the whole gateway with an unusable marker (sessionKey: ""), and the watcher later cannot enqueue the callback turn for the original conversation. Reject blank/whitespace session keys before running commands or scheduling restart.
Useful? React with 👍 / 👎.
|
CI on main is currently broken by two test regressions in |
6c94c43 to
27c9151
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 27c9151196
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| { | ||
| "name": "@openclaw/gateway-restart-plugin", | ||
| "version": "2026.4.4-beta.1", |
There was a problem hiding this comment.
Regenerate lockfile for the new gateway-restart workspace
This commit introduces a new workspace package but does not update pnpm-lock.yaml with an importers.extensions/gateway-restart entry. With a shared workspace lockfile, adding a package without regenerating the lockfile leaves manifests and lock state out of sync, which can break pnpm install --frozen-lockfile in CI and deployment automation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 314429da9e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| { | ||
| "name": "@openclaw/gateway-restart-plugin", | ||
| "version": "2026.4.4-beta.1", | ||
| "private": true, | ||
| "description": "OpenClaw gateway restart plugin with session follow-up resume", |
There was a problem hiding this comment.
Declare @sinclair/typebox as a runtime dependency
Add a dependencies entry for @sinclair/typebox in this plugin manifest. The new plugin runtime imports TypeBox at extensions/gateway-restart/index.ts, but this package currently declares no runtime dependencies, so installs that run in the plugin directory (for example npm install --omit=dev outside the monorepo hoist) can load the plugin and fail with ERR_MODULE_NOT_FOUND before registration.
Useful? React with 👍 / 👎.
314429d to
ca39c16
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 586d3ec882
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const sessionFile = | ||
| sessionEntry.sessionFile?.trim() || | ||
| resolveSessionFilePath(sessionEntry.sessionId, sessionEntry, sessionFileOpts); |
There was a problem hiding this comment.
Normalize persisted sessionFile before followup execution
This builder trusts sessionEntry.sessionFile directly via sessionEntry.sessionFile?.trim(), which bypasses resolveSessionFilePath(...)'s path-containment and stale/corrupt fallback logic. If a stored session entry contains an outdated absolute path or malformed value, cold followup runs can read/write transcripts at unintended locations (or fail to deliver entirely) instead of safely normalizing/falling back to the canonical session transcript path.
Useful? React with 👍 / 👎.
…nsion Add a new `runtime.followup.enqueueFollowupTurn()` method to the plugin runtime, enabling plugins to schedule proactive agent turns for any session — including cold sessions with no active user interaction. ## Problem Plugins have no way to inject a user-role message into an existing session and trigger an agent turn. The available primitives (`enqueueSystemEvent`, `subagent.run`) either don't initiate turns or create new sessions instead of continuing the original conversation. ## Solution ### Core API: `runtime.followup.enqueueFollowupTurn()` A new namespace on `PluginRuntimeCore` that: 1. Reconstructs a full `FollowupRun` from the session store via a new `buildFollowupRunForSession()` helper — resolving agent identity, workspace, provider/model, session file, and delivery context from the persisted `SessionEntry`. 2. Enqueues the run into the existing followup queue infrastructure. 3. Handles the cold-start drain problem by creating a fresh followup runner with a no-op typing controller and registering it as the drain callback, so the turn executes immediately even when no prior drain loop exists for that session. ### Bundled Extension: `gateway-restart` A reference implementation and practical tool that demonstrates the followup turn API: - **Tool** (`gateway_restart`): Agent calls this to write a restart marker, run optional allowlisted pre-commands, then exit the process. Systemd (or equivalent) auto-restarts the gateway. - **Service** (`gateway-restart-watcher`): On startup, checks for the restart marker. If found, calls `enqueueFollowupTurn()` to resume the original session with a completion message. The agent wakes up with full session context and continues working. ## Files - `src/auto-reply/reply/queue/build-followup-run.ts` — Cold-session FollowupRun builder - `src/plugins/runtime/runtime-followup.ts` — Plugin runtime namespace - `src/plugins/runtime/types-core.ts` — Type surface addition - `src/plugins/runtime/index.ts` — Wiring - `extensions/gateway-restart/` — Bundled extension (tool + service) - `test/helpers/plugins/plugin-runtime-mock.ts` — Test mock update ## Testing End-to-end verified on an isolated sandbox gateway: 1. Agent reads config, makes a change, calls `gateway_restart` 2. Gateway exits, restarts, watcher consumes marker 3. `enqueueFollowupTurn()` fires, agent resumes with full context 4. Agent validates the config change persisted and reports success
…latest main - Fix TS2339: use NonNullable<> wrapper for Partial<OpenClawPluginApi> parameter type - Fix eslint(curly): add required braces after if conditions in test helpers Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…rve queue mode buildFollowupRunForSession now passes the resolved storePath through resolveSessionFilePathOptions so custom session.store locations produce the correct transcript path on the fallback branch. enqueueFollowupTurn reads the existing queue mode before enqueueing instead of unconditionally forcing mode=followup, which could clobber a hot session's collect/interrupt/steer mode mid-drain. Targeted tests added for both fixes.
…errors, session store dedup - Add 5-minute timeout to execSync in gateway-restart (prevents hung commands from blocking gateway) - Wrap execSync failures in structured error result instead of throwing (matches allowlist-rejection pattern) - Eliminate double session store load in runtime-followup by returning metadata from buildFollowupRunForSession via _sessionMeta - Fix pnpm-lock.yaml to match current overrides config Addresses Greptile review feedback on PR openclaw#63330. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Avoid serializing the full session store into every queued followup item. Extract metadata for the cold runner, then pass only the clean FollowupRun to enqueueFollowupRun. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The test harness mock for pi-embedded-helpers.js listed exports explicitly but missed the recently added sanitizeUserFacingText. Spread importActual so new exports are always included. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Two test failures have been breaking CI on main: 1. temp-path-guard: extensions/qa-lab/src/multipass.runtime.ts used Math.random() + Date.now() on the same line, triggering the weak randomness guardrail. Replace with crypto.randomBytes(). 2. delivery-context: the Telegram test expected the generic "channel:" prefix for delivery targets, but the bundled Telegram plugin provides resolveDeliveryTarget which returns the raw chatId. Update the test expectation to match the actual bundled plugin behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Main updated the telegram parent-scoped thread delivery to use 'channel:' prefix. Align test expectation. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Add the gateway-restart extension to the lockfile so CI's frozen-lockfile install doesn't produce a dirty tree. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Main now explicitly mocks sanitizeUserFacingText. The actualPiHelpers spread injects real implementations where tests expect vi.fn() mocks, causing assertion failures. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
|
Codex review: needs real behavior proof before merge. Reviewed July 3, 2026, 7:06 PM ET / 23:06 UTC. Summary PR surface: Source +510, Tests +701, Config +15, Other +2. Total +1228 across 15 files. Reproducibility: yes. for the patch risks by source inspection: the PR head shows model-supplied session targeting, a plugin-owned JSON restart marker, partial cold-run reconstruction, and removed QA concurrency forwarding. No live current-head restart proof was provided. Review metrics: 5 noteworthy metrics.
Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Proof guidance:
Risk before merge
Maintainer options:
Next step before merge
Security Review findings
Review detailsBest possible solution: Settle the public API and restart ownership boundary first, then rework the implementation through trusted session identity, current SQLite restart/session-delivery primitives, complete context reconstruction, SDK docs/contracts, focused tests, and redacted current-head real proof. Do we have a high-confidence way to reproduce the issue? Yes for the patch risks by source inspection: the PR head shows model-supplied session targeting, a plugin-owned JSON restart marker, partial cold-run reconstruction, and removed QA concurrency forwarding. No live current-head restart proof was provided. Is this the best way to solve the issue? No; this is a plausible prototype, but the maintainable merge shape should reuse current host-owned restart, sentinel, delivery, and trusted session boundaries rather than adding a parallel privileged plugin path. Full review comments:
Overall correctness: patch is incorrect AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 677c8ff8466d. Label changesLabel justifications:
Evidence reviewedPR surface: Source +510, Tests +701, Config +15, Other +2. Total +1228 across 15 files. View PR surface stats
Security concerns:
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
|
ClawSweeper PR egg 🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat. Where did the egg go?
|
|
Wow, I haven't touched this in a minute, just got married and life has been busy. Glad to see that people are still working out the kinks and adapting it for the evolving OpenClaw development ecosystem. My goal of this PR was to shed some light on how we can make OpenClaw continuously work even if the running agent has to make a configuration change to itself and restart. This is important because it allows for rapid, agent driven setup, adding in a sort of "hot-reload" type feature that let the agent continue after changes. |
|
This pull request has been automatically marked as stale due to inactivity. |
|
Thanks @corbin-breton. Closing this stale feature branch, not treating the follow-up/restart extension design as settled. It still has unresolved security, session-ownership, and restart-lifecycle blockers, and the author noted the branch was no longer active. Any revival should start from current main, define the public plugin/session ownership contract first, and prove behavior across a real gateway restart. |
Summary
Add a new
runtime.followup.enqueueFollowupTurn()method to the plugin runtime, enabling plugins to schedule proactive agent turns for any session — including cold sessions with no active user interaction.Problem
Plugins have no way to inject a user-role message into an existing session and trigger an agent turn. The available primitives (
enqueueSystemEvent,subagent.run) either don't initiate turns or create new sessions instead of continuing the original conversation. This blocks use cases like:Solution
Core API:
runtime.followup.enqueueFollowupTurn()A new
followupnamespace onPluginRuntimeCorethat:Reconstructs a full
FollowupRunfrom the session store viabuildFollowupRunForSession()— resolving agent identity, workspace, provider/model, session file, and delivery context from the persistedSessionEntry.Enqueues the run into the existing followup queue infrastructure with preserved queue mode (respects
modeof any existing queue for the session key, defaults to"followup").Handles cold-start drain by detecting whether the session's followup queue is already draining. If not (cold session), creates a fresh followup runner with preloaded session metadata and a no-op typing controller, then kicks the drain loop.
Bundled Extension:
gateway-restartA reference implementation that demonstrates the followup turn API:
Tool (
gateway_restart): Agent calls this to run optional allowlisted pre-commands, write a restart marker (only after commands succeed), then exit the process. Systemd (or equivalent) auto-restarts the gateway.Service (
gateway-restart-watcher): On startup, checks for the restart marker. If found, callsenqueueFollowupTurn()to resume the original session with a completion message. Marker is deleted only after successful enqueue — preserved for retry on failure.Disabled by default (
enabledByDefault: false). Opt-in viaplugins.entries.gateway-restart.enabled: true.Files Changed
src/auto-reply/reply/queue/build-followup-run.tssrc/plugins/runtime/runtime-followup.tssrc/plugins/runtime/types-core.tsfollowuptype toPluginRuntimeCoresrc/plugins/runtime/index.tscreateRuntimeFollowup()extensions/gateway-restart/index.tsextensions/gateway-restart/openclaw.plugin.jsonextensions/gateway-restart/package.jsontest/helpers/plugins/plugin-runtime-mock.tsfollowupto mock**/**.test.tsBug Fixes (vs #60951)
enqueueFollowupTurn()returns falserememberFollowupDrainCallbackonly registers for cold sessions; existing hot-session drain runners are not overwrittenenqueueFollowupRunrespects existing queue mode instead of forcing"followup"Testing
pnpm checkpasses (tsgo + oxlint + all architecture checks)Use Cases This Enables
This API is the foundation for several plugins currently in development:
model-switch— seamless switching between local LLMs on single-GPU workstations, with session continuation viaenqueueFollowupTurn()after the new model is healthyinference-guard— priority-aware request scheduling for single-slot inference (heartbeat/cron defer behind user messages)Test plan
pnpm checkpassespnpm vitest run extensions/gateway-restart/index.test.ts— 6 tests passpnpm vitest run src/plugins/runtime/runtime-followup.test.ts— 9 tests passpnpm vitest run src/auto-reply/reply/queue/build-followup-run.test.ts— 9 tests passAI Disclosure
This PR was developed with AI assistance (Claude Code). Testing level: fully tested — 24 unit tests,
pnpm checkgreen, end-to-end verified on local hardware. The author understands all code in this PR and can explain design decisions.🤖 Generated with Claude Code