@linkshell/gateway 0.4.54 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Dockerfile +11 -3
- package/dist/gateway/src/embedded.js +0 -24
- package/dist/gateway/src/embedded.js.map +1 -1
- package/dist/gateway/src/index.js +47 -27
- package/dist/gateway/src/index.js.map +1 -1
- package/dist/gateway/src/relay.js +0 -13
- package/dist/gateway/src/relay.js.map +1 -1
- package/dist/gateway/tsconfig.tsbuildinfo +1 -1
- package/dist/gateway-v2/src/accounts.d.ts +6 -0
- package/dist/gateway-v2/src/accounts.js +32 -0
- package/dist/gateway-v2/src/accounts.js.map +1 -0
- package/dist/gateway-v2/src/index.d.ts +3 -0
- package/dist/gateway-v2/src/index.js +4 -0
- package/dist/gateway-v2/src/index.js.map +1 -0
- package/dist/gateway-v2/src/main.d.ts +1 -0
- package/dist/gateway-v2/src/main.js +17 -0
- package/dist/gateway-v2/src/main.js.map +1 -0
- package/dist/gateway-v2/src/server.d.ts +69 -0
- package/dist/gateway-v2/src/server.js +387 -0
- package/dist/gateway-v2/src/server.js.map +1 -0
- package/dist/gateway-v2/src/store.d.ts +30 -0
- package/dist/gateway-v2/src/store.js +102 -0
- package/dist/gateway-v2/src/store.js.map +1 -0
- package/dist/shared-protocol/src/index.d.ts +1495 -1339
- package/dist/shared-protocol/src/index.js +6 -0
- package/dist/shared-protocol/src/index.js.map +1 -1
- package/dist/wire/src/crypto.d.ts +65 -0
- package/dist/wire/src/crypto.js +253 -0
- package/dist/wire/src/crypto.js.map +1 -0
- package/dist/wire/src/index.d.ts +8 -0
- package/dist/wire/src/index.js +9 -0
- package/dist/wire/src/index.js.map +1 -0
- package/dist/wire/src/model.d.ts +704 -0
- package/dist/wire/src/model.js +163 -0
- package/dist/wire/src/model.js.map +1 -0
- package/dist/wire/src/peer.d.ts +42 -0
- package/dist/wire/src/peer.js +143 -0
- package/dist/wire/src/peer.js.map +1 -0
- package/dist/wire/src/relay-client.d.ts +82 -0
- package/dist/wire/src/relay-client.js +259 -0
- package/dist/wire/src/relay-client.js.map +1 -0
- package/dist/wire/src/relay.d.ts +481 -0
- package/dist/wire/src/relay.js +129 -0
- package/dist/wire/src/relay.js.map +1 -0
- package/dist/wire/src/rpc.d.ts +4170 -0
- package/dist/wire/src/rpc.js +332 -0
- package/dist/wire/src/rpc.js.map +1 -0
- package/dist/wire/src/shell.d.ts +2 -0
- package/dist/wire/src/shell.js +62 -0
- package/dist/wire/src/shell.js.map +1 -0
- package/dist/wire/src/updates.d.ts +3325 -0
- package/dist/wire/src/updates.js +210 -0
- package/dist/wire/src/updates.js.map +1 -0
- package/package.json +6 -1
- package/src/index.ts +45 -2
- package/dist/gateway/src/agent-permission-http.d.ts +0 -119
- package/dist/gateway/src/agent-permission-http.js +0 -234
- package/dist/gateway/src/agent-permission-http.js.map +0 -1
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { permissionOptionSchema, sessionDriverSchema, sessionStateSchema, toolKindSchema } from "./model.js";
|
|
3
|
+
// Session updates mirror ACP's `SessionUpdate` shapes (discriminated by
|
|
4
|
+
// `sessionUpdate`) so one renderer works for every agent. LinkShell-specific
|
|
5
|
+
// updates use the `ls_` prefix.
|
|
6
|
+
export const contentBlockSchema = z.discriminatedUnion("type", [
|
|
7
|
+
z.object({ type: z.literal("text"), text: z.string() }),
|
|
8
|
+
z.object({
|
|
9
|
+
type: z.literal("image"),
|
|
10
|
+
mimeType: z.string(),
|
|
11
|
+
data: z.string().optional(),
|
|
12
|
+
uri: z.string().optional(),
|
|
13
|
+
}),
|
|
14
|
+
z.object({
|
|
15
|
+
type: z.literal("resource_link"),
|
|
16
|
+
uri: z.string(),
|
|
17
|
+
name: z.string(),
|
|
18
|
+
/** What the link names: a file (default), a skill the user invoked, or another agent. */
|
|
19
|
+
kind: z.enum(["file", "skill", "agent"]).optional(),
|
|
20
|
+
}),
|
|
21
|
+
]);
|
|
22
|
+
export const toolCallStatusSchema = z.enum(["pending", "in_progress", "completed", "failed"]);
|
|
23
|
+
export const toolCallContentSchema = z.discriminatedUnion("type", [
|
|
24
|
+
z.object({ type: z.literal("content"), content: contentBlockSchema }),
|
|
25
|
+
/** ACP diff: full before/after text. */
|
|
26
|
+
z.object({
|
|
27
|
+
type: z.literal("diff"),
|
|
28
|
+
path: z.string(),
|
|
29
|
+
oldText: z.string().nullable().optional(),
|
|
30
|
+
newText: z.string(),
|
|
31
|
+
}),
|
|
32
|
+
/** Unified diff, for agents (Codex) that report patches rather than file text. */
|
|
33
|
+
z.object({
|
|
34
|
+
type: z.literal("patch"),
|
|
35
|
+
path: z.string(),
|
|
36
|
+
change: z.enum(["add", "delete", "update"]),
|
|
37
|
+
movePath: z.string().optional(),
|
|
38
|
+
diff: z.string(),
|
|
39
|
+
}),
|
|
40
|
+
z.object({ type: z.literal("terminal"), terminalId: z.string() }),
|
|
41
|
+
]);
|
|
42
|
+
export const toolCallLocationSchema = z.object({
|
|
43
|
+
path: z.string(),
|
|
44
|
+
line: z.number().int().optional(),
|
|
45
|
+
});
|
|
46
|
+
/**
|
|
47
|
+
* What a tool call is, when the agent tells us more than ACP's coarse `kind`.
|
|
48
|
+
* Host drivers fill it from each agent's own tool names; clients render it the
|
|
49
|
+
* same way for every agent and fall back to `title` + `kind` without it.
|
|
50
|
+
*/
|
|
51
|
+
export const toolDetailSchema = z.discriminatedUnion("type", [
|
|
52
|
+
z.object({ type: z.literal("mcp"), server: z.string(), tool: z.string() }),
|
|
53
|
+
z.object({
|
|
54
|
+
type: z.literal("subagent"),
|
|
55
|
+
/** spawn starts a sub-agent (its work nests under this call); the rest talk to one. */
|
|
56
|
+
action: z.enum(["spawn", "message", "wait", "stop", "resume", "list"]).default("spawn"),
|
|
57
|
+
task: z.string().optional(),
|
|
58
|
+
/** The sub-agent's role or type, e.g. "Explore". */
|
|
59
|
+
agentType: z.string().optional(),
|
|
60
|
+
model: z.string().optional(),
|
|
61
|
+
}),
|
|
62
|
+
z.object({ type: z.literal("skill"), name: z.string(), description: z.string().optional() }),
|
|
63
|
+
z.object({ type: z.literal("web_search"), query: z.string().optional() }),
|
|
64
|
+
z.object({ type: z.literal("image_generation"), prompt: z.string().optional() }),
|
|
65
|
+
z.object({ type: z.literal("compaction") }),
|
|
66
|
+
z.object({ type: z.literal("review"), phase: z.enum(["started", "finished"]) }),
|
|
67
|
+
]);
|
|
68
|
+
export const planEntrySchema = z.object({
|
|
69
|
+
content: z.string(),
|
|
70
|
+
priority: z.enum(["high", "medium", "low"]).default("medium"),
|
|
71
|
+
status: z.enum(["pending", "in_progress", "completed"]),
|
|
72
|
+
});
|
|
73
|
+
export const stopReasonSchema = z.enum(["end_turn", "cancelled", "error", "max_tokens", "refusal"]);
|
|
74
|
+
export const sessionConfigOptionSchema = z.object({
|
|
75
|
+
/** Agent-defined id; "model" and "mode" are the common ones. */
|
|
76
|
+
id: z.string(),
|
|
77
|
+
name: z.string(),
|
|
78
|
+
category: z.enum(["model", "mode", "effort", "other"]).default("other"),
|
|
79
|
+
current: z.string(),
|
|
80
|
+
values: z.array(z.object({ value: z.string(), name: z.string(), description: z.string().optional() })),
|
|
81
|
+
});
|
|
82
|
+
export const sessionUpdateSchema = z.discriminatedUnion("sessionUpdate", [
|
|
83
|
+
z.object({
|
|
84
|
+
sessionUpdate: z.literal("user_message_chunk"),
|
|
85
|
+
messageId: z.string().optional(),
|
|
86
|
+
content: contentBlockSchema,
|
|
87
|
+
}),
|
|
88
|
+
z.object({
|
|
89
|
+
sessionUpdate: z.literal("agent_message_chunk"),
|
|
90
|
+
parentToolCallId: z.string().optional(),
|
|
91
|
+
messageId: z.string(),
|
|
92
|
+
content: contentBlockSchema,
|
|
93
|
+
}),
|
|
94
|
+
z.object({
|
|
95
|
+
sessionUpdate: z.literal("agent_thought_chunk"),
|
|
96
|
+
parentToolCallId: z.string().optional(),
|
|
97
|
+
messageId: z.string(),
|
|
98
|
+
content: contentBlockSchema,
|
|
99
|
+
}),
|
|
100
|
+
z.object({
|
|
101
|
+
sessionUpdate: z.literal("tool_call"),
|
|
102
|
+
toolCallId: z.string(),
|
|
103
|
+
/** Set when a sub-agent made this call: the sub-agent's own (spawn) tool call. */
|
|
104
|
+
parentToolCallId: z.string().optional(),
|
|
105
|
+
title: z.string(),
|
|
106
|
+
kind: toolKindSchema,
|
|
107
|
+
status: toolCallStatusSchema,
|
|
108
|
+
rawInput: z.unknown().optional(),
|
|
109
|
+
content: z.array(toolCallContentSchema).optional(),
|
|
110
|
+
locations: z.array(toolCallLocationSchema).optional(),
|
|
111
|
+
detail: toolDetailSchema.optional(),
|
|
112
|
+
}),
|
|
113
|
+
z.object({
|
|
114
|
+
sessionUpdate: z.literal("tool_call_update"),
|
|
115
|
+
toolCallId: z.string(),
|
|
116
|
+
parentToolCallId: z.string().optional(),
|
|
117
|
+
status: toolCallStatusSchema.optional(),
|
|
118
|
+
title: z.string().optional(),
|
|
119
|
+
detail: toolDetailSchema.optional(),
|
|
120
|
+
/** Replaces the tool call's content when present. */
|
|
121
|
+
content: z.array(toolCallContentSchema).optional(),
|
|
122
|
+
/** Streamed output appended to the tool call's running output. */
|
|
123
|
+
appendOutput: z.string().optional(),
|
|
124
|
+
rawOutput: z.unknown().optional(),
|
|
125
|
+
}),
|
|
126
|
+
z.object({
|
|
127
|
+
sessionUpdate: z.literal("plan"),
|
|
128
|
+
entries: z.array(planEntrySchema),
|
|
129
|
+
}),
|
|
130
|
+
z.object({
|
|
131
|
+
sessionUpdate: z.literal("available_commands_update"),
|
|
132
|
+
availableCommands: z.array(z.object({ name: z.string(), description: z.string(), hint: z.string().optional() })),
|
|
133
|
+
}),
|
|
134
|
+
z.object({
|
|
135
|
+
sessionUpdate: z.literal("current_mode_update"),
|
|
136
|
+
currentModeId: z.string(),
|
|
137
|
+
}),
|
|
138
|
+
z.object({
|
|
139
|
+
sessionUpdate: z.literal("session_info_update"),
|
|
140
|
+
title: z.string().optional(),
|
|
141
|
+
model: z.string().optional(),
|
|
142
|
+
}),
|
|
143
|
+
z.object({
|
|
144
|
+
sessionUpdate: z.literal("usage_update"),
|
|
145
|
+
usedTokens: z.number().optional(),
|
|
146
|
+
contextWindow: z.number().optional(),
|
|
147
|
+
}),
|
|
148
|
+
// ── LinkShell extensions ──
|
|
149
|
+
z.object({
|
|
150
|
+
sessionUpdate: z.literal("ls_message_done"),
|
|
151
|
+
parentToolCallId: z.string().optional(),
|
|
152
|
+
messageId: z.string(),
|
|
153
|
+
role: z.enum(["agent", "thought", "user"]),
|
|
154
|
+
}),
|
|
155
|
+
/** Selectable session settings (model, permission mode, effort…) as the agent reports them. */
|
|
156
|
+
z.object({
|
|
157
|
+
sessionUpdate: z.literal("ls_config"),
|
|
158
|
+
options: z.array(sessionConfigOptionSchema),
|
|
159
|
+
}),
|
|
160
|
+
z.object({
|
|
161
|
+
sessionUpdate: z.literal("ls_notice"),
|
|
162
|
+
level: z.enum(["info", "warning"]),
|
|
163
|
+
title: z.string(),
|
|
164
|
+
detail: z.string().optional(),
|
|
165
|
+
}),
|
|
166
|
+
z.object({
|
|
167
|
+
sessionUpdate: z.literal("ls_turn"),
|
|
168
|
+
/** A sub-agent's turn: whether the sub-agent under this call is working. */
|
|
169
|
+
parentToolCallId: z.string().optional(),
|
|
170
|
+
state: z.enum(["started", "ended"]),
|
|
171
|
+
turnId: z.string().optional(),
|
|
172
|
+
stopReason: stopReasonSchema.optional(),
|
|
173
|
+
}),
|
|
174
|
+
z.object({
|
|
175
|
+
sessionUpdate: z.literal("ls_permission"),
|
|
176
|
+
requestId: z.string(),
|
|
177
|
+
toolCallId: z.string().optional(),
|
|
178
|
+
title: z.string(),
|
|
179
|
+
detail: z.string().optional(),
|
|
180
|
+
options: z.array(permissionOptionSchema).min(1),
|
|
181
|
+
}),
|
|
182
|
+
z.object({
|
|
183
|
+
sessionUpdate: z.literal("ls_permission_resolved"),
|
|
184
|
+
requestId: z.string(),
|
|
185
|
+
optionId: z.string().optional(),
|
|
186
|
+
}),
|
|
187
|
+
z.object({
|
|
188
|
+
sessionUpdate: z.literal("ls_driver"),
|
|
189
|
+
driver: sessionDriverSchema,
|
|
190
|
+
deviceId: z.string().optional(),
|
|
191
|
+
}),
|
|
192
|
+
z.object({
|
|
193
|
+
sessionUpdate: z.literal("ls_status"),
|
|
194
|
+
state: sessionStateSchema,
|
|
195
|
+
}),
|
|
196
|
+
z.object({
|
|
197
|
+
sessionUpdate: z.literal("ls_error"),
|
|
198
|
+
code: z.string(),
|
|
199
|
+
message: z.string(),
|
|
200
|
+
hint: z.string().optional(),
|
|
201
|
+
}),
|
|
202
|
+
]);
|
|
203
|
+
/** One entry in a session's append-only event log. */
|
|
204
|
+
export const sessionEventSchema = z.object({
|
|
205
|
+
sessionId: z.string(),
|
|
206
|
+
seq: z.number().int().positive(),
|
|
207
|
+
ts: z.number(),
|
|
208
|
+
update: sessionUpdateSchema,
|
|
209
|
+
});
|
|
210
|
+
//# sourceMappingURL=updates.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"updates.js","sourceRoot":"","sources":["../../../../wire/src/updates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE7G,wEAAwE;AACxE,6EAA6E;AAC7E,gCAAgC;AAEhC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IAC7D,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IACvD,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;QACxB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;QACpB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC3B,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC3B,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC;QAChC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;QACf,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,yFAAyF;QACzF,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,QAAQ,EAAE;KACpD,CAAC;CACH,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;AAG9F,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IAChE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;IACrE,wCAAwC;IACxC,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;QACvB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;QACzC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;KACpB,CAAC;IACF,kFAAkF;IAClF,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;QACxB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC3C,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC/B,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;KACjB,CAAC;IACF,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;CAClE,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,sBAAsB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC7C,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;CAClC,CAAC,CAAC;AAGH;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,kBAAkB,CAAC,MAAM,EAAE;IAC3D,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;IAC1E,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;QAC3B,uFAAuF;QACvF,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;QACvF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC3B,oDAAoD;QACpD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAChC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC7B,CAAC;IACF,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC;IAC5F,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC;IACzE,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC;IAChF,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC,EAAE,CAAC;IAC3C,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC;CAChF,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC;IAC7D,MAAM,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,aAAa,EAAE,WAAW,CAAC,CAAC;CACxD,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,EAAE,YAAY,EAAE,SAAS,CAAC,CAAC,CAAC;AAGpG,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,MAAM,CAAC;IAChD,gEAAgE;IAChE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC;IACvE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;IACnB,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;CACvG,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,kBAAkB,CAAC,eAAe,EAAE;IACvE,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,oBAAoB,CAAC;QAC9C,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAChC,OAAO,EAAE,kBAAkB;KAC5B,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC;QAC/C,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACvC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;QACrB,OAAO,EAAE,kBAAkB;KAC5B,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC;QAC/C,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACvC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;QACrB,OAAO,EAAE,kBAAkB;KAC5B,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;QACrC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;QACtB,kFAAkF;QAClF,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACvC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,IAAI,EAAE,cAAc;QACpB,MAAM,EAAE,oBAAoB;QAC5B,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;QAChC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;QAClD,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,QAAQ,EAAE;QACrD,MAAM,EAAE,gBAAgB,CAAC,QAAQ,EAAE;KACpC,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,kBAAkB,CAAC;QAC5C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;QACtB,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACvC,MAAM,EAAE,oBAAoB,CAAC,QAAQ,EAAE;QACvC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC5B,MAAM,EAAE,gBAAgB,CAAC,QAAQ,EAAE;QACnC,qDAAqD;QACrD,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC,QAAQ,EAAE;QAClD,kEAAkE;QAClE,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACnC,SAAS,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;KAClC,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;QAChC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC;KAClC,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,2BAA2B,CAAC;QACrD,iBAAiB,EAAE,CAAC,CAAC,KAAK,CACxB,CAAC,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,CACrF;KACF,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC;QAC/C,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;KAC1B,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,qBAAqB,CAAC;QAC/C,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC5B,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC7B,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC;QACxC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACjC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KACrC,CAAC;IACF,6BAA6B;IAC7B,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,iBAAiB,CAAC;QAC3C,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACvC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;QACrB,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;KAC3C,CAAC;IACF,+FAA+F;IAC/F,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;QACrC,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,yBAAyB,CAAC;KAC5C,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;QACrC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;QAClC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC9B,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;QACnC,4EAA4E;QAC5E,gBAAgB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACvC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACnC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC7B,UAAU,EAAE,gBAAgB,CAAC,QAAQ,EAAE;KACxC,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC;QACzC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;QACrB,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QACjC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;QACjB,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC7B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;KAChD,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,wBAAwB,CAAC;QAClD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;QACrB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAChC,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;QACrC,MAAM,EAAE,mBAAmB;QAC3B,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAChC,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC;QACrC,KAAK,EAAE,kBAAkB;KAC1B,CAAC;IACF,CAAC,CAAC,MAAM,CAAC;QACP,aAAa,EAAE,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC;QACpC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE;QACnB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC5B,CAAC;CACH,CAAC,CAAC;AAIH,sDAAsD;AACtD,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE;IACrB,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IAChC,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,MAAM,EAAE,mBAAmB;CAC5B,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@linkshell/gateway",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/gateway/src/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"zod": "^3.24.3",
|
|
30
30
|
"ws": "^8.18.1",
|
|
31
|
+
"@linkshell/wire": "0.1.0",
|
|
32
|
+
"@linkshell/gateway-v2": "0.1.0",
|
|
31
33
|
"@linkshell/protocol": "0.4.10"
|
|
32
34
|
},
|
|
33
35
|
"devDependencies": {
|
|
@@ -35,6 +37,9 @@
|
|
|
35
37
|
"tsx": "^4.19.3",
|
|
36
38
|
"vitest": "^3.2.1"
|
|
37
39
|
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=22.13"
|
|
42
|
+
},
|
|
38
43
|
"scripts": {
|
|
39
44
|
"build": "tsc -p tsconfig.json",
|
|
40
45
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
package/src/index.ts
CHANGED
|
@@ -24,7 +24,11 @@ import {
|
|
|
24
24
|
handleTunnelRequest,
|
|
25
25
|
cleanupSessionTunnels,
|
|
26
26
|
} from "./tunnel.js";
|
|
27
|
-
import { AUTH_REQUIRED, requireAuth, checkWsAuth, validateRequest, checkSubscriptionByUserId, canReadSessionDetail } from "./auth-middleware.js";
|
|
27
|
+
import { AUTH_REQUIRED, SUPABASE_ANON_KEY, SUPABASE_URL, requireAuth, checkWsAuth, validateRequest, checkSubscriptionByUserId, canReadSessionDetail } from "./auth-middleware.js";
|
|
28
|
+
import { mkdirSync } from "node:fs";
|
|
29
|
+
import { dirname } from "node:path";
|
|
30
|
+
import { Gateway as RelayGateway, supabaseVerifier } from "@linkshell/gateway-v2";
|
|
31
|
+
import { RELAY_PATH } from "@linkshell/wire";
|
|
28
32
|
import { setCors } from "./cors.js";
|
|
29
33
|
import { serveWeb, serveWebAsset, webEnabled, webDistPath } from "./static-web.js";
|
|
30
34
|
|
|
@@ -282,7 +286,7 @@ async function handleRequest(
|
|
|
282
286
|
|
|
283
287
|
// Health check
|
|
284
288
|
if (method === "GET" && url.pathname === "/healthz") {
|
|
285
|
-
json(res, 200, { ok: true, version: GATEWAY_VERSION });
|
|
289
|
+
json(res, 200, { ok: true, version: GATEWAY_VERSION, relay: relay.connected });
|
|
286
290
|
return;
|
|
287
291
|
}
|
|
288
292
|
|
|
@@ -508,9 +512,47 @@ const wss = new WebSocketServer({
|
|
|
508
512
|
maxPayload: MAX_WS_MESSAGE_SIZE,
|
|
509
513
|
});
|
|
510
514
|
|
|
515
|
+
// ── v2 relay (end-to-end encrypted, routes ciphertext only) ─────────
|
|
516
|
+
// Served on the same port at RELAY_PATH. Pairings and machine/device keys are
|
|
517
|
+
// kept in SQLite at RELAY_DATA_PATH (a persistent volume in production).
|
|
518
|
+
const relayDataPath = process.env.RELAY_DATA_PATH ?? "./data/relay.db";
|
|
519
|
+
mkdirSync(dirname(relayDataPath), { recursive: true });
|
|
520
|
+
const relay = new RelayGateway({
|
|
521
|
+
port: 0,
|
|
522
|
+
databasePath: relayDataPath,
|
|
523
|
+
verifyToken: SUPABASE_URL && SUPABASE_ANON_KEY ? supabaseVerifier(SUPABASE_URL, SUPABASE_ANON_KEY) : undefined,
|
|
524
|
+
log: (message) => log("info", `[relay] ${message}`),
|
|
525
|
+
// The official gateway is a subscription service: a computer needs an active
|
|
526
|
+
// Pro account to use it (as with v1). Devices only reach computers they're
|
|
527
|
+
// paired with or share an account with, so they need nothing more.
|
|
528
|
+
admit: AUTH_REQUIRED
|
|
529
|
+
? async ({ role, userId }) => {
|
|
530
|
+
if (role !== "machine") return undefined;
|
|
531
|
+
if (!userId) return "官方网关需要登录 Pro 账号:在电脑上运行 linkshell login";
|
|
532
|
+
const subscription = await checkSubscriptionByUserId(userId);
|
|
533
|
+
// A lookup failure isn't a lapsed subscription; don't lock people out over it.
|
|
534
|
+
if (subscription.status === "inactive") return "官方网关需要 Pro 订阅:https://itool.tech";
|
|
535
|
+
if (subscription.status === "unknown") log("warn", `[relay] subscription check unavailable (${subscription.reason}); admitting ${userId}`);
|
|
536
|
+
return undefined;
|
|
537
|
+
}
|
|
538
|
+
: undefined,
|
|
539
|
+
});
|
|
540
|
+
relay.attach();
|
|
541
|
+
|
|
511
542
|
server.on("upgrade", (request, socket, head) => {
|
|
512
543
|
const url = new URL(request.url ?? "/", `http://${request.headers.host}`);
|
|
513
544
|
|
|
545
|
+
if (url.pathname === RELAY_PATH) {
|
|
546
|
+
const ip = getClientIp(request);
|
|
547
|
+
if (!isRateLimitBypassed(ip) && !wsConnectLimiter.allow(ip)) {
|
|
548
|
+
socket.write("HTTP/1.1 429 Too Many Requests\r\n\r\n");
|
|
549
|
+
socket.destroy();
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
relay.handleUpgrade(request, socket, head);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
|
|
514
556
|
// Tunnel WebSocket upgrade (for HMR etc.)
|
|
515
557
|
const tunnelParsed = parseTunnelPath(url.pathname);
|
|
516
558
|
if (tunnelParsed) {
|
|
@@ -851,6 +893,7 @@ function shutdown() {
|
|
|
851
893
|
pairingManager.destroy();
|
|
852
894
|
tokenManager.destroy();
|
|
853
895
|
hostAuthManager.destroy();
|
|
896
|
+
void relay.stop();
|
|
854
897
|
server.close(() => {
|
|
855
898
|
process.stdout.write("[gateway] stopped\n");
|
|
856
899
|
process.exit(0);
|
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { z } from "zod";
|
|
2
|
-
import type { SessionManager } from "./sessions.js";
|
|
3
|
-
import type { TokenManager } from "./tokens.js";
|
|
4
|
-
export declare const agentPermissionHttpBodySchema: z.ZodDiscriminatedUnion<"protocol", [z.ZodObject<{
|
|
5
|
-
protocol: z.ZodLiteral<"v2">;
|
|
6
|
-
sessionId: z.ZodString;
|
|
7
|
-
conversationId: z.ZodString;
|
|
8
|
-
requestId: z.ZodString;
|
|
9
|
-
outcome: z.ZodEnum<["allow", "deny", "cancelled"]>;
|
|
10
|
-
optionId: z.ZodOptional<z.ZodString>;
|
|
11
|
-
terminalId: z.ZodOptional<z.ZodString>;
|
|
12
|
-
agentSessionId: z.ZodOptional<z.ZodString>;
|
|
13
|
-
}, "strip", z.ZodTypeAny, {
|
|
14
|
-
protocol: "v2";
|
|
15
|
-
sessionId: string;
|
|
16
|
-
conversationId: string;
|
|
17
|
-
requestId: string;
|
|
18
|
-
outcome: "allow" | "deny" | "cancelled";
|
|
19
|
-
optionId?: string | undefined;
|
|
20
|
-
terminalId?: string | undefined;
|
|
21
|
-
agentSessionId?: string | undefined;
|
|
22
|
-
}, {
|
|
23
|
-
protocol: "v2";
|
|
24
|
-
sessionId: string;
|
|
25
|
-
conversationId: string;
|
|
26
|
-
requestId: string;
|
|
27
|
-
outcome: "allow" | "deny" | "cancelled";
|
|
28
|
-
optionId?: string | undefined;
|
|
29
|
-
terminalId?: string | undefined;
|
|
30
|
-
agentSessionId?: string | undefined;
|
|
31
|
-
}>, z.ZodObject<{
|
|
32
|
-
protocol: z.ZodLiteral<"legacy">;
|
|
33
|
-
sessionId: z.ZodString;
|
|
34
|
-
conversationId: z.ZodOptional<z.ZodString>;
|
|
35
|
-
agentSessionId: z.ZodOptional<z.ZodString>;
|
|
36
|
-
requestId: z.ZodString;
|
|
37
|
-
outcome: z.ZodEnum<["allow", "deny", "cancelled"]>;
|
|
38
|
-
optionId: z.ZodOptional<z.ZodString>;
|
|
39
|
-
terminalId: z.ZodOptional<z.ZodString>;
|
|
40
|
-
}, "strip", z.ZodTypeAny, {
|
|
41
|
-
protocol: "legacy";
|
|
42
|
-
sessionId: string;
|
|
43
|
-
requestId: string;
|
|
44
|
-
outcome: "allow" | "deny" | "cancelled";
|
|
45
|
-
conversationId?: string | undefined;
|
|
46
|
-
optionId?: string | undefined;
|
|
47
|
-
terminalId?: string | undefined;
|
|
48
|
-
agentSessionId?: string | undefined;
|
|
49
|
-
}, {
|
|
50
|
-
protocol: "legacy";
|
|
51
|
-
sessionId: string;
|
|
52
|
-
requestId: string;
|
|
53
|
-
outcome: "allow" | "deny" | "cancelled";
|
|
54
|
-
conversationId?: string | undefined;
|
|
55
|
-
optionId?: string | undefined;
|
|
56
|
-
terminalId?: string | undefined;
|
|
57
|
-
agentSessionId?: string | undefined;
|
|
58
|
-
}>, z.ZodObject<{
|
|
59
|
-
protocol: z.ZodLiteral<"terminal">;
|
|
60
|
-
sessionId: z.ZodString;
|
|
61
|
-
conversationId: z.ZodOptional<z.ZodString>;
|
|
62
|
-
requestId: z.ZodString;
|
|
63
|
-
outcome: z.ZodEnum<["allow", "deny", "cancelled"]>;
|
|
64
|
-
optionId: z.ZodOptional<z.ZodString>;
|
|
65
|
-
terminalId: z.ZodOptional<z.ZodString>;
|
|
66
|
-
agentSessionId: z.ZodOptional<z.ZodString>;
|
|
67
|
-
}, "strip", z.ZodTypeAny, {
|
|
68
|
-
protocol: "terminal";
|
|
69
|
-
sessionId: string;
|
|
70
|
-
requestId: string;
|
|
71
|
-
outcome: "allow" | "deny" | "cancelled";
|
|
72
|
-
conversationId?: string | undefined;
|
|
73
|
-
optionId?: string | undefined;
|
|
74
|
-
terminalId?: string | undefined;
|
|
75
|
-
agentSessionId?: string | undefined;
|
|
76
|
-
}, {
|
|
77
|
-
protocol: "terminal";
|
|
78
|
-
sessionId: string;
|
|
79
|
-
requestId: string;
|
|
80
|
-
outcome: "allow" | "deny" | "cancelled";
|
|
81
|
-
conversationId?: string | undefined;
|
|
82
|
-
optionId?: string | undefined;
|
|
83
|
-
terminalId?: string | undefined;
|
|
84
|
-
agentSessionId?: string | undefined;
|
|
85
|
-
}>]>;
|
|
86
|
-
export type AgentPermissionHttpBody = z.infer<typeof agentPermissionHttpBodySchema>;
|
|
87
|
-
export type AgentPermissionHttpResult = {
|
|
88
|
-
status: number;
|
|
89
|
-
forwarded?: Array<{
|
|
90
|
-
type: string;
|
|
91
|
-
terminalId?: string;
|
|
92
|
-
}>;
|
|
93
|
-
ack?: AgentPermissionAck;
|
|
94
|
-
body: {
|
|
95
|
-
ok?: true;
|
|
96
|
-
error?: "unauthorized" | "session_not_found" | "host_not_connected" | "invalid_payload" | "permission_not_delivered" | "permission_ack_timeout";
|
|
97
|
-
message?: string;
|
|
98
|
-
resolved?: boolean;
|
|
99
|
-
delivered?: boolean;
|
|
100
|
-
};
|
|
101
|
-
};
|
|
102
|
-
export type AgentPermissionAck = {
|
|
103
|
-
requestId: string;
|
|
104
|
-
decision: "allow" | "deny";
|
|
105
|
-
resolved: boolean;
|
|
106
|
-
delivered: boolean;
|
|
107
|
-
source?: string;
|
|
108
|
-
message?: string;
|
|
109
|
-
};
|
|
110
|
-
export declare function resolveAgentPermissionHttpAck(input: {
|
|
111
|
-
sessionId: string;
|
|
112
|
-
ack: AgentPermissionAck;
|
|
113
|
-
}): boolean;
|
|
114
|
-
export declare function forwardAgentPermissionHttp(input: {
|
|
115
|
-
token: string | null;
|
|
116
|
-
body: AgentPermissionHttpBody;
|
|
117
|
-
sessionManager: SessionManager;
|
|
118
|
-
tokenManager: TokenManager;
|
|
119
|
-
}): Promise<AgentPermissionHttpResult>;
|
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
import { createEnvelope, parseTypedPayload, serializeEnvelope, } from "@linkshell/protocol";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
const permissionOutcomeSchema = z.enum(["allow", "deny", "cancelled"]);
|
|
4
|
-
const PERMISSION_ACK_TIMEOUT_MS = 12_000;
|
|
5
|
-
export const agentPermissionHttpBodySchema = z.discriminatedUnion("protocol", [
|
|
6
|
-
z.object({
|
|
7
|
-
protocol: z.literal("v2"),
|
|
8
|
-
sessionId: z.string().min(1),
|
|
9
|
-
conversationId: z.string().min(1),
|
|
10
|
-
requestId: z.string().min(1),
|
|
11
|
-
outcome: permissionOutcomeSchema,
|
|
12
|
-
optionId: z.string().optional(),
|
|
13
|
-
terminalId: z.string().optional(),
|
|
14
|
-
agentSessionId: z.string().optional(),
|
|
15
|
-
}),
|
|
16
|
-
z.object({
|
|
17
|
-
protocol: z.literal("legacy"),
|
|
18
|
-
sessionId: z.string().min(1),
|
|
19
|
-
conversationId: z.string().optional(),
|
|
20
|
-
agentSessionId: z.string().optional(),
|
|
21
|
-
requestId: z.string().min(1),
|
|
22
|
-
outcome: permissionOutcomeSchema,
|
|
23
|
-
optionId: z.string().optional(),
|
|
24
|
-
terminalId: z.string().optional(),
|
|
25
|
-
}),
|
|
26
|
-
z.object({
|
|
27
|
-
protocol: z.literal("terminal"),
|
|
28
|
-
sessionId: z.string().min(1),
|
|
29
|
-
conversationId: z.string().optional(),
|
|
30
|
-
requestId: z.string().min(1),
|
|
31
|
-
outcome: permissionOutcomeSchema,
|
|
32
|
-
optionId: z.string().optional(),
|
|
33
|
-
terminalId: z.string().min(1).optional(),
|
|
34
|
-
agentSessionId: z.string().optional(),
|
|
35
|
-
}),
|
|
36
|
-
]);
|
|
37
|
-
const pendingAcks = new Map();
|
|
38
|
-
function ackKey(sessionId, requestId) {
|
|
39
|
-
return `${sessionId}:${requestId}`;
|
|
40
|
-
}
|
|
41
|
-
export function resolveAgentPermissionHttpAck(input) {
|
|
42
|
-
const key = ackKey(input.sessionId, input.ack.requestId);
|
|
43
|
-
const pending = pendingAcks.get(key);
|
|
44
|
-
if (!pending)
|
|
45
|
-
return false;
|
|
46
|
-
pendingAcks.delete(key);
|
|
47
|
-
clearTimeout(pending.timer);
|
|
48
|
-
pending.resolve(input.ack);
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
function waitForAck(sessionId, requestId) {
|
|
52
|
-
const key = ackKey(sessionId, requestId);
|
|
53
|
-
const existing = pendingAcks.get(key);
|
|
54
|
-
if (existing) {
|
|
55
|
-
pendingAcks.delete(key);
|
|
56
|
-
clearTimeout(existing.timer);
|
|
57
|
-
existing.resolve({
|
|
58
|
-
requestId,
|
|
59
|
-
decision: "deny",
|
|
60
|
-
resolved: false,
|
|
61
|
-
delivered: false,
|
|
62
|
-
message: "Superseded by a newer permission response",
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
return new Promise((resolve) => {
|
|
66
|
-
const timer = setTimeout(() => {
|
|
67
|
-
pendingAcks.delete(key);
|
|
68
|
-
resolve(null);
|
|
69
|
-
}, PERMISSION_ACK_TIMEOUT_MS);
|
|
70
|
-
pendingAcks.set(key, { resolve, timer });
|
|
71
|
-
});
|
|
72
|
-
}
|
|
73
|
-
export async function forwardAgentPermissionHttp(input) {
|
|
74
|
-
const { token, body, sessionManager, tokenManager } = input;
|
|
75
|
-
if (!token || !tokenManager.owns(token, body.sessionId)) {
|
|
76
|
-
return {
|
|
77
|
-
status: 401,
|
|
78
|
-
body: {
|
|
79
|
-
error: "unauthorized",
|
|
80
|
-
message: "Valid device token required",
|
|
81
|
-
},
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
const session = sessionManager.get(body.sessionId);
|
|
85
|
-
if (!session) {
|
|
86
|
-
return {
|
|
87
|
-
status: 404,
|
|
88
|
-
body: {
|
|
89
|
-
error: "session_not_found",
|
|
90
|
-
message: "Session not found",
|
|
91
|
-
},
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
if (!session.host || session.host.socket.readyState !== session.host.socket.OPEN) {
|
|
95
|
-
return {
|
|
96
|
-
status: 409,
|
|
97
|
-
body: {
|
|
98
|
-
error: "host_not_connected",
|
|
99
|
-
message: "Host is not connected",
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
try {
|
|
104
|
-
const envelopes = createPermissionEnvelopes(body);
|
|
105
|
-
const waitsForDelivery = envelopes.some((envelope) => envelope.type === "permission.decision");
|
|
106
|
-
const ackPromise = waitsForDelivery ? waitForAck(body.sessionId, body.requestId) : null;
|
|
107
|
-
for (const envelope of envelopes) {
|
|
108
|
-
session.host.socket.send(serializeEnvelope(envelope));
|
|
109
|
-
}
|
|
110
|
-
session.lastActivity = Date.now();
|
|
111
|
-
if (ackPromise) {
|
|
112
|
-
const ack = await ackPromise;
|
|
113
|
-
if (!ack) {
|
|
114
|
-
return {
|
|
115
|
-
status: 504,
|
|
116
|
-
forwarded: envelopes.map((envelope) => ({
|
|
117
|
-
type: envelope.type,
|
|
118
|
-
terminalId: envelope.terminalId,
|
|
119
|
-
})),
|
|
120
|
-
body: {
|
|
121
|
-
error: "permission_ack_timeout",
|
|
122
|
-
message: "Timed out waiting for host permission delivery acknowledgement",
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
}
|
|
126
|
-
if (!ack.delivered) {
|
|
127
|
-
return {
|
|
128
|
-
status: 409,
|
|
129
|
-
forwarded: envelopes.map((envelope) => ({
|
|
130
|
-
type: envelope.type,
|
|
131
|
-
terminalId: envelope.terminalId,
|
|
132
|
-
})),
|
|
133
|
-
ack,
|
|
134
|
-
body: {
|
|
135
|
-
error: "permission_not_delivered",
|
|
136
|
-
message: ack.message ?? "Permission response was not delivered to the Agent",
|
|
137
|
-
resolved: ack.resolved,
|
|
138
|
-
delivered: ack.delivered,
|
|
139
|
-
},
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
return {
|
|
143
|
-
status: 200,
|
|
144
|
-
forwarded: envelopes.map((envelope) => ({
|
|
145
|
-
type: envelope.type,
|
|
146
|
-
terminalId: envelope.terminalId,
|
|
147
|
-
})),
|
|
148
|
-
ack,
|
|
149
|
-
body: {
|
|
150
|
-
ok: true,
|
|
151
|
-
resolved: ack.resolved,
|
|
152
|
-
delivered: ack.delivered,
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
return {
|
|
157
|
-
status: 200,
|
|
158
|
-
forwarded: envelopes.map((envelope) => ({
|
|
159
|
-
type: envelope.type,
|
|
160
|
-
terminalId: envelope.terminalId,
|
|
161
|
-
})),
|
|
162
|
-
body: { ok: true },
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
catch (err) {
|
|
166
|
-
return {
|
|
167
|
-
status: 400,
|
|
168
|
-
body: {
|
|
169
|
-
error: "invalid_payload",
|
|
170
|
-
message: err instanceof Error ? err.message : "Invalid permission response",
|
|
171
|
-
},
|
|
172
|
-
};
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
function createPermissionEnvelopes(body) {
|
|
176
|
-
if (body.protocol === "v2") {
|
|
177
|
-
const payload = parseTypedPayload("agent.v2.permission.respond", {
|
|
178
|
-
conversationId: body.conversationId,
|
|
179
|
-
requestId: body.requestId,
|
|
180
|
-
outcome: body.outcome,
|
|
181
|
-
optionId: body.optionId || undefined,
|
|
182
|
-
});
|
|
183
|
-
return [createEnvelope({
|
|
184
|
-
type: "agent.v2.permission.respond",
|
|
185
|
-
sessionId: body.sessionId,
|
|
186
|
-
deviceId: "live-activity",
|
|
187
|
-
payload,
|
|
188
|
-
})];
|
|
189
|
-
}
|
|
190
|
-
if (body.protocol === "legacy") {
|
|
191
|
-
const legacyPayload = parseTypedPayload("agent.permission.response", {
|
|
192
|
-
agentSessionId: body.agentSessionId || undefined,
|
|
193
|
-
requestId: body.requestId,
|
|
194
|
-
outcome: body.outcome,
|
|
195
|
-
optionId: body.optionId || undefined,
|
|
196
|
-
});
|
|
197
|
-
const envelopes = [createEnvelope({
|
|
198
|
-
type: "agent.permission.response",
|
|
199
|
-
sessionId: body.sessionId,
|
|
200
|
-
deviceId: "live-activity",
|
|
201
|
-
payload: legacyPayload,
|
|
202
|
-
})];
|
|
203
|
-
// `pr-*` requests are terminal PermissionRequest hooks surfaced through the
|
|
204
|
-
// legacy Agent UI channel. Send the terminal decision too so older hosts, and
|
|
205
|
-
// hosts that route hook permissions through terminal handling, can resolve it
|
|
206
|
-
// without involving the mobile websocket/controller path.
|
|
207
|
-
if (body.requestId.startsWith("pr-")) {
|
|
208
|
-
const decisionPayload = parseTypedPayload("permission.decision", {
|
|
209
|
-
requestId: body.requestId,
|
|
210
|
-
decision: body.outcome === "allow" ? "allow" : "deny",
|
|
211
|
-
});
|
|
212
|
-
envelopes.push(createEnvelope({
|
|
213
|
-
type: "permission.decision",
|
|
214
|
-
sessionId: body.sessionId,
|
|
215
|
-
terminalId: body.terminalId ?? "default",
|
|
216
|
-
deviceId: "live-activity",
|
|
217
|
-
payload: decisionPayload,
|
|
218
|
-
}));
|
|
219
|
-
}
|
|
220
|
-
return envelopes;
|
|
221
|
-
}
|
|
222
|
-
const payload = parseTypedPayload("permission.decision", {
|
|
223
|
-
requestId: body.requestId,
|
|
224
|
-
decision: body.outcome === "allow" ? "allow" : "deny",
|
|
225
|
-
});
|
|
226
|
-
return [createEnvelope({
|
|
227
|
-
type: "permission.decision",
|
|
228
|
-
sessionId: body.sessionId,
|
|
229
|
-
terminalId: body.terminalId ?? "default",
|
|
230
|
-
deviceId: "live-activity",
|
|
231
|
-
payload,
|
|
232
|
-
})];
|
|
233
|
-
}
|
|
234
|
-
//# sourceMappingURL=agent-permission-http.js.map
|