@songsid/agend 2.1.4 → 2.1.5-beta.2
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/dist/channel/adapters/discord.d.ts +2 -0
- package/dist/channel/adapters/discord.js +7 -0
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +5 -0
- package/dist/channel/adapters/telegram.js +11 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/types.d.ts +7 -0
- package/dist/config.js +38 -0
- package/dist/config.js.map +1 -1
- package/dist/fleet-context.d.ts +3 -0
- package/dist/fleet-manager.d.ts +19 -0
- package/dist/fleet-manager.js +209 -29
- package/dist/fleet-manager.js.map +1 -1
- package/dist/locale.js +46 -0
- package/dist/locale.js.map +1 -1
- package/dist/login-controller.d.ts +127 -0
- package/dist/login-controller.js +450 -0
- package/dist/login-controller.js.map +1 -0
- package/dist/login-flows.d.ts +41 -3
- package/dist/login-flows.js +37 -5
- package/dist/login-flows.js.map +1 -1
- package/dist/login-manager.d.ts +12 -1
- package/dist/login-manager.js +64 -7
- package/dist/login-manager.js.map +1 -1
- package/dist/login-window-lock.d.ts +26 -0
- package/dist/login-window-lock.js +64 -0
- package/dist/login-window-lock.js.map +1 -0
- package/dist/tmux-manager.d.ts +19 -0
- package/dist/tmux-manager.js +79 -12
- package/dist/tmux-manager.js.map +1 -1
- package/dist/topic-commands.js +1 -0
- package/dist/topic-commands.js.map +1 -1
- package/dist/types.d.ts +18 -0
- package/dist/ui/web-terminal/terminal.css +17 -0
- package/dist/ui/web-terminal/terminal.html +43 -0
- package/dist/ui/web-terminal/terminal.js +148 -0
- package/dist/ui/web-terminal/vendor/LICENSE.addon-fit +19 -0
- package/dist/ui/web-terminal/vendor/LICENSE.addon-web-links +19 -0
- package/dist/ui/web-terminal/vendor/LICENSE.xterm +21 -0
- package/dist/ui/web-terminal/vendor/VENDORED.md +22 -0
- package/dist/ui/web-terminal/vendor/addon-fit.js +1 -0
- package/dist/ui/web-terminal/vendor/addon-web-links.js +1 -0
- package/dist/ui/web-terminal/vendor/xterm.css +285 -0
- package/dist/ui/web-terminal/vendor/xterm.js +1 -0
- package/dist/web-terminal-http.d.ts +53 -0
- package/dist/web-terminal-http.js +396 -0
- package/dist/web-terminal-http.js.map +1 -0
- package/dist/web-terminal.d.ts +336 -0
- package/dist/web-terminal.js +888 -0
- package/dist/web-terminal.js.map +1 -0
- package/dist/ws-server.d.ts +69 -0
- package/dist/ws-server.js +392 -0
- package/dist/ws-server.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Remote `/login` in web-terminal mode (v2.1.5, design docs/design/login-relay-v2).
|
|
3
|
+
*
|
|
4
|
+
* The fleet starts ONE command (`<backend> login`, optionally preceded by a
|
|
5
|
+
* deterministic pre-command such as `kiro-cli logout`) in a dedicated tmux
|
|
6
|
+
* server and opens a time-boxed, token-gated browser terminal onto that pane
|
|
7
|
+
* (src/web-terminal.ts). The human drives whatever TUI the CLI shows; the
|
|
8
|
+
* fleet only observes the pane to post the device URL/code, judge success and
|
|
9
|
+
* restart the backend's instances afterwards. Zero LLM involvement.
|
|
10
|
+
*
|
|
11
|
+
* Authorization surface owned here (sol reviews this as a new surface):
|
|
12
|
+
* - admin-only, re-checked inside start() — callers are not trusted
|
|
13
|
+
* - one login/install window fleet-wide, claimed SYNCHRONOUSLY before the
|
|
14
|
+
* first await (LoginWindowLock, shared with relay login and install)
|
|
15
|
+
* - only flows reviewed as having no shell escape (`noShellEscape: true`)
|
|
16
|
+
* may open a terminal; per-requester rate limit 3 starts / 5 min
|
|
17
|
+
* - every start goes through a risk confirmation button (design §3.2); the
|
|
18
|
+
* kiro variant also states that the CLI will be logged out first
|
|
19
|
+
* - the link (no secret) goes to the requesting chat; the one-time access
|
|
20
|
+
* token goes ONLY to the requester privately (adapter.sendDirect). If the
|
|
21
|
+
* private route fails the token is never posted in the channel: the
|
|
22
|
+
* admin gets a requester-only "resend" button. If neither the link nor a
|
|
23
|
+
* token route can be delivered, the session is cancelled — never left
|
|
24
|
+
* half-delivered.
|
|
25
|
+
* - errors from secret-bearing sends are logged as name/code only; every
|
|
26
|
+
* step is audited to the event log without token/cookie/URL secrets
|
|
27
|
+
* - fleet shutdown cancels the active session and waits for the confirmed
|
|
28
|
+
* tmux kill (no orphaned login CLI)
|
|
29
|
+
*/
|
|
30
|
+
import { homedir } from "node:os";
|
|
31
|
+
import { LOGIN_BACKEND_ALIASES, LOGIN_FLOWS, checkAuthStatus } from "./login-flows.js";
|
|
32
|
+
import { t } from "./locale.js";
|
|
33
|
+
import { MAX_TTL_MS, TmuxTerminalBackend, WebTerminalSession, } from "./web-terminal.js";
|
|
34
|
+
import { WebTerminalHttpServer } from "./web-terminal-http.js";
|
|
35
|
+
export const LOGIN_TOKEN_RESEND_PREFIX = "login-token:";
|
|
36
|
+
export const DEFAULT_WEB_TERMINAL_TTL_MINUTES = 10;
|
|
37
|
+
/** Design §3.1: a requester may create at most this many sessions per window. */
|
|
38
|
+
export const START_RATE_LIMIT = 3;
|
|
39
|
+
export const START_RATE_WINDOW_MS = 5 * 60_000;
|
|
40
|
+
/**
|
|
41
|
+
* Describe an error from a secret-bearing operation WITHOUT trusting anything
|
|
42
|
+
* the error carries (sol B2/B3): message, name and code are all
|
|
43
|
+
* provider-controlled and any of them may echo the payload (the token). Only
|
|
44
|
+
* a bounded, errno-like code survives; everything else is a constant.
|
|
45
|
+
*/
|
|
46
|
+
const KNOWN_ERRNO = new Set([
|
|
47
|
+
"EPIPE", "ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "EAI_AGAIN", "ENETUNREACH", "EHOSTUNREACH",
|
|
48
|
+
"EACCES", "EPERM", "ENOENT", "ECONNABORTED", "ERR_NETWORK", "ABORT_ERR", "ETELEGRAM", "UND_ERR_CONNECT_TIMEOUT",
|
|
49
|
+
]);
|
|
50
|
+
function safeErr(err) {
|
|
51
|
+
const code = err?.code;
|
|
52
|
+
if (typeof code === "number" && Number.isFinite(code))
|
|
53
|
+
return { errorKind: "error", errno: code };
|
|
54
|
+
// Exact set only — a shape-based allowlist overlaps the token's own shape (sol round 3 B2).
|
|
55
|
+
if (typeof code === "string" && KNOWN_ERRNO.has(code))
|
|
56
|
+
return { errorKind: "error", errno: code };
|
|
57
|
+
return { errorKind: "error" };
|
|
58
|
+
}
|
|
59
|
+
export class LoginController {
|
|
60
|
+
deps;
|
|
61
|
+
active = null;
|
|
62
|
+
/** True between shutdown() and reopen(): no new work is accepted. */
|
|
63
|
+
stopping = false;
|
|
64
|
+
/**
|
|
65
|
+
* Monotonic: incremented by every shutdown(), never rolled back by reopen().
|
|
66
|
+
* A continuation captures it at start and treats any change as "the world
|
|
67
|
+
* I started in is gone" — so a stopAll→startAll cannot resurrect old work.
|
|
68
|
+
*/
|
|
69
|
+
shutdownGeneration = 0;
|
|
70
|
+
backendFactory = new TmuxTerminalBackend();
|
|
71
|
+
startTimes = new Map();
|
|
72
|
+
constructor(deps) {
|
|
73
|
+
this.deps = deps;
|
|
74
|
+
}
|
|
75
|
+
isActive() { return this.active !== null; }
|
|
76
|
+
get activeBackend() { return this.active?.backend ?? null; }
|
|
77
|
+
/** Configured mode; web is the default, relay is the 2.1.5-only rollback. */
|
|
78
|
+
mode() {
|
|
79
|
+
return this.deps.fleetConfig()?.login?.mode === "relay" ? "relay" : "web";
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Start a web-terminal login. Returns a status line, or null when the
|
|
83
|
+
* confirmation buttons were posted instead (every start is confirmed once;
|
|
84
|
+
* the button calls back with skipAuthCheck + tokenPresent).
|
|
85
|
+
*/
|
|
86
|
+
async start(backendArg, chat, opts = {}) {
|
|
87
|
+
const backend = LOGIN_BACKEND_ALIASES[backendArg.toLowerCase()] ?? backendArg.toLowerCase();
|
|
88
|
+
const flow = LOGIN_FLOWS[backend];
|
|
89
|
+
if (!flow)
|
|
90
|
+
return t("login.unsupported", backendArg);
|
|
91
|
+
if (flow.remoteLogin === "unsupported") {
|
|
92
|
+
this.audit("declined_unsupported", { backend, requester: chat.userId ?? null });
|
|
93
|
+
return t("login.remote_unsupported_agent_cli", backend, flow.command);
|
|
94
|
+
}
|
|
95
|
+
if (this.stopping)
|
|
96
|
+
return t("login.web_shutting_down");
|
|
97
|
+
const generation = this.shutdownGeneration;
|
|
98
|
+
// Authorization is decided HERE, not by whoever called us.
|
|
99
|
+
if (!chat.userId || !this.deps.isFleetAdmin(chat.userId, chat.adapterId)) {
|
|
100
|
+
this.audit("denied", { backend, requester: chat.userId ?? null, adapterId: chat.adapterId });
|
|
101
|
+
return t("permission.denied");
|
|
102
|
+
}
|
|
103
|
+
const cfg = this.deps.fleetConfig();
|
|
104
|
+
if (cfg?.web_terminal?.enabled === false)
|
|
105
|
+
return t("login.web_disabled");
|
|
106
|
+
if (flow.noShellEscape !== true) {
|
|
107
|
+
this.audit("flow_not_allowed", { backend, requester: chat.userId });
|
|
108
|
+
return t("login.web_flow_not_allowed", backend);
|
|
109
|
+
}
|
|
110
|
+
if (!this.admitRate(chat.userId)) {
|
|
111
|
+
this.audit("rate_limited", { backend, requester: chat.userId });
|
|
112
|
+
return t("login.web_rate_limited", String(START_RATE_LIMIT), String(START_RATE_WINDOW_MS / 60_000));
|
|
113
|
+
}
|
|
114
|
+
// Reserve the fleet-wide window NOW, before any await (sol B1). From here
|
|
115
|
+
// on the claim is owned by this region: every exit releases it unless it
|
|
116
|
+
// was transferred to a published session (`transferred`), and every
|
|
117
|
+
// resumption after an await re-checks that the claim is still current —
|
|
118
|
+
// a fleet shutdown in between must stop us (sol round 2 B1/B2).
|
|
119
|
+
const claim = this.deps.claimWindow(backend);
|
|
120
|
+
if (!claim)
|
|
121
|
+
return this.deps.windowBusyMessage();
|
|
122
|
+
let transferred = false;
|
|
123
|
+
try {
|
|
124
|
+
return await this.startClaimed(flow, backend, chat, opts, cfg, claim, generation, () => { transferred = true; });
|
|
125
|
+
}
|
|
126
|
+
finally {
|
|
127
|
+
if (!transferred)
|
|
128
|
+
this.deps.releaseWindow(claim);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/** The fence every continuation applies after an await: stopping, a newer shutdown generation, or a lost claim. */
|
|
132
|
+
stale(generation, claim) {
|
|
133
|
+
return this.stopping || this.shutdownGeneration !== generation || (claim !== null && !this.deps.isClaimCurrent(claim));
|
|
134
|
+
}
|
|
135
|
+
async startClaimed(flow, backend, chat, opts, cfg, claim, generation, markTransferred) {
|
|
136
|
+
if (!opts.skipAuthCheck) {
|
|
137
|
+
// First pass: find out whether the CLI still holds a token, then ask for
|
|
138
|
+
// the explicit go-ahead (design §3.2). The button re-enters with the
|
|
139
|
+
// answer; the window is released (by the caller's finally) meanwhile.
|
|
140
|
+
let tokenPresent = false;
|
|
141
|
+
if (flow.authCheck)
|
|
142
|
+
tokenPresent = (await (this.deps.checkAuth ?? checkAuthStatus)(flow.authCheck)) === "valid";
|
|
143
|
+
if (this.stale(generation, claim))
|
|
144
|
+
return t("login.web_shutting_down");
|
|
145
|
+
this.deps.releaseWindow(claim); // nothing runs until the button is pressed
|
|
146
|
+
const logoutFirst = tokenPresent && flow.preCommand?.when === "token-present";
|
|
147
|
+
try {
|
|
148
|
+
await this.deps.postButtons({
|
|
149
|
+
prefix: "login-confirm:",
|
|
150
|
+
instanceName: backend,
|
|
151
|
+
chat,
|
|
152
|
+
message: logoutFirst
|
|
153
|
+
? `${t("login.web_confirm", backend)}\n${t("login.still_valid_precommand", backend, flow.preCommand.command)}`
|
|
154
|
+
: t("login.web_confirm", backend),
|
|
155
|
+
choices: [
|
|
156
|
+
{ action: tokenPresent ? "go-relogin" : "go", label: t("login.web_confirm_go") },
|
|
157
|
+
{ action: "cancel", label: t("login.relogin_cancel") },
|
|
158
|
+
],
|
|
159
|
+
expiredText: t("buttons.stale"),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
this.deps.logger.warn({ ...safeErr(err), backend }, "Failed to post login confirmation");
|
|
164
|
+
return t("login.failed", backend, t("login.web_confirm_failed"));
|
|
165
|
+
}
|
|
166
|
+
if (this.stale(generation, null)) {
|
|
167
|
+
// The prompt went out while (or after) we stopped. It cannot be
|
|
168
|
+
// withdrawn from here; a click on it starts a FRESH start() that runs
|
|
169
|
+
// every check again (admin, allowlist, rate, claim, generation).
|
|
170
|
+
this.audit("stale_confirmation", { backend, requester: chat.userId });
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
const userId = chat.userId;
|
|
175
|
+
const command = this.buildCommand(flow, opts.tokenPresent === true);
|
|
176
|
+
const ttlMs = this.ttlMs(cfg);
|
|
177
|
+
const spec = {
|
|
178
|
+
kind: "login",
|
|
179
|
+
backend,
|
|
180
|
+
command,
|
|
181
|
+
cwd: process.env.HOME ?? homedir(),
|
|
182
|
+
ttlMs,
|
|
183
|
+
observe: {
|
|
184
|
+
urlPattern: flow.urlPattern,
|
|
185
|
+
codePattern: flow.codePattern,
|
|
186
|
+
successPattern: flow.successPattern,
|
|
187
|
+
failures: flow.failures,
|
|
188
|
+
},
|
|
189
|
+
requester: { adapterId: chat.adapterId, userId, chatId: chat.chatId, threadId: chat.threadId },
|
|
190
|
+
};
|
|
191
|
+
const logger = this.deps.logger;
|
|
192
|
+
const entry = {
|
|
193
|
+
claim, session: null, http: null, backend, chat,
|
|
194
|
+
requesterUserId: userId, url: "", tokenDelivered: false, silent: false,
|
|
195
|
+
};
|
|
196
|
+
const events = {
|
|
197
|
+
onHint: (url, code) => this.sendHint(chat, backend, url, code),
|
|
198
|
+
onDone: async (result) => {
|
|
199
|
+
this.releaseEntry(entry);
|
|
200
|
+
if (!entry.silent)
|
|
201
|
+
await this.reportDone(chat, backend, result);
|
|
202
|
+
},
|
|
203
|
+
onAudit: (event, fields) => this.audit(event.replace(/^web_terminal_/, ""), fields),
|
|
204
|
+
};
|
|
205
|
+
// A throwing factory must not leak the claim: the caller's finally covers
|
|
206
|
+
// it because `markTransferred` is only called once the entry is published.
|
|
207
|
+
entry.session = (this.deps.createSession ?? ((s, e, l) => new WebTerminalSession(s, e, this.backendFactory, l)))(spec, events, logger);
|
|
208
|
+
this.active = entry;
|
|
209
|
+
markTransferred(); // from here the entry owns the claim (onDone/abort/shutdown release it)
|
|
210
|
+
this.noteStart(userId);
|
|
211
|
+
try {
|
|
212
|
+
await entry.session.start();
|
|
213
|
+
if (this.stale(generation, claim))
|
|
214
|
+
return this.abort(entry, t("login.web_shutting_down"), "fleet shutdown");
|
|
215
|
+
const http = (this.deps.createHttp ?? ((s, l, o) => new WebTerminalHttpServer(s, l, o)))(entry.session, logger, {
|
|
216
|
+
bind: cfg?.web_terminal?.bind,
|
|
217
|
+
hostname: cfg?.hostname || "localhost",
|
|
218
|
+
});
|
|
219
|
+
entry.http = http;
|
|
220
|
+
entry.url = (await http.listen()).url;
|
|
221
|
+
if (this.stale(generation, claim))
|
|
222
|
+
return this.abort(entry, t("login.web_shutting_down"), "fleet shutdown");
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
return this.abort(entry, t("login.failed", backend, err.message), "startup failed");
|
|
226
|
+
}
|
|
227
|
+
// Delivery is part of starting: a link nobody received, or a token that
|
|
228
|
+
// reached neither the requester nor a resend button, means the session
|
|
229
|
+
// must not stay open (sol M1). A shutdown landing during any of these
|
|
230
|
+
// awaits aborts instead of announcing a terminal that no longer exists.
|
|
231
|
+
if (this.stale(generation, claim))
|
|
232
|
+
return this.abort(entry, t("login.web_shutting_down"), "fleet shutdown");
|
|
233
|
+
if (!(await this.sendLink(entry, Math.round(ttlMs / 60_000), command))) {
|
|
234
|
+
return this.abort(entry, t("login.failed", backend, t("login.web_link_failed")), "link delivery failed");
|
|
235
|
+
}
|
|
236
|
+
if (this.stale(generation, claim))
|
|
237
|
+
return this.abort(entry, t("login.web_shutting_down"), "fleet shutdown");
|
|
238
|
+
if (!(await this.sendToken(entry, { offerResend: true }))) {
|
|
239
|
+
return this.abort(entry, t("login.failed", backend, t("login.web_token_failed")), "token delivery failed");
|
|
240
|
+
}
|
|
241
|
+
if (this.stale(generation, claim))
|
|
242
|
+
return this.abort(entry, t("login.web_shutting_down"), "fleet shutdown");
|
|
243
|
+
return t("login.web_started", backend);
|
|
244
|
+
}
|
|
245
|
+
/** `/login cancel` in web mode. */
|
|
246
|
+
async cancel() {
|
|
247
|
+
if (!this.active)
|
|
248
|
+
return t("login.no_session");
|
|
249
|
+
const backend = this.active.backend;
|
|
250
|
+
await this.active.session.cancel("cancelled");
|
|
251
|
+
return t("login.cancelled", backend);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Fleet shutdown: end the active session and wait for the confirmed tmux
|
|
255
|
+
* kill (sol B3). Pre-active continuations (a start parked in its pre-check)
|
|
256
|
+
* are fenced by the lock being closed by FleetManager before this call —
|
|
257
|
+
* they observe !isClaimCurrent and stop without posting or starting.
|
|
258
|
+
*/
|
|
259
|
+
async shutdown() {
|
|
260
|
+
this.stopping = true;
|
|
261
|
+
this.shutdownGeneration++;
|
|
262
|
+
const entry = this.active;
|
|
263
|
+
if (!entry)
|
|
264
|
+
return;
|
|
265
|
+
entry.silent = true;
|
|
266
|
+
// No timer race here: cancel() is single-flight and bounded by the
|
|
267
|
+
// engine's own per-op timeouts (abort → ≤1 more 10 s stage → confirmed
|
|
268
|
+
// kill ≤31 s). Releasing ownership early would let the fleet exit while
|
|
269
|
+
// the dedicated tmux server is still being torn down.
|
|
270
|
+
await entry.session.cancel("fleet shutdown").catch(err => this.deps.logger.warn(safeErr(err), "web login shutdown failed"));
|
|
271
|
+
await entry.http?.close().catch(() => { });
|
|
272
|
+
this.releaseEntry(entry);
|
|
273
|
+
}
|
|
274
|
+
/** In-process restart after shutdown(): accept work again. */
|
|
275
|
+
reopen() { this.stopping = false; }
|
|
276
|
+
/** The "resend token" button: only the requester, only while the token is unredeemed. */
|
|
277
|
+
async resendToken(requesterUserId) {
|
|
278
|
+
const entry = this.active;
|
|
279
|
+
if (!entry)
|
|
280
|
+
return t("login.no_session");
|
|
281
|
+
if (!requesterUserId || requesterUserId !== entry.requesterUserId) {
|
|
282
|
+
this.audit("token_resend_denied", { backend: entry.backend, requester: requesterUserId ?? null });
|
|
283
|
+
return t("permission.denied");
|
|
284
|
+
}
|
|
285
|
+
if (entry.session.peekAccessToken() === null)
|
|
286
|
+
return t("login.web_token_already_used");
|
|
287
|
+
const ok = await this.sendToken(entry, { offerResend: false, isResend: true });
|
|
288
|
+
if (ok)
|
|
289
|
+
return t("login.web_token_resent");
|
|
290
|
+
// The one recovery route failed too: do not leave a token-less session
|
|
291
|
+
// holding the fleet-wide window until TTL (sol M1). Close it now.
|
|
292
|
+
return this.abort(entry, t("login.web_token_resend_failed", entry.backend), "token resend failed");
|
|
293
|
+
}
|
|
294
|
+
// ── Internals ──
|
|
295
|
+
admitRate(userId) {
|
|
296
|
+
const now = (this.deps.now ?? Date.now)();
|
|
297
|
+
const recent = (this.startTimes.get(userId) ?? []).filter(ts => now - ts < START_RATE_WINDOW_MS);
|
|
298
|
+
this.startTimes.set(userId, recent);
|
|
299
|
+
return recent.length < START_RATE_LIMIT;
|
|
300
|
+
}
|
|
301
|
+
noteStart(userId) {
|
|
302
|
+
const now = (this.deps.now ?? Date.now)();
|
|
303
|
+
this.startTimes.set(userId, [...(this.startTimes.get(userId) ?? []), now]);
|
|
304
|
+
}
|
|
305
|
+
releaseEntry(entry) {
|
|
306
|
+
if (this.active === entry)
|
|
307
|
+
this.active = null; // identity guard: never clear a newer owner
|
|
308
|
+
this.deps.releaseWindow(entry.claim);
|
|
309
|
+
}
|
|
310
|
+
/** Startup/delivery failure: end the session quietly and hand the caller the one report. */
|
|
311
|
+
async abort(entry, report, detail) {
|
|
312
|
+
entry.silent = true;
|
|
313
|
+
if (entry.session.state === "running") {
|
|
314
|
+
await entry.session.cancel(detail).catch(err => this.deps.logger.warn(safeErr(err), "web login abort failed"));
|
|
315
|
+
}
|
|
316
|
+
await entry.http?.close().catch(() => { });
|
|
317
|
+
this.releaseEntry(entry);
|
|
318
|
+
this.audit("aborted", { backend: entry.backend, requester: entry.requesterUserId, detail });
|
|
319
|
+
return report;
|
|
320
|
+
}
|
|
321
|
+
buildCommand(flow, tokenPresent) {
|
|
322
|
+
const pre = flow.preCommand;
|
|
323
|
+
const runPre = pre && (pre.when === "always" || (pre.when === "token-present" && tokenPresent));
|
|
324
|
+
return runPre ? `${pre.command}; ${flow.command}` : flow.command;
|
|
325
|
+
}
|
|
326
|
+
ttlMs(cfg) {
|
|
327
|
+
const minutes = cfg?.web_terminal?.ttl_minutes ?? DEFAULT_WEB_TERMINAL_TTL_MINUTES;
|
|
328
|
+
const clamped = Math.min(Math.max(1, Math.floor(minutes)), MAX_TTL_MS / 60_000);
|
|
329
|
+
return clamped * 60_000;
|
|
330
|
+
}
|
|
331
|
+
/** The link carries no secret: it may go to the requesting chat. Returns delivery success. */
|
|
332
|
+
async sendLink(entry, ttlMinutes, command) {
|
|
333
|
+
const { chat } = entry;
|
|
334
|
+
const text = t("login.web_link", entry.backend, String(ttlMinutes), command, entry.url);
|
|
335
|
+
try {
|
|
336
|
+
await chat.adapter.sendText(chat.chatId, text, { threadId: chat.threadId, disablePreview: true });
|
|
337
|
+
this.audit("link_sent", { backend: entry.backend, requester: entry.requesterUserId, host: hostOf(entry.url) });
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
catch (err) {
|
|
341
|
+
this.deps.logger.warn({ ...safeErr(err), backend: entry.backend }, "Failed to deliver web terminal link");
|
|
342
|
+
this.audit("link_failed", { backend: entry.backend, requester: entry.requesterUserId, ...safeErr(err) });
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* The access token goes ONLY to the requester, privately. Never into the
|
|
348
|
+
* channel: if the private route fails, offer a resend button instead.
|
|
349
|
+
* Returns true when the token was delivered OR the resend button was posted.
|
|
350
|
+
*/
|
|
351
|
+
async sendToken(entry, o) {
|
|
352
|
+
const { chat } = entry;
|
|
353
|
+
const token = entry.session.peekAccessToken();
|
|
354
|
+
if (token === null)
|
|
355
|
+
return true; // already redeemed: nothing to deliver
|
|
356
|
+
const body = chat.adapter.type === "telegram"
|
|
357
|
+
? `${escapeHtml(t("login.web_token", entry.backend))}\n<tg-spoiler>${escapeHtml(token)}</tg-spoiler>`
|
|
358
|
+
: `${t("login.web_token", entry.backend)}\n\`${token}\``;
|
|
359
|
+
if (typeof chat.adapter.sendDirect === "function") {
|
|
360
|
+
try {
|
|
361
|
+
await chat.adapter.sendDirect(entry.requesterUserId, body, {
|
|
362
|
+
format: chat.adapter.type === "telegram" ? "html" : "text",
|
|
363
|
+
disablePreview: true,
|
|
364
|
+
});
|
|
365
|
+
entry.tokenDelivered = true;
|
|
366
|
+
this.audit(o.isResend ? "token_resent" : "token_sent", { backend: entry.backend, requester: entry.requesterUserId, via: "dm" });
|
|
367
|
+
return true;
|
|
368
|
+
}
|
|
369
|
+
catch (err) {
|
|
370
|
+
// Never the message: a provider error may echo the payload (the token).
|
|
371
|
+
this.deps.logger.warn({ ...safeErr(err), backend: entry.backend }, "web terminal token DM failed");
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
entry.tokenDelivered = false;
|
|
375
|
+
this.audit("token_dm_failed", { backend: entry.backend, requester: entry.requesterUserId });
|
|
376
|
+
if (!o.offerResend)
|
|
377
|
+
return false;
|
|
378
|
+
try {
|
|
379
|
+
await this.deps.postButtons({
|
|
380
|
+
prefix: LOGIN_TOKEN_RESEND_PREFIX,
|
|
381
|
+
instanceName: entry.backend,
|
|
382
|
+
chat,
|
|
383
|
+
message: t("login.web_token_dm_failed"),
|
|
384
|
+
choices: [{ action: "resend", label: t("login.web_resend") }],
|
|
385
|
+
expiredText: t("buttons.stale"),
|
|
386
|
+
});
|
|
387
|
+
return true;
|
|
388
|
+
}
|
|
389
|
+
catch (err) {
|
|
390
|
+
this.deps.logger.warn({ ...safeErr(err), backend: entry.backend }, "Failed to post token resend button");
|
|
391
|
+
return false;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/** Device URL (+ code) observed in the pane: same spoiler treatment as before. Errors logged without the message. */
|
|
395
|
+
async sendHint(chat, backend, url, code) {
|
|
396
|
+
const codeLine = code ? `\n${t("login.auth_code", code)}` : "";
|
|
397
|
+
try {
|
|
398
|
+
if (chat.adapter.type === "telegram") {
|
|
399
|
+
await chat.adapter.sendText(chat.chatId, `${escapeHtml(t("login.auth_hint", backend))}\n<tg-spoiler>${escapeHtml(url)}${escapeHtml(codeLine)}</tg-spoiler>`, { threadId: chat.threadId, format: "html" });
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
await chat.adapter.sendText(chat.chatId, `${t("login.auth_hint", backend)}\n${url}${codeLine}`, { threadId: chat.threadId });
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
catch (err) {
|
|
406
|
+
this.deps.logger.warn({ ...safeErr(err), backend }, "Failed to deliver login URL");
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
async reportDone(chat, backend, result) {
|
|
410
|
+
let text;
|
|
411
|
+
if (result.ok) {
|
|
412
|
+
const { woken, restarted } = await this.deps.recoverBackendInstances(backend);
|
|
413
|
+
const none = t("login.none");
|
|
414
|
+
text = t("login.success", backend, woken.length ? woken.join(", ") : none, restarted.length ? restarted.join(", ") : none);
|
|
415
|
+
}
|
|
416
|
+
else if (result.reason === "cancel" && result.detail === "cancelled") {
|
|
417
|
+
text = ""; // the cancel command's own reply already announced this
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
text = t("login.failed", backend, result.detail);
|
|
421
|
+
if (result.suggest === "relogin")
|
|
422
|
+
text += `\n${t("login.web_suggest_relogin")}`;
|
|
423
|
+
else if (result.suggest === "check-args")
|
|
424
|
+
text += `\n${t("login.web_suggest_check_args")}`;
|
|
425
|
+
}
|
|
426
|
+
if (result.cleanupFailed)
|
|
427
|
+
text += `${text ? "\n" : ""}${t("login.web_cleanup_failed", backend)}`;
|
|
428
|
+
if (!text)
|
|
429
|
+
return;
|
|
430
|
+
await chat.adapter.sendText(chat.chatId, text, { threadId: chat.threadId }).catch(() => { });
|
|
431
|
+
}
|
|
432
|
+
audit(event, fields) {
|
|
433
|
+
try {
|
|
434
|
+
this.deps.eventLog()?.insert("login", `login_web_${event}`, fields);
|
|
435
|
+
}
|
|
436
|
+
catch { /* never break the flow */ }
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function escapeHtml(s) {
|
|
440
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
441
|
+
}
|
|
442
|
+
function hostOf(url) {
|
|
443
|
+
try {
|
|
444
|
+
return new URL(url).host;
|
|
445
|
+
}
|
|
446
|
+
catch {
|
|
447
|
+
return "?";
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
//# sourceMappingURL=login-controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login-controller.js","sourceRoot":"","sources":["../src/login-controller.ts"],"names":[],"mappings":"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"}
|
package/dist/login-flows.d.ts
CHANGED
|
@@ -4,9 +4,18 @@
|
|
|
4
4
|
* Every command, menu label, prompt, and success string here was verified
|
|
5
5
|
* against the installed CLI (`--help` output or strings extracted from the
|
|
6
6
|
* release binary) rather than written from memory:
|
|
7
|
-
* - codex: `codex login --device-auth`, success "Successfully logged in"
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* - codex: `codex login --device-auth`, success "Successfully logged in".
|
|
8
|
+
* The flag is KEPT on purpose (live-verified codex 0.153.4,
|
|
9
|
+
* headless): plain `codex login` starts a callback server on the
|
|
10
|
+
* HOST's localhost:1455 and waits for a browser redirect the
|
|
11
|
+
* admin's browser can never deliver; codex itself prints "On a
|
|
12
|
+
* remote or headless machine? Use `codex login --device-auth`".
|
|
13
|
+
* The flag is hidden from `--help` but still accepted.
|
|
14
|
+
* - grok: `grok login` — plain login already IS device-code on grok 1.0.5
|
|
15
|
+
* (identical output to `--device-auth`), success "Login successful!"
|
|
16
|
+
* - kiro: `kiro-cli login` (plain, per user request): shows the four-way
|
|
17
|
+
* selector incl. "Your Organization"; Identity Center is device-code
|
|
18
|
+
* regardless of `--use-device-flow` (live-verified 2.21.1). The
|
|
10
19
|
* arrow-key menu "Select login method" with exactly the four
|
|
11
20
|
* options below; Identity Center then asks "Enter Start URL" /
|
|
12
21
|
* "Enter Region"; success "Logged in successfully"/"Logged in with"
|
|
@@ -57,6 +66,35 @@ export interface LoginFlow {
|
|
|
57
66
|
successPattern: RegExp;
|
|
58
67
|
/** Hard cap for the whole login session. */
|
|
59
68
|
timeoutMs: number;
|
|
69
|
+
/**
|
|
70
|
+
* Deterministic shell command run in the same window right before `command`
|
|
71
|
+
* (`pre; command`). `token-present`: only when the auth pre-check said the
|
|
72
|
+
* CLI still holds a token and the admin confirmed a re-login — kiro refuses
|
|
73
|
+
* `login` outright while any token record exists (live-verified 2.21.1).
|
|
74
|
+
*/
|
|
75
|
+
preCommand?: {
|
|
76
|
+
command: string;
|
|
77
|
+
when: "always" | "token-present";
|
|
78
|
+
};
|
|
79
|
+
/** Known failure strings in the dead pane → human wording + suggested next step (web mode). */
|
|
80
|
+
failures?: Array<{
|
|
81
|
+
pattern: RegExp;
|
|
82
|
+
message: string;
|
|
83
|
+
suggest?: "relogin" | "check-args" | "retry";
|
|
84
|
+
}>;
|
|
85
|
+
/**
|
|
86
|
+
* Explicit allowlist for the web terminal (design §2.3/§3): set only after a
|
|
87
|
+
* human reviewed that this CLI's login TUI offers no shell escape. A flow
|
|
88
|
+
* without it never gets a browser terminal — the scope guarantee "one
|
|
89
|
+
* command, no shell" depends on it.
|
|
90
|
+
*/
|
|
91
|
+
noShellEscape?: true;
|
|
92
|
+
/**
|
|
93
|
+
* Remote /login is declined outright for this backend (every mode), with a
|
|
94
|
+
* user-facing reason. Kept in LOGIN_FLOWS only for authCheck /
|
|
95
|
+
* loginScreenPattern, which the daemon still uses.
|
|
96
|
+
*/
|
|
97
|
+
remoteLogin?: "unsupported";
|
|
60
98
|
}
|
|
61
99
|
/**
|
|
62
100
|
* Cheap auth probes for backends that do not have an AgEnD-managed /login
|
package/dist/login-flows.js
CHANGED
|
@@ -4,9 +4,18 @@
|
|
|
4
4
|
* Every command, menu label, prompt, and success string here was verified
|
|
5
5
|
* against the installed CLI (`--help` output or strings extracted from the
|
|
6
6
|
* release binary) rather than written from memory:
|
|
7
|
-
* - codex: `codex login --device-auth`, success "Successfully logged in"
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* - codex: `codex login --device-auth`, success "Successfully logged in".
|
|
8
|
+
* The flag is KEPT on purpose (live-verified codex 0.153.4,
|
|
9
|
+
* headless): plain `codex login` starts a callback server on the
|
|
10
|
+
* HOST's localhost:1455 and waits for a browser redirect the
|
|
11
|
+
* admin's browser can never deliver; codex itself prints "On a
|
|
12
|
+
* remote or headless machine? Use `codex login --device-auth`".
|
|
13
|
+
* The flag is hidden from `--help` but still accepted.
|
|
14
|
+
* - grok: `grok login` — plain login already IS device-code on grok 1.0.5
|
|
15
|
+
* (identical output to `--device-auth`), success "Login successful!"
|
|
16
|
+
* - kiro: `kiro-cli login` (plain, per user request): shows the four-way
|
|
17
|
+
* selector incl. "Your Organization"; Identity Center is device-code
|
|
18
|
+
* regardless of `--use-device-flow` (live-verified 2.21.1). The
|
|
10
19
|
* arrow-key menu "Select login method" with exactly the four
|
|
11
20
|
* options below; Identity Center then asks "Enter Start URL" /
|
|
12
21
|
* "Enter Region"; success "Logged in successfully"/"Logged in with"
|
|
@@ -43,7 +52,10 @@ const GENERIC_URL = /https:\/\/[^\s"'<>\])]+/;
|
|
|
43
52
|
const STANDALONE_DEVICE_CODE = /^\s*([A-Z0-9]{4,10}-[A-Z0-9]{4,10})\s*$/m;
|
|
44
53
|
export const LOGIN_FLOWS = {
|
|
45
54
|
"codex": {
|
|
55
|
+
noShellEscape: true, // login TUI reviewed: menu/prompts/device code only, no shell
|
|
46
56
|
backend: "codex",
|
|
57
|
+
// Keep --device-auth: plain login needs a browser redirect to THIS host's
|
|
58
|
+
// localhost:1455, which a remote admin's browser cannot deliver (see header).
|
|
47
59
|
command: "codex login --device-auth",
|
|
48
60
|
authCheck: { argv: ["codex", "login", "status"] },
|
|
49
61
|
loginScreenPattern: /Sign in with ChatGPT/,
|
|
@@ -52,8 +64,9 @@ export const LOGIN_FLOWS = {
|
|
|
52
64
|
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
53
65
|
},
|
|
54
66
|
"grok": {
|
|
67
|
+
noShellEscape: true, // login TUI reviewed: menu/prompts/device code only, no shell
|
|
55
68
|
backend: "grok",
|
|
56
|
-
command: "grok login
|
|
69
|
+
command: "grok login",
|
|
57
70
|
authCheck: { argv: ["grok", "models"] },
|
|
58
71
|
loginScreenPattern: /Run `grok login`/,
|
|
59
72
|
// Binary template is "enter code: $CODE"; the standalone form is a fallback.
|
|
@@ -62,8 +75,9 @@ export const LOGIN_FLOWS = {
|
|
|
62
75
|
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
63
76
|
},
|
|
64
77
|
"kiro-cli": {
|
|
78
|
+
noShellEscape: true, // login TUI reviewed: menu/prompts/device code only, no shell
|
|
65
79
|
backend: "kiro-cli",
|
|
66
|
-
command: "kiro-cli login
|
|
80
|
+
command: "kiro-cli login",
|
|
67
81
|
authCheck: { argv: ["kiro-cli", "whoami", "--format", "json"] },
|
|
68
82
|
loginScreenPattern: /Select login method/,
|
|
69
83
|
menu: {
|
|
@@ -76,8 +90,18 @@ export const LOGIN_FLOWS = {
|
|
|
76
90
|
codePattern: /Code:\s*([A-Z0-9][A-Z0-9-]{3,})/,
|
|
77
91
|
successPattern: /Logged in successfully|Logged in with /,
|
|
78
92
|
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
93
|
+
// kiro-cli 2.21.1 (live-verified): any stored token record — even an
|
|
94
|
+
// expired one with a refresh token — makes `login` exit 1 with "Already
|
|
95
|
+
// logged in"; only `logout` clears it. The saved Identity Center start
|
|
96
|
+
// URL/region survive logout and pre-fill the prompts in the terminal.
|
|
97
|
+
preCommand: { command: "kiro-cli logout", when: "token-present" },
|
|
98
|
+
failures: [
|
|
99
|
+
{ pattern: /Already logged in, please logout/, message: "kiro-cli still holds a token — use Re-login (it logs out first)", suggest: "relogin" },
|
|
100
|
+
{ pattern: /error: dispatch failure/, message: "Identity Center rejected the request — check the Start URL and Region", suggest: "check-args" },
|
|
101
|
+
],
|
|
79
102
|
},
|
|
80
103
|
"claude-code": {
|
|
104
|
+
noShellEscape: true, // login TUI reviewed: menu/prompts/device code only, no shell
|
|
81
105
|
backend: "claude-code",
|
|
82
106
|
command: "claude auth login",
|
|
83
107
|
authCheck: { argv: ["claude", "auth", "status"], validPattern: /"loggedIn":\s*true/ },
|
|
@@ -87,6 +111,14 @@ export const LOGIN_FLOWS = {
|
|
|
87
111
|
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
88
112
|
},
|
|
89
113
|
"antigravity": {
|
|
114
|
+
// Remote login is UNSUPPORTED (user decision, v2.1.5): bare `agy` is the
|
|
115
|
+
// full agent CLI (tools, permission prompts, MCP) and has no isolated
|
|
116
|
+
// login sub-command — "logging in" means running the whole agent, which
|
|
117
|
+
// violates the web terminal's "one login command, no shell" boundary.
|
|
118
|
+
// /login agy is declined in every mode (no relay fallback) until upstream
|
|
119
|
+
// ships a dedicated login command. authCheck / loginScreenPattern stay for
|
|
120
|
+
// the daemon's own use.
|
|
121
|
+
remoteLogin: "unsupported",
|
|
90
122
|
backend: "antigravity",
|
|
91
123
|
command: "agy",
|
|
92
124
|
authCheck: { argv: ["agy", "models"] },
|