agent-embassy 1.7.0 → 1.8.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.
- package/CHANGELOG.md +32 -0
- package/CONTRIBUTING.md +51 -47
- package/README.md +26 -9
- package/README.zh-CN.md +22 -9
- package/SECURITY.md +48 -35
- package/dist/src/gateway/acp-client.d.ts +12 -19
- package/dist/src/gateway/acp-client.js +90 -65
- package/dist/src/gateway/acp-client.js.map +1 -1
- package/dist/src/gateway/acp-provider.d.ts +11 -10
- package/dist/src/gateway/acp-provider.js +165 -53
- package/dist/src/gateway/acp-provider.js.map +1 -1
- package/dist/src/gateway/claude-helper-client.d.ts +2 -43
- package/dist/src/gateway/claude-helper-client.js +1 -211
- package/dist/src/gateway/claude-helper-client.js.map +1 -1
- package/dist/src/gateway/claude-helper-protocol.d.ts +16 -74
- package/dist/src/gateway/claude-helper-protocol.js +99 -378
- package/dist/src/gateway/claude-helper-protocol.js.map +1 -1
- package/dist/src/gateway/claude-helper-supervisor.d.ts +56 -44
- package/dist/src/gateway/claude-helper-supervisor.js +265 -456
- package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
- package/dist/src/gateway/claude-helper.js +184 -210
- package/dist/src/gateway/claude-helper.js.map +1 -1
- package/dist/src/gateway/claude-peer.d.ts +20 -87
- package/dist/src/gateway/claude-peer.js +310 -1009
- package/dist/src/gateway/claude-peer.js.map +1 -1
- package/dist/src/gateway/claude-runtime.d.ts +13 -15
- package/dist/src/gateway/claude-runtime.js +14 -168
- package/dist/src/gateway/claude-runtime.js.map +1 -1
- package/dist/src/gateway/cli-copy.en.d.ts +2 -2
- package/dist/src/gateway/cli-copy.en.js +4 -2
- package/dist/src/gateway/cli-copy.en.js.map +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.d.ts +2 -2
- package/dist/src/gateway/cli-copy.zh-CN.js +4 -2
- package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/cli.d.ts +7 -2
- package/dist/src/gateway/cli.js +277 -575
- package/dist/src/gateway/cli.js.map +1 -1
- package/dist/src/gateway/codex-app-server.d.ts +5 -227
- package/dist/src/gateway/codex-app-server.js +18 -1463
- package/dist/src/gateway/codex-app-server.js.map +1 -1
- package/dist/src/gateway/codex-doctor.d.ts +36 -0
- package/dist/src/gateway/codex-doctor.js +127 -0
- package/dist/src/gateway/codex-doctor.js.map +1 -0
- package/dist/src/gateway/codex-local-transport.d.ts +6 -6
- package/dist/src/gateway/codex-local-transport.js +13 -10
- package/dist/src/gateway/codex-local-transport.js.map +1 -1
- package/dist/src/gateway/codex-stateless-transport.d.ts +114 -0
- package/dist/src/gateway/codex-stateless-transport.js +1108 -0
- package/dist/src/gateway/codex-stateless-transport.js.map +1 -0
- package/dist/src/gateway/config.d.ts +2 -9
- package/dist/src/gateway/config.js +62 -100
- package/dist/src/gateway/config.js.map +1 -1
- package/dist/src/gateway/control.d.ts +36 -90
- package/dist/src/gateway/control.js +465 -1123
- package/dist/src/gateway/control.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.d.ts +1 -1
- package/dist/src/gateway/dashboard-copy.en.d.ts +8 -26
- package/dist/src/gateway/dashboard-copy.en.js +19 -37
- package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.js +8 -26
- package/dist/src/gateway/dashboard-copy.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +8 -26
- package/dist/src/gateway/dashboard-copy.zh-CN.js +19 -37
- package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/dashboard-model.d.ts +8 -22
- package/dist/src/gateway/dashboard-model.js +44 -130
- package/dist/src/gateway/dashboard-model.js.map +1 -1
- package/dist/src/gateway/dashboard.d.ts +0 -6
- package/dist/src/gateway/dashboard.js +3 -7
- package/dist/src/gateway/dashboard.js.map +1 -1
- package/dist/src/gateway/live-dashboard-app/app.js +20 -34
- package/dist/src/gateway/live-dashboard-command.js +2 -2
- package/dist/src/gateway/live-dashboard-command.js.map +1 -1
- package/dist/src/gateway/live-dashboard-http.d.ts +1 -1
- package/dist/src/gateway/live-dashboard-http.js +2 -2
- package/dist/src/gateway/live-dashboard-http.js.map +1 -1
- package/dist/src/gateway/progress-watch-machine.d.ts +1 -37
- package/dist/src/gateway/progress-watch-machine.js +4 -15
- package/dist/src/gateway/progress-watch-machine.js.map +1 -1
- package/dist/src/gateway/providers.d.ts +58 -191
- package/dist/src/gateway/providers.js +637 -2434
- package/dist/src/gateway/providers.js.map +1 -1
- package/dist/src/gateway/server.d.ts +16 -25
- package/dist/src/gateway/server.js +165 -240
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service.d.ts +149 -427
- package/dist/src/gateway/service.js +1639 -5662
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/state-v2-to-v3.d.ts +23 -0
- package/dist/src/gateway/state-v2-to-v3.js +994 -0
- package/dist/src/gateway/state-v2-to-v3.js.map +1 -0
- package/dist/src/gateway/store.d.ts +54 -309
- package/dist/src/gateway/store.js +1560 -3510
- package/dist/src/gateway/store.js.map +1 -1
- package/dist/src/gateway/types.d.ts +181 -225
- package/dist/src/gateway/types.js +115 -251
- package/dist/src/gateway/types.js.map +1 -1
- package/docs/CONFIGURATION.md +22 -9
- package/docs/CONFIGURATION.zh-CN.md +21 -9
- package/docs/DASHBOARD.md +10 -10
- package/docs/DASHBOARD.zh-CN.md +3 -3
- package/docs/DELIVERY.md +4 -4
- package/docs/DELIVERY.zh-CN.md +4 -4
- package/docs/GATEWAY-ARCHITECTURE.md +158 -187
- package/package.json +1 -1
- package/skills/embassy-peer/SKILL.md +28 -22
- package/dist/src/gateway/codex-registration-generation.d.ts +0 -5
- package/dist/src/gateway/codex-registration-generation.js +0 -19
- package/dist/src/gateway/codex-registration-generation.js.map +0 -1
- package/dist/src/gateway/codex-registration-succession.d.ts +0 -209
- package/dist/src/gateway/codex-registration-succession.js +0 -588
- package/dist/src/gateway/codex-registration-succession.js.map +0 -1
- package/dist/src/gateway/compatibility.d.ts +0 -22
- package/dist/src/gateway/compatibility.js +0 -23
- package/dist/src/gateway/compatibility.js.map +0 -1
- package/dist/src/gateway/delivery-machine.d.ts +0 -235
- package/dist/src/gateway/delivery-machine.js +0 -540
- package/dist/src/gateway/delivery-machine.js.map +0 -1
package/dist/src/gateway/cli.js
CHANGED
|
@@ -1,54 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Local gateway foreground launcher and metadata-only control client.
|
|
4
|
-
*
|
|
5
|
-
* Provider identities and message bodies are accepted only from inherited
|
|
6
|
-
* process state and stdin respectively. They are never reflected in output or
|
|
7
|
-
* error text. Only `serve` owns the long-lived provider capabilities and
|
|
8
|
-
* private control server; every other command is a bounded client operation.
|
|
9
|
-
*/
|
|
2
|
+
/** Foreground broker plus bounded metadata-only control client. */
|
|
10
3
|
import { realpathSync } from "node:fs";
|
|
11
4
|
import { lstat, realpath } from "node:fs/promises";
|
|
12
5
|
import path from "node:path";
|
|
13
6
|
import { fileURLToPath } from "node:url";
|
|
14
7
|
import { BridgeError } from "../errors.js";
|
|
15
|
-
import { getCliCopy
|
|
8
|
+
import { getCliCopy } from "./cli-copy.js";
|
|
16
9
|
import { callerIdentityConflictHintEn } from "./cli-copy.en.js";
|
|
17
10
|
import { callerIdentityConflictHintZhCn } from "./cli-copy.zh-CN.js";
|
|
18
|
-
import { GATEWAY_CONTROL_DEFAULT_TIMEOUT_MS, GATEWAY_CONTROL_MAX_MESSAGE_BYTES, GATEWAY_CONTROL_MAX_RESPONSE_BYTES, GATEWAY_CONTROL_PROTOCOL_VERSION, GatewayControlTransportError, isClaudeSessionSelector, isGatewayAlias, isGatewayConversationId, isGatewayDeliveryToken, isGatewayHostId, isGatewayReplyAddress, sendGatewayControlRequest
|
|
11
|
+
import { GATEWAY_CONTROL_DEFAULT_TIMEOUT_MS, GATEWAY_CONTROL_MAX_MESSAGE_BYTES, GATEWAY_CONTROL_MAX_RESPONSE_BYTES, GATEWAY_CONTROL_PROTOCOL_VERSION, GatewayControlTransportError, isClaudeSessionSelector, isGatewayAlias, isGatewayConversationId, isGatewayDeliveryToken, isGatewayHostId, isGatewayReplyAddress, sendGatewayControlRequest } from "./control.js";
|
|
19
12
|
import { loadGatewayConfig } from "./config.js";
|
|
20
|
-
import { isDashboardLocale
|
|
21
|
-
import { DEFAULT_LIVE_DASHBOARD_PORT, runLiveDashboardCommand
|
|
22
|
-
import { runGatewayServer
|
|
13
|
+
import { isDashboardLocale } from "./locale.js";
|
|
14
|
+
import { DEFAULT_LIVE_DASHBOARD_PORT, runLiveDashboardCommand } from "./live-dashboard-command.js";
|
|
15
|
+
import { runGatewayServer } from "./server.js";
|
|
23
16
|
import { PROGRESS_WATCH_DEFAULT_IDLE_MS } from "./progress-watch-machine.js";
|
|
17
|
+
import { convertGatewayStateV2ToV3 } from "./state-v2-to-v3.js";
|
|
24
18
|
const THREAD_ID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
25
19
|
const DEFAULT_HOST_ID = "this-mac";
|
|
26
20
|
const CLI_MAX_OUTPUT_BYTES = GATEWAY_CONTROL_MAX_RESPONSE_BYTES;
|
|
27
21
|
const DELIVERY_POLL_INTERVAL_MS = 250;
|
|
28
22
|
const DELIVERY_POLL_MIN_REQUEST_TIMEOUT_MS = 50;
|
|
29
|
-
export const EMBASSY_VERSION = "1.
|
|
23
|
+
export const EMBASSY_VERSION = "1.8.0";
|
|
30
24
|
// RELEASE VERSION SWEEP — every place the version lives: package.json,
|
|
31
|
-
// npm-shrinkwrap.json (x2), this constant,
|
|
25
|
+
// npm-shrinkwrap.json (x2), this constant,
|
|
32
26
|
// test/gateway-cli.test.ts package-metadata assertion.
|
|
33
27
|
const DEFAULT_CLI_LOCALE = "en";
|
|
34
28
|
export const gatewayCliCommands = [
|
|
35
|
-
"serve",
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
"
|
|
39
|
-
"wait-delivery",
|
|
40
|
-
"untrack",
|
|
41
|
-
"refresh-dashboard",
|
|
42
|
-
"dashboard",
|
|
43
|
-
"register-codex",
|
|
44
|
-
"unregister-codex",
|
|
45
|
-
"select-claude",
|
|
46
|
-
"unselect-claude",
|
|
47
|
-
"pair",
|
|
48
|
-
"unpair",
|
|
49
|
-
"send-to-claude",
|
|
50
|
-
"send-to-codex",
|
|
51
|
-
"reply",
|
|
29
|
+
"serve", "health", "status", "doctor", "convert-state-v2-to-v3", "delivery-status",
|
|
30
|
+
"wait-delivery", "untrack", "refresh-dashboard", "dashboard", "register-codex",
|
|
31
|
+
"unregister-codex", "select-claude", "unselect-claude", "pair", "unpair",
|
|
32
|
+
"send-to-claude", "send-to-codex", "reply",
|
|
52
33
|
];
|
|
53
34
|
export const gatewayCliExitCodes = Object.freeze({
|
|
54
35
|
ok: 0,
|
|
@@ -65,58 +46,42 @@ class CliFault extends Error {
|
|
|
65
46
|
kind;
|
|
66
47
|
constructor(code, retryable = false, hint, kind) {
|
|
67
48
|
super("The gateway client rejected the request.");
|
|
68
|
-
this.name = "CliFault";
|
|
69
49
|
this.code = code;
|
|
70
50
|
this.retryable = retryable;
|
|
71
51
|
this.hint = hint;
|
|
72
52
|
this.kind = kind;
|
|
53
|
+
this.name = "CliFault";
|
|
73
54
|
}
|
|
74
55
|
}
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
gatewayCliCommands.includes(value));
|
|
78
|
-
}
|
|
56
|
+
function fault(code = "INVALID_ARGUMENTS") { throw new CliFault(code); }
|
|
57
|
+
const isCommand = (value) => value !== undefined && gatewayCliCommands.includes(value);
|
|
79
58
|
function parseOptions(args, valueNames, flagNames = []) {
|
|
80
|
-
const values = new Set(valueNames);
|
|
81
|
-
const flags = new Set(flagNames);
|
|
59
|
+
const values = new Set(valueNames), flags = new Set(flagNames);
|
|
82
60
|
const parsed = Object.create(null);
|
|
83
61
|
for (let index = 0; index < args.length; index += 1) {
|
|
84
62
|
const token = args[index];
|
|
85
|
-
if (token === undefined || !token.startsWith("--") || token.length <= 2)
|
|
86
|
-
|
|
87
|
-
}
|
|
63
|
+
if (token === undefined || !token.startsWith("--") || token.length <= 2)
|
|
64
|
+
fault();
|
|
88
65
|
const name = token.slice(2);
|
|
89
|
-
if (Object.hasOwn(parsed, name))
|
|
90
|
-
|
|
91
|
-
}
|
|
66
|
+
if (Object.hasOwn(parsed, name))
|
|
67
|
+
fault();
|
|
92
68
|
if (flags.has(name)) {
|
|
93
69
|
parsed[name] = true;
|
|
94
70
|
continue;
|
|
95
71
|
}
|
|
96
|
-
if (!values.has(name))
|
|
97
|
-
|
|
98
|
-
}
|
|
72
|
+
if (!values.has(name))
|
|
73
|
+
fault();
|
|
99
74
|
const value = args[index + 1];
|
|
100
|
-
if (value === undefined || value.startsWith("--") || value.length === 0)
|
|
101
|
-
|
|
102
|
-
}
|
|
75
|
+
if (value === undefined || value.startsWith("--") || value.length === 0)
|
|
76
|
+
fault();
|
|
103
77
|
parsed[name] = value;
|
|
104
78
|
index += 1;
|
|
105
79
|
}
|
|
106
80
|
return parsed;
|
|
107
81
|
}
|
|
108
|
-
function
|
|
109
|
-
const value = env.EMBASSY_LOCALE;
|
|
110
|
-
if (value === undefined || value.length === 0)
|
|
111
|
-
return DEFAULT_CLI_LOCALE;
|
|
112
|
-
if (!isDashboardLocale(value))
|
|
113
|
-
throw new CliFault("INVALID_ARGUMENTS");
|
|
114
|
-
return value;
|
|
115
|
-
}
|
|
116
|
-
function resolveCommonCliOptions(args, env) {
|
|
82
|
+
function commonOptions(args, env) {
|
|
117
83
|
const stripped = [];
|
|
118
84
|
let locale;
|
|
119
|
-
let sawLocale = false;
|
|
120
85
|
for (let index = 0; index < args.length; index += 1) {
|
|
121
86
|
const token = args[index];
|
|
122
87
|
if (token !== "--lang") {
|
|
@@ -124,189 +89,144 @@ function resolveCommonCliOptions(args, env) {
|
|
|
124
89
|
stripped.push(token);
|
|
125
90
|
continue;
|
|
126
91
|
}
|
|
127
|
-
if (
|
|
128
|
-
|
|
129
|
-
sawLocale = true;
|
|
92
|
+
if (locale !== undefined)
|
|
93
|
+
fault();
|
|
130
94
|
const value = args[index + 1];
|
|
131
|
-
if (
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
if (!isDashboardLocale(value)) {
|
|
135
|
-
throw new CliFault("INVALID_ARGUMENTS");
|
|
136
|
-
}
|
|
95
|
+
if (!isDashboardLocale(value))
|
|
96
|
+
fault();
|
|
137
97
|
locale = value;
|
|
138
98
|
index += 1;
|
|
139
99
|
}
|
|
100
|
+
const inherited = env.EMBASSY_LOCALE;
|
|
101
|
+
if (locale === undefined && inherited !== undefined && inherited.length > 0 && !isDashboardLocale(inherited))
|
|
102
|
+
fault();
|
|
140
103
|
return {
|
|
141
104
|
args: stripped,
|
|
142
|
-
|
|
143
|
-
// lets an exact flag intentionally override even a malformed environment.
|
|
144
|
-
locale: locale ?? environmentLocale(env),
|
|
105
|
+
locale: locale ?? (isDashboardLocale(inherited) ? inherited : DEFAULT_CLI_LOCALE),
|
|
145
106
|
};
|
|
146
107
|
}
|
|
147
108
|
function fallbackCliLocale(args, env) {
|
|
148
|
-
const
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
if (localeFlagIndices.length === 1) {
|
|
154
|
-
const value = args[(localeFlagIndices[0] ?? -1) + 1];
|
|
155
|
-
if (isDashboardLocale(value))
|
|
156
|
-
return value;
|
|
157
|
-
}
|
|
158
|
-
return isDashboardLocale(env.EMBASSY_LOCALE)
|
|
159
|
-
? env.EMBASSY_LOCALE
|
|
160
|
-
: DEFAULT_CLI_LOCALE;
|
|
161
|
-
}
|
|
162
|
-
function fixedStderr(locale, kind) {
|
|
163
|
-
return `[embassy] ${getCliCopy(locale)[`error.${kind}`]}\n`;
|
|
109
|
+
const indices = args.flatMap((value, index) => value === "--lang" ? [index] : []);
|
|
110
|
+
const flagged = indices.length === 1 ? args[indices[0] + 1] : undefined;
|
|
111
|
+
return isDashboardLocale(flagged) ? flagged
|
|
112
|
+
: isDashboardLocale(env.EMBASSY_LOCALE) ? env.EMBASSY_LOCALE : DEFAULT_CLI_LOCALE;
|
|
164
113
|
}
|
|
114
|
+
const fixedStderr = (locale, kind) => `[embassy] ${getCliCopy(locale)[`error.${kind}`]}\n`;
|
|
165
115
|
function requireString(options, name) {
|
|
166
116
|
const value = options[name];
|
|
167
117
|
if (typeof value !== "string")
|
|
168
|
-
|
|
118
|
+
fault();
|
|
169
119
|
return value;
|
|
170
120
|
}
|
|
171
|
-
function
|
|
121
|
+
function count(options, minimum, maximum = minimum) {
|
|
172
122
|
const count = Object.keys(options).length;
|
|
173
|
-
if (count < minimum || count > maximum)
|
|
174
|
-
|
|
175
|
-
}
|
|
123
|
+
if (count < minimum || count > maximum)
|
|
124
|
+
fault();
|
|
176
125
|
}
|
|
177
126
|
function requireAlias(options, name) {
|
|
178
127
|
const alias = requireString(options, name);
|
|
179
128
|
if (!isGatewayAlias(alias))
|
|
180
|
-
|
|
129
|
+
fault();
|
|
181
130
|
return alias;
|
|
182
131
|
}
|
|
183
132
|
function requireCodexAlias(options, name) {
|
|
184
133
|
const alias = requireAlias(options, name);
|
|
185
134
|
if (!alias.startsWith("codex-"))
|
|
186
|
-
|
|
135
|
+
fault();
|
|
187
136
|
return alias;
|
|
188
137
|
}
|
|
189
|
-
|
|
190
|
-
return alias.slice(alias.lastIndexOf("@") + 1);
|
|
191
|
-
}
|
|
138
|
+
const gatewayAliasHost = (alias) => alias.slice(alias.lastIndexOf("@") + 1);
|
|
192
139
|
function requirePairAliases(options) {
|
|
193
140
|
const from = requireAlias(options, "from");
|
|
194
141
|
const to = requireAlias(options, "to");
|
|
195
|
-
if (from === to || gatewayAliasHost(from) !== gatewayAliasHost(to))
|
|
196
|
-
|
|
197
|
-
}
|
|
142
|
+
if (from === to || gatewayAliasHost(from) !== gatewayAliasHost(to))
|
|
143
|
+
fault();
|
|
198
144
|
return [from, to];
|
|
199
145
|
}
|
|
200
146
|
function requireClaudeSelector(options, name) {
|
|
201
147
|
const selector = requireString(options, name);
|
|
202
|
-
if (!isClaudeSessionSelector(selector))
|
|
203
|
-
|
|
204
|
-
}
|
|
148
|
+
if (!isClaudeSessionSelector(selector))
|
|
149
|
+
fault();
|
|
205
150
|
return selector;
|
|
206
151
|
}
|
|
207
152
|
function trackIdleMinutes(options) {
|
|
208
153
|
const tracking = options.track === true;
|
|
209
154
|
const raw = options["idle-minutes"];
|
|
210
|
-
if (raw !== undefined && !tracking)
|
|
211
|
-
|
|
212
|
-
}
|
|
155
|
+
if (raw !== undefined && !tracking)
|
|
156
|
+
fault();
|
|
213
157
|
if (!tracking)
|
|
214
158
|
return undefined;
|
|
215
159
|
if (raw === undefined)
|
|
216
160
|
return PROGRESS_WATCH_DEFAULT_IDLE_MS / 60_000;
|
|
217
|
-
if (typeof raw !== "string" ||
|
|
218
|
-
|
|
219
|
-
throw new CliFault("INVALID_ARGUMENTS");
|
|
220
|
-
}
|
|
161
|
+
if (typeof raw !== "string" || !/^[1-9][0-9]{0,3}$/.test(raw))
|
|
162
|
+
fault();
|
|
221
163
|
const minutes = Number(raw);
|
|
222
|
-
if (!Number.isSafeInteger(minutes) || minutes > 24 * 60)
|
|
223
|
-
|
|
224
|
-
}
|
|
164
|
+
if (!Number.isSafeInteger(minutes) || minutes > 24 * 60)
|
|
165
|
+
fault();
|
|
225
166
|
return minutes;
|
|
226
167
|
}
|
|
227
168
|
function requireDeliveryToken(options, name) {
|
|
228
169
|
const token = requireString(options, name);
|
|
229
|
-
if (!isGatewayDeliveryToken(token))
|
|
230
|
-
|
|
231
|
-
}
|
|
170
|
+
if (!isGatewayDeliveryToken(token))
|
|
171
|
+
fault();
|
|
232
172
|
return token;
|
|
233
173
|
}
|
|
234
174
|
function requireCodexThreadId(env) {
|
|
235
175
|
const threadId = env.CODEX_THREAD_ID;
|
|
236
|
-
if (typeof threadId !== "string" || !THREAD_ID_PATTERN.test(threadId))
|
|
237
|
-
|
|
238
|
-
}
|
|
176
|
+
if (typeof threadId !== "string" || !THREAD_ID_PATTERN.test(threadId))
|
|
177
|
+
fault("CODEX_IDENTITY_REQUIRED");
|
|
239
178
|
return threadId.toLowerCase();
|
|
240
179
|
}
|
|
241
|
-
|
|
242
|
-
return typeof value === "string" && value.length > 0;
|
|
243
|
-
}
|
|
180
|
+
const hasIdentity = (value) => typeof value === "string" && value.length > 0;
|
|
244
181
|
function callerIdentityConflictFault(env) {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
return new CliFault("CALLER_IDENTITY_CONFLICT", false, hasBothIdentities ? "callerIdentityConflict" : undefined);
|
|
182
|
+
const both = hasIdentity(env.CODEX_THREAD_ID) && hasIdentity(env.CLAUDE_CODE_MESSAGING_SOCKET);
|
|
183
|
+
return new CliFault("CALLER_IDENTITY_CONFLICT", false, both ? "callerIdentityConflict" : undefined);
|
|
248
184
|
}
|
|
249
185
|
function requireExclusiveCodexThreadId(env) {
|
|
250
|
-
if (
|
|
186
|
+
if (hasIdentity(env.CLAUDE_CODE_MESSAGING_SOCKET))
|
|
251
187
|
throw callerIdentityConflictFault(env);
|
|
252
|
-
}
|
|
253
188
|
return requireCodexThreadId(env);
|
|
254
189
|
}
|
|
255
190
|
function optionalCodexThreadId(env) {
|
|
256
191
|
const threadId = env.CODEX_THREAD_ID;
|
|
257
192
|
if (threadId === undefined || threadId.length === 0)
|
|
258
193
|
return undefined;
|
|
259
|
-
if (!THREAD_ID_PATTERN.test(threadId))
|
|
260
|
-
|
|
261
|
-
}
|
|
194
|
+
if (!THREAD_ID_PATTERN.test(threadId))
|
|
195
|
+
fault("CODEX_IDENTITY_REQUIRED");
|
|
262
196
|
return threadId.toLowerCase();
|
|
263
197
|
}
|
|
264
198
|
function optionalClaudeReplyAddress(env) {
|
|
265
199
|
const socketPath = env.CLAUDE_CODE_MESSAGING_SOCKET;
|
|
266
200
|
if (socketPath === undefined || socketPath.length === 0)
|
|
267
201
|
return undefined;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
// scheme; form it only in memory and leave generation validation to the
|
|
271
|
-
// broker's pinned Claude adapter.
|
|
272
|
-
if (socketPath.includes("\0") ||
|
|
273
|
-
!path.isAbsolute(socketPath) ||
|
|
274
|
-
path.resolve(socketPath) !== socketPath) {
|
|
275
|
-
throw new CliFault("CLAUDE_IDENTITY_INVALID");
|
|
276
|
-
}
|
|
202
|
+
if (socketPath.includes("\0") || !path.isAbsolute(socketPath) || path.resolve(socketPath) !== socketPath)
|
|
203
|
+
fault("CLAUDE_IDENTITY_INVALID");
|
|
277
204
|
const replyAddress = `uds:${socketPath}`;
|
|
278
|
-
if (!isGatewayReplyAddress(replyAddress))
|
|
279
|
-
|
|
280
|
-
}
|
|
205
|
+
if (!isGatewayReplyAddress(replyAddress))
|
|
206
|
+
fault("CLAUDE_IDENTITY_INVALID");
|
|
281
207
|
return replyAddress;
|
|
282
208
|
}
|
|
283
209
|
function requireClaudeReplyAddress(env) {
|
|
284
210
|
const replyAddress = optionalClaudeReplyAddress(env);
|
|
285
|
-
if (replyAddress === undefined)
|
|
286
|
-
|
|
287
|
-
}
|
|
211
|
+
if (replyAddress === undefined)
|
|
212
|
+
fault("CLAUDE_IDENTITY_REQUIRED");
|
|
288
213
|
return replyAddress;
|
|
289
214
|
}
|
|
290
215
|
function requireExclusiveClaudeReplyAddress(env) {
|
|
291
|
-
if (
|
|
216
|
+
if (hasIdentity(env.CODEX_THREAD_ID))
|
|
292
217
|
throw callerIdentityConflictFault(env);
|
|
293
|
-
}
|
|
294
218
|
return requireClaudeReplyAddress(env);
|
|
295
219
|
}
|
|
296
220
|
async function readMessageBody(stdin) {
|
|
297
221
|
const chunks = [];
|
|
298
222
|
let length = 0;
|
|
299
223
|
for await (const chunk of stdin) {
|
|
300
|
-
if (typeof chunk !== "string" && !Buffer.isBuffer(chunk))
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
const buffer = Buffer.isBuffer(chunk)
|
|
304
|
-
? Buffer.from(chunk)
|
|
305
|
-
: Buffer.from(chunk, "utf8");
|
|
224
|
+
if (typeof chunk !== "string" && !Buffer.isBuffer(chunk))
|
|
225
|
+
fault("INVALID_MESSAGE_INPUT");
|
|
226
|
+
const buffer = Buffer.from(chunk);
|
|
306
227
|
length += buffer.length;
|
|
307
|
-
if (length > GATEWAY_CONTROL_MAX_MESSAGE_BYTES)
|
|
228
|
+
if (length > GATEWAY_CONTROL_MAX_MESSAGE_BYTES)
|
|
308
229
|
throw new CliFault("MESSAGE_TOO_LARGE", false, "hint.messageTooLarge");
|
|
309
|
-
}
|
|
310
230
|
chunks.push(buffer);
|
|
311
231
|
}
|
|
312
232
|
let text;
|
|
@@ -314,640 +234,422 @@ async function readMessageBody(stdin) {
|
|
|
314
234
|
text = new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks, length));
|
|
315
235
|
}
|
|
316
236
|
catch {
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
if (text.trim().length === 0 || text.includes("\0")) {
|
|
320
|
-
throw new CliFault("MESSAGE_REQUIRED");
|
|
237
|
+
fault("INVALID_MESSAGE_INPUT");
|
|
321
238
|
}
|
|
239
|
+
if (text.trim().length === 0 || text.includes("\0"))
|
|
240
|
+
fault("MESSAGE_REQUIRED");
|
|
322
241
|
return text;
|
|
323
242
|
}
|
|
324
|
-
|
|
325
|
-
if (args.length !== 0)
|
|
326
|
-
throw new CliFault("INVALID_ARGUMENTS");
|
|
327
|
-
return {};
|
|
328
|
-
}
|
|
243
|
+
const emptyParams = (args) => args.length === 0 ? {} : fault();
|
|
329
244
|
function parseServeInboundMode(args) {
|
|
330
245
|
const options = parseOptions(args, ["inbound"]);
|
|
331
246
|
if (Object.keys(options).length === 0)
|
|
332
247
|
return "paired";
|
|
333
|
-
|
|
248
|
+
count(options, 1);
|
|
334
249
|
if (options.inbound !== "open")
|
|
335
|
-
|
|
250
|
+
fault();
|
|
336
251
|
return "open";
|
|
337
252
|
}
|
|
338
253
|
function parseLiveDashboardPort(value) {
|
|
339
254
|
if (value === undefined)
|
|
340
255
|
return DEFAULT_LIVE_DASHBOARD_PORT;
|
|
341
|
-
if (typeof value !== "string" || !/^[0-9]+$/.test(value))
|
|
342
|
-
|
|
343
|
-
}
|
|
256
|
+
if (typeof value !== "string" || !/^[0-9]+$/.test(value))
|
|
257
|
+
fault();
|
|
344
258
|
const port = Number(value);
|
|
345
|
-
if (!Number.isSafeInteger(port) || port < 1_024 || port > 65_535)
|
|
346
|
-
|
|
347
|
-
}
|
|
259
|
+
if (!Number.isSafeInteger(port) || port < 1_024 || port > 65_535)
|
|
260
|
+
fault();
|
|
348
261
|
return port;
|
|
349
262
|
}
|
|
350
263
|
function parseLiveDashboardArgs(args) {
|
|
351
|
-
if (args.length === 0)
|
|
264
|
+
if (args.length === 0)
|
|
352
265
|
throw new CliFault("INVALID_ARGUMENTS", false, "hint.dashboardLiveRequired");
|
|
353
|
-
}
|
|
354
266
|
const options = parseOptions(args, ["port"], ["live"]);
|
|
355
|
-
|
|
267
|
+
count(options, 1, 2);
|
|
356
268
|
if (options.live !== true)
|
|
357
|
-
|
|
269
|
+
fault();
|
|
358
270
|
return parseLiveDashboardPort(options.port);
|
|
359
271
|
}
|
|
272
|
+
const envelope = (method, params) => ({ protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION, method, params });
|
|
360
273
|
async function buildRequest(command, args, env, stdin) {
|
|
274
|
+
const simple = {
|
|
275
|
+
health: "health", status: "list_snapshot", doctor: "list_snapshot",
|
|
276
|
+
"refresh-dashboard": "refresh_dashboard",
|
|
277
|
+
};
|
|
278
|
+
const simpleMethod = simple[command];
|
|
279
|
+
if (simpleMethod !== undefined)
|
|
280
|
+
return envelope(simpleMethod, emptyParams(args));
|
|
361
281
|
switch (command) {
|
|
362
282
|
case "serve":
|
|
363
283
|
case "dashboard":
|
|
364
|
-
|
|
284
|
+
case "convert-state-v2-to-v3":
|
|
285
|
+
return fault();
|
|
365
286
|
case "health":
|
|
366
|
-
return {
|
|
367
|
-
protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
|
|
368
|
-
method: "health",
|
|
369
|
-
params: emptyParams(args),
|
|
370
|
-
};
|
|
371
287
|
case "status":
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
method: "list_snapshot",
|
|
375
|
-
params: emptyParams(args),
|
|
376
|
-
};
|
|
288
|
+
case "doctor":
|
|
289
|
+
case "refresh-dashboard": return fault();
|
|
377
290
|
case "delivery-status":
|
|
378
291
|
case "wait-delivery": {
|
|
379
292
|
const options = parseOptions(args, ["token"]);
|
|
380
|
-
|
|
381
|
-
return {
|
|
382
|
-
protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
|
|
383
|
-
method: "delivery_status",
|
|
384
|
-
params: { token: requireDeliveryToken(options, "token") },
|
|
385
|
-
};
|
|
293
|
+
count(options, 1);
|
|
294
|
+
return envelope("delivery_status", { token: requireDeliveryToken(options, "token") });
|
|
386
295
|
}
|
|
387
296
|
case "untrack": {
|
|
388
297
|
const options = parseOptions(args, ["conversation"]);
|
|
389
|
-
|
|
298
|
+
count(options, 1);
|
|
390
299
|
const conversationId = requireString(options, "conversation");
|
|
391
|
-
if (!isGatewayConversationId(conversationId))
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
return {
|
|
395
|
-
protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
|
|
396
|
-
method: "untrack",
|
|
397
|
-
params: { conversationId },
|
|
398
|
-
};
|
|
300
|
+
if (!isGatewayConversationId(conversationId))
|
|
301
|
+
fault();
|
|
302
|
+
return envelope("untrack", { conversationId });
|
|
399
303
|
}
|
|
400
|
-
case "refresh-dashboard":
|
|
401
|
-
return {
|
|
402
|
-
protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
|
|
403
|
-
method: "refresh_dashboard",
|
|
404
|
-
params: emptyParams(args),
|
|
405
|
-
};
|
|
406
304
|
case "register-codex": {
|
|
407
305
|
const options = parseOptions(args, ["alias", "host", "succeeds"]);
|
|
408
306
|
const alias = requireCodexAlias(options, "alias");
|
|
409
|
-
const succeedsAlias = options.succeeds === undefined
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
}
|
|
415
|
-
else {
|
|
416
|
-
assertExactOptionCount(options, 2);
|
|
417
|
-
if (options.host !== undefined) {
|
|
418
|
-
throw new CliFault("INVALID_ARGUMENTS");
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
const host = succeedsAlias === undefined
|
|
422
|
-
? (options.host ?? DEFAULT_HOST_ID)
|
|
423
|
-
: gatewayAliasHost(alias);
|
|
307
|
+
const succeedsAlias = options.succeeds === undefined ? undefined : requireCodexAlias(options, "succeeds");
|
|
308
|
+
count(options, succeedsAlias === undefined ? 1 : 2, 2);
|
|
309
|
+
if (succeedsAlias !== undefined && options.host !== undefined)
|
|
310
|
+
fault();
|
|
311
|
+
const host = succeedsAlias === undefined ? (options.host ?? DEFAULT_HOST_ID) : gatewayAliasHost(alias);
|
|
424
312
|
if (typeof host !== "string" ||
|
|
425
313
|
!isGatewayHostId(host) ||
|
|
426
314
|
!alias.endsWith(`@${host}`) ||
|
|
427
|
-
(succeedsAlias !== undefined &&
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
|
|
434
|
-
method: "register_codex",
|
|
435
|
-
params: {
|
|
436
|
-
alias,
|
|
437
|
-
threadId: requireExclusiveCodexThreadId(env),
|
|
438
|
-
hostId: host,
|
|
439
|
-
busyPolicy: "queue",
|
|
440
|
-
...(succeedsAlias === undefined ? {} : { succeedsAlias }),
|
|
441
|
-
},
|
|
442
|
-
};
|
|
315
|
+
(succeedsAlias !== undefined && (succeedsAlias === alias || gatewayAliasHost(succeedsAlias) !== host)))
|
|
316
|
+
fault();
|
|
317
|
+
return envelope("register_codex", {
|
|
318
|
+
alias, threadId: requireExclusiveCodexThreadId(env), hostId: host, busyPolicy: "queue",
|
|
319
|
+
...(succeedsAlias === undefined ? {} : { succeedsAlias }),
|
|
320
|
+
});
|
|
443
321
|
}
|
|
444
322
|
case "unregister-codex": {
|
|
445
323
|
const options = parseOptions(args, ["alias"]);
|
|
446
|
-
|
|
447
|
-
return {
|
|
448
|
-
protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
|
|
449
|
-
method: "unregister_codex",
|
|
450
|
-
params: {
|
|
451
|
-
alias: requireCodexAlias(options, "alias"),
|
|
452
|
-
threadId: requireExclusiveCodexThreadId(env),
|
|
453
|
-
},
|
|
454
|
-
};
|
|
324
|
+
count(options, 1);
|
|
325
|
+
return envelope("unregister_codex", { alias: requireCodexAlias(options, "alias"), threadId: requireExclusiveCodexThreadId(env) });
|
|
455
326
|
}
|
|
456
327
|
case "select-claude":
|
|
457
328
|
case "unselect-claude": {
|
|
458
329
|
const options = parseOptions(args, ["alias", "session"]);
|
|
459
|
-
|
|
460
|
-
const selector = options.alias === undefined
|
|
461
|
-
? requireClaudeSelector(options, "session")
|
|
462
|
-
: requireClaudeSelector(options, "alias");
|
|
330
|
+
count(options, 1);
|
|
331
|
+
const selector = requireClaudeSelector(options, options.alias === undefined ? "session" : "alias");
|
|
463
332
|
const codexThreadId = optionalCodexThreadId(env);
|
|
464
|
-
return {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
params: {
|
|
468
|
-
alias: selector,
|
|
469
|
-
...(codexThreadId === undefined ? {} : { codexThreadId }),
|
|
470
|
-
},
|
|
471
|
-
};
|
|
333
|
+
return envelope(command === "select-claude" ? "select_claude" : "unselect_claude", {
|
|
334
|
+
alias: selector, ...(codexThreadId === undefined ? {} : { codexThreadId }),
|
|
335
|
+
});
|
|
472
336
|
}
|
|
473
337
|
case "pair":
|
|
474
338
|
case "unpair": {
|
|
475
339
|
const options = parseOptions(args, ["claude", "codex", "from", "to"]);
|
|
476
|
-
|
|
340
|
+
count(options, 2);
|
|
477
341
|
const legacyArm = options.claude !== undefined || options.codex !== undefined;
|
|
478
342
|
if (legacyArm) {
|
|
479
|
-
if (options.from !== undefined || options.to !== undefined)
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
params: {
|
|
486
|
-
claudeAlias: requireClaudeSelector(options, "claude"),
|
|
487
|
-
codexAlias: requireCodexAlias(options, "codex"),
|
|
488
|
-
codexThreadId: requireExclusiveCodexThreadId(env),
|
|
489
|
-
},
|
|
490
|
-
};
|
|
343
|
+
if (options.from !== undefined || options.to !== undefined)
|
|
344
|
+
fault();
|
|
345
|
+
return envelope(command, {
|
|
346
|
+
claudeAlias: requireClaudeSelector(options, "claude"), codexAlias: requireCodexAlias(options, "codex"),
|
|
347
|
+
codexThreadId: requireExclusiveCodexThreadId(env),
|
|
348
|
+
});
|
|
491
349
|
}
|
|
492
|
-
return {
|
|
493
|
-
protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
|
|
494
|
-
method: command,
|
|
495
|
-
params: {
|
|
496
|
-
aliases: requirePairAliases(options),
|
|
497
|
-
},
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
case "send-to-claude": {
|
|
501
|
-
const options = parseOptions(args, ["from", "to", "idle-minutes"], ["expects-reply", "track"]);
|
|
502
|
-
assertExactOptionCount(options, 2, 5);
|
|
503
|
-
const threadId = requireExclusiveCodexThreadId(env);
|
|
504
|
-
const idleMinutes = trackIdleMinutes(options);
|
|
505
|
-
return {
|
|
506
|
-
protocolVersion: GATEWAY_CONTROL_PROTOCOL_VERSION,
|
|
507
|
-
method: "send_to_claude",
|
|
508
|
-
params: {
|
|
509
|
-
fromAlias: requireAlias(options, "from"),
|
|
510
|
-
threadId,
|
|
511
|
-
toAlias: requireClaudeSelector(options, "to"),
|
|
512
|
-
text: await readMessageBody(stdin),
|
|
513
|
-
expectsReply: options["expects-reply"] === true,
|
|
514
|
-
...(idleMinutes === undefined
|
|
515
|
-
? {}
|
|
516
|
-
: { trackIdleMinutes: idleMinutes }),
|
|
517
|
-
},
|
|
518
|
-
};
|
|
350
|
+
return envelope(command, { aliases: requirePairAliases(options) });
|
|
519
351
|
}
|
|
352
|
+
case "send-to-claude":
|
|
520
353
|
case "send-to-codex": {
|
|
521
354
|
const options = parseOptions(args, ["from", "to", "idle-minutes"], ["expects-reply", "track"]);
|
|
522
|
-
|
|
523
|
-
const
|
|
355
|
+
count(options, 2, 5);
|
|
356
|
+
const authority = command === "send-to-claude"
|
|
357
|
+
? requireExclusiveCodexThreadId(env) : requireExclusiveClaudeReplyAddress(env);
|
|
524
358
|
const idleMinutes = trackIdleMinutes(options);
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
text: await readMessageBody(stdin),
|
|
532
|
-
replyAddress,
|
|
533
|
-
expectsReply: options["expects-reply"] === true,
|
|
534
|
-
...(idleMinutes === undefined
|
|
535
|
-
? {}
|
|
536
|
-
: { trackIdleMinutes: idleMinutes }),
|
|
537
|
-
},
|
|
359
|
+
const common = {
|
|
360
|
+
fromAlias: requireAlias(options, "from"),
|
|
361
|
+
toAlias: command === "send-to-claude" ? requireClaudeSelector(options, "to") : requireAlias(options, "to"),
|
|
362
|
+
text: await readMessageBody(stdin),
|
|
363
|
+
expectsReply: options["expects-reply"] === true,
|
|
364
|
+
...(idleMinutes === undefined ? {} : { trackIdleMinutes: idleMinutes }),
|
|
538
365
|
};
|
|
366
|
+
return command === "send-to-claude"
|
|
367
|
+
? envelope("send_to_claude", { ...common, threadId: authority })
|
|
368
|
+
: envelope("send_to_codex", { ...common, replyAddress: authority });
|
|
539
369
|
}
|
|
540
370
|
case "reply": {
|
|
541
371
|
const options = parseOptions(args, ["conversation", "alias", "idle-minutes"], ["track"]);
|
|
542
|
-
|
|
372
|
+
count(options, 2, 4);
|
|
543
373
|
const conversationId = requireString(options, "conversation");
|
|
544
|
-
if (!isGatewayConversationId(conversationId))
|
|
545
|
-
|
|
546
|
-
}
|
|
374
|
+
if (!isGatewayConversationId(conversationId))
|
|
375
|
+
fault();
|
|
547
376
|
const alias = requireAlias(options, "alias");
|
|
548
377
|
const idleMinutes = trackIdleMinutes(options);
|
|
549
378
|
const threadId = env.CODEX_THREAD_ID;
|
|
550
|
-
if (
|
|
551
|
-
hasInheritedIdentity(env.CLAUDE_CODE_MESSAGING_SOCKET)) {
|
|
379
|
+
if (hasIdentity(threadId) && hasIdentity(env.CLAUDE_CODE_MESSAGING_SOCKET))
|
|
552
380
|
throw callerIdentityConflictFault(env);
|
|
553
|
-
}
|
|
554
381
|
const replyAddress = optionalClaudeReplyAddress(env);
|
|
555
|
-
const
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
text: await readMessageBody(stdin),
|
|
566
|
-
...(idleMinutes === undefined
|
|
567
|
-
? {}
|
|
568
|
-
: { trackIdleMinutes: idleMinutes }),
|
|
569
|
-
caller: hasCodexIdentity
|
|
570
|
-
? {
|
|
571
|
-
kind: "codex",
|
|
572
|
-
alias,
|
|
573
|
-
threadId: requireCodexThreadId(env),
|
|
574
|
-
}
|
|
575
|
-
: {
|
|
576
|
-
kind: "claude",
|
|
577
|
-
alias,
|
|
578
|
-
replyAddress: requireClaudeReplyAddress(env),
|
|
579
|
-
},
|
|
580
|
-
},
|
|
581
|
-
};
|
|
382
|
+
const codex = hasIdentity(threadId);
|
|
383
|
+
if (!codex && replyAddress === undefined)
|
|
384
|
+
fault("CALLER_IDENTITY_REQUIRED");
|
|
385
|
+
return envelope("reply", {
|
|
386
|
+
conversationId, text: await readMessageBody(stdin),
|
|
387
|
+
...(idleMinutes === undefined ? {} : { trackIdleMinutes: idleMinutes }),
|
|
388
|
+
caller: codex
|
|
389
|
+
? { kind: "codex", alias, threadId: requireCodexThreadId(env) }
|
|
390
|
+
: { kind: "claude", alias, replyAddress: requireClaudeReplyAddress(env) },
|
|
391
|
+
});
|
|
582
392
|
}
|
|
583
393
|
}
|
|
584
394
|
}
|
|
585
395
|
export async function validatePrivateGatewayControlSocket(stateDir, socketPath) {
|
|
586
|
-
if (process.platform === "win32")
|
|
396
|
+
if (process.platform === "win32")
|
|
587
397
|
throw new CliFault("CONTROL_SOCKET_UNAVAILABLE", true);
|
|
588
|
-
|
|
589
|
-
let state;
|
|
590
|
-
let socket;
|
|
398
|
+
let state, socket;
|
|
591
399
|
try {
|
|
592
400
|
[state, socket] = await Promise.all([lstat(stateDir), lstat(socketPath)]);
|
|
593
401
|
}
|
|
594
402
|
catch {
|
|
595
403
|
throw new CliFault("CONTROL_SOCKET_UNAVAILABLE", true);
|
|
596
404
|
}
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
if (state.isSymbolicLink() ||
|
|
600
|
-
!state.isDirectory() ||
|
|
601
|
-
(state.mode & 0o700) !== 0o700 ||
|
|
602
|
-
(state.mode & 0o077) !== 0 ||
|
|
603
|
-
(expectedUid !== undefined && state.uid !== expectedUid)) {
|
|
405
|
+
const uid = process.getuid?.();
|
|
406
|
+
if (state.isSymbolicLink() || !state.isDirectory() || (state.mode & 0o777) !== 0o700 || (uid !== undefined && state.uid !== uid)) {
|
|
604
407
|
throw new CliFault("CONTROL_STATE_UNSAFE", false, undefined, "unsafe");
|
|
605
408
|
}
|
|
606
|
-
if (socket.isSymbolicLink() ||
|
|
607
|
-
!socket.isSocket() ||
|
|
608
|
-
(socket.mode & 0o777) !== 0o600 ||
|
|
609
|
-
(expectedUid !== undefined && socket.uid !== expectedUid)) {
|
|
409
|
+
if (socket.isSymbolicLink() || !socket.isSocket() || (socket.mode & 0o777) !== 0o600 || (uid !== undefined && socket.uid !== uid)) {
|
|
610
410
|
throw new CliFault("CONTROL_SOCKET_UNSAFE", false, undefined, "unsafe");
|
|
611
411
|
}
|
|
612
|
-
let stateReal;
|
|
613
|
-
let socketParentReal;
|
|
412
|
+
let stateReal, parentReal;
|
|
614
413
|
try {
|
|
615
|
-
[stateReal,
|
|
616
|
-
realpath(stateDir),
|
|
617
|
-
realpath(path.dirname(socketPath)),
|
|
618
|
-
]);
|
|
414
|
+
[stateReal, parentReal] = await Promise.all([realpath(stateDir), realpath(path.dirname(socketPath))]);
|
|
619
415
|
}
|
|
620
416
|
catch {
|
|
621
417
|
throw new CliFault("CONTROL_SOCKET_UNSAFE", false, undefined, "unsafe");
|
|
622
418
|
}
|
|
623
|
-
if (path.dirname(socketPath) !== stateDir ||
|
|
624
|
-
stateReal !== socketParentReal) {
|
|
419
|
+
if (path.dirname(socketPath) !== stateDir || stateReal !== parentReal) {
|
|
625
420
|
throw new CliFault("CONTROL_SOCKET_UNSAFE", false, undefined, "unsafe");
|
|
626
421
|
}
|
|
627
422
|
}
|
|
628
|
-
function safeCommandLabel(command) {
|
|
629
|
-
return command ?? "unknown";
|
|
630
|
-
}
|
|
631
423
|
function serializedOutput(value) {
|
|
632
424
|
const line = `${JSON.stringify(value)}\n`;
|
|
633
|
-
if (Buffer.byteLength(line, "utf8") > CLI_MAX_OUTPUT_BYTES)
|
|
634
|
-
|
|
635
|
-
}
|
|
425
|
+
if (Buffer.byteLength(line, "utf8") > CLI_MAX_OUTPUT_BYTES)
|
|
426
|
+
fault("OUTPUT_TOO_LARGE");
|
|
636
427
|
return line;
|
|
637
428
|
}
|
|
638
429
|
function writeFailure(stdout, stderr, locale, command, code, options) {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
ok: false,
|
|
643
|
-
command: safeCommandLabel(command),
|
|
644
|
-
error: { code, ambiguous, retryable },
|
|
645
|
-
}));
|
|
430
|
+
stdout.write(serializedOutput({ ok: false, command: command ?? "unknown", error: {
|
|
431
|
+
code, ambiguous: options.ambiguous ?? false, retryable: options.retryable ?? false,
|
|
432
|
+
} }));
|
|
646
433
|
stderr.write(fixedStderr(locale, options.kind));
|
|
647
434
|
}
|
|
648
435
|
function isRejectedResult(result) {
|
|
649
|
-
return
|
|
650
|
-
typeof result === "object" &&
|
|
651
|
-
Object.hasOwn(result, "accepted") &&
|
|
652
|
-
result.accepted === false);
|
|
436
|
+
return result !== null && typeof result === "object" && result.accepted === false;
|
|
653
437
|
}
|
|
654
438
|
function isProgressWatchOwnerConflict(result) {
|
|
655
|
-
return (result
|
|
656
|
-
typeof result === "object" &&
|
|
657
|
-
Object.hasOwn(result, "accepted") &&
|
|
658
|
-
Object.hasOwn(result, "code") &&
|
|
659
|
-
result.accepted === false &&
|
|
660
|
-
result.code === "watch_owner_conflict");
|
|
439
|
+
return isRejectedResult(result) && result.code === "watch_owner_conflict";
|
|
661
440
|
}
|
|
662
441
|
function responseExitCode(response) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
442
|
+
return !response.ok ? gatewayCliExitCodes.failure
|
|
443
|
+
: isRejectedResult(response.result) ? gatewayCliExitCodes.rejected : gatewayCliExitCodes.ok;
|
|
444
|
+
}
|
|
445
|
+
function codexDoctorConditions(result) {
|
|
446
|
+
const connectors = result !== null && typeof result === "object"
|
|
447
|
+
? result.connectors : undefined;
|
|
448
|
+
const connector = Array.isArray(connectors) ? connectors.find((row) => row !== null && typeof row === "object" && row.provider === "codex") : undefined;
|
|
449
|
+
const doctor = connector !== null && typeof connector === "object"
|
|
450
|
+
? connector.codexDoctor : undefined;
|
|
451
|
+
const conditions = doctor !== null && typeof doctor === "object"
|
|
452
|
+
? doctor.conditions : undefined;
|
|
453
|
+
return Array.isArray(conditions)
|
|
454
|
+
? conditions.filter((value) => typeof value === "string") : ["unknown"];
|
|
668
455
|
}
|
|
669
456
|
function waitDeliveryExitCode(response) {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
!response.result.terminal) {
|
|
673
|
-
return gatewayCliExitCodes.failure;
|
|
674
|
-
}
|
|
675
|
-
return response.result.state === "delivered"
|
|
676
|
-
? gatewayCliExitCodes.ok
|
|
677
|
-
: gatewayCliExitCodes.failure;
|
|
678
|
-
}
|
|
679
|
-
async function defaultDelay(milliseconds) {
|
|
680
|
-
await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
457
|
+
return response.ok && response.result.found && response.result.terminal && response.result.state === "delivered"
|
|
458
|
+
? gatewayCliExitCodes.ok : gatewayCliExitCodes.failure;
|
|
681
459
|
}
|
|
460
|
+
const defaultDelay = async (milliseconds) => await new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
682
461
|
async function waitForDelivery(socketPath, request, sendRequest, now, delay) {
|
|
683
|
-
let
|
|
462
|
+
let deadline;
|
|
684
463
|
while (true) {
|
|
685
|
-
const
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
if (remainingMs < DELIVERY_POLL_MIN_REQUEST_TIMEOUT_MS) {
|
|
692
|
-
await delay(remainingMs);
|
|
693
|
-
return { kind: "timeout" };
|
|
694
|
-
}
|
|
695
|
-
requestTimeoutMs = Math.min(GATEWAY_CONTROL_DEFAULT_TIMEOUT_MS, Math.floor(remainingMs));
|
|
464
|
+
const remaining = deadline === undefined ? undefined : deadline - now();
|
|
465
|
+
if (remaining !== undefined && remaining <= 0)
|
|
466
|
+
return { kind: "timeout" };
|
|
467
|
+
if (remaining !== undefined && remaining < DELIVERY_POLL_MIN_REQUEST_TIMEOUT_MS) {
|
|
468
|
+
await delay(remaining);
|
|
469
|
+
return { kind: "timeout" };
|
|
696
470
|
}
|
|
697
471
|
let response;
|
|
698
472
|
try {
|
|
699
473
|
response = await sendRequest({
|
|
700
|
-
socketPath,
|
|
701
|
-
|
|
702
|
-
|
|
474
|
+
socketPath, request,
|
|
475
|
+
timeoutMs: remaining === undefined ? GATEWAY_CONTROL_DEFAULT_TIMEOUT_MS
|
|
476
|
+
: Math.min(GATEWAY_CONTROL_DEFAULT_TIMEOUT_MS, Math.floor(remaining)),
|
|
703
477
|
});
|
|
704
478
|
}
|
|
705
479
|
catch (error) {
|
|
706
|
-
if (
|
|
707
|
-
now() >= clientDeadlineMs &&
|
|
708
|
-
error instanceof GatewayControlTransportError &&
|
|
709
|
-
error.recoverable) {
|
|
480
|
+
if (deadline !== undefined && now() >= deadline && error instanceof GatewayControlTransportError && error.recoverable)
|
|
710
481
|
return { kind: "timeout" };
|
|
711
|
-
}
|
|
712
482
|
throw error;
|
|
713
483
|
}
|
|
714
484
|
if (!response.ok) {
|
|
715
|
-
if (response.error.code === "REQUEST_TIMEOUT" &&
|
|
716
|
-
clientDeadlineMs !== undefined &&
|
|
717
|
-
now() >= clientDeadlineMs) {
|
|
485
|
+
if (response.error.code === "REQUEST_TIMEOUT" && deadline !== undefined && now() >= deadline)
|
|
718
486
|
return { kind: "timeout" };
|
|
719
|
-
}
|
|
720
487
|
return { kind: "response", response };
|
|
721
488
|
}
|
|
722
489
|
if (!response.result.found)
|
|
723
490
|
return { kind: "unknown" };
|
|
724
|
-
|
|
725
|
-
// invoked after the original delivery deadline and client grace window.
|
|
726
|
-
if (response.result.terminal) {
|
|
491
|
+
if (response.result.terminal)
|
|
727
492
|
return { kind: "response", response };
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
if (!Number.isFinite(parsedDeadlineMs)) {
|
|
493
|
+
const observed = Date.parse(response.result.deadlineAt) + GATEWAY_CONTROL_DEFAULT_TIMEOUT_MS;
|
|
494
|
+
if (!Number.isFinite(observed))
|
|
731
495
|
throw new Error("invalid validated delivery deadline");
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
clientDeadlineMs === undefined
|
|
736
|
-
? observedClientDeadlineMs
|
|
737
|
-
: Math.min(clientDeadlineMs, observedClientDeadlineMs);
|
|
738
|
-
const afterRequest = now();
|
|
739
|
-
const remainingMs = clientDeadlineMs - afterRequest;
|
|
740
|
-
if (remainingMs <= 0)
|
|
496
|
+
deadline = deadline === undefined ? observed : Math.min(deadline, observed);
|
|
497
|
+
const after = deadline - now();
|
|
498
|
+
if (after <= 0)
|
|
741
499
|
return { kind: "timeout" };
|
|
742
|
-
await delay(Math.min(DELIVERY_POLL_INTERVAL_MS,
|
|
500
|
+
await delay(Math.min(DELIVERY_POLL_INTERVAL_MS, after));
|
|
743
501
|
}
|
|
744
502
|
}
|
|
745
503
|
/** Run one command; foreground runners own and release their signal handlers. */
|
|
746
504
|
export async function runGatewayCli(argv = process.argv.slice(2), dependencies = {}) {
|
|
747
505
|
const env = dependencies.env ?? process.env;
|
|
748
|
-
const stdin = dependencies.stdin ?? process.stdin;
|
|
749
|
-
const
|
|
750
|
-
const stderr = dependencies.stderr ?? process.stderr;
|
|
751
|
-
const loadConfig = dependencies.loadConfig ?? loadGatewayConfig;
|
|
506
|
+
const stdin = dependencies.stdin ?? process.stdin, stdout = dependencies.stdout ?? process.stdout;
|
|
507
|
+
const stderr = dependencies.stderr ?? process.stderr, loadConfig = dependencies.loadConfig ?? loadGatewayConfig;
|
|
752
508
|
const sendRequest = dependencies.sendRequest ?? sendGatewayControlRequest;
|
|
753
|
-
const
|
|
754
|
-
|
|
755
|
-
const runLiveDashboard = dependencies.runLiveDashboard ?? runLiveDashboardCommand;
|
|
756
|
-
const now = dependencies.now ?? Date.now;
|
|
757
|
-
const delay = dependencies.delay ?? defaultDelay;
|
|
758
|
-
if (argv.length === 1 &&
|
|
759
|
-
(argv[0] === "--version" || argv[0] === "-v")) {
|
|
509
|
+
const validateSocket = dependencies.validateControlSocket ?? validatePrivateGatewayControlSocket;
|
|
510
|
+
if (argv.length === 1 && (argv[0] === "--version" || argv[0] === "-v")) {
|
|
760
511
|
stdout.write(`embassy ${EMBASSY_VERSION}\n`);
|
|
761
512
|
return gatewayCliExitCodes.ok;
|
|
762
513
|
}
|
|
763
514
|
const command = isCommand(argv[0]) ? argv[0] : undefined;
|
|
764
515
|
let locale = fallbackCliLocale(argv.slice(1), env);
|
|
765
|
-
let
|
|
766
|
-
let liveDashboardReadyEmitted = false;
|
|
516
|
+
let serverReady = false, dashboardReady = false;
|
|
767
517
|
let liveDashboardPort;
|
|
518
|
+
const success = (result) => {
|
|
519
|
+
stdout.write(serializedOutput({ ok: true, command: command, result }));
|
|
520
|
+
};
|
|
768
521
|
try {
|
|
769
|
-
const common =
|
|
522
|
+
const common = commonOptions(argv.slice(1), env);
|
|
770
523
|
locale = common.locale;
|
|
771
|
-
if (argv.length === 0 ||
|
|
772
|
-
argv[0] === "--help" ||
|
|
773
|
-
argv[0] === "-h") {
|
|
524
|
+
if (argv.length === 0 || argv[0] === "--help" || argv[0] === "-h") {
|
|
774
525
|
emptyParams(common.args);
|
|
775
526
|
stdout.write(getCliCopy(locale)["help.usage"]);
|
|
776
527
|
return gatewayCliExitCodes.ok;
|
|
777
528
|
}
|
|
778
529
|
if (command === undefined)
|
|
779
|
-
|
|
530
|
+
fault("UNKNOWN_COMMAND");
|
|
780
531
|
if (command === "serve") {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
locale,
|
|
785
|
-
inboundMode,
|
|
786
|
-
...(dependencies.serverSignal === undefined
|
|
787
|
-
? {}
|
|
788
|
-
: { signal: dependencies.serverSignal }),
|
|
532
|
+
await (dependencies.runServer ?? runGatewayServer)({
|
|
533
|
+
env, locale, inboundMode: parseServeInboundMode(common.args),
|
|
534
|
+
...(dependencies.serverSignal === undefined ? {} : { signal: dependencies.serverSignal }),
|
|
789
535
|
onReady: async (result) => {
|
|
790
|
-
if (
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
ok: true,
|
|
795
|
-
command,
|
|
796
|
-
result,
|
|
797
|
-
}));
|
|
798
|
-
serverReadyEmitted = true;
|
|
536
|
+
if (serverReady)
|
|
537
|
+
fault("SERVER_READY_ALREADY_EMITTED");
|
|
538
|
+
success(result);
|
|
539
|
+
serverReady = true;
|
|
799
540
|
},
|
|
800
541
|
});
|
|
801
|
-
if (!
|
|
802
|
-
|
|
803
|
-
|
|
542
|
+
if (!serverReady)
|
|
543
|
+
fault("SERVER_NOT_READY");
|
|
544
|
+
return gatewayCliExitCodes.ok;
|
|
545
|
+
}
|
|
546
|
+
if (command === "convert-state-v2-to-v3") {
|
|
547
|
+
emptyParams(common.args);
|
|
548
|
+
const config = loadConfig(env);
|
|
549
|
+
const converted = await (dependencies.convertState ?? convertGatewayStateV2ToV3)({ stateDir: config.stateDir });
|
|
550
|
+
success({ converted: true, backupFile: path.basename(converted.backupFile) });
|
|
804
551
|
return gatewayCliExitCodes.ok;
|
|
805
552
|
}
|
|
806
553
|
if (command === "dashboard") {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
locale,
|
|
812
|
-
port,
|
|
813
|
-
...(dependencies.liveDashboardSignal === undefined
|
|
814
|
-
? {}
|
|
815
|
-
: { signal: dependencies.liveDashboardSignal }),
|
|
816
|
-
loadConfig,
|
|
817
|
-
validateControlSocket,
|
|
818
|
-
sendRequest,
|
|
554
|
+
liveDashboardPort = parseLiveDashboardArgs(common.args);
|
|
555
|
+
const outcome = await (dependencies.runLiveDashboard ?? runLiveDashboardCommand)({
|
|
556
|
+
env, locale, port: liveDashboardPort, loadConfig, sendRequest, validateControlSocket: validateSocket,
|
|
557
|
+
...(dependencies.liveDashboardSignal === undefined ? {} : { signal: dependencies.liveDashboardSignal }),
|
|
819
558
|
onReady: async (result) => {
|
|
820
|
-
if (
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
ok: true,
|
|
825
|
-
command,
|
|
826
|
-
result,
|
|
827
|
-
}));
|
|
828
|
-
liveDashboardReadyEmitted = true;
|
|
559
|
+
if (dashboardReady)
|
|
560
|
+
fault("LIVE_DASHBOARD_READY_ALREADY_EMITTED");
|
|
561
|
+
success(result);
|
|
562
|
+
dashboardReady = true;
|
|
829
563
|
},
|
|
830
564
|
});
|
|
831
|
-
if (!
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
}
|
|
565
|
+
if (!dashboardReady && outcome?.status === "cancelled")
|
|
566
|
+
return gatewayCliExitCodes.ok;
|
|
567
|
+
if (!dashboardReady)
|
|
835
568
|
throw new Error("LIVE_DASHBOARD_NOT_READY");
|
|
836
|
-
}
|
|
837
569
|
return gatewayCliExitCodes.ok;
|
|
838
570
|
}
|
|
839
571
|
const request = await buildRequest(command, common.args, env, stdin);
|
|
840
572
|
const config = loadConfig(env);
|
|
841
|
-
await
|
|
573
|
+
await validateSocket(config.stateDir, config.controlSocketPath);
|
|
842
574
|
let response;
|
|
843
|
-
let
|
|
575
|
+
let waited;
|
|
844
576
|
if (command === "wait-delivery") {
|
|
845
|
-
if (request.method !== "delivery_status")
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
const outcome = await waitForDelivery(config.controlSocketPath, request, sendRequest, now, delay);
|
|
577
|
+
if (request.method !== "delivery_status")
|
|
578
|
+
fault();
|
|
579
|
+
const outcome = await waitForDelivery(config.controlSocketPath, request, sendRequest, dependencies.now ?? Date.now, dependencies.delay ?? defaultDelay);
|
|
849
580
|
if (outcome.kind === "unknown") {
|
|
850
|
-
writeFailure(stdout, stderr, locale, command, "DELIVERY_TOKEN_UNKNOWN", {
|
|
851
|
-
kind: "tokenUnknown",
|
|
852
|
-
});
|
|
581
|
+
writeFailure(stdout, stderr, locale, command, "DELIVERY_TOKEN_UNKNOWN", { kind: "tokenUnknown" });
|
|
853
582
|
return gatewayCliExitCodes.rejected;
|
|
854
583
|
}
|
|
855
584
|
if (outcome.kind === "timeout") {
|
|
856
|
-
writeFailure(stdout, stderr, locale, command, "DELIVERY_WAIT_TIMEOUT", {
|
|
857
|
-
retryable: true,
|
|
858
|
-
kind: "deliveryTimeout",
|
|
859
|
-
});
|
|
585
|
+
writeFailure(stdout, stderr, locale, command, "DELIVERY_WAIT_TIMEOUT", { retryable: true, kind: "deliveryTimeout" });
|
|
860
586
|
return gatewayCliExitCodes.unavailable;
|
|
861
587
|
}
|
|
862
|
-
|
|
863
|
-
response =
|
|
588
|
+
waited = outcome.response;
|
|
589
|
+
response = waited;
|
|
864
590
|
}
|
|
865
591
|
else {
|
|
866
|
-
response = await sendRequest({
|
|
867
|
-
socketPath: config.controlSocketPath,
|
|
868
|
-
request,
|
|
869
|
-
});
|
|
592
|
+
response = await sendRequest({ socketPath: config.controlSocketPath, request });
|
|
870
593
|
}
|
|
871
594
|
if (!response.ok) {
|
|
872
|
-
writeFailure(stdout, stderr, locale, command, response.error.code, {
|
|
873
|
-
kind: "failure",
|
|
874
|
-
});
|
|
595
|
+
writeFailure(stdout, stderr, locale, command, response.error.code, { kind: "failure" });
|
|
875
596
|
return gatewayCliExitCodes.failure;
|
|
876
597
|
}
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
command,
|
|
880
|
-
result: response.result,
|
|
881
|
-
}));
|
|
882
|
-
const exitCode = waitedDeliveryResponse === undefined
|
|
883
|
-
? responseExitCode(response)
|
|
884
|
-
: waitDeliveryExitCode(waitedDeliveryResponse);
|
|
598
|
+
success(command === "doctor" ? { conditions: codexDoctorConditions(response.result) } : response.result);
|
|
599
|
+
const exitCode = waited === undefined ? responseExitCode(response) : waitDeliveryExitCode(waited);
|
|
885
600
|
if (exitCode === gatewayCliExitCodes.rejected) {
|
|
886
601
|
stderr.write(fixedStderr(locale, "decision"));
|
|
887
602
|
if (isProgressWatchOwnerConflict(response.result)) {
|
|
888
603
|
stderr.write(`[embassy] ${getCliCopy(locale)["hint.progressWatchOwnerConflict"]}\n`);
|
|
889
604
|
}
|
|
890
605
|
}
|
|
891
|
-
else if (command === "wait-delivery" &&
|
|
892
|
-
exitCode === gatewayCliExitCodes.failure) {
|
|
606
|
+
else if (command === "wait-delivery" && exitCode === gatewayCliExitCodes.failure)
|
|
893
607
|
stderr.write(fixedStderr(locale, "failure"));
|
|
894
|
-
}
|
|
895
608
|
return exitCode;
|
|
896
609
|
}
|
|
897
610
|
catch (error) {
|
|
898
|
-
if ((command === "serve" &&
|
|
899
|
-
(command === "dashboard" && liveDashboardReadyEmitted)) {
|
|
611
|
+
if ((command === "serve" && serverReady) || (command === "dashboard" && dashboardReady)) {
|
|
900
612
|
stderr.write(fixedStderr(locale, "failure"));
|
|
901
613
|
return gatewayCliExitCodes.failure;
|
|
902
614
|
}
|
|
903
615
|
if (error instanceof GatewayControlTransportError) {
|
|
904
616
|
const ambiguous = error.ambiguous;
|
|
905
617
|
writeFailure(stdout, stderr, locale, command, error.code, {
|
|
906
|
-
ambiguous,
|
|
907
|
-
retryable: ambiguous ? false : error.recoverable,
|
|
618
|
+
ambiguous, retryable: ambiguous ? false : error.recoverable,
|
|
908
619
|
kind: ambiguous ? "ambiguous" : "unavailable",
|
|
909
620
|
});
|
|
910
621
|
if (error.code === "CONTROL_INVALID_RESPONSE") {
|
|
911
622
|
stderr.write(`[embassy] ${getCliCopy(locale)["hint.controlInvalidResponse"]}\n`);
|
|
912
623
|
}
|
|
913
|
-
return ambiguous
|
|
914
|
-
? gatewayCliExitCodes.ambiguous
|
|
915
|
-
: gatewayCliExitCodes.unavailable;
|
|
624
|
+
return ambiguous ? gatewayCliExitCodes.ambiguous : gatewayCliExitCodes.unavailable;
|
|
916
625
|
}
|
|
917
626
|
if (error instanceof CliFault) {
|
|
918
627
|
writeFailure(stdout, stderr, locale, command, error.code, {
|
|
919
|
-
retryable: error.retryable,
|
|
920
|
-
kind: error.kind ?? (error.retryable ? "unavailable" : "input"),
|
|
628
|
+
retryable: error.retryable, kind: error.kind ?? (error.retryable ? "unavailable" : "input"),
|
|
921
629
|
});
|
|
922
630
|
if (error.hint !== undefined) {
|
|
923
631
|
const hint = error.hint === "callerIdentityConflict"
|
|
924
|
-
? locale === "zh-CN"
|
|
925
|
-
? callerIdentityConflictHintZhCn
|
|
926
|
-
: callerIdentityConflictHintEn
|
|
632
|
+
? locale === "zh-CN" ? callerIdentityConflictHintZhCn : callerIdentityConflictHintEn
|
|
927
633
|
: getCliCopy(locale)[error.hint];
|
|
928
634
|
stderr.write(`[embassy] ${hint}\n`);
|
|
929
635
|
}
|
|
930
|
-
return error.retryable
|
|
931
|
-
? gatewayCliExitCodes.unavailable
|
|
932
|
-
: gatewayCliExitCodes.invalidInput;
|
|
636
|
+
return error.retryable ? gatewayCliExitCodes.unavailable : gatewayCliExitCodes.invalidInput;
|
|
933
637
|
}
|
|
934
638
|
if (error instanceof BridgeError) {
|
|
639
|
+
if (error.code === "GATEWAY_STATE_COMMIT_OUTCOME_UNKNOWN") {
|
|
640
|
+
writeFailure(stdout, stderr, locale, command, error.code, { ambiguous: true, kind: "ambiguous" });
|
|
641
|
+
return gatewayCliExitCodes.ambiguous;
|
|
642
|
+
}
|
|
935
643
|
writeFailure(stdout, stderr, locale, command, error.code, {
|
|
936
|
-
retryable: error.recoverable,
|
|
937
|
-
kind: error.recoverable ? "unavailable" : "input",
|
|
644
|
+
retryable: error.recoverable, kind: error.recoverable ? "unavailable" : "input",
|
|
938
645
|
});
|
|
939
|
-
if (error.code === "LIVE_DASHBOARD_PORT_IN_USE" &&
|
|
940
|
-
liveDashboardPort !== undefined) {
|
|
646
|
+
if (error.code === "LIVE_DASHBOARD_PORT_IN_USE" && liveDashboardPort !== undefined) {
|
|
941
647
|
const hint = getCliCopy(locale)["hint.dashboardPortInUse"].replace("{port}", String(liveDashboardPort));
|
|
942
648
|
stderr.write(`[embassy] ${hint}\n`);
|
|
943
649
|
}
|
|
944
|
-
return error.recoverable
|
|
945
|
-
? gatewayCliExitCodes.unavailable
|
|
946
|
-
: gatewayCliExitCodes.invalidInput;
|
|
650
|
+
return error.recoverable ? gatewayCliExitCodes.unavailable : gatewayCliExitCodes.invalidInput;
|
|
947
651
|
}
|
|
948
|
-
writeFailure(stdout, stderr, locale, command, "INTERNAL_ERROR", {
|
|
949
|
-
kind: "failure",
|
|
950
|
-
});
|
|
652
|
+
writeFailure(stdout, stderr, locale, command, "INTERNAL_ERROR", { kind: "failure" });
|
|
951
653
|
return gatewayCliExitCodes.failure;
|
|
952
654
|
}
|
|
953
655
|
}
|