@songsid/agend 2.3.4-beta.9 → 2.3.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 (75) hide show
  1. package/dist/auth-api.js +5 -0
  2. package/dist/auth-api.js.map +1 -1
  3. package/dist/backend/codex-metadata.d.ts +14 -0
  4. package/dist/backend/codex-metadata.js +45 -0
  5. package/dist/backend/codex-metadata.js.map +1 -1
  6. package/dist/backend/codex-speed.d.ts +14 -0
  7. package/dist/backend/codex-speed.js +35 -0
  8. package/dist/backend/codex-speed.js.map +1 -0
  9. package/dist/backend/codex.js +9 -2
  10. package/dist/backend/codex.js.map +1 -1
  11. package/dist/backend/kiro.js +11 -1
  12. package/dist/backend/kiro.js.map +1 -1
  13. package/dist/cache-ledger.d.ts +10 -0
  14. package/dist/cache-ledger.js +49 -12
  15. package/dist/cache-ledger.js.map +1 -1
  16. package/dist/channel/adapters/telegram.d.ts +51 -0
  17. package/dist/channel/adapters/telegram.js +231 -14
  18. package/dist/channel/adapters/telegram.js.map +1 -1
  19. package/dist/channel/types.d.ts +2 -0
  20. package/dist/config-validator.js +5 -0
  21. package/dist/config-validator.js.map +1 -1
  22. package/dist/fleet-manager.d.ts +11 -0
  23. package/dist/fleet-manager.js +90 -10
  24. package/dist/fleet-manager.js.map +1 -1
  25. package/dist/general-knowledge/skills/concepts/SKILL.md +3 -0
  26. package/dist/general-knowledge/skills/web-dashboard/SKILL.md +7 -2
  27. package/dist/instance-config-impact.js +2 -0
  28. package/dist/instance-config-impact.js.map +1 -1
  29. package/dist/locale.js +10 -0
  30. package/dist/locale.js.map +1 -1
  31. package/dist/log-service.d.ts +28 -0
  32. package/dist/log-service.js +29 -0
  33. package/dist/log-service.js.map +1 -1
  34. package/dist/logger.d.ts +12 -0
  35. package/dist/logger.js +43 -1
  36. package/dist/logger.js.map +1 -1
  37. package/dist/missed-offline.d.ts +25 -0
  38. package/dist/missed-offline.js +79 -0
  39. package/dist/missed-offline.js.map +1 -0
  40. package/dist/needs-you-hub.d.ts +6 -1
  41. package/dist/needs-you-hub.js +30 -7
  42. package/dist/needs-you-hub.js.map +1 -1
  43. package/dist/needs-you.d.ts +16 -2
  44. package/dist/needs-you.js +9 -1
  45. package/dist/needs-you.js.map +1 -1
  46. package/dist/settings-api.js +10 -0
  47. package/dist/settings-api.js.map +1 -1
  48. package/dist/settings-change.js +16 -1
  49. package/dist/settings-change.js.map +1 -1
  50. package/dist/settings-instance-patch.js +27 -0
  51. package/dist/settings-instance-patch.js.map +1 -1
  52. package/dist/ui/chat-md-strings.js +9 -0
  53. package/dist/ui/chat-thread.js +53 -3
  54. package/dist/ui/panel-chat.js +3 -2
  55. package/dist/ui/panel-needs.js +13 -5
  56. package/dist/ui/preview-panel.js +78 -2
  57. package/dist/ui/settings-codex-copy.js +19 -0
  58. package/dist/ui/settings-dialogs.js +8 -0
  59. package/dist/ui/settings-model.js +16 -2
  60. package/dist/ui/shared/app-i18n.js +2 -2
  61. package/dist/ui/shared/app.css +4 -0
  62. package/dist/ui/shared/panel-view.js +77 -4
  63. package/dist/ui/shared/view-history.css +17 -0
  64. package/dist/ui/shared/view-history.js +438 -0
  65. package/dist/usage-rollup.d.ts +2 -0
  66. package/dist/usage-rollup.js +9 -6
  67. package/dist/usage-rollup.js.map +1 -1
  68. package/dist/view-api.d.ts +16 -0
  69. package/dist/view-api.js +100 -16
  70. package/dist/view-api.js.map +1 -1
  71. package/dist/view-history.d.ts +84 -0
  72. package/dist/view-history.js +116 -0
  73. package/dist/view-history.js.map +1 -0
  74. package/launcher/private-home.cjs +1 -1
  75. package/package.json +1 -1
