@songsid/agend 2.3.2-beta.4 → 2.3.2-beta.5

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 (156) hide show
  1. package/THIRD_PARTY_NOTICES.md +40 -0
  2. package/dist/antigravity-usage-scan.d.ts +58 -0
  3. package/dist/antigravity-usage-scan.js +257 -0
  4. package/dist/antigravity-usage-scan.js.map +1 -0
  5. package/dist/antigravity-usage-worker.d.ts +1 -0
  6. package/dist/antigravity-usage-worker.js +10 -0
  7. package/dist/antigravity-usage-worker.js.map +1 -0
  8. package/dist/backend/kiro-identity.d.ts +45 -0
  9. package/dist/backend/kiro-identity.js +74 -0
  10. package/dist/backend/kiro-identity.js.map +1 -1
  11. package/dist/backend/kiro.d.ts +15 -0
  12. package/dist/backend/kiro.js +42 -5
  13. package/dist/backend/kiro.js.map +1 -1
  14. package/dist/backend/types.d.ts +6 -0
  15. package/dist/backend/types.js.map +1 -1
  16. package/dist/backend-signin-guide.d.ts +1 -0
  17. package/dist/backend-signin-guide.js +9 -0
  18. package/dist/backend-signin-guide.js.map +1 -0
  19. package/dist/backend-signin-pending.d.ts +10 -0
  20. package/dist/backend-signin-pending.js +44 -0
  21. package/dist/backend-signin-pending.js.map +1 -0
  22. package/dist/browser-backend-signins.d.ts +56 -0
  23. package/dist/browser-backend-signins.js +189 -0
  24. package/dist/browser-backend-signins.js.map +1 -0
  25. package/dist/cache-ledger.d.ts +38 -2
  26. package/dist/cache-ledger.js +133 -8
  27. package/dist/cache-ledger.js.map +1 -1
  28. package/dist/cache-prices.d.ts +2 -1
  29. package/dist/cache-prices.js +5 -2
  30. package/dist/cache-prices.js.map +1 -1
  31. package/dist/cache-service.d.ts +68 -0
  32. package/dist/cache-service.js +355 -13
  33. package/dist/cache-service.js.map +1 -1
  34. package/dist/channel/mcp-tools.js +3 -1
  35. package/dist/channel/mcp-tools.js.map +1 -1
  36. package/dist/cli.js +21 -2
  37. package/dist/cli.js.map +1 -1
  38. package/dist/config.d.ts +19 -0
  39. package/dist/config.js +25 -0
  40. package/dist/config.js.map +1 -1
  41. package/dist/cpu-quota.d.ts +23 -0
  42. package/dist/cpu-quota.js +86 -0
  43. package/dist/cpu-quota.js.map +1 -0
  44. package/dist/daemon.js +6 -3
  45. package/dist/daemon.js.map +1 -1
  46. package/dist/fleet-manager.d.ts +35 -0
  47. package/dist/fleet-manager.js +233 -11
  48. package/dist/fleet-manager.js.map +1 -1
  49. package/dist/general-knowledge/skills/credential-profiles/SKILL.md +5 -2
  50. package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +14 -1
  51. package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +6 -3
  52. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +6 -3
  53. package/dist/instance-lifecycle.d.ts +9 -0
  54. package/dist/instance-lifecycle.js +27 -1
  55. package/dist/instance-lifecycle.js.map +1 -1
  56. package/dist/kiro-db-reader.d.ts +5 -0
  57. package/dist/kiro-db-reader.js +18 -0
  58. package/dist/kiro-db-reader.js.map +1 -1
  59. package/dist/kiro-engine-status.d.ts +2 -0
  60. package/dist/kiro-engine-status.js +9 -0
  61. package/dist/kiro-engine-status.js.map +1 -1
  62. package/dist/kiro-migrate.d.ts +49 -0
  63. package/dist/kiro-migrate.js +105 -0
  64. package/dist/kiro-migrate.js.map +1 -0
  65. package/dist/kiro-transcript-lane.d.ts +2 -0
  66. package/dist/kiro-transcript-lane.js.map +1 -1
  67. package/dist/kiro-transcript-worker.js +1 -0
  68. package/dist/kiro-transcript-worker.js.map +1 -1
  69. package/dist/kiro-usage-run.d.ts +7 -0
  70. package/dist/kiro-usage-run.js +39 -0
  71. package/dist/kiro-usage-run.js.map +1 -0
  72. package/dist/kiro-usage-scan.d.ts +49 -0
  73. package/dist/kiro-usage-scan.js +223 -0
  74. package/dist/kiro-usage-scan.js.map +1 -0
  75. package/dist/kiro-usage-worker.d.ts +1 -0
  76. package/dist/kiro-usage-worker.js +10 -0
  77. package/dist/kiro-usage-worker.js.map +1 -0
  78. package/dist/kiro-usage.d.ts +58 -0
  79. package/dist/kiro-usage.js +310 -0
  80. package/dist/kiro-usage.js.map +1 -0
  81. package/dist/locale.js +70 -2
  82. package/dist/locale.js.map +1 -1
  83. package/dist/log-service.js +1 -1
  84. package/dist/log-service.js.map +1 -1
  85. package/dist/login-url.d.ts +4 -0
  86. package/dist/login-url.js +22 -0
  87. package/dist/login-url.js.map +1 -0
  88. package/dist/needs-you-hub.d.ts +2 -1
  89. package/dist/needs-you-hub.js +1 -1
  90. package/dist/needs-you-hub.js.map +1 -1
  91. package/dist/needs-you.d.ts +8 -2
  92. package/dist/needs-you.js +6 -0
  93. package/dist/needs-you.js.map +1 -1
  94. package/dist/opencode-usage-scan.d.ts +28 -0
  95. package/dist/opencode-usage-scan.js +92 -0
  96. package/dist/opencode-usage-scan.js.map +1 -0
  97. package/dist/opencode-usage-worker.d.ts +1 -0
  98. package/dist/opencode-usage-worker.js +10 -0
  99. package/dist/opencode-usage-worker.js.map +1 -0
  100. package/dist/outbound-handlers.js +22 -2
  101. package/dist/outbound-handlers.js.map +1 -1
  102. package/dist/outbound-schemas.d.ts +4 -0
  103. package/dist/outbound-schemas.js +4 -0
  104. package/dist/outbound-schemas.js.map +1 -1
  105. package/dist/quickstart-api.js +13 -2
  106. package/dist/quickstart-api.js.map +1 -1
  107. package/dist/quickstart.js +2 -1
  108. package/dist/quickstart.js.map +1 -1
  109. package/dist/setup-form.d.ts +1 -1
  110. package/dist/setup-form.js +7 -1
  111. package/dist/setup-form.js.map +1 -1
  112. package/dist/setup-host.d.ts +9 -0
  113. package/dist/setup-host.js +124 -5
  114. package/dist/setup-host.js.map +1 -1
  115. package/dist/setup-signin-assets.d.ts +2 -0
  116. package/dist/setup-signin-assets.js +6 -0
  117. package/dist/setup-signin-assets.js.map +1 -0
  118. package/dist/setup-wizard.js +2 -0
  119. package/dist/setup-wizard.js.map +1 -1
  120. package/dist/structured-signin-transport.d.ts +8 -0
  121. package/dist/structured-signin-transport.js +79 -0
  122. package/dist/structured-signin-transport.js.map +1 -0
  123. package/dist/structured-signin.d.ts +131 -0
  124. package/dist/structured-signin.js +388 -0
  125. package/dist/structured-signin.js.map +1 -0
  126. package/dist/tool-permissions.js +2 -0
  127. package/dist/tool-permissions.js.map +1 -1
  128. package/dist/topic-commands.js +4 -1
  129. package/dist/topic-commands.js.map +1 -1
  130. package/dist/transcript-sources.d.ts +26 -2
  131. package/dist/transcript-sources.js +87 -40
  132. package/dist/transcript-sources.js.map +1 -1
  133. package/dist/types.d.ts +3 -2
  134. package/dist/ui/backend-signin.js +63 -10
  135. package/dist/ui/fleet-usage.js +19 -15
  136. package/dist/ui/panel-log.js +2 -2
  137. package/dist/ui/panel-needs.js +4 -4
  138. package/dist/ui/shared/app-i18n.js +2 -2
  139. package/dist/ui/shared/app.css +1 -0
  140. package/dist/ui/structured-signin.js +115 -0
  141. package/dist/usage-report.d.ts +20 -0
  142. package/dist/usage-report.js +54 -17
  143. package/dist/usage-report.js.map +1 -1
  144. package/dist/usage-rollup.d.ts +9 -1
  145. package/dist/usage-rollup.js +20 -4
  146. package/dist/usage-rollup.js.map +1 -1
  147. package/dist/web-api.d.ts +1 -0
  148. package/dist/web-api.js +99 -0
  149. package/dist/web-api.js.map +1 -1
  150. package/dist/web-auth.js +1 -1
  151. package/dist/web-auth.js.map +1 -1
  152. package/dist/web-backend-login.d.ts +1 -7
  153. package/dist/web-backend-login.js +2 -24
  154. package/dist/web-backend-login.js.map +1 -1
  155. package/package.json +2 -1
  156. package/templates/windows-shortcuts.ps1 +7 -5
