@cydm/happy-elves 0.1.0-beta.54 → 0.1.0-beta.55
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/apps/cli/dist/commands/app.js +4 -1
- package/apps/cli/dist/commands/gateway.d.ts +2 -0
- package/apps/cli/dist/commands/gateway.js +534 -0
- package/apps/cli/dist/commands/lib/args.js +30 -1
- package/apps/cli/dist/commands/lib/orchestrator.d.ts +0 -1
- package/apps/cli/dist/commands/lib/orchestrator.js +0 -1
- package/apps/cli/dist/commands/lib/usage.js +38 -9
- package/apps/cli/dist/commands/orchestrator.js +276 -96
- package/apps/cli/dist/commands/skill.js +118 -105
- package/apps/cli/dist/skills/registry.d.ts +20 -0
- package/apps/cli/dist/skills/registry.js +279 -0
- package/apps/daemon/dist/audit.d.ts +1 -1
- package/apps/daemon/dist/gateway/adapters.d.ts +37 -0
- package/apps/daemon/dist/gateway/adapters.js +1 -0
- package/apps/daemon/dist/gateway/fake.d.ts +2 -0
- package/apps/daemon/dist/gateway/fake.js +22 -0
- package/apps/daemon/dist/gateway/feishu.d.ts +66 -0
- package/apps/daemon/dist/gateway/feishu.js +481 -0
- package/apps/daemon/dist/gateway/format.d.ts +9 -0
- package/apps/daemon/dist/gateway/format.js +33 -0
- package/apps/daemon/dist/gateway/runtime.d.ts +22 -0
- package/apps/daemon/dist/gateway/runtime.js +936 -0
- package/apps/daemon/dist/gateway/store.d.ts +77 -0
- package/apps/daemon/dist/gateway/store.js +218 -0
- package/apps/daemon/dist/loop/runner.js +8 -0
- package/apps/daemon/dist/paths.d.ts +2 -0
- package/apps/daemon/dist/paths.js +2 -0
- package/apps/daemon/dist/relay/connection.js +35 -1
- package/apps/daemon/dist/relay/send.d.ts +5 -2
- package/apps/daemon/dist/relay/send.js +30 -1
- package/apps/daemon/dist/session/events.d.ts +1 -0
- package/apps/daemon/dist/session/events.js +17 -1
- package/apps/daemon/dist/session/prompt.js +16 -3
- package/apps/daemon/dist/start.js +2 -0
- package/apps/daemon/dist/turn-coordinator.d.ts +1 -1
- package/apps/daemon/package.json +1 -1
- package/apps/relay/dist/gateway-handlers.d.ts +23 -0
- package/apps/relay/dist/gateway-handlers.js +394 -0
- package/apps/relay/dist/gateway-repository.d.ts +10 -0
- package/apps/relay/dist/gateway-repository.js +90 -0
- package/apps/relay/dist/machine-handler-context.d.ts +5 -1
- package/apps/relay/dist/session-projection-reducer.js +62 -0
- package/apps/relay/dist/websocket.js +27 -2
- package/npm-shrinkwrap.json +37 -13
- package/package.json +1 -1
- package/packages/shared/dist/protocol-schemas.d.ts +135 -0
- package/packages/shared/dist/protocol-schemas.js +95 -0
- package/packages/shared/dist/protocol-types.d.ts +86 -0
- package/packages/shared/dist/protocol.d.ts +18 -2
- package/packages/shared/dist/protocol.js +41 -1
|
@@ -4,6 +4,7 @@ import { handleAccount } from "./account.js";
|
|
|
4
4
|
import { handleCollect } from "./collect.js";
|
|
5
5
|
import { handleConfig } from "./config.js";
|
|
6
6
|
import { handleDaemon } from "./daemon.js";
|
|
7
|
+
import { handleGateway } from "./gateway.js";
|
|
7
8
|
import { handleLoop } from "./loop.js";
|
|
8
9
|
import { handleMachine } from "./machine.js";
|
|
9
10
|
import { handleOrchestrator } from "./orchestrator.js";
|
|
@@ -24,6 +25,7 @@ const domainHandlers = [
|
|
|
24
25
|
handleDaemon,
|
|
25
26
|
handleRelay,
|
|
26
27
|
handleMachine,
|
|
28
|
+
handleGateway,
|
|
27
29
|
handleOrchestrator,
|
|
28
30
|
handleSession,
|
|
29
31
|
handleSkill,
|
|
@@ -31,7 +33,8 @@ const domainHandlers = [
|
|
|
31
33
|
handleCollect,
|
|
32
34
|
];
|
|
33
35
|
export async function main() {
|
|
34
|
-
const
|
|
36
|
+
const rawArgv = process.argv.slice(2);
|
|
37
|
+
const argv = rawArgv[0] === "--" ? rawArgv.slice(1) : rawArgv;
|
|
35
38
|
const [domain] = argv;
|
|
36
39
|
if (!domain)
|
|
37
40
|
usage();
|
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { CliError, configDir, ok, parseCsvFlag, randomId, requirePositional, requireString, wantsJson, } from "./lib/index.js";
|
|
4
|
+
const defaultGatewayInboundMode = "mention";
|
|
5
|
+
const defaultGatewayProcessingReaction = {
|
|
6
|
+
enabled: true,
|
|
7
|
+
emojiType: "Typing",
|
|
8
|
+
};
|
|
9
|
+
const gatewayConfigPath = path.join(configDir, "gateways.json");
|
|
10
|
+
const gatewayStatePath = path.join(configDir, "gateway-state.json");
|
|
11
|
+
export async function handleGateway({ domain, action, positional, flags }) {
|
|
12
|
+
if (domain !== "gateway")
|
|
13
|
+
return false;
|
|
14
|
+
if (action === "list") {
|
|
15
|
+
const store = await readGatewayStore();
|
|
16
|
+
if (!wantsJson(flags)) {
|
|
17
|
+
if (store.gateways.length === 0) {
|
|
18
|
+
console.log("No local gateways.");
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
for (const gateway of store.gateways) {
|
|
22
|
+
const destination = gateway.defaultDestination ? ` chat:${gateway.defaultDestination.platformChatId}` : "";
|
|
23
|
+
console.log(`${gateway.id} ${gateway.enabled ? "enabled" : "disabled"} ${gateway.platform} inbound:${gateway.inboundMode} agent:${gateway.defaultAgent}${destination}`);
|
|
24
|
+
}
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
ok("gateway.list", { gateways: store.gateways.map(publicGateway) });
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (action === "create") {
|
|
31
|
+
const platform = platformFlag(flags);
|
|
32
|
+
const store = await readGatewayStore();
|
|
33
|
+
const now = Date.now();
|
|
34
|
+
const id = stringFlag(flags, "id") ?? randomId("gw");
|
|
35
|
+
if (store.gateways.some((gateway) => gateway.id === id)) {
|
|
36
|
+
throw new CliError(`Gateway already exists: ${id}`, "GATEWAY_EXISTS");
|
|
37
|
+
}
|
|
38
|
+
const defaultChatId = stringFlag(flags, "default-chat-id");
|
|
39
|
+
const gateway = {
|
|
40
|
+
id,
|
|
41
|
+
platform,
|
|
42
|
+
name: stringFlag(flags, "name") ?? `${platform} gateway`,
|
|
43
|
+
enabled: enabledFlag(flags, false),
|
|
44
|
+
defaultAgent: stringFlag(flags, "default-agent") ?? stringFlag(flags, "agent") ?? "codex",
|
|
45
|
+
defaultCwd: stringFlag(flags, "default-cwd") ?? stringFlag(flags, "cwd") ?? process.cwd(),
|
|
46
|
+
...(defaultChatId ? { defaultDestination: { platformChatId: defaultChatId, contextKind: "main" } } : {}),
|
|
47
|
+
inboundMode: inboundModeFlag(flags, defaultGatewayInboundMode),
|
|
48
|
+
processingReaction: processingReactionFromFlags(flags, defaultGatewayProcessingReaction),
|
|
49
|
+
allowAllUsers: flags["allow-all-users"] === true,
|
|
50
|
+
identities: [],
|
|
51
|
+
createdAt: now,
|
|
52
|
+
updatedAt: now,
|
|
53
|
+
};
|
|
54
|
+
await writeGatewayStore({ version: 1, gateways: [...store.gateways, gateway] });
|
|
55
|
+
if (!wantsJson(flags)) {
|
|
56
|
+
console.log(`Created local gateway ${gateway.id} (${gateway.platform}) at ${gatewayConfigPath}`);
|
|
57
|
+
console.log(`Set platform secret: happy-elves gateway secret set ${gateway.id} --platform ${gateway.platform} ...`);
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
ok("gateway.create", { gateway: publicGateway(gateway), path: gatewayConfigPath });
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
if (action === "status") {
|
|
64
|
+
const gatewayId = requirePositional(positional[0], "gatewayId");
|
|
65
|
+
const store = await readGatewayStore();
|
|
66
|
+
const gateway = requireGateway(store, gatewayId);
|
|
67
|
+
const state = await readGatewayState();
|
|
68
|
+
const bindings = state.bindings?.filter((binding) => isRecord(binding) && binding.gatewayId === gatewayId) ?? [];
|
|
69
|
+
const queue = state.queue?.filter((item) => isRecord(item) && item.gatewayId === gatewayId) ?? [];
|
|
70
|
+
if (!wantsJson(flags)) {
|
|
71
|
+
console.log(`${gateway.id} ${gateway.enabled ? "enabled" : "disabled"} ${gateway.platform}`);
|
|
72
|
+
console.log(`config: ${gatewayConfigPath}`);
|
|
73
|
+
console.log(`secret: ${gateway.appSecret ? "set" : "missing"}`);
|
|
74
|
+
console.log(`inbound: ${gateway.inboundMode}`);
|
|
75
|
+
console.log(`reaction: ${gateway.processingReaction.enabled ? gateway.processingReaction.emojiType : "disabled"}`);
|
|
76
|
+
console.log(`default: ${gateway.defaultAgent} ${gateway.defaultCwd}`);
|
|
77
|
+
if (gateway.defaultDestination)
|
|
78
|
+
console.log(`default chat: ${gateway.defaultDestination.platformChatId}`);
|
|
79
|
+
console.log(`identities: ${gateway.identities.length}`);
|
|
80
|
+
console.log(`bindings: ${bindings.length}`);
|
|
81
|
+
console.log(`queued inbound: ${queue.length}`);
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
ok("gateway.status", { gateway: publicGateway(gateway), bindings, queueLength: queue.length, statePath: gatewayStatePath });
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
if (action === "secret" && positional[0] === "set") {
|
|
88
|
+
const gatewayId = requirePositional(positional[1], "gatewayId");
|
|
89
|
+
const platform = platformFlag(flags);
|
|
90
|
+
const store = await readGatewayStore();
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
const existing = store.gateways.find((gateway) => gateway.id === gatewayId);
|
|
93
|
+
const gateway = {
|
|
94
|
+
...(existing ?? {
|
|
95
|
+
id: gatewayId,
|
|
96
|
+
platform,
|
|
97
|
+
name: `${platform} gateway`,
|
|
98
|
+
defaultAgent: "codex",
|
|
99
|
+
defaultCwd: process.cwd(),
|
|
100
|
+
inboundMode: defaultGatewayInboundMode,
|
|
101
|
+
processingReaction: { ...defaultGatewayProcessingReaction },
|
|
102
|
+
identities: [],
|
|
103
|
+
createdAt: now,
|
|
104
|
+
}),
|
|
105
|
+
platform,
|
|
106
|
+
enabled: enabledFlag(flags, existing?.enabled ?? false),
|
|
107
|
+
...(platform === "feishu" ? {
|
|
108
|
+
appId: requireString(flags, "app-id"),
|
|
109
|
+
appSecret: appSecretFlag(flags),
|
|
110
|
+
domain: domainFlag(flags),
|
|
111
|
+
} : {}),
|
|
112
|
+
updatedAt: now,
|
|
113
|
+
};
|
|
114
|
+
await writeGatewayStore({ version: 1, gateways: [...store.gateways.filter((item) => item.id !== gatewayId), gateway] });
|
|
115
|
+
if (!wantsJson(flags)) {
|
|
116
|
+
console.log(`Saved local gateway secret for ${gatewayId} at ${gatewayConfigPath}`);
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
ok("gateway.secret.set", { gatewayId, platform, path: gatewayConfigPath, enabled: gateway.enabled, hasAppSecret: Boolean(gateway.appSecret) });
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
if (action === "config" && positional[0] === "set") {
|
|
123
|
+
const gatewayId = requirePositional(positional[1], "gatewayId");
|
|
124
|
+
const store = await readGatewayStore();
|
|
125
|
+
const gateway = requireGateway(store, gatewayId);
|
|
126
|
+
const updated = applyGatewayConfigFlags(gateway, flags);
|
|
127
|
+
await writeGatewayStore({ version: 1, gateways: store.gateways.map((item) => item.id === gatewayId ? updated : item) });
|
|
128
|
+
if (!wantsJson(flags)) {
|
|
129
|
+
console.log(`Updated local gateway ${gatewayId} at ${gatewayConfigPath}`);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
ok("gateway.config.set", { gateway: publicGateway(updated), path: gatewayConfigPath });
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
if (action === "identity" && positional[0] === "allow") {
|
|
136
|
+
const gatewayId = requirePositional(positional[1], "gatewayId");
|
|
137
|
+
const platformUserId = stringFlag(flags, "platform-user") ?? stringFlag(flags, "user");
|
|
138
|
+
if (!platformUserId)
|
|
139
|
+
throw new CliError("Missing --platform-user <id>", "MISSING_ARGUMENT");
|
|
140
|
+
const store = await readGatewayStore();
|
|
141
|
+
const gateway = requireGateway(store, gatewayId);
|
|
142
|
+
const allowedActions = parseCsvFlag(flags.actions) ?? ["session.create", "session.run"];
|
|
143
|
+
const allowedSessions = parseCsvFlag(flags.sessions);
|
|
144
|
+
const grant = {
|
|
145
|
+
platformUserId,
|
|
146
|
+
...(stringFlag(flags, "display-name") ? { displayName: stringFlag(flags, "display-name") } : {}),
|
|
147
|
+
allowedActions,
|
|
148
|
+
...(allowedSessions ? { allowedSessions } : {}),
|
|
149
|
+
};
|
|
150
|
+
const updated = {
|
|
151
|
+
...gateway,
|
|
152
|
+
identities: [...gateway.identities.filter((identity) => identity.platformUserId !== platformUserId), grant],
|
|
153
|
+
updatedAt: Date.now(),
|
|
154
|
+
};
|
|
155
|
+
await writeGatewayStore({ version: 1, gateways: store.gateways.map((item) => item.id === gatewayId ? updated : item) });
|
|
156
|
+
if (!wantsJson(flags)) {
|
|
157
|
+
console.log(`Allowed ${platformUserId} on local gateway ${gatewayId}`);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
ok("gateway.identity.allow", { gateway: publicGateway(updated), identity: grant });
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
if (action === "identity" && positional[0] === "remove") {
|
|
164
|
+
const gatewayId = requirePositional(positional[1], "gatewayId");
|
|
165
|
+
const platformUserId = stringFlag(flags, "platform-user") ?? stringFlag(flags, "user");
|
|
166
|
+
if (!platformUserId)
|
|
167
|
+
throw new CliError("Missing --platform-user <id>", "MISSING_ARGUMENT");
|
|
168
|
+
const store = await readGatewayStore();
|
|
169
|
+
const gateway = requireGateway(store, gatewayId);
|
|
170
|
+
const updated = {
|
|
171
|
+
...gateway,
|
|
172
|
+
identities: gateway.identities.filter((identity) => identity.platformUserId !== platformUserId),
|
|
173
|
+
updatedAt: Date.now(),
|
|
174
|
+
};
|
|
175
|
+
await writeGatewayStore({ version: 1, gateways: store.gateways.map((item) => item.id === gatewayId ? updated : item) });
|
|
176
|
+
if (!wantsJson(flags)) {
|
|
177
|
+
console.log(`Removed ${platformUserId} from local gateway ${gatewayId}`);
|
|
178
|
+
return true;
|
|
179
|
+
}
|
|
180
|
+
ok("gateway.identity.remove", { gateway: publicGateway(updated), removedPlatformUserId: platformUserId });
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
if (action === "binding" && positional[0] === "list") {
|
|
184
|
+
const gatewayId = requirePositional(positional[1], "gatewayId");
|
|
185
|
+
const store = await readGatewayStore();
|
|
186
|
+
requireGateway(store, gatewayId);
|
|
187
|
+
const state = await readGatewayState();
|
|
188
|
+
const bindings = gatewayBindings(state, gatewayId);
|
|
189
|
+
if (!wantsJson(flags)) {
|
|
190
|
+
if (bindings.length === 0) {
|
|
191
|
+
console.log(`No bindings for local gateway ${gatewayId}.`);
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
for (const binding of bindings) {
|
|
195
|
+
const thread = binding.contextKind === "thread" ? ` thread:${stringFlag(binding, "contextKey") ?? ""}` : "";
|
|
196
|
+
const last = stringFlag(binding, "lastPlatformMessageId") ? ` last:${stringFlag(binding, "lastPlatformMessageId")}` : "";
|
|
197
|
+
console.log(`${stringFlag(binding, "sessionId") ?? "(missing-session)"} chat:${stringFlag(binding, "platformChatId") ?? "(missing-chat)"}${thread} agent:${stringFlag(binding, "agent") ?? "codex"} cwd:${stringFlag(binding, "cwd") ?? ""}${last}`);
|
|
198
|
+
}
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
ok("gateway.binding.list", { gatewayId, bindings, statePath: gatewayStatePath });
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
if (action === "binding" && positional[0] === "clear") {
|
|
205
|
+
const gatewayId = requirePositional(positional[1], "gatewayId");
|
|
206
|
+
const store = await readGatewayStore();
|
|
207
|
+
requireGateway(store, gatewayId);
|
|
208
|
+
const state = await readGatewayState();
|
|
209
|
+
const filter = {
|
|
210
|
+
...(stringFlag(flags, "chat-id") ? { chatId: stringFlag(flags, "chat-id") } : {}),
|
|
211
|
+
...(stringFlag(flags, "thread-id") ? { threadId: stringFlag(flags, "thread-id") } : {}),
|
|
212
|
+
...(stringFlag(flags, "session") ? { sessionId: stringFlag(flags, "session") } : {}),
|
|
213
|
+
};
|
|
214
|
+
const bindings = Array.isArray(state.bindings) ? state.bindings : [];
|
|
215
|
+
const removedBindings = bindings.filter((binding) => bindingMatchesFilter(binding, gatewayId, filter));
|
|
216
|
+
const removedConversationKeys = new Set(removedBindings.flatMap((binding) => bindingConversationKey(binding) ?? []));
|
|
217
|
+
const queue = Array.isArray(state.queue) ? state.queue : [];
|
|
218
|
+
const nextState = {
|
|
219
|
+
...state,
|
|
220
|
+
bindings: bindings.filter((binding) => !bindingMatchesFilter(binding, gatewayId, filter)),
|
|
221
|
+
queue: queue.filter((item) => !queueMatchesConversation(item, gatewayId, removedConversationKeys) && !queueMatchesFilter(item, gatewayId, filter)),
|
|
222
|
+
};
|
|
223
|
+
const removedQueuedInbound = queue.length - (Array.isArray(nextState.queue) ? nextState.queue.length : 0);
|
|
224
|
+
await writeGatewayState(nextState);
|
|
225
|
+
if (!wantsJson(flags)) {
|
|
226
|
+
console.log(`Cleared ${removedBindings.length} binding(s) and ${removedQueuedInbound} queued inbound message(s) for local gateway ${gatewayId}`);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
ok("gateway.binding.clear", { gatewayId, removedBindings: removedBindings.length, removedQueuedInbound, statePath: gatewayStatePath });
|
|
230
|
+
return true;
|
|
231
|
+
}
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
async function readGatewayStore() {
|
|
235
|
+
try {
|
|
236
|
+
const parsed = JSON.parse(await fs.readFile(gatewayConfigPath, "utf8"));
|
|
237
|
+
return {
|
|
238
|
+
version: 1,
|
|
239
|
+
gateways: Array.isArray(parsed.gateways) ? parsed.gateways.flatMap(normalizeGateway) : [],
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
if (error.code === "ENOENT")
|
|
244
|
+
return { version: 1, gateways: [] };
|
|
245
|
+
throw error;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
async function writeGatewayStore(store) {
|
|
249
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
250
|
+
await fs.writeFile(gatewayConfigPath, `${JSON.stringify(store, null, 2)}\n`, { mode: 0o600 });
|
|
251
|
+
await fs.chmod(gatewayConfigPath, 0o600);
|
|
252
|
+
}
|
|
253
|
+
async function readGatewayState() {
|
|
254
|
+
try {
|
|
255
|
+
return JSON.parse(await fs.readFile(gatewayStatePath, "utf8"));
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
if (error.code === "ENOENT")
|
|
259
|
+
return { version: 1, bindings: [], queue: [], processedEvents: [] };
|
|
260
|
+
throw error;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async function writeGatewayState(state) {
|
|
264
|
+
await fs.mkdir(configDir, { recursive: true });
|
|
265
|
+
await fs.writeFile(gatewayStatePath, `${JSON.stringify({ ...state, version: 1 }, null, 2)}\n`, { mode: 0o600 });
|
|
266
|
+
await fs.chmod(gatewayStatePath, 0o600);
|
|
267
|
+
}
|
|
268
|
+
function applyGatewayConfigFlags(gateway, flags) {
|
|
269
|
+
const next = {
|
|
270
|
+
...gateway,
|
|
271
|
+
enabled: enabledFlag(flags, gateway.enabled),
|
|
272
|
+
inboundMode: inboundModeFlag(flags, gateway.inboundMode),
|
|
273
|
+
processingReaction: processingReactionFromFlags(flags, gateway.processingReaction),
|
|
274
|
+
defaultAgent: stringFlag(flags, "default-agent") ?? stringFlag(flags, "agent") ?? gateway.defaultAgent,
|
|
275
|
+
defaultCwd: stringFlag(flags, "default-cwd") ?? stringFlag(flags, "cwd") ?? gateway.defaultCwd,
|
|
276
|
+
allowAllUsers: allowAllUsersFlag(flags, gateway.allowAllUsers === true),
|
|
277
|
+
updatedAt: Date.now(),
|
|
278
|
+
};
|
|
279
|
+
const defaultChatId = stringFlag(flags, "default-chat-id");
|
|
280
|
+
if (flags["clear-default-chat"] === true && defaultChatId) {
|
|
281
|
+
throw new CliError("Use only one of --default-chat-id or --clear-default-chat", "INVALID_ARGUMENT");
|
|
282
|
+
}
|
|
283
|
+
if (flags["clear-default-chat"] === true) {
|
|
284
|
+
delete next.defaultDestination;
|
|
285
|
+
}
|
|
286
|
+
else if (defaultChatId) {
|
|
287
|
+
next.defaultDestination = { platformChatId: defaultChatId, contextKind: "main" };
|
|
288
|
+
}
|
|
289
|
+
return next;
|
|
290
|
+
}
|
|
291
|
+
function normalizeGateway(value) {
|
|
292
|
+
if (!isRecord(value))
|
|
293
|
+
return [];
|
|
294
|
+
const id = stringFlag(value, "id") ?? stringFlag(value, "gatewayId");
|
|
295
|
+
if (!id)
|
|
296
|
+
return [];
|
|
297
|
+
const platform = value.platform === "fake" ? "fake" : value.platform === "feishu" ? "feishu" : undefined;
|
|
298
|
+
if (!platform)
|
|
299
|
+
return [];
|
|
300
|
+
const now = Date.now();
|
|
301
|
+
const defaultChatId = stringFlag(value, "defaultChatId");
|
|
302
|
+
const defaultDestination = normalizeDestination(value.defaultDestination)
|
|
303
|
+
?? (defaultChatId ? { platformChatId: defaultChatId, contextKind: "main" } : undefined);
|
|
304
|
+
const inboundMode = normalizeInboundMode(value.inboundMode) ?? legacyInboundMode(value) ?? defaultGatewayInboundMode;
|
|
305
|
+
return [{
|
|
306
|
+
id,
|
|
307
|
+
platform,
|
|
308
|
+
name: stringFlag(value, "name") ?? `${platform} gateway`,
|
|
309
|
+
enabled: value.enabled === true,
|
|
310
|
+
defaultAgent: stringFlag(value, "defaultAgent") ?? "codex",
|
|
311
|
+
defaultCwd: typeof value.defaultCwd === "string" ? value.defaultCwd : "",
|
|
312
|
+
...(defaultDestination ? { defaultDestination } : {}),
|
|
313
|
+
inboundMode,
|
|
314
|
+
processingReaction: normalizeProcessingReaction(value.processingReaction),
|
|
315
|
+
allowAllUsers: value.allowAllUsers === true,
|
|
316
|
+
...(stringFlag(value, "appId") ? { appId: stringFlag(value, "appId") } : {}),
|
|
317
|
+
...(stringFlag(value, "appSecret") ? { appSecret: stringFlag(value, "appSecret") } : {}),
|
|
318
|
+
...(value.domain === "lark" ? { domain: "lark" } : { domain: "feishu" }),
|
|
319
|
+
identities: Array.isArray(value.identities) ? value.identities.flatMap(normalizeIdentity) : [],
|
|
320
|
+
createdAt: typeof value.createdAt === "number" ? value.createdAt : now,
|
|
321
|
+
updatedAt: typeof value.updatedAt === "number" ? value.updatedAt : now,
|
|
322
|
+
}];
|
|
323
|
+
}
|
|
324
|
+
function normalizeInboundMode(value) {
|
|
325
|
+
return value === "all" || value === "mention" ? value : undefined;
|
|
326
|
+
}
|
|
327
|
+
function legacyInboundMode(record) {
|
|
328
|
+
if (!Object.hasOwn(record, "requireMention"))
|
|
329
|
+
return undefined;
|
|
330
|
+
return record.requireMention === true ? "mention" : "all";
|
|
331
|
+
}
|
|
332
|
+
function normalizeProcessingReaction(value) {
|
|
333
|
+
if (!isRecord(value))
|
|
334
|
+
return { ...defaultGatewayProcessingReaction };
|
|
335
|
+
return {
|
|
336
|
+
enabled: value.enabled !== false,
|
|
337
|
+
emojiType: stringFlag(value, "emojiType") ?? defaultGatewayProcessingReaction.emojiType,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function normalizeIdentity(value) {
|
|
341
|
+
if (!isRecord(value))
|
|
342
|
+
return [];
|
|
343
|
+
const platformUserId = stringFlag(value, "platformUserId");
|
|
344
|
+
if (!platformUserId)
|
|
345
|
+
return [];
|
|
346
|
+
const allowedActions = Array.isArray(value.allowedActions)
|
|
347
|
+
? value.allowedActions.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
348
|
+
: ["session.create", "session.run"];
|
|
349
|
+
const allowedSessions = Array.isArray(value.allowedSessions)
|
|
350
|
+
? value.allowedSessions.filter((item) => typeof item === "string" && item.trim().length > 0)
|
|
351
|
+
: undefined;
|
|
352
|
+
return [{
|
|
353
|
+
platformUserId,
|
|
354
|
+
...(stringFlag(value, "displayName") ? { displayName: stringFlag(value, "displayName") } : {}),
|
|
355
|
+
allowedActions,
|
|
356
|
+
...(allowedSessions && allowedSessions.length > 0 ? { allowedSessions } : {}),
|
|
357
|
+
}];
|
|
358
|
+
}
|
|
359
|
+
function normalizeDestination(value) {
|
|
360
|
+
if (!isRecord(value))
|
|
361
|
+
return undefined;
|
|
362
|
+
const platformChatId = stringFlag(value, "platformChatId");
|
|
363
|
+
if (!platformChatId)
|
|
364
|
+
return undefined;
|
|
365
|
+
const contextKind = value.contextKind === "thread" ? "thread" : "main";
|
|
366
|
+
const replyToMessageId = stringFlag(value, "replyToMessageId");
|
|
367
|
+
if (contextKind === "thread") {
|
|
368
|
+
const contextKey = stringFlag(value, "contextKey");
|
|
369
|
+
if (!contextKey)
|
|
370
|
+
return undefined;
|
|
371
|
+
return {
|
|
372
|
+
platformChatId,
|
|
373
|
+
contextKind,
|
|
374
|
+
contextKey,
|
|
375
|
+
...(replyToMessageId ? { replyToMessageId } : {}),
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return {
|
|
379
|
+
platformChatId,
|
|
380
|
+
contextKind,
|
|
381
|
+
...(replyToMessageId ? { replyToMessageId } : {}),
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function publicGateway(gateway) {
|
|
385
|
+
const { appSecret: _appSecret, ...publicFields } = gateway;
|
|
386
|
+
return { ...publicFields, hasAppSecret: Boolean(gateway.appSecret) };
|
|
387
|
+
}
|
|
388
|
+
function requireGateway(store, gatewayId) {
|
|
389
|
+
const gateway = store.gateways.find((item) => item.id === gatewayId);
|
|
390
|
+
if (!gateway)
|
|
391
|
+
throw new CliError(`Gateway not found: ${gatewayId}`, "GATEWAY_NOT_FOUND");
|
|
392
|
+
return gateway;
|
|
393
|
+
}
|
|
394
|
+
function platformFlag(flags) {
|
|
395
|
+
const platform = flags.platform;
|
|
396
|
+
if (platform === "feishu" || platform === "fake")
|
|
397
|
+
return platform;
|
|
398
|
+
throw new CliError("Missing or invalid --platform <feishu|fake>", "INVALID_ARGUMENT");
|
|
399
|
+
}
|
|
400
|
+
function domainFlag(flags) {
|
|
401
|
+
const domain = flags.domain;
|
|
402
|
+
if (domain === undefined || domain === false)
|
|
403
|
+
return "feishu";
|
|
404
|
+
if (domain === "feishu" || domain === "lark")
|
|
405
|
+
return domain;
|
|
406
|
+
throw new CliError("Invalid --domain <feishu|lark>", "INVALID_ARGUMENT");
|
|
407
|
+
}
|
|
408
|
+
function enabledFlag(flags, fallback) {
|
|
409
|
+
if (flags.enabled === true && flags.disabled === true) {
|
|
410
|
+
throw new CliError("Use only one of --enabled or --disabled", "INVALID_ARGUMENT");
|
|
411
|
+
}
|
|
412
|
+
if (flags.enabled === true)
|
|
413
|
+
return true;
|
|
414
|
+
if (flags.disabled === true)
|
|
415
|
+
return false;
|
|
416
|
+
return fallback;
|
|
417
|
+
}
|
|
418
|
+
function allowAllUsersFlag(flags, fallback) {
|
|
419
|
+
if (flags["allow-all-users"] === true && flags["deny-all-users"] === true) {
|
|
420
|
+
throw new CliError("Use only one of --allow-all-users or --deny-all-users", "INVALID_ARGUMENT");
|
|
421
|
+
}
|
|
422
|
+
if (flags["allow-all-users"] === true)
|
|
423
|
+
return true;
|
|
424
|
+
if (flags["deny-all-users"] === true)
|
|
425
|
+
return false;
|
|
426
|
+
return fallback;
|
|
427
|
+
}
|
|
428
|
+
function inboundModeFlag(flags, fallback) {
|
|
429
|
+
const explicit = stringFlag(flags, "inbound-mode");
|
|
430
|
+
const aliases = [
|
|
431
|
+
...(flags["require-mention"] === true ? ["mention"] : []),
|
|
432
|
+
...(flags["respond-all"] === true ? ["all"] : []),
|
|
433
|
+
];
|
|
434
|
+
if (aliases.length > 1)
|
|
435
|
+
throw new CliError("Use only one of --require-mention or --respond-all", "INVALID_ARGUMENT");
|
|
436
|
+
if (explicit) {
|
|
437
|
+
const mode = normalizeInboundMode(explicit);
|
|
438
|
+
if (!mode)
|
|
439
|
+
throw new CliError("Invalid --inbound-mode <mention|all>", "INVALID_ARGUMENT");
|
|
440
|
+
if (aliases.length === 1 && aliases[0] !== mode) {
|
|
441
|
+
throw new CliError("Conflicting inbound mode flags", "INVALID_ARGUMENT");
|
|
442
|
+
}
|
|
443
|
+
return mode;
|
|
444
|
+
}
|
|
445
|
+
return aliases[0] ?? fallback;
|
|
446
|
+
}
|
|
447
|
+
function processingReactionFromFlags(flags, fallback) {
|
|
448
|
+
if (flags["reaction-enabled"] === true && flags["reaction-disabled"] === true) {
|
|
449
|
+
throw new CliError("Use only one of --reaction-enabled or --reaction-disabled", "INVALID_ARGUMENT");
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
enabled: flags["reaction-enabled"] === true ? true : flags["reaction-disabled"] === true ? false : fallback.enabled,
|
|
453
|
+
emojiType: stringFlag(flags, "reaction-emoji") ?? fallback.emojiType,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
function appSecretFlag(flags) {
|
|
457
|
+
const direct = stringFlag(flags, "app-secret");
|
|
458
|
+
const envName = stringFlag(flags, "app-secret-env");
|
|
459
|
+
if (direct && envName)
|
|
460
|
+
throw new CliError("Use only one of --app-secret or --app-secret-env", "INVALID_ARGUMENT");
|
|
461
|
+
if (direct)
|
|
462
|
+
return direct;
|
|
463
|
+
if (envName) {
|
|
464
|
+
const value = process.env[envName];
|
|
465
|
+
if (typeof value === "string" && value.length > 0)
|
|
466
|
+
return value;
|
|
467
|
+
throw new CliError(`Environment variable is empty or missing: ${envName}`, "MISSING_ARGUMENT");
|
|
468
|
+
}
|
|
469
|
+
return requireString(flags, "app-secret");
|
|
470
|
+
}
|
|
471
|
+
function stringFlag(flags, key) {
|
|
472
|
+
const value = flags[key];
|
|
473
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
474
|
+
}
|
|
475
|
+
function isRecord(value) {
|
|
476
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
477
|
+
}
|
|
478
|
+
function gatewayBindings(state, gatewayId) {
|
|
479
|
+
return (Array.isArray(state.bindings) ? state.bindings : [])
|
|
480
|
+
.filter((binding) => isRecord(binding) && binding.gatewayId === gatewayId);
|
|
481
|
+
}
|
|
482
|
+
function bindingMatchesFilter(value, gatewayId, filter) {
|
|
483
|
+
if (!isRecord(value) || value.gatewayId !== gatewayId)
|
|
484
|
+
return false;
|
|
485
|
+
if (filter.chatId && value.platformChatId !== filter.chatId)
|
|
486
|
+
return false;
|
|
487
|
+
if (filter.threadId && (value.contextKind !== "thread" || value.contextKey !== filter.threadId))
|
|
488
|
+
return false;
|
|
489
|
+
if (filter.sessionId && value.sessionId !== filter.sessionId)
|
|
490
|
+
return false;
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
function bindingConversationKey(value) {
|
|
494
|
+
if (!isRecord(value))
|
|
495
|
+
return undefined;
|
|
496
|
+
const platformChatId = stringFlag(value, "platformChatId");
|
|
497
|
+
const contextKind = value.contextKind === "thread" ? "thread" : "main";
|
|
498
|
+
const contextKey = contextKind === "thread" ? stringFlag(value, "contextKey") : "";
|
|
499
|
+
if (!platformChatId || (contextKind === "thread" && !contextKey))
|
|
500
|
+
return undefined;
|
|
501
|
+
return `${platformChatId}:${contextKind}:${contextKey}`;
|
|
502
|
+
}
|
|
503
|
+
function queueMatchesConversation(value, gatewayId, conversationKeys) {
|
|
504
|
+
if (conversationKeys.size === 0 || !isRecord(value) || value.gatewayId !== gatewayId)
|
|
505
|
+
return false;
|
|
506
|
+
const destination = isRecord(value.destination) ? value.destination : undefined;
|
|
507
|
+
if (!destination)
|
|
508
|
+
return false;
|
|
509
|
+
const platformChatId = stringFlag(destination, "platformChatId");
|
|
510
|
+
const contextKind = destination.contextKind === "thread" ? "thread" : "main";
|
|
511
|
+
const contextKey = contextKind === "thread" ? stringFlag(destination, "contextKey") : "";
|
|
512
|
+
if (!platformChatId || (contextKind === "thread" && !contextKey))
|
|
513
|
+
return false;
|
|
514
|
+
return conversationKeys.has(`${platformChatId}:${contextKind}:${contextKey}`);
|
|
515
|
+
}
|
|
516
|
+
function queueMatchesFilter(value, gatewayId, filter) {
|
|
517
|
+
if (!isRecord(value) || value.gatewayId !== gatewayId)
|
|
518
|
+
return false;
|
|
519
|
+
if (filter.sessionId)
|
|
520
|
+
return false;
|
|
521
|
+
const destination = isRecord(value.destination) ? value.destination : undefined;
|
|
522
|
+
if (!destination)
|
|
523
|
+
return false;
|
|
524
|
+
const platformChatId = stringFlag(destination, "platformChatId");
|
|
525
|
+
const contextKind = destination.contextKind === "thread" ? "thread" : "main";
|
|
526
|
+
const contextKey = contextKind === "thread" ? stringFlag(destination, "contextKey") : "";
|
|
527
|
+
if (!platformChatId || (contextKind === "thread" && !contextKey))
|
|
528
|
+
return false;
|
|
529
|
+
if (filter.chatId && platformChatId !== filter.chatId)
|
|
530
|
+
return false;
|
|
531
|
+
if (filter.threadId && (contextKind !== "thread" || contextKey !== filter.threadId))
|
|
532
|
+
return false;
|
|
533
|
+
return true;
|
|
534
|
+
}
|
|
@@ -2,13 +2,27 @@ import { parseFlags as parseSharedFlags } from "../../../../../packages/shared/d
|
|
|
2
2
|
import { CliError } from "../../errors.js";
|
|
3
3
|
const valueFlags = new Set([
|
|
4
4
|
"agent",
|
|
5
|
+
"app-id",
|
|
6
|
+
"app-secret",
|
|
7
|
+
"app-secret-env",
|
|
5
8
|
"code",
|
|
6
9
|
"controller",
|
|
10
|
+
"context-turn",
|
|
7
11
|
"cwd",
|
|
12
|
+
"chat-id",
|
|
13
|
+
"default-agent",
|
|
14
|
+
"default-chat-id",
|
|
15
|
+
"default-cwd",
|
|
16
|
+
"default-machine",
|
|
8
17
|
"device-id",
|
|
9
18
|
"device-name",
|
|
19
|
+
"display-name",
|
|
20
|
+
"domain",
|
|
10
21
|
"from",
|
|
11
22
|
"host",
|
|
23
|
+
"host-machine",
|
|
24
|
+
"id",
|
|
25
|
+
"inbound-mode",
|
|
12
26
|
"invite-secret",
|
|
13
27
|
"join-url-base",
|
|
14
28
|
"limit",
|
|
@@ -19,12 +33,15 @@ const valueFlags = new Set([
|
|
|
19
33
|
"model",
|
|
20
34
|
"output",
|
|
21
35
|
"path",
|
|
36
|
+
"platform",
|
|
37
|
+
"platform-user",
|
|
22
38
|
"port",
|
|
23
39
|
"permission",
|
|
24
40
|
"permission-mode",
|
|
25
41
|
"prompt",
|
|
26
42
|
"prompt-file",
|
|
27
43
|
"query",
|
|
44
|
+
"reaction-emoji",
|
|
28
45
|
"request-id",
|
|
29
46
|
"every",
|
|
30
47
|
"reason",
|
|
@@ -39,6 +56,7 @@ const valueFlags = new Set([
|
|
|
39
56
|
"loops",
|
|
40
57
|
"machines",
|
|
41
58
|
"messages",
|
|
59
|
+
"session",
|
|
42
60
|
"sessions",
|
|
43
61
|
"secret",
|
|
44
62
|
"since",
|
|
@@ -48,6 +66,7 @@ const valueFlags = new Set([
|
|
|
48
66
|
"tags",
|
|
49
67
|
"text",
|
|
50
68
|
"text-file",
|
|
69
|
+
"thread-id",
|
|
51
70
|
"timeout",
|
|
52
71
|
"timeout-ms",
|
|
53
72
|
"thought-level",
|
|
@@ -63,8 +82,14 @@ const valueFlags = new Set([
|
|
|
63
82
|
]);
|
|
64
83
|
const booleanFlags = new Set([
|
|
65
84
|
"all",
|
|
85
|
+
"allow-all-users",
|
|
86
|
+
"clear-default-chat",
|
|
87
|
+
"deny-all-users",
|
|
66
88
|
"detach",
|
|
89
|
+
"disabled",
|
|
90
|
+
"enabled",
|
|
67
91
|
"force",
|
|
92
|
+
"full-context",
|
|
68
93
|
"help",
|
|
69
94
|
"h",
|
|
70
95
|
"include-output",
|
|
@@ -75,12 +100,16 @@ const booleanFlags = new Set([
|
|
|
75
100
|
"no-open",
|
|
76
101
|
"no-wait",
|
|
77
102
|
"repair-head",
|
|
103
|
+
"reaction-disabled",
|
|
104
|
+
"reaction-enabled",
|
|
105
|
+
"require-mention",
|
|
106
|
+
"respond-all",
|
|
78
107
|
"summary",
|
|
79
108
|
"verbose",
|
|
80
109
|
"wait",
|
|
81
110
|
]);
|
|
82
111
|
const knownFlags = new Set([...valueFlags, ...booleanFlags]);
|
|
83
|
-
const dashPrefixedValueFlags = new Set(["secret"]);
|
|
112
|
+
const dashPrefixedValueFlags = new Set(["app-secret", "secret"]);
|
|
84
113
|
export function parseCliFlags(argv) {
|
|
85
114
|
const parsed = parseSharedFlags(argv, { valueFlags, dashPrefixedValueFlags });
|
|
86
115
|
const unknown = Object.keys(parsed.flags).filter((flag) => !knownFlags.has(flag));
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { SessionSnapshot } from "../../../../../packages/shared/dist/index.js";
|
|
2
2
|
import type { OrchestrationSummary } from "./types.js";
|
|
3
3
|
import type { TurnSummary } from "./session-output.js";
|
|
4
|
-
export declare const ORCHESTRATOR_CONTRACT_VERSION = 1;
|
|
5
4
|
export type OrchestratorStatus = "idle" | "inProgress" | "completed" | "needsInput" | "blocked" | "failed" | "cancelled" | "offline" | "closed";
|
|
6
5
|
export type OrchestratorRequestRecord = {
|
|
7
6
|
turnId: string;
|