@minniexcode/codex-switch 0.0.7 → 0.0.9
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 +1 -1
- package/dist/app/add-provider.js +70 -3
- package/dist/app/bridge.js +296 -0
- package/dist/app/get-status.js +39 -1
- package/dist/app/run-doctor.js +68 -1
- package/dist/app/setup-codex.js +2 -2
- package/dist/app/switch-provider.js +75 -1
- package/dist/cli.js +1 -1
- package/dist/commands/handlers.js +58 -2
- package/dist/commands/help.js +1 -0
- package/dist/commands/registry.js +56 -2
- package/dist/domain/providers.js +74 -0
- package/dist/runtime/copilot-adapter.js +173 -0
- package/dist/runtime/copilot-bridge-worker.js +25 -0
- package/dist/runtime/copilot-bridge.js +474 -0
- package/dist/runtime/copilot-installer.js +125 -0
- package/dist/runtime/copilot-sdk-loader.js +59 -0
- package/dist/storage/fs-utils.js +3 -0
- package/dist/storage/runtime-state-repo.js +80 -0
- package/docs/Design/codex-switch-v0.0.8-design.md +132 -0
- package/docs/Design/codex-switch-v0.0.9-design.md +182 -0
- package/docs/Design/codex-switch-v0.0.9-to-v0.0.12-roadmap.md +413 -0
- package/docs/PRD/codex-switch-prd-v0.0.8.md +62 -0
- package/docs/PRD/codex-switch-prd-v0.0.9.md +166 -0
- package/docs/Tests/testing-bridge-v0.0.9.md +367 -0
- package/package.json +1 -1
- /package/docs/{test-report-0.0.5.md → Tests/test-report-0.0.5.md} +0 -0
- /package/docs/{test-report-0.0.7.md → Tests/test-report-0.0.7.md} +0 -0
- /package/docs/{testing.md → Tests/testing.md} +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.getCopilotBridgeStatePath = getCopilotBridgeStatePath;
|
|
37
|
+
exports.readCopilotBridgeState = readCopilotBridgeState;
|
|
38
|
+
exports.writeCopilotBridgeState = writeCopilotBridgeState;
|
|
39
|
+
exports.clearCopilotBridgeState = clearCopilotBridgeState;
|
|
40
|
+
const fs = __importStar(require("node:fs"));
|
|
41
|
+
const os = __importStar(require("node:os"));
|
|
42
|
+
const path = __importStar(require("node:path"));
|
|
43
|
+
const fs_utils_1 = require("./fs-utils");
|
|
44
|
+
/**
|
|
45
|
+
* Returns the user-level runtime state file used by Copilot bridge helpers.
|
|
46
|
+
*/
|
|
47
|
+
function getCopilotBridgeStatePath() {
|
|
48
|
+
const override = process.env.CODEX_SWITCH_RUNTIME_STATE_DIR;
|
|
49
|
+
if (override && override.trim() !== "") {
|
|
50
|
+
return path.join(path.resolve(override), "copilot-bridge-state.json");
|
|
51
|
+
}
|
|
52
|
+
return path.join(os.homedir(), ".codex-switch", "runtime", "copilot-bridge-state.json");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Reads the Copilot bridge state manifest when present.
|
|
56
|
+
*/
|
|
57
|
+
function readCopilotBridgeState() {
|
|
58
|
+
const statePath = getCopilotBridgeStatePath();
|
|
59
|
+
if (!fs.existsSync(statePath)) {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
return JSON.parse(fs.readFileSync(statePath, "utf8"));
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Persists the Copilot bridge state manifest.
|
|
66
|
+
*/
|
|
67
|
+
function writeCopilotBridgeState(state) {
|
|
68
|
+
const statePath = getCopilotBridgeStatePath();
|
|
69
|
+
(0, fs_utils_1.ensureDir)(path.dirname(statePath));
|
|
70
|
+
(0, fs_utils_1.writeTextFileAtomic)(statePath, `${JSON.stringify(state, null, 2)}\n`);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Deletes the Copilot bridge state manifest when present.
|
|
74
|
+
*/
|
|
75
|
+
function clearCopilotBridgeState() {
|
|
76
|
+
const statePath = getCopilotBridgeStatePath();
|
|
77
|
+
if (fs.existsSync(statePath)) {
|
|
78
|
+
fs.rmSync(statePath, { force: true });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# codex-switch `0.0.8` 设计文档
|
|
2
|
+
|
|
3
|
+
## 文档信息
|
|
4
|
+
|
|
5
|
+
- 文档类型:详细设计文档
|
|
6
|
+
- 适用版本:`0.0.8`
|
|
7
|
+
- 目标范围:`0.0.7 -> 0.0.8`
|
|
8
|
+
- 对应 PRD:[`../PRD/codex-switch-prd-v0.0.8.md`](../PRD/codex-switch-prd-v0.0.8.md)
|
|
9
|
+
|
|
10
|
+
## 1. 文档目标
|
|
11
|
+
|
|
12
|
+
本设计把 `0.0.8` 的 Copilot runtime integration 收口到可实现的范围:
|
|
13
|
+
|
|
14
|
+
- Copilot provider 的 schema 如何表达
|
|
15
|
+
- SDK 的 lazy-load 与自动安装如何落地
|
|
16
|
+
- `add`、`switch`、`status`、`doctor` 分别承担什么职责
|
|
17
|
+
- `providers.json`、`config.toml`、`auth.json` 与 runtime manifest 的边界是什么
|
|
18
|
+
- 代码和测试应该落到哪些模块
|
|
19
|
+
|
|
20
|
+
## 2. 版本定位
|
|
21
|
+
|
|
22
|
+
`0.0.8` 是第一个 runtime-backed provider 版本。它不会把 `codex-switch` 扩成通用插件系统,而是先用 GitHub Copilot 把 runtime integration 这条线真正跑通。
|
|
23
|
+
|
|
24
|
+
## 3. 数据模型
|
|
25
|
+
|
|
26
|
+
`ProviderRecord` 在 `0.0.8` 中扩展为兼容 direct provider 和 runtime-backed provider:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
type ProviderRuntime =
|
|
30
|
+
| {
|
|
31
|
+
kind: "copilot-sdk-bridge";
|
|
32
|
+
upstream: "github-copilot";
|
|
33
|
+
bridgeHost: string;
|
|
34
|
+
bridgePort: number;
|
|
35
|
+
bridgePath: "/v1";
|
|
36
|
+
premiumRequests: true;
|
|
37
|
+
authSource: "official-sdk";
|
|
38
|
+
sdkInstallMode: "lazy";
|
|
39
|
+
};
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
语义:
|
|
43
|
+
|
|
44
|
+
- 无 `runtime`:现有 direct provider
|
|
45
|
+
- `runtime.kind = "copilot-sdk-bridge"`:Copilot provider
|
|
46
|
+
- Copilot provider 的 `apiKey` 是本地 bridge 的 shared secret,不是 GitHub token
|
|
47
|
+
- `baseUrl` 必须与 `http://<bridgeHost>:<bridgePort>/v1` 一致
|
|
48
|
+
|
|
49
|
+
## 4. 命令设计
|
|
50
|
+
|
|
51
|
+
扩展现有命令面,不新增 `copilot` 或 `proxy` 子命令:
|
|
52
|
+
|
|
53
|
+
- `codexs add <provider> --copilot --profile <name> [--bridge-host <host>] [--bridge-port <port>] [--bridge-api-key <secret>] [--install-copilot-sdk]`
|
|
54
|
+
- `codexs switch <provider>`
|
|
55
|
+
- `codexs status`
|
|
56
|
+
- `codexs doctor`
|
|
57
|
+
|
|
58
|
+
规则:
|
|
59
|
+
|
|
60
|
+
- `--copilot` 与 direct provider 输入模式互斥
|
|
61
|
+
- `--bridge-host` 默认 `127.0.0.1`
|
|
62
|
+
- `--bridge-port` 默认 `4141`
|
|
63
|
+
- `--bridge-api-key` 若缺失则自动生成本地 secret
|
|
64
|
+
- SDK 自动安装只允许发生在 `add --copilot`
|
|
65
|
+
|
|
66
|
+
## 5. Runtime 模块
|
|
67
|
+
|
|
68
|
+
新增模块:
|
|
69
|
+
|
|
70
|
+
- `src/runtime/copilot-sdk-loader.ts`
|
|
71
|
+
- `src/runtime/copilot-installer.ts`
|
|
72
|
+
- `src/runtime/copilot-adapter.ts`
|
|
73
|
+
- `src/runtime/copilot-bridge.ts`
|
|
74
|
+
- `src/storage/runtime-state-repo.ts`
|
|
75
|
+
|
|
76
|
+
职责:
|
|
77
|
+
|
|
78
|
+
- loader:按用户级 runtime 目录懒加载 SDK
|
|
79
|
+
- installer:探测与安装 SDK
|
|
80
|
+
- adapter:优先按官方 `CopilotClient -> createSession -> sendAndWait` 形态包装 Copilot SDK 的 probe / auth / request 能力
|
|
81
|
+
- bridge:管理本地 OpenAI 兼容 bridge 的状态
|
|
82
|
+
- runtime state repo:保存 bridge runtime manifest
|
|
83
|
+
|
|
84
|
+
前置条件:
|
|
85
|
+
|
|
86
|
+
- 本地官方 SDK 依赖已安装并可加载
|
|
87
|
+
- 用户机器上已有可用的 Copilot CLI / login 状态,`codex-switch` 不接管上游账号登录
|
|
88
|
+
|
|
89
|
+
## 6. 运行态与文件边界
|
|
90
|
+
|
|
91
|
+
- `providers.json`:保存 Copilot provider 与 bridge 配置
|
|
92
|
+
- `config.toml`:保存 bridge 的 `base_url` 与 `env_key`
|
|
93
|
+
- `auth.json`:保存 Codex 到 bridge 的 shared secret
|
|
94
|
+
- runtime manifest:保存 bridge 进程状态,不进入 managed backup 事务
|
|
95
|
+
|
|
96
|
+
## 7. `switch` 时序
|
|
97
|
+
|
|
98
|
+
1. 读取 provider
|
|
99
|
+
2. 校验 `runtime` 配置
|
|
100
|
+
3. probe SDK
|
|
101
|
+
4. 缺失则直接报错
|
|
102
|
+
5. 检查 Copilot auth state
|
|
103
|
+
6. 启动或复用 bridge
|
|
104
|
+
7. healthcheck 通过
|
|
105
|
+
8. 再进入现有 `config.toml + auth.json` 写入事务
|
|
106
|
+
9. 文件写入失败时回滚,并对本次新启动 bridge 做 best-effort 清理
|
|
107
|
+
|
|
108
|
+
## 8. 错误语义
|
|
109
|
+
|
|
110
|
+
新增错误码:
|
|
111
|
+
|
|
112
|
+
- `COPILOT_SDK_MISSING`
|
|
113
|
+
- `COPILOT_SDK_INSTALL_FAILED`
|
|
114
|
+
- `COPILOT_SDK_INSTALL_REQUIRES_TTY`
|
|
115
|
+
- `COPILOT_SDK_UNSUPPORTED`
|
|
116
|
+
- `COPILOT_AUTH_REQUIRED`
|
|
117
|
+
- `COPILOT_PREMIUM_UNAVAILABLE`
|
|
118
|
+
- `BRIDGE_PORT_CONFLICT`
|
|
119
|
+
- `BRIDGE_START_FAILED`
|
|
120
|
+
- `BRIDGE_HEALTHCHECK_FAILED`
|
|
121
|
+
- `RUNTIME_PROVIDER_INVALID`
|
|
122
|
+
- `PROVIDER_BASE_URL_MISMATCH`
|
|
123
|
+
|
|
124
|
+
## 9. 测试
|
|
125
|
+
|
|
126
|
+
需要覆盖:
|
|
127
|
+
|
|
128
|
+
- `add --copilot` 参数与写入
|
|
129
|
+
- 非交互缺失 SDK 的失败路径
|
|
130
|
+
- `--install-copilot-sdk` 的允许安装路径
|
|
131
|
+
- Copilot provider 的 `status` / `doctor`
|
|
132
|
+
- direct provider 回归
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# codex-switch `0.0.9` Design
|
|
2
|
+
|
|
3
|
+
## 1. Purpose
|
|
4
|
+
|
|
5
|
+
This design turns the `0.0.9` PRD into an implementable CLI and runtime spec for the local Copilot bridge path.
|
|
6
|
+
|
|
7
|
+
The release goal is not to introduce a background service product. It is to make the existing local Copilot bridge safe to start, stop, inspect, and reuse from a single-process user-space workflow.
|
|
8
|
+
|
|
9
|
+
## 2. Scope
|
|
10
|
+
|
|
11
|
+
### In scope
|
|
12
|
+
|
|
13
|
+
- `codexs bridge start [provider]`
|
|
14
|
+
- `codexs bridge stop [provider]`
|
|
15
|
+
- `codexs bridge status [provider]`
|
|
16
|
+
- single-instance bridge reuse and replacement
|
|
17
|
+
- detached user-space bridge workers
|
|
18
|
+
- 5-digit default port selection with recovery when the preferred port is occupied
|
|
19
|
+
- runtime state persistence outside the managed backup transaction boundary
|
|
20
|
+
- Copilot-only target selection helpers
|
|
21
|
+
- switch-time bridge reuse through the same lifecycle code path
|
|
22
|
+
|
|
23
|
+
### Out of scope
|
|
24
|
+
|
|
25
|
+
- boot autostart
|
|
26
|
+
- login autostart
|
|
27
|
+
- Windows Service support
|
|
28
|
+
- multiple concurrent managed bridge instances
|
|
29
|
+
- non-Copilot runtime families
|
|
30
|
+
- any new auth storage scheme for upstream Copilot login state
|
|
31
|
+
|
|
32
|
+
## 3. Command Surface
|
|
33
|
+
|
|
34
|
+
### 3.1 New commands
|
|
35
|
+
|
|
36
|
+
- `codexs bridge start [provider]`
|
|
37
|
+
- `codexs bridge stop [provider]`
|
|
38
|
+
- `codexs bridge status [provider]`
|
|
39
|
+
|
|
40
|
+
The command registry must add nested tokens under the public `bridge` namespace, with the longest token sequence winning during argument resolution.
|
|
41
|
+
|
|
42
|
+
### 3.2 Command semantics
|
|
43
|
+
|
|
44
|
+
- `start` is a write command
|
|
45
|
+
- `stop` is an operational write/recovery command
|
|
46
|
+
- `status` is a read command
|
|
47
|
+
|
|
48
|
+
### 3.3 Target resolution
|
|
49
|
+
|
|
50
|
+
For `bridge start`, provider resolution proceeds in this order:
|
|
51
|
+
|
|
52
|
+
1. explicit provider argument
|
|
53
|
+
2. current active provider, if it is a Copilot bridge provider
|
|
54
|
+
3. sole configured Copilot bridge provider
|
|
55
|
+
4. interactive Copilot-only provider picker in a TTY
|
|
56
|
+
|
|
57
|
+
If none of those paths resolves a target, the command fails with a design-level bridge target error.
|
|
58
|
+
|
|
59
|
+
For `bridge stop` and `bridge status`, resolution prefers the current runtime-state instance when present. An explicit provider argument acts as a guard and must not silently target a different provider instance.
|
|
60
|
+
|
|
61
|
+
## 4. Runtime Model
|
|
62
|
+
|
|
63
|
+
### 4.1 Single-instance policy
|
|
64
|
+
|
|
65
|
+
The bridge runtime is a single detached user-space worker.
|
|
66
|
+
|
|
67
|
+
- If the bridge is already healthy for the same provider, `start` and `switch` reuse it.
|
|
68
|
+
- If the bridge is healthy for a different provider, the current managed instance is stopped before a new one is started.
|
|
69
|
+
- Runtime state is stored separately from the managed file backup transaction, so the bridge can survive or be inspected outside file rollbacks.
|
|
70
|
+
|
|
71
|
+
### 4.2 Health checks
|
|
72
|
+
|
|
73
|
+
Bridge startup must verify the worker by probing the local `/healthz` endpoint before the command reports success.
|
|
74
|
+
|
|
75
|
+
Bridge status must report:
|
|
76
|
+
|
|
77
|
+
- the last known runtime state
|
|
78
|
+
- whether the provider binding matches the expected provider
|
|
79
|
+
- whether the live worker is healthy
|
|
80
|
+
- the reason if the state is stale or mismatched
|
|
81
|
+
|
|
82
|
+
### 4.3 Stop behavior
|
|
83
|
+
|
|
84
|
+
`bridge stop` is protection-first.
|
|
85
|
+
|
|
86
|
+
- It stops the managed worker when one exists.
|
|
87
|
+
- It clears the runtime-state manifest.
|
|
88
|
+
- It must not mutate `providers.json`, `config.toml`, or `auth.json`.
|
|
89
|
+
- If the caller supplies a provider that does not match the tracked runtime state, the command fails rather than stopping the wrong instance.
|
|
90
|
+
|
|
91
|
+
## 5. Port Policy
|
|
92
|
+
|
|
93
|
+
- The default bridge host is `127.0.0.1`.
|
|
94
|
+
- The default bridge port is fixed at `41415`.
|
|
95
|
+
- The port must always remain in the 5-digit range.
|
|
96
|
+
- If the preferred port is occupied, the runtime must automatically select another free 5-digit port.
|
|
97
|
+
- Any recovered port must be persisted back into the provider record and the corresponding `config.toml` runtime projection before the command reports success.
|
|
98
|
+
- The persisted port must match the live worker port.
|
|
99
|
+
|
|
100
|
+
The key design constraint is that runtime state and managed config cannot diverge after a successful start.
|
|
101
|
+
|
|
102
|
+
## 6. Persistence Boundaries
|
|
103
|
+
|
|
104
|
+
`bridge start` may update:
|
|
105
|
+
|
|
106
|
+
- `providers.json`
|
|
107
|
+
- the matching `config.toml` bridge projection
|
|
108
|
+
- runtime state manifest
|
|
109
|
+
|
|
110
|
+
`bridge start` must not rewrite:
|
|
111
|
+
|
|
112
|
+
- `auth.json`
|
|
113
|
+
- active profile state except when invoked through the shared `switch` flow
|
|
114
|
+
|
|
115
|
+
`switch` keeps using the same bridge lifecycle path and performs its own auth/config transaction afterward.
|
|
116
|
+
|
|
117
|
+
Runtime state is explicitly outside backup transactions. If file persistence fails after the worker has started, the implementation must clean up the new worker unless it was reused from a previous healthy instance.
|
|
118
|
+
|
|
119
|
+
## 7. Error Model
|
|
120
|
+
|
|
121
|
+
The implementation should reuse existing error families where possible and add bridge-specific errors for unresolved target and provider mismatch cases.
|
|
122
|
+
|
|
123
|
+
Relevant bridge/runtime errors:
|
|
124
|
+
|
|
125
|
+
- `BRIDGE_TARGET_UNRESOLVED`
|
|
126
|
+
- `BRIDGE_PROVIDER_MISMATCH`
|
|
127
|
+
- `BRIDGE_PORT_CONFLICT`
|
|
128
|
+
- `BRIDGE_START_FAILED`
|
|
129
|
+
- `BRIDGE_HEALTHCHECK_FAILED`
|
|
130
|
+
- `RUNTIME_PROVIDER_INVALID`
|
|
131
|
+
- `PROVIDER_BASE_URL_MISMATCH`
|
|
132
|
+
|
|
133
|
+
The design intent is that the bridge commands fail clearly before mutating managed files when the provider cannot be resolved or the provider/runtime binding is inconsistent.
|
|
134
|
+
|
|
135
|
+
## 8. Implementation Shape
|
|
136
|
+
|
|
137
|
+
The implementation should be split into three layers:
|
|
138
|
+
|
|
139
|
+
- command wiring in `src/commands/`
|
|
140
|
+
- app use cases in `src/app/`
|
|
141
|
+
- worker and healthcheck code in `src/runtime/`
|
|
142
|
+
|
|
143
|
+
The bridge-specific app layer should own:
|
|
144
|
+
|
|
145
|
+
- target resolution
|
|
146
|
+
- port recovery
|
|
147
|
+
- provider/config projection updates
|
|
148
|
+
- runtime-state cleanup
|
|
149
|
+
|
|
150
|
+
The runtime layer should own:
|
|
151
|
+
|
|
152
|
+
- worker spawning
|
|
153
|
+
- healthcheck probing
|
|
154
|
+
- detached-process cleanup
|
|
155
|
+
- runtime-state manifest read/write
|
|
156
|
+
|
|
157
|
+
## 9. Tests
|
|
158
|
+
|
|
159
|
+
Minimum coverage for `0.0.9`:
|
|
160
|
+
|
|
161
|
+
- command parsing and help for `bridge start|stop|status`
|
|
162
|
+
- longest-token resolution for nested commands
|
|
163
|
+
- explicit provider, active-provider, sole-provider, and TTY fallback selection
|
|
164
|
+
- current-runtime-state preference for stop/status
|
|
165
|
+
- mismatch-guard behavior for stop/status
|
|
166
|
+
- single-instance reuse and replacement
|
|
167
|
+
- stale runtime-state cleanup
|
|
168
|
+
- port conflict recovery
|
|
169
|
+
- persisted port updates
|
|
170
|
+
- cleanup when file mutation fails after a successful bridge start
|
|
171
|
+
- direct-provider regressions
|
|
172
|
+
|
|
173
|
+
## 10. Release Criteria
|
|
174
|
+
|
|
175
|
+
`0.0.9` is complete when the following are true:
|
|
176
|
+
|
|
177
|
+
- Copilot bridge can be managed manually with `bridge start|stop|status`
|
|
178
|
+
- `switch` reuses the same lifecycle path
|
|
179
|
+
- the default bridge port is 5 digits and port conflicts recover automatically
|
|
180
|
+
- runtime state is durable enough for status and stop behavior, but remains outside managed backup transactions
|
|
181
|
+
- direct providers keep their existing behavior
|
|
182
|
+
|