@@ -85,6 +85,7 @@ import { type CrossInstanceRecord, type XmsgVisibility } from "./search/search-d
85
85
  import { WebSettingsTools } from "./web-settings-tools.js";
86
86
  import { type Delegation, type TrafficItem } from "./fleet-traffic.js";
87
87
  import { WebBackendLogins, type SignInTarget } from "./web-backend-login.js";
88
+ import { BrowserBackendSignIns } from "./browser-backend-signins.js";
88
89
  import { type RawConfigPatch } from "./settings-api.js";
89
90
  import { type AgentEndpointContext } from "./agent-endpoint.js";
90
91
  import { ClassicChannelManager } from "./classic-channel-manager.js";
@@ -246,6 +247,7 @@ export declare const CLI_ENV_PROBE_DEADLINE_MS = 16000;
246
247
  * started ten CLIs together, saturated the CPU, and healthy starts then missed
247
248
  * their startup budget — which used to cost the user their conversation. Cores
248
249
  * bound how many can actually make progress; memory bounds how many fit.
250
+ * `cores` is what this process may use (cpuset and cgroup quota, #749), not the host's count.
249
251
  */
250
252
  export declare function deriveSpawnConcurrency(freeMemMB: number, cores: number): number;
251
253
  /** #2499 review: one line of Settings → Guests' Recent changes (guestOverview), from the event log. */
@@ -454,6 +456,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
454
456
  private pendingBackendSelects;
455
457
  /** nonce → pending button prompt (hang restart, interactive assist, clean-exit restart). */
456
458
  private pendingNonceButtons;
459
+ /** #2598 B2: the kiro moves waiting on a fleet admin's Confirm in General, by prompt nonce. */
460
+ private pendingKiroMigrations;
457
461
  /** #1386 "Needs you": the list, its live chat messages and Acknowledge. Started with the fleet (finishStartup). */
458
462
  private needsYou;
459
463
  /** When the fleet first saw an instance crashed, for its "Needs you" age; cleared when it is not. */
@@ -764,6 +768,21 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
764
768
  /** Exact configured effect owners; this does not depend on prompt location. */
765
769
  private settingsChatAuthority;
766
770
  private promptSettingsChange;
771
+ private kiroMigrateDeps;
772
+ /**
773
+ * #2598 B2, migrate_kiro_instance: judge the move, then ask in General for a fleet admin's Confirm. Nothing changes
774
+ * here; the move runs on the Confirm (handleKiroMigrateCallback), judged again then.
775
+ */
776
+ requestKiroMigration(name: string, startFresh: boolean): Promise<{
777
+ ok: true;
778
+ text: string;
779
+ classic_conversation: string | null;
780
+ } | {
781
+ ok: false;
782
+ error: string;
783
+ }>;
784
+ /** A fleet admin's Confirm or Cancel on a kiro move. Any fleet admin (the default nonce authorization), in General. */
785
+ private handleKiroMigrateCallback;
767
786
  private handleSettingsChangeCallback;
768
787
  startSettingsConfirmationControl(): Promise<void>;
769
788
  private get webToken();
@@ -2410,6 +2429,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
2410
2429
  * non-identity instance leaves are canonicalized to inheritance afterward.
2411
2430
  */
2412
2431
  saveFleetConfig(explicitPatches?: RawConfigPatch[]): void;
2432
+ /** A permission tightening is effective when its save returns, independently of the later apply job or IPC. */
2433
+ private refreshSavedDashboardPermissions;
2413
2434
  /** Why the config written to `candidate` must not replace fleet.yaml, or null: it does not load, or it adds errors. */
2414
2435
  private savedFleetConfigProblem;
2415
2436
  /**
@@ -2890,6 +2911,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
2890
2911
  * without one); codex "unrecorded" (its rollouts don't say which login wrote them).
2891
2912
  */
2892
2913
  private usageProfileOf;
2914
+ /** #2122 §10: a Kiro instance with a credential profile writes its classic store in that profile's home. */
2915
+ private kiroDbPathOf;
2893
2916
  /** #2122: the fleet's day boundary zone — defaults.scheduler.default_timezone, else this host's. */
2894
2917
  usageZone(): string;
2895
2918
  /**
@@ -3370,6 +3393,13 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
3370
3393
  */
3371
3394
  private readonly loginWindow;
3372
3395
  private get webLogin();
3396
+ private browserBackendSignInsInstance;
3397
+ private structuredLoginCleanupFailed;
3398
+ private readonly pendingBackendSince;
3399
+ private browserSignInOwnerCurrent;
3400
+ /** #2599: default-account controls share the exact writer reservation with chat/profile/install. */
3401
+ get browserBackendSignIns(): BrowserBackendSignIns;
3402
+ private needsYouBackendSignIns;
3373
3403
  /** #2233: sign-ins started from the web dashboard (local admin sessions), on the same controller as chat's. */
3374
3404
  private webBackendLoginsInstance;
3375
3405
  get webBackendLogins(): WebBackendLogins;
@@ -3663,6 +3693,11 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
3663
3693
  private cliEnvPath;
3664
3694
  /** Read the cached CLI env for a backend (a credential profile's own, when given), or null if missing/stale/unparseable. */
3665
3695
  private readCliEnv;
3696
+ /**
3697
+ * #2122 §10: the version the CLI probe last recorded for a backend — its cache file read whatever its age (the usage
3698
+ * reader's version gate needs the last known version, not a fresh one); null when none was recorded. Never a probe.
3699
+ */
3700
+ private lastRecordedCliVersion;
3666
3701
  /** Return cached CLI versions immediately. Refresh is started after /sysinfo is sent. */
3667
3702
  getBackendCliVersionSnapshot(): BackendCliVersionSnapshot;
3668
3703
  /** Called only after the user-visible sysinfo send/respond has settled. */
@@ -31,13 +31,16 @@ import { ProfileControlServer } from "./profile-control.js";
31
31
  import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, rmSync, readdirSync, renameSync, copyFileSync, chmodSync, statSync, accessSync, realpathSync, lstatSync, constants as fsConstants, closeSync } from "node:fs";