@@ -62,6 +62,7 @@ export declare function applyTaskListCap<T extends {
62
62
  } | T[];
63
63
  import { type PublicPreviewForUi } from "./web-api.js";
64
64
  import { type LogAnswer, type LogQuery } from "./log-service.js";
65
+ import { type ViewHistoryAnswer, type ViewHistoryQuery } from "./view-history.js";
65
66
  import { WebChatHistory, type WebChatPost } from "./web-chat-history.js";
66
67
  import { type WebEchoCopy } from "./web-reactions.js";
67
68
  import { SeenUsers, type MentionNames, type MentionQuery } from "./web-mentions.js";
@@ -478,6 +479,7 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
478
479
  private kiroMigrationRuns;
479
480
  /** #1386 "Needs you": the list, its live chat messages and Acknowledge. Started with the fleet (finishStartup). */
480
481
  private needsYou;
482
+ private missedOfflineStore;
481
483
  private configHistory;
482
484
  private configHistoryTimer;
483
485
  private loadedFleetSource;
@@ -1221,6 +1223,11 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
1221
1223
  /** Board 003648ac: a web /compact followed to its end, from the transcript the Log reads (none: nothing to follow). */
1222
1224
  private compactWatchService;
1223
1225
  private get compactWatch();
1226
+ /**
1227
+ * Claude history in the View: an instance's earlier conversation for its View, from the transcript the Log reads —
1228
+ * the View's route applied its own access rules first (view-api.ts); the Log's switch plays no part.
1229
+ */
1230
+ viewHistory(name: string, q: ViewHistoryQuery): Promise<ViewHistoryAnswer | null>;
1224
1231
  /** #1218: GET /ui/steps — an instance's steps, read on demand (null: no such instance). */
1225
1232
  logSteps(name: string, q: LogQuery): Promise<LogAnswer | null>;
1226
1233
  /**
@@ -2307,6 +2314,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
2307
2314
  * copies retain their permissive handling when that identity is unknown.
2308
2315
  */
2309
2316
  private isOwnerCommandCopy;
2317
+ /** File a Telegram update that was too old to inject; Needs you lists it until Acknowledge. */
2318
+ private recordMissedOffline;
2310
2319
  /**
2311
2320
  * #1085: coincident topic numbers must not route across Telegram groups.
2312
2321
  */
@@ -3549,6 +3558,8 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
3549
3558
  */
3550
3559
  /** Configured threshold before elapsed time appears, in ms. */
3551
3560
  progressMinElapsedMs(): number;
3561
+ /** M-2391: when the instance's live (not retiring) bubble started — the current turn's start — or undefined. */
3562
+ private liveButtonStart;
3552
3563
  private startProgressTicker;
3553
3564
  /**
3554
3565
  * After a reply: give the instance REPLY_RETIRE_GRACE_MS to resume working; if
@@ -92,6 +92,7 @@ import { formatFleetTime } from "./tz-utils.js";
92
92
  import { createAdapter } from "./channel/factory.js";
93
93
  import { isWebChannelEcho, WEB_ECHO_PREFIX } from "./web-channel-echo.js";
94
94
  import { TelegramAdapter } from "./channel/adapters/telegram.js";
95
+ import { MissedOfflineStore } from "./missed-offline.js";
95
96
  import { createBackend } from "./backend/factory.js";
96
97
  import { readEffortMetadata } from "./backend/effort-metadata.js";
97
98
  import { codexEffort, flagEffort } from "./backend/launch-effort.js";
@@ -171,6 +172,7 @@ export function applyTaskListCap(tasks, filterAssignee, filterStatus) {
171
172
  }
172
173
  import { handleWebRequest, webPeerOf, broadcastSseEvent, broadcastToGuests, SSE_HEARTBEAT_MS } from "./web-api.js";
173
174
  import { LogService } from "./log-service.js";
175
+ import { viewHistory } from "./view-history.js";
174
176
  import { WebChatHistory, WEB_CHAT_TEXT_MAX, isWebMessageId, newWebMessageId, webChatFrom } from "./web-chat-history.js";
175
177
  import { WEB_ECHO_COPIES_MAX, WEB_MIRROR_TIMEOUT_MS, webReactionEmoji, webReactionProblem } from "./web-reactions.js";
176
178
  import { WebChatDiskStore } from "./web-chat-store.js";
@@ -467,6 +469,8 @@ const INSTALL_TIMEOUT_MS = 10 * 60 * 1000;
467
469
  * while anything real shows signs of life well before the old two minutes.
468
470
  */
