@songsid/agend 2.2.0-beta.2 → 2.2.0-beta.4
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/agent-cli.js +6 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/auth-api.js +2 -0
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/claude-code.d.ts +39 -2
- package/dist/backend/claude-code.js +115 -31
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +33 -3
- package/dist/backend/credential-profile.js +107 -5
- package/dist/backend/credential-profile.js.map +1 -1
- package/dist/backend/types.d.ts +1 -1
- package/dist/cache-service.d.ts +2 -0
- package/dist/cache-service.js +2 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +14 -1
- package/dist/channel/adapters/discord.js +42 -0
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +18 -3
- package/dist/channel/adapters/telegram.js +66 -10
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/telegram-markdown.d.ts +6 -0
- package/dist/channel/telegram-markdown.js +104 -0
- package/dist/channel/telegram-markdown.js.map +1 -0
- package/dist/channel/tool-router.js +5 -4
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +27 -4
- package/dist/config-validator.js +10 -5
- package/dist/config-validator.js.map +1 -1
- package/dist/daemon.d.ts +108 -0
- package/dist/daemon.js +432 -32
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +29 -7
- package/dist/delivery-outbox.js +134 -16
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +18 -1
- package/dist/delivery-reconciliation.js +67 -2
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/fleet-context.d.ts +1 -0
- package/dist/fleet-manager.d.ts +81 -4
- package/dist/fleet-manager.js +452 -85
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +43 -10
- package/dist/host-memory.d.ts +10 -0
- package/dist/host-memory.js +33 -1
- package/dist/host-memory.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +25 -6
- package/dist/instance-lifecycle.js +73 -26
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +48 -18
- package/dist/locale.js.map +1 -1
- package/dist/login-controller.d.ts +20 -3
- package/dist/login-controller.js +71 -19
- package/dist/login-controller.js.map +1 -1
- package/dist/login-flows.d.ts +5 -2
- package/dist/login-flows.js +5 -4
- package/dist/login-flows.js.map +1 -1
- package/dist/memory-pressure.d.ts +13 -1
- package/dist/memory-pressure.js +50 -10
- package/dist/memory-pressure.js.map +1 -1
- package/dist/outbound-handlers.d.ts +63 -0
- package/dist/outbound-handlers.js +121 -46
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +3 -2
- package/dist/outbound-schemas.js +3 -1
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +8 -5
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/settings-api.js +10 -4
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +2 -2
- package/dist/spawn-gate.js +5 -1
- package/dist/spawn-gate.js.map +1 -1
- package/dist/spawn-hold-reply.d.ts +12 -0
- package/dist/spawn-hold-reply.js +39 -0
- package/dist/spawn-hold-reply.js.map +1 -0
- package/dist/tips.js +1 -1
- package/dist/tips.js.map +1 -1
- package/dist/topic-commands.js +9 -2
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-delta-reader.d.ts +3 -3
- package/dist/transcript-delta-reader.js +8 -8
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/turn-resume.d.ts +2 -0
- package/dist/turn-resume.js +1 -1
- package/dist/turn-resume.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/ui/chat-mentions.js +63 -0
- package/dist/ui/chat-render.js +25 -0
- package/dist/ui/chat-store.js +1 -1
- package/dist/ui/chat-strings.js +12 -12
- package/dist/ui/chat-thread.js +48 -11
- package/dist/ui/panel-chat.js +20 -9
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +51 -22
- package/dist/ui/panel-settings.js +18 -9
- package/dist/ui/preview-panel.js +48 -14
- package/dist/ui/preview.js +5 -1
- package/dist/ui/settings-strings.js +6 -2
- package/dist/ui/settings-token.js +1 -1
- package/dist/ui/shared/app-i18n.js +35 -13
- package/dist/ui/shared/app-prefs.js +249 -0
- package/dist/ui/shared/app-shell.js +17 -4
- package/dist/ui/shared/app-store.js +5 -1
- package/dist/ui/shared/app.css +41 -7
- package/dist/ui/shared/app.js +6 -1
- package/dist/ui/shared/header-tools.js +55 -5
- package/dist/ui/shared/instance-nav.js +131 -38
- package/dist/ui/shared/panel-view.js +6 -0
- package/dist/ui/shared/theme.js +39 -7
- package/dist/ui/shared/ui-icons.js +1 -0
- package/dist/ui/shared/usage-dialog.js +13 -4
- package/dist/ui/shared/view-strings.js +26 -2
- package/dist/unrecognised-prompt.d.ts +23 -0
- package/dist/unrecognised-prompt.js +74 -0
- package/dist/unrecognised-prompt.js.map +1 -0
- package/dist/view-api.js +5 -0
- package/dist/view-api.js.map +1 -1
- package/dist/web-api.d.ts +13 -1
- package/dist/web-api.js +218 -18
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.js +3 -2
- package/dist/web-auth.js.map +1 -1
- package/dist/web-mentions.d.ts +44 -0
- package/dist/web-mentions.js +75 -0
- package/dist/web-mentions.js.map +1 -0
- package/dist/web-prefs.d.ts +63 -0
- package/dist/web-prefs.js +263 -0
- package/dist/web-prefs.js.map +1 -0
- package/dist/web-session.js +1 -1
- package/dist/web-session.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: credential-profiles
|
|
3
|
-
description: Run agents on more than one subscription of the same backend. Use when someone asks for a worker on a different account, wants an agent moved to another subscription, or asks which subscription an agent is using. Currently kiro-cli and
|
|
3
|
+
description: Run agents on more than one subscription of the same backend. Use when someone asks for a worker on a different account, wants an agent moved to another subscription, or asks which subscription an agent is using. Currently kiro-cli, codex and claude-code.
|
|
4
4
|
roles: [general]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -10,15 +10,17 @@ One AgEnD fleet normally shares one login per backend. A **credential profile**
|
|
|
10
10
|
is a named, separate login for the same backend, so two agents can run on two
|
|
11
11
|
subscriptions at once.
|
|
12
12
|
|
|
13
|
-
`kiro-cli` and `
|
|
14
|
-
than setting an option that will be ignored.
|
|
13
|
+
`kiro-cli`, `codex` and `claude-code` have profiles today. For any other backend,
|
|
14
|
+
say so rather than setting an option that will be ignored.
|
|
15
15
|
|
|
16
16
|
**The two behave differently when you switch, and it is not a setting.** Kiro
|
|
17
17
|
keeps its conversations inside the same database as its login, so a different
|
|
18
18
|
subscription is a different set of them: switching is a new conversation. Codex
|
|
19
19
|
keeps its login in one file beside conversation stores that carry no account, so
|
|
20
|
-
switching leaves the history where it is and both subscriptions see it.
|
|
21
|
-
|
|
20
|
+
switching leaves the history where it is and both subscriptions see it. Claude
|
|
21
|
+
Code behaves like Kiro: the profile is its whole config directory, conversations
|
|
22
|
+
included, so switching is a new conversation. Say which one you are talking
|
|
23
|
+
about.
|
|
22
24
|
|
|
23
25
|
## Is a profile already set up?
|
|
24
26
|
|
|
@@ -34,15 +36,44 @@ the command, do not try to work around the refusal.
|
|
|
34
36
|
Ask the operator to run this on the host once per subscription:
|
|
35
37
|
|
|
36
38
|
```
|
|
37
|
-
XDG_DATA_HOME="${AGEND_HOME
|
|
39
|
+
XDG_DATA_HOME="${AGEND_HOME:-$HOME/.agend}/credential-profiles/kiro-cli/<profile>" kiro-cli login
|
|
38
40
|
```
|
|
39
41
|
|
|
40
42
|
For codex the variable and the path are different:
|
|
41
43
|
|
|
42
44
|
```
|
|
43
|
-
CODEX_HOME="${AGEND_HOME
|
|
45
|
+
CODEX_HOME="${AGEND_HOME:-$HOME/.agend}/credential-profiles/codex/<profile>" codex login
|
|
44
46
|
```
|
|
45
47
|
|
|
48
|
+
For claude-code a fleet admin can do it **from chat**, no host access needed:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
/login claude-code <profile>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
It opens the same browser-terminal login as `/login claude-code`, for that
|
|
55
|
+
profile only. Or on the host:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
CLAUDE_CONFIG_DIR="${AGEND_HOME:-$HOME/.agend}/credential-profiles/claude-code/<profile>" claude auth login
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Kiro and codex profiles cannot be signed in from chat yet: `/login kiro-cli <profile>`
|
|
62
|
+
answers with the host command to run.
|
|
63
|
+
|
|
64
|
+
A Claude Code profile keeps its own login, trust and conversations, and a
|
|
65
|
+
`settings.json` that starts as a copy of the user's; the user's `CLAUDE.md`,
|
|
66
|
+
agents, commands, skills, output styles and plugins are shared with it.
|
|
67
|
+
|
|
68
|
+
**When a switch was refused, relay the command from the refusal, character for
|
|
69
|
+
character** — never one you assembled from the templates above. It carries the
|
|
70
|
+
exact absolute path AgEnD launches with. For claude-code this matters more than
|
|
71
|
+
anywhere: on macOS the login is stored under a Keychain item named after that
|
|
72
|
+
path string, so a login under a differently spelled path (a relative
|
|
73
|
+
`AGEND_HOME`, a trailing slash, a `~` that was never expanded) lands in another
|
|
74
|
+
item, and the agent still finds no login.
|
|
75
|
+
For claude-code the refusal also names `/login claude-code <profile>`: offer that first, since it works from chat.
|
|
76
|
+
|
|
46
77
|
## Create a worker on another subscription
|
|
47
78
|
|
|
48
79
|
```
|
|
@@ -71,7 +102,7 @@ restarted for you** and the reply says `restarted: true`. A paused or stopped on
|
|
|
71
102
|
is not started: the reply says `restarted: false`, and the switch applies at its
|
|
72
103
|
next start.
|
|
73
104
|
|
|
74
|
-
**For kiro**, a switch is always a **new conversation**, and the reply says so
|
|
105
|
+
**For kiro and claude-code**, a switch is always a **new conversation**, and the reply says so
|
|
75
106
|
(`conversation_carried_over: false`). kiro keeps its conversations in the same
|
|
76
107
|
database as its login, so a different subscription has a different set of them
|
|
77
108
|
and there is nothing to resume — AgEnD does not even try. What it does instead
|
|
@@ -132,6 +163,8 @@ and log it in, so report it as such rather than as a missing subscription.
|
|
|
132
163
|
- Quotas only add up if the profiles are genuinely different billing accounts.
|
|
133
164
|
Two profiles logged into the same account do not double anything, and it is
|
|
134
165
|
worth saying so before someone sets this up expecting more headroom.
|
|
166
|
+
- Claude Code profiles do not have usage rows of their own yet: the plain
|
|
167
|
+
`Claude` row reads the default login. Do not report it as a profile's quota.
|
|
135
168
|
- Switching a profile does not carry the conversation across; the agent gets a
|
|
136
169
|
summary of the work, not the transcript. Say anything that must survive
|
|
137
170
|
verbatim in the channel before switching.
|
|
@@ -140,8 +173,8 @@ and log it in, so report it as such rather than as a missing subscription.
|
|
|
140
173
|
|
|
141
174
|
- Do not invent a profile that has never been logged in and report success —
|
|
142
175
|
the switch is refused, and the refusal tells you what to relay.
|
|
143
|
-
- Do not set `credential_profile` for a backend other than `kiro-cli`
|
|
144
|
-
`
|
|
176
|
+
- Do not set `credential_profile` for a backend other than `kiro-cli`, `codex`
|
|
177
|
+
or `claude-code`; it is ignored, and the config validator warns about it.
|
|
145
178
|
- Do not tell a codex user their conversation will definitely continue across a
|
|
146
179
|
switch. Say the history is shared; that part is verified.
|
|
147
180
|
- Do not use a profile name with slashes or spaces — it becomes a directory
|
package/dist/host-memory.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
export interface LinuxMemoryPsi {
|
|
2
|
+
someAvg60: number;
|
|
3
|
+
fullAvg10: number;
|
|
4
|
+
}
|
|
5
|
+
/** Kernel percentage averages; missing/ambiguous output is unavailable, not zero pressure. */
|
|
6
|
+
export declare function parseLinuxMemoryPsi(output: string): LinuxMemoryPsi | null;
|
|
1
7
|
export interface HostMemory {
|
|
2
8
|
totalBytes: number;
|
|
3
9
|
availableBytes: number | null;
|
|
@@ -8,12 +14,16 @@ export interface HostMemory {
|
|
|
8
14
|
darwinPressureLevel?: 1 | 2 | 4 | null;
|
|
9
15
|
/** Bounded diagnostic output; null when unavailable or not requested. */
|
|
10
16
|
darwinPressureRaw?: string | null;
|
|
17
|
+
/** Linux pressure sampling only; null means PSI cannot be used. */
|
|
18
|
+
linuxPsi?: LinuxMemoryPsi | null;
|
|
11
19
|
}
|
|
12
20
|
interface HostMemoryDeps {
|
|
13
21
|
platform: NodeJS.Platform;
|
|
14
22
|
meminfo: () => string;
|
|
15
23
|
totalmem: () => number;
|
|
16
24
|
freemem: () => number;
|
|
25
|
+
includePressure: boolean;
|
|
26
|
+
psi: () => string;
|
|
17
27
|
}
|
|
18
28
|
/** Host memory, not the fleet's RSS/cgroup charge. Missing swap data is not zero swap. */
|
|
19
29
|
export declare function readHostMemory(overrides?: Partial<HostMemoryDeps>): HostMemory;
|
package/dist/host-memory.js
CHANGED
|
@@ -1,15 +1,44 @@
|
|
|
1
1
|
import { DarwinMemoryProbe } from "./darwin-memory.js";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { freemem, totalmem, platform } from "node:os";
|
|
4
|
+
/** Kernel percentage averages; missing/ambiguous output is unavailable, not zero pressure. */
|
|
5
|
+
export function parseLinuxMemoryPsi(output) {
|
|
6
|
+
if (output.length > 4096)
|
|
7
|
+
return null;
|
|
8
|
+
const rows = new Map();
|
|
9
|
+
const lines = output.trim().split(/\r?\n/);
|
|
10
|
+
if (lines.length !== 2)
|
|
11
|
+
return null;
|
|
12
|
+
for (const line of lines) {
|
|
13
|
+
const match = /^(some|full) avg10=(\d+(?:\.\d+)?) avg60=(\d+(?:\.\d+)?) avg300=(\d+(?:\.\d+)?) total=\d+$/.exec(line);
|
|
14
|
+
if (!match || rows.has(match[1]))
|
|
15
|
+
return null;
|
|
16
|
+
const averages = match.slice(2).map(Number);
|
|
17
|
+
if (averages.some(n => !Number.isFinite(n) || n < 0 || n > 100))
|
|
18
|
+
return null;
|
|
19
|
+
rows.set(match[1], averages);
|
|
20
|
+
}
|
|
21
|
+
if (!rows.has("some") || !rows.has("full"))
|
|
22
|
+
return null;
|
|
23
|
+
return { someAvg60: rows.get("some")[1], fullAvg10: rows.get("full")[0] };
|
|
24
|
+
}
|
|
4
25
|
/** Host memory, not the fleet's RSS/cgroup charge. Missing swap data is not zero swap. */
|
|
5
26
|
export function readHostMemory(overrides = {}) {
|
|
6
|
-
const deps = { platform: platform(), meminfo: () => readFileSync("/proc/meminfo", "utf8"), totalmem, freemem,
|
|
27
|
+
const deps = { platform: platform(), meminfo: () => readFileSync("/proc/meminfo", "utf8"), totalmem, freemem,
|
|
28
|
+
includePressure: false, psi: () => readFileSync("/proc/pressure/memory", "utf8"), ...overrides };
|
|
7
29
|
// macOS keeps reclaimable pages in use. Its free count is not availability.
|
|
8
30
|
// Synchronous readers cannot run the native probe; absence stays unknown.
|
|
9
31
|
if (deps.platform === "darwin") {
|
|
10
32
|
return { totalBytes: deps.totalmem(), availableBytes: null, availableKind: "unknown", swapTotalBytes: null, swapFreeBytes: null };
|
|
11
33
|
}
|
|
12
34
|
if (deps.platform === "linux") {
|
|
35
|
+
let linuxPsi = null;
|
|
36
|
+
if (deps.includePressure) {
|
|
37
|
+
try {
|
|
38
|
+
linuxPsi = parseLinuxMemoryPsi(deps.psi());
|
|
39
|
+
}
|
|
40
|
+
catch { /* Fall back to RAM, without inventing PSI. */ }
|
|
41
|
+
}
|
|
13
42
|
try {
|
|
14
43
|
const fields = new Map();
|
|
15
44
|
for (const match of deps.meminfo().matchAll(/^(\w+):\s+(\d+)\s+kB\s*$/gm)) {
|
|
@@ -28,10 +57,13 @@ export function readHostMemory(overrides = {}) {
|
|
|
28
57
|
availableKind: fields.has("MemAvailable") ? "available" : "free",
|
|
29
58
|
swapTotalBytes: hasSwap ? swapTotal : null,
|
|
30
59
|
swapFreeBytes: hasSwap ? swapFree : null,
|
|
60
|
+
...(deps.includePressure ? { linuxPsi } : {}),
|
|
31
61
|
};
|
|
32
62
|
}
|
|
33
63
|
}
|
|
34
64
|
catch { /* non-procfs Linux, or an unreadable snapshot: portable fallback */ }
|
|
65
|
+
return { totalBytes: deps.totalmem(), availableBytes: deps.freemem(), availableKind: "free", swapTotalBytes: null, swapFreeBytes: null,
|
|
66
|
+
...(deps.includePressure ? { linuxPsi } : {}) };
|
|
35
67
|
}
|
|
36
68
|
return { totalBytes: deps.totalmem(), availableBytes: deps.freemem(), availableKind: "free", swapTotalBytes: null, swapFreeBytes: null };
|
|
37
69
|
}
|
package/dist/host-memory.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-memory.js","sourceRoot":"","sources":["../src/host-memory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"host-memory.js","sourceRoot":"","sources":["../src/host-memory.ts"],"names":[],"mappings":"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"}
|
|
@@ -105,7 +105,13 @@ export interface LifecycleContext {
|
|
|
105
105
|
* Offer a one-tap re-login beside an auth alert. Optional: contexts without
|
|
106
106
|
* it (tests, lightweight fleets) still get the alert's written remedy.
|
|
107
107
|
*/
|
|
108
|
-
|
|
108
|
+
/** `profile` (#1699 part 2): the button signs that credential profile in, not the backend's default login. */
|
|
109
|
+
offerBackendLogin?(targetInstance: string, backend: string, profile?: string): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* The credential profile an instance's CLI signs in with on `backend` (#1699 follow-up), or null for that backend's
|
|
112
|
+
* default login. Absent: every instance is on the default login (as before profiles).
|
|
113
|
+
*/
|
|
114
|
+
credentialProfileOf?(name: string, backend: string): string | null;
|
|
109
115
|
/** Notify a clean CLI exit and offer an admin-only restart action in General. */
|
|
110
116
|
notifyNormalExit(name: string): Promise<void>;
|
|
111
117
|
/** Tell every General, as an agent, that kiro-cli can no longer run this instance as configured. */
|
|
@@ -281,10 +287,10 @@ export declare class InstanceLifecycle {
|
|
|
281
287
|
private activitySeedNow;
|
|
282
288
|
/** When each instance was last woken or started for work — the pre-claim lease clock. */
|
|
283
289
|
private wokeAt;
|
|
284
|
-
/** backend → last auth-error alert time, so one expiry sends one alert. */
|
|
290
|
+
/** login (backend, or backend:profile) → last auth-error alert time, so one expiry sends one alert. */
|
|
285
291
|
private lastAuthAlertAt;
|
|
286
292
|
/**
|
|
287
|
-
* backend → cached token-free auth verification (see AUTH_VERIFY_CACHE_MS).
|
|
293
|
+
* login (backend, or backend:profile) → cached token-free auth verification (see AUTH_VERIFY_CACHE_MS).
|
|
288
294
|
* The PROMISE is cached, not the result: a shared credential expiring makes
|
|
289
295
|
* every instance of the backend fire in the same tick, and they must join
|
|
290
296
|
* one in-flight check instead of each spawning their own.
|
|
@@ -361,10 +367,20 @@ export declare class InstanceLifecycle {
|
|
|
361
367
|
* CLI is not. Cached per backend so simultaneous alerts run one check.
|
|
362
368
|
*/
|
|
363
369
|
private verifyAuthError;
|
|
370
|
+
/**
|
|
371
|
+
* The credential profile a running instance signs in with (#1699 follow-up): its login is that profile's, so its
|
|
372
|
+
* auth error is judged, cached and reported against that login — never the backend's default one, which can be fine
|
|
373
|
+
* while the profile's has expired.
|
|
374
|
+
*/
|
|
375
|
+
private profileOf;
|
|
376
|
+
private static loginKey;
|
|
364
377
|
private verifyCodexQuota;
|
|
365
378
|
private verifyClaudeQuota;
|
|
366
|
-
/**
|
|
367
|
-
|
|
379
|
+
/**
|
|
380
|
+
* Same verification keyed by login — a backend's default one, or one of its credential profiles, probed with that
|
|
381
|
+
* profile's environment (used by startup pre-flight priming).
|
|
382
|
+
*/
|
|
383
|
+
verifyBackendAuth(backend: string, profile?: string | null): Promise<"valid" | "invalid" | "unknown">;
|
|
368
384
|
/**
|
|
369
385
|
* Startup pre-flight: warm the per-backend auth verification cache so the
|
|
370
386
|
* detectors that fire seconds later (login-screen scan, MCP-died gate) get an
|
|
@@ -372,7 +388,10 @@ export declare class InstanceLifecycle {
|
|
|
372
388
|
* anything, because e.g. codex on a custom provider runs fine while
|
|
373
389
|
* `codex login status` reports logged out (live-verified on this fleet).
|
|
374
390
|
*/
|
|
375
|
-
primeAuthVerification(
|
|
391
|
+
primeAuthVerification(logins: Iterable<{
|
|
392
|
+
backend: string;
|
|
393
|
+
profile: string | null;
|
|
394
|
+
}>): void;
|
|
376
395
|
/**
|
|
377
396
|
* One alert per backend per cooldown, naming every affected instance — a CLI's
|
|
378
397
|
* credentials are shared, so N instances failing is ONE problem with ONE fix
|
|
@@ -19,6 +19,7 @@ import { reportProviderRateLimit } from "./usage/provider-alerts.js";
|
|
|
19
19
|
import { isFleetStartCommandLine } from "./fleet-lock.js";
|
|
20
20
|
import { GENERAL_PAUSE_ERROR, isGeneralInstance } from "./general-instance.js";
|
|
21
21
|
import { BACKEND_AUTH_CHECKS, checkAuthStatus, LOGIN_FLOWS } from "./login-flows.js";
|
|
22
|
+
import { CHAT_PROFILE_LOGIN_BACKENDS, credentialProfileEnv, credentialProfileLoginCommand } from "./backend/credential-profile.js";
|
|
22
23
|
import { fetchClaudeUsage, fetchCodexUsage } from "./usage/providers.js";
|
|
23
24
|
import { assertExplicitInstanceRemoval, authorizeExplicitInstanceRemoval } from "./instance-removal.js";
|
|
24
25
|
import { capturePendingTargetReconciliation, finishTargetReconciliation, reconcileTargetBeforeStart, RECONCILIATION_WRITE_DRAIN_TIMEOUT_MS, retireCapturedTargetWindows, } from "./delivery-reconciliation.js";
|
|
@@ -258,10 +259,10 @@ export class InstanceLifecycle {
|
|
|
258
259
|
activitySeedNow = new Set();
|
|
259
260
|
/** When each instance was last woken or started for work — the pre-claim lease clock. */
|
|
260
261
|
wokeAt = new Map();
|
|
261
|
-
/** backend → last auth-error alert time, so one expiry sends one alert. */
|
|
262
|
+
/** login (backend, or backend:profile) → last auth-error alert time, so one expiry sends one alert. */
|
|
262
263
|
lastAuthAlertAt = new Map();
|
|
263
264
|
/**
|
|
264
|
-
* backend → cached token-free auth verification (see AUTH_VERIFY_CACHE_MS).
|
|
265
|
+
* login (backend, or backend:profile) → cached token-free auth verification (see AUTH_VERIFY_CACHE_MS).
|
|
265
266
|
* The PROMISE is cached, not the result: a shared credential expiring makes
|
|
266
267
|
* every instance of the backend fire in the same tick, and they must join
|
|
267
268
|
* one in-flight check instead of each spawning their own.
|
|
@@ -370,7 +371,19 @@ export class InstanceLifecycle {
|
|
|
370
371
|
* CLI is not. Cached per backend so simultaneous alerts run one check.
|
|
371
372
|
*/
|
|
372
373
|
verifyAuthError(name) {
|
|
373
|
-
|
|
374
|
+
const backend = this.backendOf(name);
|
|
375
|
+
return this.verifyBackendAuth(backend, this.profileOf(name, backend));
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* The credential profile a running instance signs in with (#1699 follow-up): its login is that profile's, so its
|
|
379
|
+
* auth error is judged, cached and reported against that login — never the backend's default one, which can be fine
|
|
380
|
+
* while the profile's has expired.
|
|
381
|
+
*/
|
|
382
|
+
profileOf(name, backend) {
|
|
383
|
+
return this.ctx.credentialProfileOf?.(name, backend) ?? null;
|
|
384
|
+
}
|
|
385
|
+
static loginKey(backend, profile) {
|
|
386
|
+
return profile ? `${backend}:${profile}` : backend;
|
|
374
387
|
}
|
|
375
388
|
verifyCodexQuota() {
|
|
376
389
|
if (this.codexQuotaVerifyInFlight)
|
|
@@ -396,14 +409,19 @@ export class InstanceLifecycle {
|
|
|
396
409
|
});
|
|
397
410
|
return promise;
|
|
398
411
|
}
|
|
399
|
-
/**
|
|
400
|
-
|
|
401
|
-
|
|
412
|
+
/**
|
|
413
|
+
* Same verification keyed by login — a backend's default one, or one of its credential profiles, probed with that
|
|
414
|
+
* profile's environment (used by startup pre-flight priming).
|
|
415
|
+
*/
|
|
416
|
+
verifyBackendAuth(backend, profile = null) {
|
|
417
|
+
const key = InstanceLifecycle.loginKey(backend, profile);
|
|
418
|
+
const cached = this.authVerifyCache.get(key);
|
|
402
419
|
if (cached && Date.now() - cached.at < AUTH_VERIFY_CACHE_MS)
|
|
403
420
|
return cached.promise;
|
|
404
421
|
const check = LOGIN_FLOWS[backend]?.authCheck ?? BACKEND_AUTH_CHECKS[backend];
|
|
405
|
-
const
|
|
406
|
-
|
|
422
|
+
const env = profile ? credentialProfileEnv(getAgendHome(), backend, profile) : null;
|
|
423
|
+
const promise = check ? (env ? checkAuthStatus(check, env) : checkAuthStatus(check)) : Promise.resolve("unknown");
|
|
424
|
+
this.authVerifyCache.set(key, { at: Date.now(), promise });
|
|
407
425
|
return promise;
|
|
408
426
|
}
|
|
409
427
|
/**
|
|
@@ -413,11 +431,16 @@ export class InstanceLifecycle {
|
|
|
413
431
|
* anything, because e.g. codex on a custom provider runs fine while
|
|
414
432
|
* `codex login status` reports logged out (live-verified on this fleet).
|
|
415
433
|
*/
|
|
416
|
-
primeAuthVerification(
|
|
417
|
-
|
|
418
|
-
|
|
434
|
+
primeAuthVerification(logins) {
|
|
435
|
+
const seen = new Set();
|
|
436
|
+
for (const { backend, profile } of logins) {
|
|
437
|
+
const key = InstanceLifecycle.loginKey(backend, profile);
|
|
438
|
+
if (seen.has(key))
|
|
439
|
+
continue;
|
|
440
|
+
seen.add(key);
|
|
441
|
+
void this.verifyBackendAuth(backend, profile).then(result => {
|
|
419
442
|
if (result === "invalid") {
|
|
420
|
-
this.ctx.logger.warn({ backend }, "Pre-flight auth check failed — marking backend as auth-suspect");
|
|
443
|
+
this.ctx.logger.warn({ backend, profile }, "Pre-flight auth check failed — marking backend as auth-suspect");
|
|
421
444
|
}
|
|
422
445
|
}).catch(() => { });
|
|
423
446
|
}
|
|
@@ -430,27 +453,43 @@ export class InstanceLifecycle {
|
|
|
430
453
|
*/
|
|
431
454
|
notifyAuthErrorOnce(name, message, notificationTarget = name) {
|
|
432
455
|
const backend = this.backendOf(name);
|
|
456
|
+
// #1699 follow-up: a credential profile is a login of its own — its own alert, its own instances, its own fix.
|
|
457
|
+
const profile = this.profileOf(name, backend);
|
|
458
|
+
const key = InstanceLifecycle.loginKey(backend, profile);
|
|
433
459
|
const now = Date.now();
|
|
434
|
-
const last = this.lastAuthAlertAt.get(
|
|
460
|
+
const last = this.lastAuthAlertAt.get(key) ?? 0;
|
|
435
461
|
if (now - last < AUTH_ALERT_COOLDOWN_MS) {
|
|
436
|
-
this.ctx.logger.info({ name, backend }, "auth error suppressed (
|
|
462
|
+
this.ctx.logger.info({ name, backend, profile }, "auth error suppressed (this login already alerted)");
|
|
437
463
|
return;
|
|
438
464
|
}
|
|
439
|
-
this.lastAuthAlertAt.set(
|
|
440
|
-
const affected = [...this.daemons.keys()].filter(n => this.backendOf(n) === backend);
|
|
465
|
+
this.lastAuthAlertAt.set(key, now);
|
|
466
|
+
const affected = [...this.daemons.keys()].filter(n => this.backendOf(n) === backend && this.profileOf(n, backend) === profile);
|
|
441
467
|
const others = affected.filter(n => n !== name);
|
|
468
|
+
const on = profile ? `\`${backend}\` profile \`${profile}\`` : `\`${backend}\``;
|
|
442
469
|
const scope = others.length
|
|
443
|
-
? `${affected.length} instances on
|
|
444
|
-
: `\`${name}\` (${backend})`;
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
const
|
|
470
|
+
? `${affected.length} instances on ${on}: ${affected.join(", ")}`
|
|
471
|
+
: profile ? `\`${name}\` (${backend}, profile \`${profile}\`)` : `\`${name}\` (${backend})`;
|
|
472
|
+
// A profile is signed in with its own variable: `/login <backend>` would sign the DEFAULT login in. claude-code's
|
|
473
|
+
// can be signed in from chat (`/login <backend> <profile>`, #1699 part 2); the others' on the host.
|
|
474
|
+
const profileLogin = profile ? credentialProfileLoginCommand(getAgendHome(), backend, profile) : null;
|
|
475
|
+
const chatLogin = !!profileLogin && CHAT_PROFILE_LOGIN_BACKENDS.has(backend);
|
|
476
|
+
const remedy = chatLogin
|
|
477
|
+
? `Use \`/login ${backend} ${profile}\` to sign that profile in remotely (or run \`${profileLogin}\` on the host).`
|
|
478
|
+
: profileLogin
|
|
479
|
+
? `Run \`${profileLogin}\` on the host, then wake the affected instance(s).`
|
|
480
|
+
: backend === "opencode"
|
|
481
|
+
? "Run `opencode auth login` in a terminal, then wake the affected instance(s)."
|
|
482
|
+
: `Use \`/login ${backend}\` to re-login remotely.`;
|
|
483
|
+
const shared = profile
|
|
484
|
+
? "Credentials are shared per profile — one sign-in restores all of them; affected instances pause until then."
|
|
485
|
+
: "Credentials are shared per backend — one re-login restores all of them; affected instances pause until then.";
|
|
486
|
+
const dispatched = this.notifyIncident(notificationTarget, "auth_error", `🔑 ${message}\n\nAffects ${scope}. ${shared} ${remedy}`);
|
|
449
487
|
// Only alongside an alert somebody actually saw: a button on its own, or one
|
|
450
|
-
// trailing a suppressed alert, is a control with no explanation next to it.
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
488
|
+
// trailing a suppressed alert, is a control with no explanation next to it. For a profile only where its login
|
|
489
|
+
// runs from chat, and then the button carries the profile — the plain button signs the default login in.
|
|
490
|
+
if (dispatched && (!profileLogin || chatLogin)) {
|
|
491
|
+
void (profile ? this.ctx.offerBackendLogin?.(notificationTarget, backend, profile) : this.ctx.offerBackendLogin?.(notificationTarget, backend))
|
|
492
|
+
?.catch(err => this.ctx.logger.warn({ err, backend, profile }, "Could not offer the re-login button"));
|
|
454
493
|
}
|
|
455
494
|
}
|
|
456
495
|
/**
|
|
@@ -804,6 +843,14 @@ export class InstanceLifecycle {
|
|
|
804
843
|
// (Kept `description` for kind === "dialog" rather than a dedicated
|
|
805
844
|
// kiro interactionKind: a new union member would flow into the category
|
|
806
845
|
// labels, summaries and every exhaustive match for one picker's text.)
|
|
846
|
+
// #1691: a prompt no table knows — what it asks (one sanitised, bounded line from the daemon, never the pane), and
|
|
847
|
+
// that AgEnD will not press anything.
|
|
848
|
+
if (data.unrecognised) {
|
|
849
|
+
const asked = data.question ? t("inst.unrecognised_prompt_question", data.question) : "";
|
|
850
|
+
this.notifyIncident(name, "dialog_parked", t("inst.unrecognised_prompt", name, asked));
|
|
851
|
+
this.ctx.notifyFleetError?.(t("fleet.unrecognised_prompt", name, asked));
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
807
854
|
const category = data.kind && data.kind !== "dialog" ? interactionCategory(data.kind, data.backend) : null;
|
|
808
855
|
const text = category ? t("inst.interaction_parked", name, category) : data.holdOnly
|
|
809
856
|
? t("inst.dialog_parked_hold", name, data.description)
|