32
32
  import { atomicWriteFileSync } from "./atomic-write.js";
33
33
  import { createHash, randomBytes, randomUUID } from "node:crypto";
34
- import { freemem, totalmem, cpus, homedir } from "node:os";
34
+ import { freemem, totalmem, homedir } from "node:os";
35
+ import { effectiveCpuCount } from "./cpu-quota.js";
35
36
  import { createServer } from "node:http";
36
37
  import { Worker } from "node:worker_threads";
37
38
  import { ProbeWorkerPool } from "./probe-worker-pool.js";
38
39
  import { join, dirname, basename, delimiter, isAbsolute, relative, resolve as resolvePath } from "node:path";
39
40
  import { fileURLToPath } from "node:url";
41
+ import { isDeepStrictEqual } from "node:util";
40
42
  import { getAgendHome, ensureWorkspaceGit } from "./paths.js";
43
+ import { applyKiroMigration, kiroMigrationPrompt, planKiroMigration } from "./kiro-migrate.js";
41
44
  import { fleetLabel } from "./fleet-label.js";
42
45
  import { beginFullRestartProgress as persistFullRestartProgress, beginUpdateProgress as persistUpdateProgress, clearUpdateMarker, isUpdateInProgress, readUpdateProgress, sameUpdateMarker, setUpdateProgressStage, updateMarkerIdentity, updateProgressOperation, updateReceiptOf, } from "./update-marker.js";
43
46
  import { formatUpdateProgress } from "./update-progress.js";
@@ -56,7 +59,7 @@ const __dirname = dirname(__filename);
56
59
  const DEFAULT_OPEN_ACCESS = { mode: "open", allowed_users: [], max_pending_codes: 0, code_expiry_minutes: 0 };
57
60
  import { STATUS_EMOJI_CONFIG_KEYS, STATUS_EMOJI_KEYS, STATUS_EMOJI_SUGGESTIONS, TELEGRAM_REACTION_EMOJIS, builtinStatusEmojis, customEmojiValue, emojiImageUrl, normalizeEmoji, previewStatusEmojis, reactionForm, reactionMatchKey, resolveStatusEmojis, statusAvoidList, statusEmojiProblem, statusMatchKey, statusMatchKeys, textForm, } from "./status-emojis.js";