469
471
  const PROGRESS_MIN_ELAPSED_MS = 30_000;
472
+ /** M-2391: the threshold tick fires this long after it, never a millisecond before (timers can fire early). */
473
+ const PROGRESS_THRESHOLD_MARGIN_MS = 50;
470
474
  /** How much of a tool summary the progress line will show before eliding. */
471
475
  const PROGRESS_ACTIVITY_MAX_CHARS = 48;
472
476
  /**
@@ -926,6 +930,7 @@ export class FleetManager {
926
930
  kiroMigrationRuns = new Map();
927
931
  /** #1386 "Needs you": the list, its live chat messages and Acknowledge. Started with the fleet (finishStartup). */
928
932
  needsYou = null;
933
+ missedOfflineStore = null;
929
934
  configHistory = null;
930
935
  configHistoryTimer = null;
931
936
  loadedFleetSource = null;
@@ -2205,6 +2210,7 @@ export class FleetManager {
2205
2210
  this.topicCommands = new TopicCommands(this);
2206
2211
  this.topicArchiver = new TopicArchiver(this);
2207
2212
  this.statuslineWatcher = new StatuslineWatcher(this);
2213
+ this.missedOfflineStore = new MissedOfflineStore(this.dataDir);
2208
2214
  }
2209
2215
  /** #1266: a reply's buttons — the store (reply-buttons.db) and what a click does. Created on first use. */
2210
2216
  replyButtonsCtl = null;
@@ -4095,6 +4101,13 @@ export class FleetManager {
4095
4101
  changed: () => this.emitSseEvent("status", this.getUiStatus()),
4096
4102
  });
4097
4103
  }
4104
+ /**
4105
+ * Claude history in the View: an instance's earlier conversation for its View, from the transcript the Log reads —
4106
+ * the View's route applied its own access rules first (view-api.ts); the Log's switch plays no part.
4107
+ */
4108
+ viewHistory(name, q) {
4109
+ return viewHistory((n, rq) => this.log.readBack(n, rq), name, q);
4110
+ }
4098
4111
  /** #1218: GET /ui/steps — an instance's steps, read on demand (null: no such instance). */
4099
4112
  logSteps(name, q) { return this.log.steps(name, q); }
4100
4113
  /**
@@ -9446,6 +9459,11 @@ export class FleetManager {
9446
9459
  adapter.on("callback_query", safeHandler(async (data) => {
9447
9460
  await this.receiveAdapterCallback(data, adapterId, adapter, isCurrentAdapter);
9448
9461
  }, this.logger, "adapter.callback_query"));
9462
+ adapter.on("missed_offline", (ev) => {
9463
+ if (!isCurrentAdapter())
9464
+ return;
9465
+ this.recordMissedOffline(ev);
9466
+ });
9449
9467
  this.bindTopicClosedHandler(adapter, adapterId, "adapter.topic_closed");
9450
9468
  // Handle classic bot slash commands (/start, /stop, /chat, /compact, /save, /load)
9451
9469
  adapter.on("slash_command", safeHandler(async (data) => {
@@ -9599,6 +9617,11 @@ export class FleetManager {
9599
9617
  adapter.on("callback_query", safeHandler(async (data) => {
9600
9618
  await this.receiveAdapterCallback(data, adapterId, adapter, isCurrentAdapter);
9601
9619
  }, this.logger, `adapter[${adapterId}].callback_query`));
9620
+ adapter.on("missed_offline", (ev) => {
9621
+ if (!isCurrentAdapter())
9622
+ return;
9623
+ this.recordMissedOffline(ev);
9624
+ });
9602
9625
  this.bindTopicClosedHandler(adapter, adapterId, `adapter[${adapterId}].topic_closed`);
9603
9626
  // Slash commands: classic bot + admin commands
9604
9627
  adapter.on("slash_command", safeHandler(async (data) => {
@@ -10335,6 +10358,34 @@ export class FleetManager {
10335
10358
  }
10336
10359
  return true;
10337
10360
  }
10361
+ /** File a Telegram update that was too old to inject; Needs you lists it until Acknowledge. */
10362
+ recordMissedOffline(ev) {
10363
+ if (!this.missedOfflineStore)
10364
+ this.missedOfflineStore = new MissedOfflineStore(this.dataDir);
10365
+ const stub = {
10366
+ source: "telegram", adapterId: ev.adapterId, chatId: ev.chatId, threadId: ev.threadId,
10367
+ messageId: ev.messageId, userId: ev.userId, username: ev.username, text: ev.preview,
10368
+ timestamp: ev.timestamp, isBotMessage: false,
10369
+ };
10370
+ const threadId = this.inboundRouteThreadId(stub);
10371
+ const target = threadId ? this.resolveInboundTarget(stub, threadId) : undefined;
10372
+ let instance = target?.name ?? "";
10373
+ if (!instance) {
10374
+ instance = Object.entries(this.fleetConfig?.instances ?? {}).find(([name, cfg]) => cfg.general_topic && this.getInstanceAdapterId(name) === ev.adapterId)?.[0] ?? "";
10375
+ }
10376
+ const place = [ev.chatTitle, ev.threadId ? `#${ev.threadId}` : ""].filter(Boolean).join(" · ") || ev.chatId;
10377
+ this.missedOfflineStore.add({
10378
+ id: `missed:${ev.adapterId}:${ev.updateId}`,
10379
+ adapterId: ev.adapterId,
10380
+ instance,
10381
+ username: ev.username,
10382
+ place,
10383
+ preview: ev.preview,
10384
+ since: ev.timestamp.getTime(),
10385
+ });
10386
+ this.logger.info({ adapterId: ev.adapterId, updateId: ev.updateId, instance, chatId: ev.chatId, threadId: ev.threadId }, "Telegram message missed while AgEnD was offline — filed for Needs you");
10387
+ this.needsYou?.poke();
10388
+ }
10338
10389
  /**
10339
10390
  * #1085: coincident topic numbers must not route across Telegram groups.
10340
10391
  */
