coding-tool-x 3.8.2 → 3.8.4
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/CHANGELOG.md +3 -0
- package/docs/superpowers/plans/2026-08-13-omp-restart-recovery.md +48 -0
- package/docs/superpowers/specs/2026-08-13-omp-restart-recovery-design.md +22 -0
- package/package.json +8 -7
- package/src/commands/daemon.js +3 -12
- package/src/index.js +8 -1
- package/src/server/omp-proxy-server.js +9 -6
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
### Security
|
|
10
|
+
- **依赖安全升级** - adm-zip 升至 0.6.0(GHSA-xcpc-8h2w-3j85)、js-yaml 升至 4.3.1(GHSA-h67p-54hq-rp68 / GHSA-52cp-r559-cp3m / GHSA-5p4m-2wfm-xmqj)、pm2 升至 7.0.3(GHSA-x5gf-qvw8-r2rm),overrides 同步更新 basic-ftp / qs / systeminformation / js-yaml;`npm audit` 报告归零
|
|
11
|
+
|
|
9
12
|
### Fixed
|
|
10
13
|
- **Claude / Gemini 原生 wire 兼容** - OpenCode Claude / Gemini 代理、模型探测和测速统一使用 Anthropic Messages 与 Google Generative AI(含 Gemini CLI)原生请求格式、认证头与目标 URL;OMP managed provider 写入 `anthropic-messages` / `google-generative-ai` 标识,避免代理、adapter 与探测各自维护不一致的请求格式
|
|
11
14
|
- **OMP Codex Responses 路径兼容** - managed gateway 将 OMP 原生 `openai-codex-responses` 的 `/codex/responses` 请求映射到 OpenAI-compatible 渠道的 `/v1/responses`,避免 Codex-only 服务返回 HTTP 404
|
|
@@ -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.
|
|
3
|
+
"version": "3.8.4",
|
|
4
4
|
"description": "Vibe Coding 增强工作助手 - 智能会话管理、动态渠道切换、全局搜索、实时监控",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -58,28 +58,29 @@
|
|
|
58
58
|
],
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"@iarna/toml": "^2.2.5",
|
|
61
|
-
"adm-zip": "^0.
|
|
61
|
+
"adm-zip": "^0.6.0",
|
|
62
62
|
"ajv": "^8.17.1",
|
|
63
63
|
"chalk": "^4.1.2",
|
|
64
64
|
"express": "^4.21.2",
|
|
65
65
|
"http-proxy": "^1.18.1",
|
|
66
66
|
"https-proxy-agent": "^7.0.6",
|
|
67
67
|
"inquirer": "^8.2.7",
|
|
68
|
-
"js-yaml": "^4.
|
|
68
|
+
"js-yaml": "^4.3.1",
|
|
69
69
|
"open": "^8.4.2",
|
|
70
70
|
"ora": "^5.4.1",
|
|
71
|
-
"pm2": "^
|
|
71
|
+
"pm2": "^7.0.3",
|
|
72
72
|
"rxjs": "^7.8.2",
|
|
73
73
|
"semver": "^7.6.0",
|
|
74
74
|
"toml": "^3.0.0",
|
|
75
75
|
"ws": "^8.18.3"
|
|
76
76
|
},
|
|
77
77
|
"overrides": {
|
|
78
|
-
"basic-ftp": "^
|
|
78
|
+
"basic-ftp": "^6.2.0",
|
|
79
|
+
"js-yaml": "^4.3.1",
|
|
79
80
|
"lodash": "^4.17.23",
|
|
80
81
|
"lodash-es": "^4.17.23",
|
|
81
|
-
"qs": "^6.15.
|
|
82
|
-
"systeminformation": "^5.
|
|
82
|
+
"qs": "^6.15.3",
|
|
83
|
+
"systeminformation": "^5.33.1"
|
|
83
84
|
},
|
|
84
85
|
"devDependencies": {
|
|
85
86
|
"@vitest/coverage-v8": "^4.1.0",
|
package/src/commands/daemon.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 (
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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: [] };
|