@songsid/agend 2.1.4-beta.27 → 2.1.4-beta.28
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/README.md +1 -1
- package/README.zh-TW.md +1 -1
- package/dist/backend/antigravity.d.ts +8 -5
- package/dist/backend/antigravity.js +150 -30
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/channel/adapters/discord.js +36 -0
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/agy-mcp-launcher.d.ts +2 -0
- package/dist/channel/agy-mcp-launcher.js +28 -0
- package/dist/channel/agy-mcp-launcher.js.map +1 -0
- package/dist/daemon.js +4 -1
- package/dist/daemon.js.map +1 -1
- package/dist/fleet-context.d.ts +9 -0
- package/dist/fleet-manager.d.ts +18 -0
- package/dist/fleet-manager.js +202 -10
- package/dist/fleet-manager.js.map +1 -1
- package/dist/instance-lifecycle.js +4 -2
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +79 -35
- package/dist/locale.js.map +1 -1
- package/dist/login-flows.js +2 -0
- package/dist/login-flows.js.map +1 -1
- package/dist/setup-wizard.js +2 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/tips.js +111 -111
- package/dist/tips.js.map +1 -1
- package/dist/topic-commands.d.ts +2 -0
- package/dist/topic-commands.js +37 -0
- package/dist/topic-commands.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-context.d.ts
CHANGED
|
@@ -116,6 +116,15 @@ export interface FleetContext {
|
|
|
116
116
|
loginSubmitInput?(text: string): Promise<string>;
|
|
117
117
|
/** Abort the active login session. */
|
|
118
118
|
cancelLoginSession?(): Promise<string>;
|
|
119
|
+
/** `/install-cli <backend>` — install a CLI in a dedicated window; returns a status line. */
|
|
120
|
+
startInstallSession?(backend: string, chat: {
|
|
121
|
+
adapter: import("./channel/types.js").ChannelAdapter;
|
|
122
|
+
adapterId: string;
|
|
123
|
+
chatId: string;
|
|
124
|
+
threadId?: string;
|
|
125
|
+
}): Promise<string>;
|
|
126
|
+
/** Abort the active install session. */
|
|
127
|
+
cancelInstallSession?(): Promise<string>;
|
|
119
128
|
/** Human-readable effective model for an instance (resolves inherited defaults). */
|
|
120
129
|
modelDisplayForInstance?(name: string): string;
|
|
121
130
|
/**
|
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -857,6 +857,24 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
857
857
|
private handleLoginConfirm;
|
|
858
858
|
/** Kiro provider button → drive the CLI's arrow-key selector. */
|
|
859
859
|
private handleLoginMenuSelect;
|
|
860
|
+
private activeInstall;
|
|
861
|
+
/** Start a CLI install session. Caller enforces admin. */
|
|
862
|
+
startInstallSession(backendArg: string, chat: {
|
|
863
|
+
adapter: ChannelAdapter;
|
|
864
|
+
adapterId: string;
|
|
865
|
+
chatId: string;
|
|
866
|
+
threadId?: string;
|
|
867
|
+
}): Promise<string>;
|
|
868
|
+
/** `/install-cli cancel` — abort the active install and remove its window. */
|
|
869
|
+
cancelInstallSession(): Promise<string>;
|
|
870
|
+
/** `command -v` on a login shell, so PATH additions from rc files count. */
|
|
871
|
+
private verifyBinaryOnLoginShell;
|
|
872
|
+
/** "Sign in now?" button after a successful install. */
|
|
873
|
+
private handleInstallLoginConfirm;
|
|
874
|
+
/** Discord native `/login` slash — shared by every adapter dispatch block. */
|
|
875
|
+
private handleLoginSlash;
|
|
876
|
+
/** Discord native `/install-cli` slash — shared by every dispatch block. */
|
|
877
|
+
private handleInstallCliSlash;
|
|
860
878
|
queueMirrorMessage(text: string): void;
|
|
861
879
|
/** Push an SSE event to all connected Web UI clients. */
|
|
862
880
|
emitSseEvent(event: string, data: unknown): void;
|
package/dist/fleet-manager.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, rmSync, readdirSync, renameSync, copyFileSync, chmodSync, statSync } from "node:fs";
|
|
2
2
|
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { spawnSync } from "node:child_process";
|
|
3
4
|
import { createServer } from "node:http";
|
|
4
5
|
import { join, dirname, basename } from "node:path";
|
|
5
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -214,6 +215,7 @@ const TIP_UNLOCK_CALLBACK_PREFIX = "tip-unlock:";
|
|
|
214
215
|
const LOGIN_CALLBACK_PREFIX = "login:";
|
|
215
216
|
const LOGIN_MENU_CALLBACK_PREFIX = "login-menu:";
|
|
216
217
|
const LOGIN_CONFIRM_CALLBACK_PREFIX = "login-confirm:";
|
|
218
|
+
const INSTALL_LOGIN_CALLBACK_PREFIX = "install-login:";
|
|
217
219
|
const CLEAR_CONFIRM_TIMEOUT_MS = 15_000;
|
|
218
220
|
/** Default lifetime for long-lived nonce prompts (clear overrides this to 15s). */
|
|
219
221
|
const NONCE_BUTTON_TIMEOUT_MS = 15 * 60_000;
|
|
@@ -1273,12 +1275,6 @@ export class FleetManager {
|
|
|
1273
1275
|
}
|
|
1274
1276
|
const backend = config.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
1275
1277
|
if (config.general_topic) {
|
|
1276
|
-
// antigravity (agy) does not read MCP instructions — fleet context and
|
|
1277
|
-
// routing instructions are not injected, so it cannot act as a dispatcher.
|
|
1278
|
-
if (backend === "antigravity") {
|
|
1279
|
-
this.logger.warn({ name }, "antigravity backend does not support MCP instructions — general dispatcher will not work correctly");
|
|
1280
|
-
this.notifyInstanceTopic(name, t("general.antigravity_unsupported"));
|
|
1281
|
-
}
|
|
1282
1278
|
this.ensureGeneralInstructions(config.working_directory, backend, name);
|
|
1283
1279
|
}
|
|
1284
1280
|
else if (kind === "fleet-topic") {
|
|
@@ -2379,6 +2375,8 @@ export class FleetManager {
|
|
|
2379
2375
|
return;
|
|
2380
2376
|
if (await this.handleLoginConfirm(data, adapterId, this.adapter ?? undefined))
|
|
2381
2377
|
return;
|
|
2378
|
+
if (await this.handleInstallLoginConfirm(data, adapterId, this.adapter ?? undefined))
|
|
2379
|
+
return;
|
|
2382
2380
|
if (await this.handleClearConfirmation(data, adapterId, this.adapter ?? undefined))
|
|
2383
2381
|
return;
|
|
2384
2382
|
if (await this.handleExitRestartPrompt(data, adapterId, this.adapter ?? undefined))
|
|
@@ -2508,6 +2506,12 @@ export class FleetManager {
|
|
|
2508
2506
|
});
|
|
2509
2507
|
await data.respond(result);
|
|
2510
2508
|
}
|
|
2509
|
+
else if (data.command === "login") {
|
|
2510
|
+
await this.handleLoginSlash(data, adapterId, this.adapter);
|
|
2511
|
+
}
|
|
2512
|
+
else if (data.command === "install-cli") {
|
|
2513
|
+
await this.handleInstallCliSlash(data, adapterId, this.adapter);
|
|
2514
|
+
}
|
|
2511
2515
|
else if (data.command === "clear") {
|
|
2512
2516
|
await this.handleClearSlash(data, adapterId);
|
|
2513
2517
|
}
|
|
@@ -2672,6 +2676,12 @@ export class FleetManager {
|
|
|
2672
2676
|
});
|
|
2673
2677
|
await data.respond(result);
|
|
2674
2678
|
}
|
|
2679
|
+
else if (data.command === "login") {
|
|
2680
|
+
await this.handleLoginSlash(data, adapterId, this.adapter);
|
|
2681
|
+
}
|
|
2682
|
+
else if (data.command === "install-cli") {
|
|
2683
|
+
await this.handleInstallCliSlash(data, adapterId, this.adapter);
|
|
2684
|
+
}
|
|
2675
2685
|
}, this.logger, "adapter.slash_command"));
|
|
2676
2686
|
await this.topicCommands.registerBotCommands().catch(e => this.logger.warn({ err: e }, "registerBotCommands failed (non-fatal)"));
|
|
2677
2687
|
// Background-probe each backend's CLI env (version/models) → cli-env cache.
|
|
@@ -2758,6 +2768,8 @@ export class FleetManager {
|
|
|
2758
2768
|
return;
|
|
2759
2769
|
if (await this.handleLoginConfirm(data, adapterId, adapter))
|
|
2760
2770
|
return;
|
|
2771
|
+
if (await this.handleInstallLoginConfirm(data, adapterId, adapter))
|
|
2772
|
+
return;
|
|
2761
2773
|
if (await this.handleClearConfirmation(data, adapterId, adapter))
|
|
2762
2774
|
return;
|
|
2763
2775
|
if (await this.handleExitRestartPrompt(data, adapterId, adapter))
|
|
@@ -3004,6 +3016,12 @@ export class FleetManager {
|
|
|
3004
3016
|
});
|
|
3005
3017
|
await data.respond(result);
|
|
3006
3018
|
}
|
|
3019
|
+
else if (data.command === "login") {
|
|
3020
|
+
await this.handleLoginSlash(data, adapterId, adapter);
|
|
3021
|
+
}
|
|
3022
|
+
else if (data.command === "install-cli") {
|
|
3023
|
+
await this.handleInstallCliSlash(data, adapterId, adapter);
|
|
3024
|
+
}
|
|
3007
3025
|
}, this.logger, `adapter[${adapterId}].slash_command`));
|
|
3008
3026
|
await adapter.start();
|
|
3009
3027
|
if (channelConfig.group_id) {
|
|
@@ -6242,6 +6260,9 @@ export class FleetManager {
|
|
|
6242
6260
|
return t("login.unsupported", backendArg);
|
|
6243
6261
|
if (this.activeLogin)
|
|
6244
6262
|
return t("login.busy", this.activeLogin.backend);
|
|
6263
|
+
// Install sessions share the window namespace — never run both at once.
|
|
6264
|
+
if (this.activeInstall)
|
|
6265
|
+
return t("install.busy");
|
|
6245
6266
|
// Token-free pre-check (5s cap): re-login while auth still works is
|
|
6246
6267
|
// usually a mistake, so it needs a confirmed click. An invalid OR
|
|
6247
6268
|
// uncertain result (timeout, missing binary) proceeds straight to login —
|
|
@@ -6273,6 +6294,9 @@ export class FleetManager {
|
|
|
6273
6294
|
async launchLoginSession(flow, backend, chat) {
|
|
6274
6295
|
if (this.activeLogin)
|
|
6275
6296
|
return t("login.busy", this.activeLogin.backend);
|
|
6297
|
+
// Install sessions share the window namespace — never run both at once.
|
|
6298
|
+
if (this.activeInstall)
|
|
6299
|
+
return t("install.busy");
|
|
6276
6300
|
const sessionName = getTmuxSession();
|
|
6277
6301
|
await TmuxManager.ensureSession(sessionName);
|
|
6278
6302
|
const tmux = new TmuxManager(sessionName, "");
|
|
@@ -6306,7 +6330,9 @@ export class FleetManager {
|
|
|
6306
6330
|
text = t("login.success", backend, woken.length ? woken.join(", ") : none, restarted.length ? restarted.join(", ") : none);
|
|
6307
6331
|
}
|
|
6308
6332
|
else if (detail === "cancelled") {
|
|
6309
|
-
|
|
6333
|
+
// The cancel command's own reply already announced this — a second
|
|
6334
|
+
// message here was a duplicate.
|
|
6335
|
+
return;
|
|
6310
6336
|
}
|
|
6311
6337
|
else {
|
|
6312
6338
|
text = t("login.failed", backend, detail);
|
|
@@ -6451,6 +6477,173 @@ export class FleetManager {
|
|
|
6451
6477
|
await this.retireNonceButtons(entry, entry.messageId ?? data.messageId, ok ? t("login.provider_selected", label) : t("login.no_session"));
|
|
6452
6478
|
return true;
|
|
6453
6479
|
}
|
|
6480
|
+
// ── Remote CLI install (`/install-cli`) ──────────────────────────────────
|
|
6481
|
+
//
|
|
6482
|
+
// Same dedicated-window model as /login (and the same LoginSession state
|
|
6483
|
+
// machine — an install is a login flow with no auth hints): run the
|
|
6484
|
+
// installer, judge by exit code, verify the binary on a fresh login shell,
|
|
6485
|
+
// then offer to chain straight into /login.
|
|
6486
|
+
activeInstall = null;
|
|
6487
|
+
/** Start a CLI install session. Caller enforces admin. */
|
|
6488
|
+
async startInstallSession(backendArg, chat) {
|
|
6489
|
+
const backend = LOGIN_BACKEND_ALIASES[backendArg.toLowerCase()] ?? backendArg.toLowerCase();
|
|
6490
|
+
const info = BACKEND_INSTALLATION_INFO[backend];
|
|
6491
|
+
if (!info)
|
|
6492
|
+
return t("install.unsupported", backendArg);
|
|
6493
|
+
if (checkBinaryInstalled(info.binary))
|
|
6494
|
+
return t("install.already", backend, info.binary);
|
|
6495
|
+
if (this.activeInstall)
|
|
6496
|
+
return t("install.busy");
|
|
6497
|
+
if (this.activeLogin)
|
|
6498
|
+
return t("login.busy", this.activeLogin.backend);
|
|
6499
|
+
const sessionName = getTmuxSession();
|
|
6500
|
+
await TmuxManager.ensureSession(sessionName);
|
|
6501
|
+
const tmux = new TmuxManager(sessionName, "");
|
|
6502
|
+
// A synthetic login flow: success is decided by the installer's exit code
|
|
6503
|
+
// (LoginSession treats a clean exit as success), never by pane text — and
|
|
6504
|
+
// installer output that happens to contain a URL must not be forwarded as
|
|
6505
|
+
// an auth hint, hence the no-op events below.
|
|
6506
|
+
const flow = {
|
|
6507
|
+
backend,
|
|
6508
|
+
command: info.install,
|
|
6509
|
+
successPattern: /$^/,
|
|
6510
|
+
timeoutMs: 10 * 60 * 1000,
|
|
6511
|
+
};
|
|
6512
|
+
const session = new LoginSession(flow, tmux, {
|
|
6513
|
+
onMenu: () => { },
|
|
6514
|
+
onAuthHint: () => { },
|
|
6515
|
+
onNeedInput: () => { },
|
|
6516
|
+
onDone: async ({ ok, detail }) => {
|
|
6517
|
+
this.activeInstall = null;
|
|
6518
|
+
if (!ok) {
|
|
6519
|
+
// A cancel is user-initiated — the cancel command's own reply already
|
|
6520
|
+
// said so; a second message here would be a duplicate.
|
|
6521
|
+
if (detail !== "cancelled") {
|
|
6522
|
+
await chat.adapter.sendText(chat.chatId, t("install.failed", backend, detail), { threadId: chat.threadId }).catch(() => { });
|
|
6523
|
+
}
|
|
6524
|
+
return;
|
|
6525
|
+
}
|
|
6526
|
+
// The installer may only have added the binary to a profile PATH; a
|
|
6527
|
+
// fresh login shell sees that, the fleet process's PATH may not.
|
|
6528
|
+
if (!this.verifyBinaryOnLoginShell(info.binary)) {
|
|
6529
|
+
await chat.adapter.sendText(chat.chatId, t("install.verify_failed", backend, info.binary), { threadId: chat.threadId }).catch(() => { });
|
|
6530
|
+
return;
|
|
6531
|
+
}
|
|
6532
|
+
if (!LOGIN_FLOWS[backend]) {
|
|
6533
|
+
await chat.adapter.sendText(chat.chatId, t("install.success_no_login", backend), { threadId: chat.threadId }).catch(() => { });
|
|
6534
|
+
return;
|
|
6535
|
+
}
|
|
6536
|
+
await this.postNonceButtonPrompt({
|
|
6537
|
+
prefix: INSTALL_LOGIN_CALLBACK_PREFIX,
|
|
6538
|
+
alertType: "login",
|
|
6539
|
+
instanceName: backend,
|
|
6540
|
+
adapter: chat.adapter,
|
|
6541
|
+
adapterId: chat.adapterId,
|
|
6542
|
+
chatId: chat.chatId,
|
|
6543
|
+
threadId: chat.threadId,
|
|
6544
|
+
message: t("install.login_prompt", backend),
|
|
6545
|
+
choices: [
|
|
6546
|
+
{ action: "go", label: t("install.login_now") },
|
|
6547
|
+
{ action: "later", label: t("install.later") },
|
|
6548
|
+
],
|
|
6549
|
+
expiredText: t("install.later_ack", backend),
|
|
6550
|
+
});
|
|
6551
|
+
},
|
|
6552
|
+
}, this.logger);
|
|
6553
|
+
this.activeInstall = { session, backend, chat };
|
|
6554
|
+
try {
|
|
6555
|
+
await session.start();
|
|
6556
|
+
}
|
|
6557
|
+
catch (err) {
|
|
6558
|
+
this.activeInstall = null;
|
|
6559
|
+
return t("install.failed", backend, err.message);
|
|
6560
|
+
}
|
|
6561
|
+
return t("install.started", backend);
|
|
6562
|
+
}
|
|
6563
|
+
/** `/install-cli cancel` — abort the active install and remove its window. */
|
|
6564
|
+
async cancelInstallSession() {
|
|
6565
|
+
if (!this.activeInstall)
|
|
6566
|
+
return t("install.no_session");
|
|
6567
|
+
const backend = this.activeInstall.backend;
|
|
6568
|
+
await this.activeInstall.session.cancel();
|
|
6569
|
+
return t("install.cancelled", backend);
|
|
6570
|
+
}
|
|
6571
|
+
/** `command -v` on a login shell, so PATH additions from rc files count. */
|
|
6572
|
+
verifyBinaryOnLoginShell(binary) {
|
|
6573
|
+
try {
|
|
6574
|
+
const result = spawnSync("bash", ["-lc", `command -v ${binary}`], { timeout: 10_000, stdio: "pipe" });
|
|
6575
|
+
return result.status === 0;
|
|
6576
|
+
}
|
|
6577
|
+
catch {
|
|
6578
|
+
return false;
|
|
6579
|
+
}
|
|
6580
|
+
}
|
|
6581
|
+
/** "Sign in now?" button after a successful install. */
|
|
6582
|
+
async handleInstallLoginConfirm(data, callbackAdapterId, receivingAdapter) {
|
|
6583
|
+
const claimed = this.consumeNonceCallback(INSTALL_LOGIN_CALLBACK_PREFIX, /^install-login:([0-9a-f]+):(go|later)$/, data, callbackAdapterId, receivingAdapter);
|
|
6584
|
+
if (claimed === null)
|
|
6585
|
+
return false;
|
|
6586
|
+
if (claimed === "consumed")
|
|
6587
|
+
return true;
|
|
6588
|
+
const { entry, action } = claimed;
|
|
6589
|
+
const backend = entry.instanceName;
|
|
6590
|
+
if (action === "later") {
|
|
6591
|
+
await this.retireNonceButtons(entry, entry.messageId ?? data.messageId, t("install.later_ack", backend));
|
|
6592
|
+
return true;
|
|
6593
|
+
}
|
|
6594
|
+
await this.retireNonceButtons(entry, entry.messageId ?? data.messageId, t("login.starting_backend", backend));
|
|
6595
|
+
const text = await this.startLoginSession(backend, {
|
|
6596
|
+
adapter: entry.adapter,
|
|
6597
|
+
adapterId: entry.adapterId,
|
|
6598
|
+
chatId: entry.chatId,
|
|
6599
|
+
threadId: entry.threadId,
|
|
6600
|
+
});
|
|
6601
|
+
if (text)
|
|
6602
|
+
await entry.adapter.sendText(entry.chatId, text, { threadId: entry.threadId }).catch(() => { });
|
|
6603
|
+
return true;
|
|
6604
|
+
}
|
|
6605
|
+
/** Discord native `/login` slash — shared by every adapter dispatch block. */
|
|
6606
|
+
async handleLoginSlash(data, adapterId, adapter) {
|
|
6607
|
+
if (!data.userId || !this.isFleetAdmin(data.userId, adapterId)) {
|
|
6608
|
+
await data.respond(t("permission.denied"));
|
|
6609
|
+
return;
|
|
6610
|
+
}
|
|
6611
|
+
const chat = { adapter, adapterId, chatId: data.channelId };
|
|
6612
|
+
if (data.options?.cancel === true) {
|
|
6613
|
+
await data.respond(await this.cancelLoginSession());
|
|
6614
|
+
return;
|
|
6615
|
+
}
|
|
6616
|
+
const code = String(data.options?.code ?? "").trim();
|
|
6617
|
+
if (code) {
|
|
6618
|
+
await data.respond(await this.loginSubmitInput(code));
|
|
6619
|
+
return;
|
|
6620
|
+
}
|
|
6621
|
+
const backend = String(data.options?.backend ?? "").trim();
|
|
6622
|
+
if (backend) {
|
|
6623
|
+
const text = await this.startLoginSession(backend, chat);
|
|
6624
|
+
await data.respond(text ?? t("login.confirm_posted"));
|
|
6625
|
+
return;
|
|
6626
|
+
}
|
|
6627
|
+
await this.promptLoginBackends(chat);
|
|
6628
|
+
await data.respond(t("login.chooser_posted"));
|
|
6629
|
+
}
|
|
6630
|
+
/** Discord native `/install-cli` slash — shared by every dispatch block. */
|
|
6631
|
+
async handleInstallCliSlash(data, adapterId, adapter) {
|
|
6632
|
+
if (!data.userId || !this.isFleetAdmin(data.userId, adapterId)) {
|
|
6633
|
+
await data.respond(t("permission.denied"));
|
|
6634
|
+
return;
|
|
6635
|
+
}
|
|
6636
|
+
if (data.options?.cancel === true) {
|
|
6637
|
+
await data.respond(await this.cancelInstallSession());
|
|
6638
|
+
return;
|
|
6639
|
+
}
|
|
6640
|
+
const backend = String(data.options?.backend ?? "").trim();
|
|
6641
|
+
if (!backend) {
|
|
6642
|
+
await data.respond(t("install.usage"));
|
|
6643
|
+
return;
|
|
6644
|
+
}
|
|
6645
|
+
await data.respond(await this.startInstallSession(backend, { adapter, adapterId, chatId: data.channelId }));
|
|
6646
|
+
}
|
|
6454
6647
|
queueMirrorMessage(text) {
|
|
6455
6648
|
const mirrorTopicId = this.fleetConfig?.channel?.mirror_topic_id;
|
|
6456
6649
|
if (mirrorTopicId == null || !this.adapter)
|
|
@@ -6622,9 +6815,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
6622
6815
|
}
|
|
6623
6816
|
/** Resolve the workspace path a backend actually uses before publishing knowledge. */
|
|
6624
6817
|
resolveKnowledgeWorkDir(workDir, backend, instanceName) {
|
|
6625
|
-
//
|
|
6626
|
-
//
|
|
6627
|
-
// CLI actually runs, not where fleet.yaml points.
|
|
6818
|
+
// Backend resolution may create/normalize the real cwd. Instructions and
|
|
6819
|
+
// skills must land where the CLI actually runs, not an assumed path.
|
|
6628
6820
|
try {
|
|
6629
6821
|
const resolved = createBackend(backend, join(getAgendHome(), "cli-env"))
|
|
6630
6822
|
.resolveWorkingDirectory?.(workDir, instanceName);
|