@@ -15539,6 +15590,10 @@ export class FleetManager {
15539
15590
  throw new Error("the delivery outbox is not open");
15540
15591
  return this.deliveryOutbox.acknowledge(deliveryId, by);
15541
15592
  },
15593
+ missedOffline: () => (this.missedOfflineStore?.list(Date.now()) ?? []).map(m => ({
15594
+ id: m.id, instance: m.instance, owner: m.adapterId, username: m.username, place: m.place, preview: m.preview, since: m.since,
15595
+ })),
15596
+ acknowledgeMissed: (id, by) => this.missedOfflineStore?.acknowledge(id, by) ?? false,
15542
15597
  tasks: () => this.needsYouTaskItems(),
15543
15598
  backendSignIns: () => this.needsYouBackendSignIns(),
15544
15599
  configIssues: () => (this.configHistory?.currentIssues() ?? []).map(issue => ({
@@ -17189,7 +17244,14 @@ export class FleetManager {
17189
17244
  return;
17190
17245
  }
17191
17246
  const publication = this.beginCancelButtonPublication(instanceName, correlationId);
17192
- const initialProgressText = FleetManager.progressText(0, null, 1, this.progressPrefixFor(instanceName, 0, 1));
17247
+ // M-2391: a re-post below a reply (preserveProgress) continues the same turn — its elapsed time continues from
17248
+ // the live bubble's start, as its tool list does. Restarting it at 0 meant an agent replying more often than the
17249
+ // threshold never showed elapsed time at all. A fresh turn (no preserve) starts at 0 as before.
17250
+ const carriedStart = preserveProgress ? this.liveButtonStart(instanceName) : undefined;
17251
+ const carriedMs = carriedStart !== undefined ? Math.max(0, Date.now() - carriedStart) : 0;
17252
+ const initialProgressText = carriedStart !== undefined && carriedMs >= this.progressMinElapsedMs()
17253
+ ? FleetManager.progressText(carriedMs, null, this.progressMinElapsedMs(), this.progressPrefixFor(instanceName, carriedMs))
17254
+ : FleetManager.progressText(0, null, 1, this.progressPrefixFor(instanceName, 0, 1));
17193
17255
  try {
17194
17256
  const sent = await adapter.notifyAlert(chatId, {
17195
17257
  type: "cancel",
@@ -17212,7 +17274,7 @@ export class FleetManager {
17212
17274
  // Elapsed time is measured from when this button was posted — i.e. from
17213
17275
  // when the work was handed over — not from the pane's working transition,
17214
17276
  // which resets if the CLI blips idle mid-turn.
17215
- startedAt: Date.now(),
17277
+ startedAt: carriedStart ?? Date.now(),
17216
17278
  // Matches the text notifyAlert just posted, so the first 60s tick does
17217
17279
  // not re-edit identical text — which put a "(edited)" mark on Discord
17218
17280
  // with nothing visibly changed.
@@ -17463,6 +17525,12 @@ export class FleetManager {
17463
17525
  }
17464
17526
  return PROGRESS_MIN_ELAPSED_MS;
17465
17527
  }
17528
+ /** M-2391: when the instance's live (not retiring) bubble started — the current turn's start — or undefined. */
17529
+ liveButtonStart(instanceName) {
17530
+ const starts = [...this.cancelButtons.values()].filter((e) => e.instanceName === instanceName && !e.retiring && typeof e.startedAt === "number")
17531
+ .map((e) => e.startedAt);
17532
+ return starts.length ? Math.min(...starts) : undefined;
17533
+ }
17466
17534
  startProgressTicker(entry) {
17467
17535
  const tick = () => this.refreshBubble(entry);
17468
17536
  entry.progressTimer = setInterval(tick, PROGRESS_UPDATE_INTERVAL_MS);
@@ -17472,7 +17540,9 @@ export class FleetManager {
17472
17540
  // one additional edit per turn that lives past the threshold.
17473
17541
  const firstAt = this.progressMinElapsedMs() - (Date.now() - (entry.startedAt ?? Date.now()));
17474
17542
  if (firstAt > 0 && firstAt < PROGRESS_UPDATE_INTERVAL_MS) {
17475
- const firstTick = setTimeout(tick, firstAt);
17543
+ // M-2391: a little past the threshold — a timer can fire a millisecond before Date.now() reaches it, which
17544
+ // rendered the unchanged "處理中…" text and skipped the edit until the next 60 s interval.
17545
+ const firstTick = setTimeout(tick, firstAt + PROGRESS_THRESHOLD_MARGIN_MS);
17476
17546
  firstTick.unref?.();
17477
17547
  }
17478
17548
  }
@@ -21827,6 +21897,22 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
21827
21897
  // because we asked it to. Set synchronously — doStopAll runs to its first
21828
21898
  // await in the same tick as the signal handler, so no event can slip in.
21829
21899
  this.shuttingDown = true;
21900
+ // Stop receive admission before the first await or any instance shutdown; outbound
21901
+ // adapters remain available below to retire notices and pending prompts.
21902
+ for (const state of this.adapterState.values()) {
21903
+ if (state.retryTimer) {
21904
+ clearTimeout(state.retryTimer);
21905
+ state.retryTimer = undefined;
21906
+ }
21907
+ }
21908
+ const inboundStopped = Promise.all([...this.adapters.values()].map(async (adapter) => {
21909
+ try {
21910
+ await adapter.stopReceiving?.();
21911
+ }
21912
+ catch (err) {
21913
+ this.logger.warn({ adapterId: adapter.id, err }, "Adapter receive stop failed; inbound remains fenced");
21914
+ }
21915
+ }));
21830
21916
  this.workingRecovery?.reset();
21831
21917
  if (this.workingRecoveryTimer)
21832
21918
  clearInterval(this.workingRecoveryTimer);
@@ -21886,6 +21972,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
21886
21972
  // A login/install window is a dedicated tmux server with its own TTL
21887
21973
  // timer and HTTP listener living in THIS process: without an explicit
21888
21974
  // shutdown it would outlive us as an owner-less login CLI (sol B3).
21975
+ await inboundStopped;
21889
21976
  await publicStopped;
21890
21977
  await sharesStopped?.catch(() => { });
21891
21978
  await profileStopped;
@@ -21896,13 +21983,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
21896
21983
  await this.cpuProfileControl?.close();
21897
21984
  this.cpuProfileControl = null;
21898
21985
  await this.shutdownLoginWindows();
21899
- // Cancel adapter retry timers
21900
- for (const state of this.adapterState.values()) {
21901
- if (state.retryTimer) {
21902
- clearTimeout(state.retryTimer);
21903
- state.retryTimer = undefined;
21904
- }
21905
- }
21906
21986
  this.clearStatuslineWatchers();
21907
21987
  this.costGuard?.stop();
21908
21988
  this.dailySummary?.stop();