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