coding-tool-x 3.8.2 → 3.8.3

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.
@@ -0,0 +1,48 @@
1
+ # OMP Restart Recovery Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Preserve OMP dynamic-provider state during process shutdown and allow `ctx start` to restore the gateway after `ctx stop`.
6
+
7
+ **Architecture:** Add an explicit `preserveManagedMode` option to the OMP gateway lifecycle. Explicit OMP stop and foreground UI exit keep the current direct-channel handoff; the PM2 daemon process drains only the local gateway and leaves the persisted managed state for boot recovery. Increase PM2's graceful shutdown window.
8
+
9
+ **Tech Stack:** Node.js, PM2, Vitest, CommonJS.
10
+
11
+ ---
12
+
13
+ ### Task 1: Add the failing OMP lifecycle regression test
14
+
15
+ **Files:**
16
+ - Modify: `tests/unit/services/omp-proxy-server.test.js`
17
+
18
+ - [ ] Add a test asserting `stopOmpProxyServer({ preserveManagedMode: true })` stops the gateway without calling `activateStaticOmpChannel`, `disableManagedOmpProviders`, or `disableManagedOmpMode`.
19
+ - [ ] Run the focused test and confirm it fails because the option is not implemented.
20
+
21
+ ### Task 2: Implement restart-safe OMP shutdown
22
+
23
+ **Files:**
24
+ - Modify: `src/server/omp-proxy-server.js`
25
+ - Modify: `src/index.js`
26
+
27
+ - [ ] Branch the stop operation on `preserveManagedMode` while retaining the existing default handoff path.
28
+ - [ ] Pass `preserveManagedMode: true` from process-exit cleanup.
29
+ - [ ] Run the focused OMP suite and confirm both explicit stop and preserved shutdown pass.
30
+
31
+ ### Task 3: Extend PM2 graceful shutdown time and regression coverage
32
+
33
+ **Files:**
34
+ - Modify: `src/commands/daemon.js`
35
+ - Modify: `tests/unit/commands/daemon.test.js`
36
+
37
+ - [ ] Set `kill_timeout: 5000` in the PM2 start options.
38
+ - [ ] Add a focused assertion for the configured timeout.
39
+ - [ ] Run daemon and OMP tests.
40
+
41
+ ### Task 4: Verify end-to-end behavior
42
+
43
+ **Files:**
44
+ - No source changes unless verification exposes a defect.
45
+
46
+ - [ ] Run the focused Vitest suites.
47
+ - [ ] Run a service status/start smoke check without claiming success beyond observed output.
48
+ - [ ] Inspect the final diff and report any remaining runtime caveat.
@@ -0,0 +1,22 @@
1
+ # OMP Restart Recovery Design
2
+
3
+ ## Goal
4
+
5
+ Keep OMP managed-provider state across a full `ctx stop`/`ctx start` cycle so the local dynamic gateway is automatically recoverable without changing the explicit `ctx omp stop` behavior.
6
+
7
+ ## Design
8
+
9
+ The OMP gateway stop operation gains an opt-in `preserveManagedMode` flag. The default remains the existing explicit-stop behavior: drain, hand off to one direct channel, remove managed providers, and delete the managed-mode marker. The PM2 daemon process uses `preserveManagedMode: true` during process exit: drain and stop the local gateway, but leave the managed marker and `ctx-*` provider configuration intact. Foreground `ctx ui` shutdown keeps the existing explicit handoff behavior. On the next daemon boot, the existing marker-based auto-restore starts the gateway and re-syncs providers.
10
+
11
+ PM2 receives a five-second `kill_timeout` so the daemon shutdown has enough time to complete while active streaming requests are being drained.
12
+
13
+ ## Invariants
14
+
15
+ - `ctx omp stop` still restores one direct OMP channel.
16
+ - `ctx stop` does not delete the OMP managed-mode marker.
17
+ - The persisted gateway secret remains unchanged across restart.
18
+ - Failed explicit handoff still leaves managed mode active and resumes log observation.
19
+
20
+ ## Verification
21
+
22
+ Unit tests cover preserved shutdown state, existing explicit-stop handoff, and the PM2 kill timeout. The focused OMP and daemon suites plus a live service status/start smoke check verify the behavior.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coding-tool-x",
3
- "version": "3.8.2",
3
+ "version": "3.8.3",
4
4
  "description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -410,6 +410,7 @@ function buildStartOptions(port, enableHost, enableHttps) {
