@songsid/agend 2.3.3-beta.2 → 2.3.3-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 (46) hide show
  1. package/dist/backend/claude-code.js +3 -25
  2. package/dist/backend/claude-code.js.map +1 -1
  3. package/dist/backend/claude-transcript-errors.d.ts +18 -0
  4. package/dist/backend/claude-transcript-errors.js +19 -0
  5. package/dist/backend/claude-transcript-errors.js.map +1 -0
  6. package/dist/classic-channel-manager.d.ts +4 -1
  7. package/dist/classic-channel-manager.js +21 -7
  8. package/dist/classic-channel-manager.js.map +1 -1
  9. package/dist/cli.js +2 -2
  10. package/dist/cli.js.map +1 -1
  11. package/dist/config-history.d.ts +50 -0
  12. package/dist/config-history.js +263 -0
  13. package/dist/config-history.js.map +1 -0
  14. package/dist/config.d.ts +3 -0
  15. package/dist/config.js +7 -2
  16. package/dist/config.js.map +1 -1
  17. package/dist/daemon.d.ts +6 -0
  18. package/dist/daemon.js +22 -3
  19. package/dist/daemon.js.map +1 -1
  20. package/dist/fleet-manager.d.ts +8 -1
  21. package/dist/fleet-manager.js +47 -16
  22. package/dist/fleet-manager.js.map +1 -1
  23. package/dist/fleet-process-handlers.d.ts +5 -0
  24. package/dist/fleet-process-handlers.js +21 -0
  25. package/dist/fleet-process-handlers.js.map +1 -1
  26. package/dist/locale.js +20 -0
  27. package/dist/locale.js.map +1 -1
  28. package/dist/needs-you-hub.d.ts +1 -0
  29. package/dist/needs-you-hub.js +2 -2
  30. package/dist/needs-you-hub.js.map +1 -1
  31. package/dist/needs-you.d.ts +8 -2
  32. package/dist/needs-you.js +7 -1
  33. package/dist/needs-you.js.map +1 -1
  34. package/dist/task-board-waker.d.ts +4 -1
  35. package/dist/task-board-waker.js +13 -2
  36. package/dist/task-board-waker.js.map +1 -1
  37. package/dist/transcript-monitor.d.ts +7 -0
  38. package/dist/transcript-monitor.js +32 -4
  39. package/dist/transcript-monitor.js.map +1 -1
  40. package/dist/ui/panel-needs.js +11 -9
  41. package/dist/ui/shared/app-i18n.js +2 -2
  42. package/dist/ui/shared/app-needs.js +5 -1
  43. package/dist/web-api.js +2 -0
  44. package/dist/web-api.js.map +1 -1
  45. package/launcher/fleet-service-owner.cjs +4 -1
  46. package/package.json +1 -1
@@ -466,6 +466,10 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
466
466
  private kiroMigrationRuns;
467
467
  /** #1386 "Needs you": the list, its live chat messages and Acknowledge. Started with the fleet (finishStartup). */
468
468
  private needsYou;
469
+ private configHistory;
470
+ private configHistoryTimer;
471
+ private loadedFleetSource;
472
+ private getConfigHistory;
469
473
  /** When the fleet first saw an instance crashed, for its "Needs you" age; cleared when it is not. */
470
474
  private readonly needsCrashedAt;
471
475
  /** Pause reason/time read from the marker, per paused instance; dropped on any attention change of it, and at most
@@ -1065,7 +1069,10 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
1065
1069
  webhookEmit(event: string, name: string, data?: Record<string, unknown>): void;
1066
1070
  getSysInfo(): import("./fleet-context.js").SysInfo;
1067
1071
  /** Load fleet.yaml and build routing table */
1068
- loadConfig(configPath: string): FleetConfig;
1072
+ loadConfig(configPath: string, options?: {
1073
+ startup?: boolean;
1074
+ remember?: boolean;
1075
+ }): FleetConfig;
1069
1076
  private relayLoginModeWarned;
