@songsid/agend 2.2.0-beta.3 → 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.
Files changed (96) hide show
  1. package/dist/agent-cli.js +6 -2
  2. package/dist/agent-cli.js.map +1 -1
  3. package/dist/backend/claude-code.d.ts +39 -2
  4. package/dist/backend/claude-code.js +115 -31
  5. package/dist/backend/claude-code.js.map +1 -1
  6. package/dist/backend/credential-profile.d.ts +33 -3
  7. package/dist/backend/credential-profile.js +107 -5
  8. package/dist/backend/credential-profile.js.map +1 -1
  9. package/dist/backend/types.d.ts +1 -1
  10. package/dist/cache-service.d.ts +2 -0
  11. package/dist/cache-service.js +2 -1
  12. package/dist/cache-service.js.map +1 -1
  13. package/dist/channel/adapters/discord.d.ts +13 -0
  14. package/dist/channel/adapters/discord.js +42 -0
  15. package/dist/channel/adapters/discord.js.map +1 -1
  16. package/dist/channel/adapters/telegram.d.ts +6 -1
  17. package/dist/channel/adapters/telegram.js +27 -2
  18. package/dist/channel/adapters/telegram.js.map +1 -1
  19. package/dist/channel/types.d.ts +22 -1
  20. package/dist/config-validator.js +4 -0
  21. package/dist/config-validator.js.map +1 -1
  22. package/dist/daemon.d.ts +96 -0
  23. package/dist/daemon.js +339 -16
  24. package/dist/daemon.js.map +1 -1
  25. package/dist/delivery-outbox.d.ts +18 -5
  26. package/dist/delivery-outbox.js +121 -14
  27. package/dist/delivery-outbox.js.map +1 -1
  28. package/dist/fleet-context.d.ts +1 -0
  29. package/dist/fleet-manager.d.ts +59 -3
  30. package/dist/fleet-manager.js +357 -71
  31. package/dist/fleet-manager.js.map +1 -1
  32. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +43 -10
  33. package/dist/host-memory.d.ts +10 -0
  34. package/dist/host-memory.js +33 -1
  35. package/dist/host-memory.js.map +1 -1
  36. package/dist/instance-lifecycle.d.ts +25 -6
  37. package/dist/instance-lifecycle.js +73 -26
  38. package/dist/instance-lifecycle.js.map +1 -1
  39. package/dist/locale.js +44 -18
  40. package/dist/locale.js.map +1 -1
  41. package/dist/login-controller.d.ts +20 -3
  42. package/dist/login-controller.js +71 -19
  43. package/dist/login-controller.js.map +1 -1
  44. package/dist/login-flows.d.ts +5 -2
  45. package/dist/login-flows.js +5 -4
  46. package/dist/login-flows.js.map +1 -1
  47. package/dist/memory-pressure.d.ts +13 -1
  48. package/dist/memory-pressure.js +50 -10
  49. package/dist/memory-pressure.js.map +1 -1
  50. package/dist/outbound-handlers.d.ts +61 -0
  51. package/dist/outbound-handlers.js +100 -35
  52. package/dist/outbound-handlers.js.map +1 -1
  53. package/dist/outbound-schemas.d.ts +1 -0
  54. package/dist/outbound-schemas.js +2 -0
  55. package/dist/outbound-schemas.js.map +1 -1
  56. package/dist/public-web-gateway.js +4 -3
  57. package/dist/public-web-gateway.js.map +1 -1
  58. package/dist/settings-api.js +10 -4
  59. package/dist/settings-api.js.map +1 -1
  60. package/dist/spawn-gate.js +5 -1
  61. package/dist/spawn-gate.js.map +1 -1
  62. package/dist/spawn-hold-reply.d.ts +12 -0
  63. package/dist/spawn-hold-reply.js +39 -0
  64. package/dist/spawn-hold-reply.js.map +1 -0
  65. package/dist/topic-commands.js +9 -2
  66. package/dist/topic-commands.js.map +1 -1
  67. package/dist/turn-resume.d.ts +2 -0
  68. package/dist/turn-resume.js +1 -1
  69. package/dist/turn-resume.js.map +1 -1
  70. package/dist/types.d.ts +2 -0
  71. package/dist/ui/chat-mentions.js +63 -0
  72. package/dist/ui/chat-render.js +25 -0
  73. package/dist/ui/chat-thread.js +26 -8
  74. package/dist/ui/panel-chat.js +17 -9
  75. package/dist/ui/panel-details.js +2 -0
  76. package/dist/ui/panel-fleet.js +51 -22
  77. package/dist/ui/panel-settings.js +3 -1
  78. package/dist/ui/preview-panel.js +48 -14
  79. package/dist/ui/preview.js +5 -1
  80. package/dist/ui/settings-token.js +1 -1
  81. package/dist/ui/shared/app-store.js +3 -1
  82. package/dist/ui/shared/app.css +11 -7
  83. package/dist/ui/shared/instance-nav.js +32 -7
  84. package/dist/ui/shared/view-strings.js +6 -0
  85. package/dist/unrecognised-prompt.d.ts +23 -0
  86. package/dist/unrecognised-prompt.js +74 -0
  87. package/dist/unrecognised-prompt.js.map +1 -0
  88. package/dist/web-api.d.ts +3 -0
  89. package/dist/web-api.js +115 -17
  90. package/dist/web-api.js.map +1 -1
  91. package/dist/web-auth.js +1 -1
  92. package/dist/web-auth.js.map +1 -1
  93. package/dist/web-mentions.d.ts +44 -0
  94. package/dist/web-mentions.js +75 -0
  95. package/dist/web-mentions.js.map +1 -0
  96. 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 codex.
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 `codex` have profiles today. For any other backend, say so rather
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. Say
21
- which one you are talking about.
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:-~/.agend}/credential-profiles/kiro-cli/<profile>" kiro-cli login
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:-~/.agend}/credential-profiles/codex/<profile>" codex login
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` or
144
- `codex`; it is ignored, and the config validator warns about it.
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
@@ -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;
@@ -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, ...overrides };
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"host-memory.js","sourceRoot":"","sources":["../src/host-memory.ts"],"names":[],"mappings":"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"}
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
- offerBackendLogin?(targetInstance: string, backend: string): Promise<void>;
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
- /** Same verification keyed by backend (used by startup pre-flight priming). */
367
- verifyBackendAuth(backend: string): Promise<"valid" | "invalid" | "unknown">;
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(backends: Iterable<string>): void;
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
- return this.verifyBackendAuth(this.backendOf(name));
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
- /** Same verification keyed by backend (used by startup pre-flight priming). */
400
- verifyBackendAuth(backend) {
401
- const cached = this.authVerifyCache.get(backend);
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 promise = check ? checkAuthStatus(check) : Promise.resolve("unknown");
406
- this.authVerifyCache.set(backend, { at: Date.now(), promise });
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(backends) {
417
- for (const backend of new Set(backends)) {
418
- void this.verifyBackendAuth(backend).then(result => {
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(backend) ?? 0;
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 (backend already alerted)");
462
+ this.ctx.logger.info({ name, backend, profile }, "auth error suppressed (this login already alerted)");
437
463
  return;
438
464
  }
439
- this.lastAuthAlertAt.set(backend, now);
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 \`${backend}\`: ${affected.join(", ")}`
444
- : `\`${name}\` (${backend})`;
445
- const remedy = backend === "opencode"
446
- ? "Run `opencode auth login` in a terminal, then wake the affected instance(s)."
447
- : `Use \`/login ${backend}\` to re-login remotely.`;
448
- const dispatched = this.notifyIncident(notificationTarget, "auth_error", `🔑 ${message}\n\nAffects ${scope}. Credentials are shared per backend — one re-login restores all of them; affected instances pause until then. ${remedy}`);
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
- if (dispatched) {
452
- void this.ctx.offerBackendLogin?.(notificationTarget, backend)
453
- .catch(err => this.ctx.logger.warn({ err, backend }, "Could not offer the re-login button"));
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)