58
61
  import { isProbeableRouteTarget } from "./fleet-context.js";
59
- import { getEffectiveInstanceDefaults, loadFleetConfig, loadRawFleetConfig, DEFAULT_COST_GUARD, DEFAULT_DAILY_SUMMARY, DEFAULT_INSTANCE_CONFIG } from "./config.js";
62
+ import { getEffectiveInstanceDefaults, loadFleetConfig, loadRawFleetConfig, DEFAULT_COST_GUARD, DEFAULT_DAILY_SUMMARY, DEFAULT_INSTANCE_CONFIG, autoGeneralInstanceConfig } from "./config.js";
60
63
  import { EventLog } from "./event-log.js";
61
64
  import { binaryProbe } from "./binary-probe.js";
62
65
  import { AVAILABILITY_STALE_MS, BackendAvailabilityStore, hostAvailabilityProbes } from "./backend-availability.js";
@@ -193,7 +196,7 @@ import { discordGeneralChannelId } from "./discord-general.js";
193
196
  import { parseEnvText } from "./env-file.js";
194
197
  import { isDisallowedIntentsError, isRejectedTokenError } from "./discord-permissions.js";
195
198
  import { filterUsageProviders, formatDiscordUsageActivity, getUsageSnapshot, handleUsageRequest, isUsagePath, usageProviderIdForBackend } from "./usage/usage-api.js";
196
- import { LOGIN_FLOWS, LOGIN_BACKEND_ALIASES } from "./login-flows.js";
199
+ import { LOGIN_FLOWS, checkAuthStatus, LOGIN_BACKEND_ALIASES } from "./login-flows.js";
197
200
  import { LoginSession } from "./login-manager.js";
198
201
  import { runOnLoginShell } from "./login-shell.js";
199
202
  import { tightenInstanceDirs } from "./private-dir.js";