1070
1077
  /** `login.mode: relay` was removed (#1139). Still accepted so an upgrade cannot stop a fleet from starting. */
1071
1078
  private warnAboutRemovedLoginMode;
@@ -67,7 +67,7 @@ const __dirname = dirname(__filename);
67
67
  const DEFAULT_OPEN_ACCESS = { mode: "open", allowed_users: [], max_pending_codes: 0, code_expiry_minutes: 0 };
68
68
  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";
69
69
  import { isProbeableRouteTarget } from "./fleet-context.js";
70
- import { getEffectiveInstanceDefaults, loadFleetConfig, loadRawFleetConfig, DEFAULT_COST_GUARD, DEFAULT_DAILY_SUMMARY, DEFAULT_INSTANCE_CONFIG, autoGeneralInstanceConfig } from "./config.js";
70
+ import { getEffectiveInstanceDefaults, loadFleetConfig, loadRawFleetConfig, loadFleetConfigSource, loadRawFleetConfigSource, DEFAULT_COST_GUARD, DEFAULT_DAILY_SUMMARY, DEFAULT_INSTANCE_CONFIG, autoGeneralInstanceConfig } from "./config.js";
71
71
  import { EventLog } from "./event-log.js";
72
72
  import { binaryProbe } from "./binary-probe.js";
73
73
  import { AVAILABILITY_STALE_MS, BackendAvailabilityStore, hostAvailabilityProbes } from "./backend-availability.js";