410
410
  args: pmArgs,
411
411
  interpreter: 'node',
412
412
  autorestart: true,
413
+ kill_timeout: 5000,
413
414
  max_memory_restart: '500M',
414
415
  env: {
415
416
  NODE_ENV: 'production',
@@ -617,18 +618,8 @@ async function handleStop() {
617
618
  */
618
619
  async function handleRestart() {
619
620
  try {
620
- await connectPM2();
621
-
622
- const existing = await getCCToolProcess();
623
- if (!existing) {
624
- console.log(chalk.yellow('\n[WARN] 服务未在运行,请使用 ') + chalk.cyan('ctx start') + chalk.yellow(' 启动\n'));
625
- disconnectPM2();
626
- return;
627
- }
628
-
629
- await restartPM2Process(PM2_APP_NAME);
630
- console.log(chalk.green('\n[OK] Coding-Tool 服务已重启\n'));
631
- await finalizePM2Session({ persist: true });
621
+ await handleStop();
622
+ await handleStart();
632
623
  } catch (error) {
633
624
  console.error(chalk.red('重启失败:'), error.message);
634
625
  disconnectPM2();
package/src/index.js CHANGED
@@ -135,7 +135,8 @@ async function stopOwnedOmpGatewayBeforeExit() {
135
135
  stopOmpProxyServer
136
136
  } = require('./server/omp-proxy-server');
137
137
  if (getOmpProxyStatus().running) {
138
- await stopOmpProxyServer({ forceAfterMs: 1500 });
138
+ const preserveManagedMode = process.argv.includes('--daemon');
139
+ await stopOmpProxyServer({ forceAfterMs: 1500, preserveManagedMode });
139
140
  }
140
141
  } catch (error) {
141
142
  console.error(chalk.yellow(`[WARN] OMP 动态网关退出清理失败: ${error.message}`));
@@ -741,3 +742,9 @@ async function main() {
741
742
  main().catch((error) => {
742
743
  void shutdownProcess(1, error);
743
744
  });
745
+
746
+ module.exports = {
747
+ _test: {
748
+ stopOwnedOmpGatewayBeforeExit
749
+ }
750
+ };
@@ -107,6 +107,7 @@ function startOmpProxyServer(options = {}) {
107
107
 
108
108
  async function stopOmpProxyServerUnlocked(options = {}) {
109
109
  const clearStartTime = options.clearStartTime !== false;
110
+ const preserveManagedMode = options.preserveManagedMode === true;
110
111
  const stoppedPort = currentPort;
111
112
  if (!gateway.status().listening && !isManagedOmpModeEnabled()) {
112
113
  return {
@@ -126,18 +127,20 @@ async function stopOmpProxyServerUnlocked(options = {}) {
126
127
  gateway.beginDraining();
127
128
  stopOmpSessionLogObserver();
128
129
  try {
129
- if (activeChannel) {
130
- const handoff = activateStaticOmpChannel(activeChannel.id);
131
- lastSyncResult = handoff?.sync || lastSyncResult;
132
- } else {
133
- lastSyncResult = disableManagedOmpProviders();
130
+ if (!preserveManagedMode) {
131
+ if (activeChannel) {
132
+ const handoff = activateStaticOmpChannel(activeChannel.id);
133
+ lastSyncResult = handoff?.sync || lastSyncResult;
134
+ } else {
135
+ lastSyncResult = disableManagedOmpProviders();
136
+ }
137
+ disableManagedOmpMode();
134
138
  }
135
139
  } catch (error) {
136
140
  startOmpSessionLogObserver();
137
141
  gateway.cancelDraining();
138
142
  throw error;
139
143
  }
140
- disableManagedOmpMode();
141
144
  await gateway.stop({ forceAfterMs: options.forceAfterMs });
142
145
  currentPort = null;
143
146
  lastRoutingResult = { routes: [], unsupportedChannels: [] };