@@ -213,6 +216,10 @@ import { admitProfileLogin, LoginController, LOGIN_TOKEN_RESEND_PREFIX, POST_LOG
213
216
  import { WebBackendLogins, dashboardSignInSupported } from "./web-backend-login.js";
214
217
  import { runBeforeDeadline } from "./deadline.js";
215
218
  import { LoginWindowLock } from "./login-window-lock.js";
219
+ import { BrowserBackendSignIns } from "./browser-backend-signins.js";
220
+ import { createStructuredSignInTransport } from "./structured-signin-transport.js";
221
+ import { ONBOARDING_BACKENDS, readPendingBackendSignIns } from "./backend-signin-pending.js";
222
+ import { structuredSignInSupported } from "./structured-signin.js";
216
223
  import { handleSettingsRequest } from "./settings-api.js";
217
224
  import { setLocale, detectLocale, getLocale, t } from "./locale.js";
218
225
  import { recentChatContext } from "./log-tail.js";
@@ -276,6 +283,7 @@ import { PROVIDER_SECRET_SPECS, PUBLIC_TUNNEL_SECRET_SPEC, providerSecretSpec, p
276
283
  /** A self-restart is a whole service restart; 120s is the apply budget, not this. */
277
284
  const SELF_RESTART_DEADLINE_MS = 300_000;
278
285
  import { APPLY_FLEET_TARGET, ApplyJobStore, viewOf, } from "./apply-job.js";
286
+ import { kiroHome } from "./backend/kiro-kas-store.js";
279
287
  import { CHAT_PROFILE_LOGIN_BACKENDS, credentialHomeSpec, credentialProfileHome, credentialProfileLogin, credentialProfileLoginCommand, credentialProfileStoreHome, credentialSwitchCarriesConversation, credentialSwitchStartsFresh, instanceCredentialProfile } from "./backend/credential-profile.js";
280
288
  import { carryClaudeConversation } from "./backend/claude-carry.js";
281
289
  import { mergeBackendOptions, profileKey, profileName, profileOf } from "./classic-bindings.js";
@@ -473,6 +481,10 @@ const OOM_RETRY_CALLBACK_PREFIX = "oom-retry:";
473
481
  const NEEDS_PAUSE_TTL_MS = 10_000;
474
482
  const HANG_CALLBACK_PREFIX = "hang:";
475
483
  const CLEAR_CONFIRM_CALLBACK_PREFIX = "clear-confirm:";
484
+ /** #2598 B2: a kiro classic → TUI move's Confirm in General. "kiro-migrate:" (13) + 32 hex + ":confirm" (8) = 53 bytes. */
485
+ const KIRO_MIGRATE_CALLBACK_PREFIX = "kiro-migrate:";
486
+ /** Long enough for an admin to read the prompt; the move is judged again on the click. */
487
+ const KIRO_MIGRATE_TIMEOUT_MS = 10 * 60_000;
476
488
  /**
477
489
  * The prompts the web dashboard also offers: the ones about an instance's own health, which a dashboard
478
490
  * user — holding the full-fleet web credential — may answer exactly as a fleet admin on the platform may.
@@ -568,6 +580,7 @@ export const CLI_ENV_PROBE_DEADLINE_MS = 16_000;
568
580
  * started ten CLIs together, saturated the CPU, and healthy starts then missed
569
581
  * their startup budget — which used to cost the user their conversation. Cores
570
582
  * bound how many can actually make progress; memory bounds how many fit.
583
+ * `cores` is what this process may use (cpuset and cgroup quota, #749), not the host's count.
571
584
  */
572
585
  export function deriveSpawnConcurrency(freeMemMB, cores) {
573
586
  const byMemory = Math.floor(freeMemMB / 300);
@@ -885,6 +898,8 @@ export class FleetManager {
885
898
  pendingBackendSelects = new Map();
886
899
  /** nonce → pending button prompt (hang restart, interactive assist, clean-exit restart). */
887
900
  pendingNonceButtons = new Map();
901
+ /** #2598 B2: the kiro moves waiting on a fleet admin's Confirm in General, by prompt nonce. */
902
+ pendingKiroMigrations = new Map();
888
903
  /** #1386 "Needs you": the list, its live chat messages and Acknowledge. Started with the fleet (finishStartup). */
889
904
  needsYou = null;
890
905
  /** When the fleet first saw an instance crashed, for its "Needs you" age; cleared when it is not. */
@@ -1723,6 +1738,74 @@ export class FleetManager {
1723
1738
  // No platform confirmation was reachable. Host inspection is still required.
1724
1739
  this.logger.info({ id: view.id }, "Settings change requires local `agend settings confirm <id>`");
1725
1740
  }
1741
+ kiroMigrateDeps() {
1742
+ return {
1743
+ agendHome: getAgendHome(),
1744
+ fleetConfig: () => this.fleetConfig,
1745
+ saveFleetConfig: () => this.saveFleetConfig(),
1746
+ status: name => this.getInstanceStatus(name),
1747
+ restart: name => this.restartSingleInstance(name, { explicit: true }),
1748
+ };
1749
+ }
1750
+ /**
1751
+ * #2598 B2, migrate_kiro_instance: judge the move, then ask in General for a fleet admin's Confirm. Nothing changes
1752
+ * here; the move runs on the Confirm (handleKiroMigrateCallback), judged again then.
1753
+ */
1754
+ async requestKiroMigration(name, startFresh) {
1755
+ const judged = planKiroMigration(this.kiroMigrateDeps(), name, startFresh);
1756
+ if (!judged.ok)
1757
+ return judged;
1758
+ const general = this.findGeneralInstance();
1759
+ const adapterId = general ? this.getInstanceAdapterId(general) : undefined;
1760
+ const adapter = general ? this.getAdapterForInstance(general) : undefined;
1761
+ const chatId = general ? this.getGroupIdForInstance(general) : undefined;
1762
+ const topic = general ? String(this.fleetConfig?.instances[general]?.topic_id ?? "") : "";
1763
+ if (!general || !adapterId || !adapter || !chatId)
1764
+ return { ok: false, error: t("kiro_migrate.no_general") };
1765
+ const { plan } = judged;
1766
+ try {
1767
+ const nonce = await this.postNonceButtonPromptOrThrow({
1768
+ prefix: KIRO_MIGRATE_CALLBACK_PREFIX, alertType: "clear_confirm", instanceName: name, adapter, adapterId, chatId,
1769
+ ...(topic ? { threadId: topic } : {}),
1770
+ message: kiroMigrationPrompt(plan, this.getInstanceStatus(name) === "running"),
1771
+ choices: [{ action: "confirm", label: t("kiro_migrate.confirm_button") }, { action: "cancel", label: t("kiro_migrate.cancel_button") }],
1772
+ expiredText: t("kiro_migrate.expired", name),
1773
+ timeoutMs: KIRO_MIGRATE_TIMEOUT_MS,
1774
+ });
1775
+ this.pendingKiroMigrations.set(nonce, plan);
1776
+ const expiry = setTimeout(() => this.pendingKiroMigrations.delete(nonce), KIRO_MIGRATE_TIMEOUT_MS + 60_000);
1777
+ expiry.unref?.();
1778
+ }
1779
+ catch (err) {
1780
+ return { ok: false, error: `${t("kiro_migrate.no_general")} (${err.message})` };
1781
+ }
1782
+ return { ok: true, text: t("kiro_migrate.requested"), classic_conversation: plan.classicId };
1783
+ }
1784
+ /** A fleet admin's Confirm or Cancel on a kiro move. Any fleet admin (the default nonce authorization), in General. */
1785
+ async handleKiroMigrateCallback(data, adapterId, adapter) {
1786
+ const claimed = this.consumeNonceCallback(KIRO_MIGRATE_CALLBACK_PREFIX, /^kiro-migrate:([0-9a-f]{32}):(confirm|cancel)$/, data, adapterId, adapter);
1787
+ if (claimed === null)
1788
+ return false;
1789
+ if (claimed === "consumed")
1790
+ return true;
1791
+ const { entry, action } = claimed;
1792
+ const nonce = data.callbackData.slice(KIRO_MIGRATE_CALLBACK_PREFIX.length, KIRO_MIGRATE_CALLBACK_PREFIX.length + 32);
1793
+ const plan = this.pendingKiroMigrations.get(nonce);
1794
+ this.pendingKiroMigrations.delete(nonce);
1795
+ const messageId = entry.messageId ?? data.messageId;
1796
+ if (action === "cancel" || !plan) {
1797
+ await this.retireNonceButtons(entry, messageId, t(plan ? "kiro_migrate.cancelled" : "kiro_migrate.expired", entry.instanceName));
1798
+ return true;
1799
+ }
1800
+ this.eventLog?.insert(plan.name, "kiro_migrate", { classicId: plan.classicId, userId: data.userId });
1801
+ await this.retireNonceButtons(entry, messageId, t("kiro_migrate.applying", plan.name));
1802
+ const outcome = await applyKiroMigration(this.kiroMigrateDeps(), plan);
1803
+ this.logger.info({ name: plan.name, ok: outcome.ok, classicId: plan.classicId }, "kiro classic → TUI move");
1804
+ this.notifyFleetError(outcome.text);
1805
+ if (outcome.ok)
1806
+ this.notifyInstanceTopic(plan.name, outcome.text);
1807
+ return true;
1808
+ }
1726
1809
  async handleSettingsChangeCallback(data, adapterId, adapter) {
1727
1810
  const result = this.consumeNonceCallback("settings-confirm:", /^settings-confirm:([0-9a-f]{32}):(confirm|reject)$/, data, adapterId, adapter, undefined, (userId, entry) => entry.confirmationAdmin?.(userId) === true);
1728
1811
  if (!result)
@@ -2576,7 +2659,7 @@ export class FleetManager {
2576
2659
  const explicit = this.fleetConfig?.defaults?.startup?.concurrency;
2577
2660
  if (explicit != null)
2578
2661
  return Math.max(1, Math.min(20, explicit));
2579
- return deriveSpawnConcurrency(Math.round(freemem() / (1024 * 1024)), cpus().length);
2662
+ return deriveSpawnConcurrency(Math.round(freemem() / (1024 * 1024)), effectiveCpuCount());
2580
2663
  }
2581
2664
  /** #1685: "elevated" or "critical" while host memory pressure slows or holds new agents; otherwise null (an
2582
2665
  * unknown macOS reading — the advisory case — is unknown, so null too). */
@@ -7774,7 +7857,8 @@ export class FleetManager {
7774
7857
  }
7775
7858
  }
7776
7859
  async startAllWithinDeadline(configPath, deadline) {
7777
- this.loginWindow.reopen(); // a stopAll → startAll restart must accept login windows again
7860
+ if (!this.structuredLoginCleanupFailed)
7861
+ this.loginWindow.reopen(); // a stopAll → startAll restart must accept login windows again
7778
7862
  this.loginController?.reopen();
7779
7863
  const startupStartedAt = Date.now();
7780
7864
  FleetManager.signalTarget = this;
@@ -8035,12 +8119,7 @@ export class FleetManager {
8035
8119
  mkdirSync(generalDir, { recursive: true, mode: 0o700 });
8036
8120
  const backendName = fleet.defaults.backend ?? "claude-code";
8037
8121
  this.ensureGeneralInstructions(generalDir, backendName, name);
8038
- fleet.instances[name] = {
8039
- ...DEFAULT_INSTANCE_CONFIG,
8040
- working_directory: generalDir,
8041
- general_topic: true,
8042
- channel_id: need.adapterId,
8043
- };
8122
+ fleet.instances[name] = autoGeneralInstanceConfig(fleet.defaults, generalDir, need.adapterId); // #2598: its kiro_ui is the fleet's
8044
8123
  generalsCreated = true;
8045
8124
  }
8046
8125
  if (generalsCreated)
@@ -12251,9 +12330,46 @@ export class FleetManager {
12251
12330
  },
12252
12331
  });
12253
12332
  this.rawFleetConfig = loadRawFleetConfig(this.configPath);
12333
+ // The instance map is defaults-expanded. A defaults edit changes that view even when it writes no instance
12334
+ // leaves. Re-resolve from the committed YAML so inherited values cannot become stale overrides on the next save.
12335
+ // Keep the FleetConfig object itself: Settings and other request contexts hold it.
12336
+ const resolvedInstances = loadFleetConfig(this.configPath).instances;
12337
+ for (const [name, resolved] of Object.entries(resolvedInstances)) {
12338
+ const current = this.fleetConfig.instances[name];
12339
+ if (!current) {
12340
+ this.fleetConfig.instances[name] = resolved;
12341
+ continue;
12342
+ }
12343
+ // Lifecycle rollback fences hold these object identities across save. Refresh their values, not their owner.
12344
+ for (const key of Object.keys(current))
12345
+ if (!Object.hasOwn(resolved, key))
12346
+ delete current[key];
12347
+ Object.assign(current, resolved);
12348
+ }
12254
12349
  this.savedFleetConfigSnapshot = structuredClone(this.fleetConfig);
12350
+ this.refreshSavedDashboardPermissions();
12255
12351
  this.logger.info({ path: this.configPath, strippedDefaults: redundantPaths.length }, "Saved fleet config (lossless patch)");
12256
12352
  }
12353
+ /** A permission tightening is effective when its save returns, independently of the later apply job or IPC. */
12354
+ refreshSavedDashboardPermissions() {
12355
+ if (!this.fleetConfig)
12356
+ return;
12357
+ const classicNames = new Set(this.classicChannels?.getAll().map(ch => ch.instanceName) ?? []);
12358
+ for (const [name, daemon] of this.daemons) {
12359
+ const next = this.fleetConfig.instances[name] ?? (classicNames.has(name) ? getEffectiveInstanceDefaults(this.fleetConfig.defaults) : undefined);
12360
+ const previous = daemon.getConfigSnapshot?.();
12361
+ if (!next || !previous)
12362
+ continue;
12363
+ if (isDeepStrictEqual(previous.dashboard, next.dashboard) && previous.office_access === next.office_access)
12364
+ continue;
12365
+ // Only permissions are adopted here. Cold fields retain the live daemon's old snapshot, so reconcile still
12366
+ // detects that they require a restart. MCP menus also watch the raw file; this updates the next spawn's env.
12367
+ daemon.applyConfigUpdate({ dashboard: next.dashboard ?? null, office_access: next.office_access ?? null });
12368
+ if (taskBoardSwitchedOn(previous.dashboard, next.dashboard)) {
12369
+ this.admitTaskBoardNotice(name, TASK_BOARD_ON_NOTICE, `task-board-on:${name}:${Date.now()}`);
12370
+ }
12371
+ }
12372
+ }
12257
12373
  /** Why the config written to `candidate` must not replace fleet.yaml, or null: it does not load, or it adds errors. */
12258
12374
  savedFleetConfigProblem(candidate) {
12259
12375
  let after;
@@ -14357,6 +14473,8 @@ export class FleetManager {
14357
14473
  return true;
14358
14474
  if (await this.handleSettingsChangeCallback(data, adapterId, adapter))
14359
14475
  return true;
14476
+ if (await this.handleKiroMigrateCallback(data, adapterId, adapter))
14477
+ return true;
14360
14478
  if (await this.handleDashboardCallback(data, adapterId, adapter))
14361
14479
  return true;
14362
14480
  if (await this.handleClassicApproval(data, adapterId, adapter))
@@ -14477,6 +14595,7 @@ export class FleetManager {
14477
14595
  const fleet = Object.entries(this.fleetConfig?.instances ?? {}).map(([name, cfg]) => ({
14478
14596
  name, backend: this.backendNameOf(name), workingDirectory: cfg.working_directory, ledgerPath: join(this.getInstanceDir(name), "cache-ledger.json"),
14479
14597
  claudeConfigDir: this.claudeConfigDirOf(name), profile: this.usageProfileOf(name, this.backendNameOf(name)),
14598
+ kiroDbPath: this.kiroDbPathOf(name, this.backendNameOf(name)),
14480
14599
  }));
14481
14600
  const taken = new Set(fleet.map(i => i.name));
14482
14601
  const classic = (this.classicChannels?.getAll() ?? []).filter(ch => !taken.has(ch.instanceName)).map(ch => ({
@@ -14486,6 +14605,7 @@ export class FleetManager {
14486
14605
  ledgerPath: join(this.getInstanceDir(ch.instanceName), "cache-ledger.json"),
14487
14606
  claudeConfigDir: this.claudeConfigDirOf(ch.instanceName),
14488
14607
  profile: this.usageProfileOf(ch.instanceName, this.classicChannels?.getBackendByInstance(ch.instanceName, this.fleetConfig?.defaults?.backend) ?? this.fleetConfig?.defaults?.backend ?? "claude-code"),
14608
+ kiroDbPath: this.kiroDbPathOf(ch.instanceName, this.classicChannels?.getBackendByInstance(ch.instanceName, this.fleetConfig?.defaults?.backend) ?? this.fleetConfig?.defaults?.backend ?? "claude-code"),
14489
14609
  }));
14490
14610
  return [...fleet, ...new Map(classic.map(c => [c.name, c])).values()];
14491
14611
  }
@@ -14506,8 +14626,19 @@ export class FleetManager {
14506
14626
  return this.credentialProfileOf(name, "claude-code") ?? "default";
14507
14627
  if (backend === "codex")
14508
14628
  return "unrecorded";
14629
+ if (backend === "kiro-cli")
14630
+ return this.credentialProfileOf(name, "kiro-cli") ?? "default";
14631
+ if (backend === "grok" || backend === "muse" || backend === "opencode" || backend === "antigravity")
14632
+ return "default";
14509
14633
  return undefined;
14510
14634
  }
14635
+ /** #2122 §10: a Kiro instance with a credential profile writes its classic store in that profile's home. */
14636
+ kiroDbPathOf(name, backend) {
14637
+ if (backend !== "kiro-cli")
14638
+ return undefined;
14639
+ const profile = this.credentialProfileOf(name, "kiro-cli");
14640
+ return profile ? join(credentialProfileStoreHome(getAgendHome(), "kiro-cli", profile), "data.sqlite3") : undefined;
14641
+ }
14511
14642
  /** #2122: the fleet's day boundary zone — defaults.scheduler.default_timezone, else this host's. */
14512
14643
  usageZone() {
14513
14644
  return this.fleetConfig?.defaults?.scheduler?.default_timezone?.trim() || Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
@@ -14550,6 +14681,19 @@ export class FleetManager {
14550
14681
  rolloutsUnreadable: root => sharedRolloutIndex(root).unreadable,
14551
14682
  metaPath: join(this.dataDir, "cache-codex-rollouts.json"),
14552
14683
  zone: () => this.usageZone(),
14684
+ // #2122 §10: Kiro's shared classic store (XDG_DATA_HOME, as kiro-cli resolves it) and its KIRO_HOME.
14685
+ kiroDbPath: () => join(process.env.XDG_DATA_HOME?.trim() || join(homedir(), ".local", "share"), "kiro-cli", "data.sqlite3"),
14686
+ kiroHome: () => kiroHome(),
14687
+ // #2122 §10 step 3: grok keeps its sessions under ~/.grok (grok.ts reads them there too).
14688
+ grokSessionsDir: () => join(homedir(), ".grok", "sessions"),
14689
+ // #2122 §10 step 4: muse keeps sessions under $XDG_DATA_HOME/muse (else ~/.local/share/muse), as muse.ts reads them.
14690
+ // #2122 §10 step 5: OpenCode's database, under XDG_DATA_HOME as opencode resolves it.
14691
+ // #2122 §10 step 6: agy's conversations, under ~/.gemini as the antigravity backend reads its settings.
14692
+ antigravityDir: () => join(homedir(), ".gemini", "antigravity-cli", "conversations"),
14693
+ // The version AgEnD's CLI probe last recorded for agy: its cache file, whatever its age (never runs agy).
14694
+ antigravityVersion: () => this.lastRecordedCliVersion("antigravity"),
14695
+ opencodeDbPath: () => join(process.env.XDG_DATA_HOME?.trim() || join(homedir(), ".local", "share"), "opencode", "opencode.db"),
14696
+ museSessionsDir: () => join(process.env.XDG_DATA_HOME?.trim() || join(homedir(), ".local", "share"), "muse", "sessions"),
14553
14697
  log: (level, msg, extra) => this.logger[level](extra ?? {}, msg),
14554
14698
  });
14555
14699
  }
@@ -14670,6 +14814,7 @@ export class FleetManager {
14670
14814
  return this.deliveryOutbox.acknowledge(deliveryId, by);
14671
14815
  },
14672
14816
  tasks: () => this.needsYouTaskItems(),
14817
+ backendSignIns: () => this.needsYouBackendSignIns(),
14673
14818
  taskAction: action => this.taskActionFromChat(action),
14674
14819
  settings: () => ({
14675
14820
  liveMessage: this.fleetConfig?.needs_you?.live_message !== false,
@@ -16685,6 +16830,55 @@ export class FleetManager {
16685
16830
  }
16686
16831
  return this.loginController;
16687
16832
  }
16833
+ browserBackendSignInsInstance = null;
16834
+ structuredLoginCleanupFailed = false;
16835
+ pendingBackendSince = new Map();
16836
+ browserSignInOwnerCurrent(owner) {
16837
+ const record = owner;
16838
+ return !this.shuttingDown && typeof record.handle === "string" && this.webSessions?.findByHandle(record.handle) === owner
16839
+ && this.isLocalAdminSession(record.handle);
16840
+ }
16841
+ /** #2599: default-account controls share the exact writer reservation with chat/profile/install. */
16842
+ get browserBackendSignIns() {
16843
+ return this.browserBackendSignInsInstance ??= new BrowserBackendSignIns({
16844
+ dataDir: this.dataDir, lock: this.loginWindow,
16845
+ current: owner => this.browserSignInOwnerCurrent(owner),
16846
+ installed: async () => ONBOARDING_BACKENDS.filter(backend => this.isCliInstalled(backend)),
16847
+ checkAuth: backend => checkAuthStatus(LOGIN_FLOWS[backend].authCheck),
16848
+ checkConsoleAuth: () => checkAuthStatus(LOGIN_FLOWS.antigravity.authCheck),
16849
+ transport: () => createStructuredSignInTransport(),
16850
+ changed: (event, owner, launched) => {
16851
+ try {
16852
+ this.eventLog?.insert("login", "browser_backend_signin", event);
16853
+ }
16854
+ catch { /* safe metadata only */ }
16855
+ broadcastSseEvent(this.sseClients, "backend_login", {}, err => this.logger.debug({ err }, "backend_login event failed"));
16856
+ this.needsYou?.poke();
16857
+ if (!this.browserSignInOwnerCurrent(owner))
16858
+ return;
16859
+ if (event.phase === "starting") {
16860
+ const record = owner;
16861
+ this.notifyFleetError(t("login.web_dashboard_notice", event.backend, record.label, record.handle.slice(0, 8)));
16862
+ }
16863
+ if (event.phase === "done") {
16864
+ void this.backendAvailability.refresh([event.backend], { force: true }).catch(() => { });
16865
+ if (launched)
16866
+ void this.recoverBackendInstances(event.backend, undefined, null).catch(() => this.logger.warn({ backend: event.backend }, "Post sign-in recovery failed"));
16867
+ }
16868
+ },
16869
+ });
16870
+ }
16871
+ needsYouBackendSignIns() {
16872
+ const pending = readPendingBackendSignIns(this.dataDir).pending;
16873
+ for (const backend of this.pendingBackendSince?.keys() ?? [])
16874
+ if (!pending.includes(backend))
16875
+ this.pendingBackendSince.delete(backend);
16876
+ return pending.map(backend => {
16877
+ if (!this.pendingBackendSince.has(backend))
16878
+ this.pendingBackendSince.set(backend, Date.now());
16879
+ return { backend, since: this.pendingBackendSince.get(backend), browser: structuredSignInSupported(backend) };
16880
+ });
16881
+ }
16688
16882
  /** #2233: sign-ins started from the web dashboard (local admin sessions), on the same controller as chat's. */
16689
16883
  webBackendLoginsInstance = null;
16690
16884
  get webBackendLogins() {
@@ -17037,6 +17231,14 @@ export class FleetManager {
17037
17231
  // "cancelled" is the detail both legacy onDone handlers keep quiet about —
17038
17232
  // a stopping fleet must not announce "login failed — fleet shutdown".
17039
17233
  await Promise.all([
17234
+ bounded(this.browserBackendSignInsInstance?.close().then(confirmed => {
17235
+ if (confirmed)
17236
+ this.browserBackendSignInsInstance = null;
17237
+ else {
17238
+ this.structuredLoginCleanupFailed = true;
17239
+ this.logger.error("Structured login cleanup failed; sign-in admission remains closed until the host is restarted.");
17240
+ }
17241
+ }), "browser-controls"),
17040
17242
  bounded(this.loginController?.shutdown(), "web-login"),
17041
17243
  bounded(this.activeInstall?.session.cancel("cancelled"), "install"),
17042
17244
  ]);
@@ -17053,6 +17255,13 @@ export class FleetManager {
17053
17255
  this.installHandoff = null;
17054
17256
  return t("login.cancelled", backend);
17055
17257
  }
17258
+ const browserLogin = this.browserBackendSignInsInstance, browserBackend = browserLogin?.activeBackend;
17259
+ if (browserLogin && browserBackend) {
17260
+ const confirmed = await browserLogin.close();
17261
+ if (confirmed)
17262
+ this.browserBackendSignInsInstance = null;
17263
+ return confirmed ? t("login.cancelled", browserBackend) : t("login.browser_cleanup_failed");
17264
+ }
17056
17265
  if (this.loginController?.isActive())
17057
17266
  return this.loginController.cancel();
17058
17267
  return t("login.no_session");
@@ -18564,6 +18773,19 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
18564
18773
  catch { /* missing / stale / corrupt */ }
18565
18774
  return null;
18566
18775
  }
18776
+ /**
18777
+ * #2122 §10: the version the CLI probe last recorded for a backend — its cache file read whatever its age (the usage
18778
+ * reader's version gate needs the last known version, not a fresh one); null when none was recorded. Never a probe.
18779
+ */
18780
+ lastRecordedCliVersion(backend) {
18781
+ try {
18782
+ const v = JSON.parse(readFileSync(this.cliEnvPath(backend), "utf-8")).version;
18783
+ return typeof v === "string" && v.trim() ? v.trim() : null;
18784
+ }
18785
+ catch {
18786
+ return null;
18787
+ }
18788
+ }
18567
18789
  /** Return cached CLI versions immediately. Refresh is started after /sysinfo is sent. */
18568
18790
  getBackendCliVersionSnapshot() {
18569
18791
  const snapshot = {};