@@ -234,6 +234,7 @@ import { remoteCardAction, remoteCardChoiceLabel, remoteCardOutcomeText, remoteC
234
234
  import { recentChatContext } from "./log-tail.js";
235
235
  import { describeSignalSource, recordInternalRequest, withOrigin } from "./fleet-control-audit.js";
236
236
  import { handleAgentRequest, ToolNotPermittedError } from "./agent-endpoint.js";
237
+ import { ConfigHistory, ConfigSyntaxError, configIssueText } from "./config-history.js";
237
238
  import { ClassicChannelManager, classicWebEchoOn, getClassicBackendChoices, inferClassicChannelType, isSelectableClassicBackend, readClassicLastActivityAt, readClassicRoomNames } from "./classic-channel-manager.js";
238
239
  import { assertExplicitInstanceRemoval } from "./instance-removal.js";
239
240
  import { KNOWN_BACKENDS, validateFleetConfig } from "./config-validator.js";
@@ -906,6 +907,12 @@ export class FleetManager {
906
907
  kiroMigrationRuns = new Map();
907
908
  /** #1386 "Needs you": the list, its live chat messages and Acknowledge. Started with the fleet (finishStartup). */
908
909
  needsYou = null;
910
+ configHistory = null;
911
+ configHistoryTimer = null;
912
+ loadedFleetSource = null;
913
+ getConfigHistory() {
914
+ return this.configHistory ??= new ConfigHistory(this.dataDir, () => this.needsYou?.poke(), message => this.logger.warn(message));
915
+ }
909
916
  /** When the fleet first saw an instance crashed, for its "Needs you" age; cleared when it is not. */
910
917
  needsCrashedAt = new Map();
911
918
  /** Pause reason/time read from the marker, per paused instance; dropped on any attention change of it, and at most
@@ -3101,7 +3108,8 @@ export class FleetManager {
3101
3108
  // fleet.yaml, sent SIGHUP, and got no reaction and no explanation.
3102
3109
  this.logger.error({ err }, "SIGHUP config reload failed");
3103
3110
  const message = err instanceof Error ? err.message : String(err);
3104
- this.notifyFleetError(t("fleet.reload_failed", message));
3111
+ if (!(err instanceof ConfigSyntaxError))
3112
+ this.notifyFleetError(t("fleet.reload_failed", message));
3105
3113
  });
3106
3114
  }
3107
3115
  /**
@@ -3337,19 +3345,19 @@ export class FleetManager {
3337
3345
  };
3338
3346
  }
3339
3347
  /** Load fleet.yaml and build routing table */
3340
- loadConfig(configPath) {
3348
+ loadConfig(configPath, options = {}) {
3341
3349
  this.configPath = configPath;
3342
- const source = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "{}\n";
3343
- this.rawFleetDocument = parseDocument(source, { keepSourceTokens: true });
3344
- if (this.rawFleetDocument.errors.length > 0) {
3345
- throw new Error(`Invalid fleet.yaml: ${this.rawFleetDocument.errors[0].message}`);
3346
- }
3347
- const raw = loadRawFleetConfig(configPath);
3348
- const loaded = loadFleetConfig(configPath);
3350
+ const history = this.getConfigHistory();
3351
+ history.track("fleet.yaml", configPath);
3352
+ const selected = existsSync(configPath) ? history.read("fleet.yaml", configPath, options.startup) : { source: "{}\n", fallback: false };
3353
+ this.rawFleetDocument = parseDocument(selected.source, { keepSourceTokens: true });
3354
+ const raw = loadRawFleetConfigSource(selected.source);
3355
+ const loaded = loadFleetConfigSource(selected.source);
3349
3356
  this.assertProviderSecretEnvKeys(loaded);
3350
3357
  this.rawFleetConfig = raw;
3351
3358
  this.settingsRuntimeRevision++;
3352
3359
  this.fleetConfig = loaded;
3360
+ this.loadedFleetSource = selected;
3353
3361
  this.savedFleetConfigSnapshot = structuredClone(this.fleetConfig);
3354
3362
  this.warnAboutRemovedLoginMode(loaded);
3355
3363
  this.enforceGuestSwitch();
@@ -3358,6 +3366,8 @@ export class FleetManager {
3358
3366
  // Scope the path to its owning data directory so another inherited home's override cannot enable CW.
3359
3367
  process.env.AGEND_FLEET_DATA_DIR = resolvePath(this.dataDir);
3360
3368
  process.env.AGEND_FLEET_CONFIG_PATH = resolvePath(configPath);
3369
+ if (options.remember !== false && existsSync(configPath))
3370
+ history.accepted("fleet.yaml", configPath, selected);
3361
3371
  return this.fleetConfig;
3362
3372
  }
3363
3373
  relayLoginModeWarned = false;
@@ -8167,7 +8177,7 @@ export class FleetManager {
8167
8177
  this.tmuxExecHelperTimer ??= setInterval(() => tmuxExecHelper.maintain(), 5_000);
8168
8178
  this.tmuxExecHelperTimer.unref?.();
8169
8179
  this.rotateFleetLogs();
8170
- const fleet = this.loadConfig(configPath);
8180
+ const fleet = this.loadConfig(configPath, { startup: true });
8171
8181
  this.slimFleetConfigAtStartup();
8172
8182
  this.logConfigWarningsAtStartup();
8173
8183
  setLocale(detectLocale(fleet)); // user-facing text language (fleet.yaml defaults.locale / timezone)
@@ -8245,7 +8255,7 @@ export class FleetManager {
8245
8255
  // migrates legacy single-bot entries and names without a suffix. Classic
8246
8256
  // routing does NOT go through the routing engine (single-key, can't hold two
8247
8257
  // bots in one channel) — it resolves per-bot via getInstanceByChannel.
8248
- this.classicChannels = new ClassicChannelManager(this.dataDir, this.logger);
8258
+ this.classicChannels = new ClassicChannelManager(this.dataDir, this.logger, this.getConfigHistory());
8249
8259
  const classicAdapters = fleet.channels?.length ? fleet.channels : (fleet.channel ? [fleet.channel] : []);
8250
8260
  this.classicChannels.configureAdapters(classicAdapters);
8251
8261
  // The unrecoverable-id report is deliberately NOT sent here: no adapter
@@ -8263,6 +8273,9 @@ export class FleetManager {
8263
8273
  // rooms), before the dashboard serves anything; from now on every change is written back (off the loop).
8264
8274
  this.restoreWebChat(fleet);
8265
8275
  this.startSearchIndex(fleet);
8276
+ // Parse-only source monitoring; it does not apply hand-edited Fleet settings.
8277
+ this.configHistoryTimer = setInterval(() => this.configHistory?.check(), 1_000);
8278
+ this.configHistoryTimer.unref?.();
8266
8279
  // Poll classicBot.yaml for external changes every 30s
8267
8280
  this.classicReloadTimer = setInterval(() => {
8268
8281
  // Attributes the synchronous part, up to the reload's first await (#1235).
@@ -8401,7 +8414,7 @@ export class FleetManager {
8401
8414
  fleet.instances[name] = autoGeneralInstanceConfig(fleet.defaults, generalDir, need.adapterId); // #2598: its kiro_ui is the fleet's
8402
8415
  generalsCreated = true;
8403
8416
  }
8404
- if (generalsCreated)
8417
+ if (generalsCreated && !this.configHistory?.isFallback(this.configPath))
8405
8418
  this.saveFleetConfig();
8406
8419
  if (topicMode && (fleet.channel || fleet.channels?.length)) {
8407
8420
  const schedulerConfig = {
@@ -8577,7 +8590,7 @@ export class FleetManager {
8577
8590
  fixedGeneral = true;
8578
8591
  }
8579
8592
  }
8580
- if (fixedGeneral)
8593
+ if (fixedGeneral && !this.configHistory?.isFallback(this.configPath))
8581
8594
  this.saveFleetConfig();
8582
8595
  // Auto-create topics AFTER adapter is ready (needs adapter.createTopic)
8583
8596
  await this.topicCommands.autoCreateTopics();
@@ -12604,6 +12617,7 @@ export class FleetManager {
12604
12617
  },
12605
12618
  });
12606
12619
  this.rawFleetConfig = loadRawFleetConfig(this.configPath);
12620
+ this.configHistory?.accepted("fleet.yaml", this.configPath, { source: output, fallback: false });
12607
12621
  // The instance map is defaults-expanded. A defaults edit changes that view even when it writes no instance
12608
12622
  // leaves. Re-resolve from the committed YAML so inherited values cannot become stale overrides on the next save.
12609
12623
  // Keep the FleetConfig object itself: Settings and other request contexts hold it.
@@ -12682,6 +12696,8 @@ export class FleetManager {
12682
12696
  }
12683
12697
  /** One-time upgrade migration; invalid YAML is never rewritten. */
12684
12698
  migrateNoticeDeliveryStorage() {
12699
+ if (this.configHistory?.isFallback(this.configPath))
12700
+ return;
12685
12701
  if (this.fleetConfig && this.configPath && (this.fleetConfig.web?.notify_login === "dm" || this.fleetConfig.defaults.tips === "dm"
12686
12702
  || this.fleetConfig.web?.notice_delivery !== undefined)
12687
12703
  && validateFleetConfig(this.rawFleetConfig).valid) {
@@ -12696,6 +12712,8 @@ export class FleetManager {
12696
12712
  }
12697
12713
  }
12698
12714
  migrateOfficeStorage() {
12715
+ if (this.configHistory?.isFallback(this.configPath))
12716
+ return;
12699
12717
  if (!this.fleetConfig || !this.configPath || !hasLegacyOffice(this.rawFleetConfig) || !validateFleetConfig(this.rawFleetConfig).valid)
12700
12718
  return;
12701
12719
  try {
@@ -12719,6 +12737,8 @@ export class FleetManager {
12719
12737
  }
12720
12738
  }
12721
12739
  slimFleetConfigAtStartup() {
12740
+ if (this.configHistory?.isFallback(this.configPath))
12741
+ return;
12722
12742
  this.migrateOfficeStorage();
12723
12743
  this.migrateNoticeDeliveryStorage();
12724
12744
  const redundantPaths = collectRedundantInstanceDefaultPaths(this.rawFleetConfig);
@@ -15083,6 +15103,9 @@ export class FleetManager {
15083
15103
  },
15084
15104
  tasks: () => this.needsYouTaskItems(),
15085
15105
  backendSignIns: () => this.needsYouBackendSignIns(),
15106
+ configIssues: () => (this.configHistory?.currentIssues() ?? []).map(issue => ({
15107
+ ...issue, detail: configIssueText(issue, t), owner: this.worlds.keys().next().value,
15108
+ })),
15086
15109
  taskAction: action => this.taskActionFromChat(action),
15087
15110
  settings: () => ({
15088
15111
  liveMessage: this.fleetConfig?.needs_you?.live_message !== false,
@@ -21222,6 +21245,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
21222
21245
  const sharesStopped = this.officeSharesInstance?.close(); // #2636: every Office share ends, and its tunnel
21223
21246
  // #1386: every live "Needs you" message says the fleet stopped (capabilities revoked first) — while the
21224
21247
  // adapters can still edit. Bounded: a platform that does not answer must not hold the shutdown.
21248
+ if (this.configHistoryTimer)
21249
+ clearInterval(this.configHistoryTimer);
21250
+ this.configHistoryTimer = null;
21225
21251
  const needsStopped = this.stopNeedsYou();
21226
21252
  // #1490 P3: stop() now returns a Promise; await it so the final
21227
21253
  // ledger flush completes before process.exit(0) in doStopAll's caller.
@@ -21567,14 +21593,16 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
21567
21593
  const previousRawConfig = this.rawFleetConfig;
21568
21594
  const previousRawDocument = this.rawFleetDocument;
21569
21595
  const previousSavedSnapshot = this.savedFleetConfigSnapshot;
21596
+ const previousSource = this.loadedFleetSource;
21570
21597
  try {
21571
- this.loadConfig(this.configPath);
21598
+ this.loadConfig(this.configPath, { remember: false });
21572
21599
  }
21573
21600
  catch (err) {
21574
21601
  this.fleetConfig = oldConfig;
21575
21602
  this.rawFleetConfig = previousRawConfig;
21576
21603
  this.rawFleetDocument = previousRawDocument;
21577
21604
  this.savedFleetConfigSnapshot = previousSavedSnapshot;
21605
+ this.loadedFleetSource = previousSource;
21578
21606
  throw err;
21579
21607
  }
21580
21608
  const validation = validateFleetConfig(this.rawFleetConfig);
@@ -21590,6 +21618,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
21590
21618
  this.rawFleetConfig = previousRawConfig;
21591
21619
  this.rawFleetDocument = previousRawDocument;
21592
21620
  this.savedFleetConfigSnapshot = previousSavedSnapshot;
21621
+ this.loadedFleetSource = previousSource;
21593
21622
  this.observeWebAuthority(); // #2453 review: the configuration back in force is the one seen
21594
21623
  this.logger.error({
21595
21624
  oldCount,
@@ -21610,6 +21639,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
21610
21639
  // mark every row done and tell the user "changes applied".
21611
21640
  return { rejected: why };
21612
21641
  }
21642
+ if (this.loadedFleetSource)
21643
+ this.getConfigHistory().accepted("fleet.yaml", this.configPath, this.loadedFleetSource);
21613
21644
  this.migrateOfficeStorage();
21614
21645
  this.migrateNoticeDeliveryStorage();
21615
21646
  if (canvasWorkerEnabled(oldConfig?.web) !== this.canvasWorkerEnabled()) {
@@ -21824,7 +21855,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
21824
21855
  * the panel shows the mismatch instead of a button.
21825
21856
  */
21826
21857
  checkStartupSignatureConsistency() {
21827
- if (!this.configPath) {
21858
+ if (!this.configPath || this.configHistory?.isFallback(this.configPath)) {
21828
21859
  this.fleetSignatureMismatch = null;
21829
21860
  return;
21830
21861
  }