@songsid/agend 2.3.3-beta.1 → 2.3.3-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/authority/authority-state.js +1 -1
- package/dist/authority/authority-state.js.map +1 -1
- package/dist/authority/web-authority.d.ts +9 -4
- package/dist/authority/web-authority.js +10 -5
- package/dist/authority/web-authority.js.map +1 -1
- package/dist/backend/claude-code.js +3 -25
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/claude-transcript-errors.d.ts +18 -0
- package/dist/backend/claude-transcript-errors.js +19 -0
- package/dist/backend/claude-transcript-errors.js.map +1 -0
- package/dist/backend/codex-session-lookup.d.ts +16 -3
- package/dist/backend/codex-session-lookup.js +45 -11
- package/dist/backend/codex-session-lookup.js.map +1 -1
- package/dist/classic-channel-manager.d.ts +4 -1
- package/dist/classic-channel-manager.js +21 -7
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +18 -3
- package/dist/cli.js.map +1 -1
- package/dist/config-history.d.ts +50 -0
- package/dist/config-history.js +263 -0
- package/dist/config-history.js.map +1 -0
- package/dist/config-validator.js +1 -4
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +3 -0
- package/dist/config.js +11 -4
- package/dist/config.js.map +1 -1
- package/dist/daemon.d.ts +37 -0
- package/dist/daemon.js +156 -4
- package/dist/daemon.js.map +1 -1
- package/dist/daily-summary.d.ts +8 -1
- package/dist/daily-summary.js +8 -5
- package/dist/daily-summary.js.map +1 -1
- package/dist/delivery-outbox.d.ts +4 -0
- package/dist/delivery-outbox.js +12 -2
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-level-config.js +1 -1
- package/dist/fleet-level-config.js.map +1 -1
- package/dist/fleet-manager.d.ts +8 -10
- package/dist/fleet-manager.js +57 -83
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-process-handlers.d.ts +5 -0
- package/dist/fleet-process-handlers.js +21 -0
- package/dist/fleet-process-handlers.js.map +1 -1
- package/dist/fleet-service-owner.d.ts +11 -0
- package/dist/fleet-service-owner.js +3 -0
- package/dist/fleet-service-owner.js.map +1 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.canvas-worker-off.md +3 -4
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +3 -4
- package/dist/instance-lifecycle.js +2 -0
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +24 -36
- package/dist/locale.js.map +1 -1
- package/dist/needs-you-hub.d.ts +1 -0
- package/dist/needs-you-hub.js +6 -3
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +3 -1
- package/dist/needs-you-live.js +24 -2
- package/dist/needs-you-live.js.map +1 -1
- package/dist/needs-you.d.ts +13 -2
- package/dist/needs-you.js +11 -1
- package/dist/needs-you.js.map +1 -1
- package/dist/scheduler/task-board.d.ts +0 -13
- package/dist/scheduler/task-board.js +0 -14
- package/dist/scheduler/task-board.js.map +1 -1
- package/dist/setup-wizard.d.ts +3 -0
- package/dist/setup-wizard.js +4 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/transcript-monitor.d.ts +7 -0
- package/dist/transcript-monitor.js +48 -3
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/types.d.ts +6 -5
- package/dist/ui/panel-needs.js +11 -9
- package/dist/ui/settings-strings.js +2 -2
- package/dist/ui/settings-web-authority.js +4 -3
- package/dist/ui/shared/app-i18n.js +2 -2
- package/dist/ui/shared/app-needs.js +5 -1
- package/dist/web-api.js +2 -0
- package/dist/web-api.js.map +1 -1
- package/launcher/agend-install.cjs +7 -1
- package/launcher/fleet-service-owner.cjs +138 -0
- package/package.json +1 -1
- package/dist/task-board-summary.d.ts +0 -101
- package/dist/task-board-summary.js +0 -177
- package/dist/task-board-summary.js.map +0 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -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";
|
|
@@ -100,7 +100,6 @@ import { escapeTelegramHtml, scheduleClock, scheduleRetryLabel } from "./schedul
|
|
|
100
100
|
import { DEFAULT_SCHEDULER_CONFIG } from "./scheduler/index.js";
|
|
101
101
|
import { TopicCommands, saveCommandForBackend, parseSaveFilename, parsePauseWakeCommand, parseCompactCommand, SAVE_FILENAME_RE, resolveInstanceContext, forgetInstanceContext, readStatuslineJson, statuslineModel } from "./topic-commands.js";
|
|
102
102
|
import { DailySummary } from "./daily-summary.js";
|
|
103
|
-
import { TaskBoardSummaryClock, buildSummary, isQuiet, renderSummary, summaryMarkFile, summaryTime } from "./task-board-summary.js";
|
|
104
103
|
import { WebhookEmitter } from "./webhook-emitter.js";
|
|
105
104
|
import { TmuxControlClient } from "./tmux-control.js";
|
|
106
105
|
import { safeHandler } from "./safe-async.js";
|
|
@@ -235,6 +234,7 @@ import { remoteCardAction, remoteCardChoiceLabel, remoteCardOutcomeText, remoteC
|
|
|
235
234
|
import { recentChatContext } from "./log-tail.js";
|
|
236
235
|
import { describeSignalSource, recordInternalRequest, withOrigin } from "./fleet-control-audit.js";
|
|
237
236
|
import { handleAgentRequest, ToolNotPermittedError } from "./agent-endpoint.js";
|
|
237
|
+
import { ConfigHistory, ConfigSyntaxError, configIssueText } from "./config-history.js";
|
|
238
238
|
import { ClassicChannelManager, classicWebEchoOn, getClassicBackendChoices, inferClassicChannelType, isSelectableClassicBackend, readClassicLastActivityAt, readClassicRoomNames } from "./classic-channel-manager.js";
|
|
239
239
|
import { assertExplicitInstanceRemoval } from "./instance-removal.js";
|
|
240
240
|
import { KNOWN_BACKENDS, validateFleetConfig } from "./config-validator.js";
|
|
@@ -797,8 +797,6 @@ export class FleetManager {
|
|
|
797
797
|
costGuard = null;
|
|
798
798
|
statuslineWatcher;
|
|
799
799
|
dailySummary = null;
|
|
800
|
-
/** #2246 step 3: the task board's daily summary for the person (task-board-summary.ts). */
|
|
801
|
-
taskBoardSummary = null;
|
|
802
800
|
dailyTipScheduler = null;
|
|
803
801
|
/** The task board's wakes (step 2): an instance hears when someone else changes a card it owns or handed out. */
|
|
804
802
|
taskBoardWaker = null;
|
|
@@ -909,6 +907,12 @@ export class FleetManager {
|
|
|
909
907
|
kiroMigrationRuns = new Map();
|
|
910
908
|
/** #1386 "Needs you": the list, its live chat messages and Acknowledge. Started with the fleet (finishStartup). */
|
|
911
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
|
+
}
|
|
912
916
|
/** When the fleet first saw an instance crashed, for its "Needs you" age; cleared when it is not. */
|
|
913
917
|
needsCrashedAt = new Map();
|
|
914
918
|
/** Pause reason/time read from the marker, per paused instance; dropped on any attention change of it, and at most
|
|
@@ -2408,12 +2412,10 @@ export class FleetManager {
|
|
|
2408
2412
|
const notice = silent
|
|
2409
2413
|
? t(`delivery.fleet_${row.state}_silent_schedule`, row.targetInstance, row.operationId, row.deliveryId, row.lastError ?? "unknown")
|
|
2410
2414
|
: t(`delivery.fleet_${row.state}`, row.targetInstance, row.operationId, row.deliveryId);
|
|
2415
|
+
// #2804: log-only. The person sees a failed or unconfirmed delivery as a Needs-you item (with Acknowledge),
|
|
2416
|
+
// not as a separate General post; the sending agent still gets its delivery-outcome notice.
|
|
2411
2417
|
this.logger.error({ deliveryId: row.deliveryId, operationId: row.operationId, target: row.targetInstance, state: row.state,
|
|
2412
2418
|
cause: row.lastError, ...(silent ? { silentSchedule: true } : {}) }, notice);
|
|
2413
|
-
// A self-scheduled silent heartbeat stays log-only, including the
|
|
2414
|
-
// operator alert; another source still receives the normal notice.
|
|
2415
|
-
if (!silent || row.sourceInstance !== row.targetInstance)
|
|
2416
|
-
this.notifyFleetError(notice);
|
|
2417
2419
|
}
|
|
2418
2420
|
}
|
|
2419
2421
|
});
|
|
@@ -2445,8 +2447,9 @@ export class FleetManager {
|
|
|
2445
2447
|
if (event.count)
|
|
2446
2448
|
this.needsYou?.poke(); // #1386: rows that expired failed — they wait on someone now
|
|
2447
2449
|
this.wakeCoordinator?.kick();
|
|
2450
|
+
// #2804: the expired rows are Needs-you items now; the count is logged, not posted to General.
|
|
2448
2451
|
if (event.count)
|
|
2449
|
-
this.
|
|
2452
|
+
this.logger.warn({ count: event.count, uncertain: event.uncertain ?? 0 }, t("delivery.expired", event.count, event.uncertain ?? 0));
|
|
2450
2453
|
});
|
|
2451
2454
|
this.scheduleDeliveryOutboxPump();
|
|
2452
2455
|
}
|
|
@@ -2931,7 +2934,9 @@ export class FleetManager {
|
|
|
2931
2934
|
if ((current?.state === "delivering" || current?.state === "submission_started")
|
|
2932
2935
|
&& current.targetDaemonBootId === claimed.targetDaemonBootId
|
|
2933
2936
|
&& current.managerBootId === claimed.managerBootId) {
|
|
2934
|
-
|
|
2937
|
+
// #2804: logged, not posted. A delivery still in flight needs nobody yet; if it ends failed or unconfirmed it
|
|
2938
|
+
// becomes a Needs-you item.
|
|
2939
|
+
this.logger.warn({ deliveryId, target: claimed.targetInstance }, t("delivery.lane_waiting", claimed.targetInstance, Math.round(DURABLE_DELIVERY_LANE_ALERT_MS / 60_000), deliveryId));
|
|
2935
2940
|
}
|
|
2936
2941
|
}, DURABLE_DELIVERY_LANE_ALERT_MS);
|
|
2937
2942
|
alertTimer.unref?.();
|
|
@@ -3103,7 +3108,8 @@ export class FleetManager {
|
|
|
3103
3108
|
// fleet.yaml, sent SIGHUP, and got no reaction and no explanation.
|
|
3104
3109
|
this.logger.error({ err }, "SIGHUP config reload failed");
|
|
3105
3110
|
const message = err instanceof Error ? err.message : String(err);
|
|
3106
|
-
|
|
3111
|
+
if (!(err instanceof ConfigSyntaxError))
|
|
3112
|
+
this.notifyFleetError(t("fleet.reload_failed", message));
|
|
3107
3113
|
});
|
|
3108
3114
|
}
|
|
3109
3115
|
/**
|
|
@@ -3339,19 +3345,19 @@ export class FleetManager {
|
|
|
3339
3345
|
};
|
|
3340
3346
|
}
|
|
3341
3347
|
/** Load fleet.yaml and build routing table */
|
|
3342
|
-
loadConfig(configPath) {
|
|
3348
|
+
loadConfig(configPath, options = {}) {
|
|
3343
3349
|
this.configPath = configPath;
|
|
3344
|
-
const
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
const
|
|
3350
|
-
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);
|
|
3351
3356
|
this.assertProviderSecretEnvKeys(loaded);
|
|
3352
3357
|
this.rawFleetConfig = raw;
|
|
3353
3358
|
this.settingsRuntimeRevision++;
|
|
3354
3359
|
this.fleetConfig = loaded;
|
|
3360
|
+
this.loadedFleetSource = selected;
|
|
3355
3361
|
this.savedFleetConfigSnapshot = structuredClone(this.fleetConfig);
|
|
3356
3362
|
this.warnAboutRemovedLoginMode(loaded);
|
|
3357
3363
|
this.enforceGuestSwitch();
|
|
@@ -3360,6 +3366,8 @@ export class FleetManager {
|
|
|
3360
3366
|
// Scope the path to its owning data directory so another inherited home's override cannot enable CW.
|
|
3361
3367
|
process.env.AGEND_FLEET_DATA_DIR = resolvePath(this.dataDir);
|
|
3362
3368
|
process.env.AGEND_FLEET_CONFIG_PATH = resolvePath(configPath);
|
|
3369
|
+
if (options.remember !== false && existsSync(configPath))
|
|
3370
|
+
history.accepted("fleet.yaml", configPath, selected);
|
|
3363
3371
|
return this.fleetConfig;
|
|
3364
3372
|
}
|
|
3365
3373
|
relayLoginModeWarned = false;
|
|
@@ -8169,7 +8177,7 @@ export class FleetManager {
|
|
|
8169
8177
|
this.tmuxExecHelperTimer ??= setInterval(() => tmuxExecHelper.maintain(), 5_000);
|
|
8170
8178
|
this.tmuxExecHelperTimer.unref?.();
|
|
8171
8179
|
this.rotateFleetLogs();
|
|
8172
|
-
const fleet = this.loadConfig(configPath);
|
|
8180
|
+
const fleet = this.loadConfig(configPath, { startup: true });
|
|
8173
8181
|
this.slimFleetConfigAtStartup();
|
|
8174
8182
|
this.logConfigWarningsAtStartup();
|
|
8175
8183
|
setLocale(detectLocale(fleet)); // user-facing text language (fleet.yaml defaults.locale / timezone)
|
|
@@ -8247,7 +8255,7 @@ export class FleetManager {
|
|
|
8247
8255
|
// migrates legacy single-bot entries and names without a suffix. Classic
|
|
8248
8256
|
// routing does NOT go through the routing engine (single-key, can't hold two
|
|
8249
8257
|
// bots in one channel) — it resolves per-bot via getInstanceByChannel.
|
|
8250
|
-
this.classicChannels = new ClassicChannelManager(this.dataDir, this.logger);
|
|
8258
|
+
this.classicChannels = new ClassicChannelManager(this.dataDir, this.logger, this.getConfigHistory());
|
|
8251
8259
|
const classicAdapters = fleet.channels?.length ? fleet.channels : (fleet.channel ? [fleet.channel] : []);
|
|
8252
8260
|
this.classicChannels.configureAdapters(classicAdapters);
|
|
8253
8261
|
// The unrecoverable-id report is deliberately NOT sent here: no adapter
|
|
@@ -8265,6 +8273,9 @@ export class FleetManager {
|
|
|
8265
8273
|
// rooms), before the dashboard serves anything; from now on every change is written back (off the loop).
|
|
8266
8274
|
this.restoreWebChat(fleet);
|
|
8267
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?.();
|
|
8268
8279
|
// Poll classicBot.yaml for external changes every 30s
|
|
8269
8280
|
this.classicReloadTimer = setInterval(() => {
|
|
8270
8281
|
// Attributes the synchronous part, up to the reload's first await (#1235).
|
|
@@ -8296,7 +8307,8 @@ export class FleetManager {
|
|
|
8296
8307
|
...fleet.defaults.daily_summary,
|
|
8297
8308
|
};
|
|
8298
8309
|
this.dailySummary = new DailySummary(summaryConfig, costGuardConfig.timezone, (text) => {
|
|
8299
|
-
|
|
8310
|
+
if (text !== null)
|
|
8311
|
+
this.postDailySummary(text);
|
|
8300
8312
|
// Rotate classic channel chat logs daily
|
|
8301
8313
|
this.classicChannels?.rotateLogs();
|
|
8302
8314
|
this.rotateInboxes();
|
|
@@ -8314,17 +8326,6 @@ export class FleetManager {
|
|
|
8314
8326
|
return DailySummary.generateText(this.eventLog, instances, costMap, this.costGuard?.getFleetTotalCents() ?? 0);
|
|
8315
8327
|
});
|
|
8316
8328
|
this.dailySummary.start();
|
|
8317
|
-
// #2246 step 3: the task board's daily summary — its own time (defaults.task_board.summary_at, read at every look),
|
|
8318
|
-
// in the fleet's zone; a summary missed while the fleet was down goes out when it is back.
|
|
8319
|
-
this.taskBoardSummary = new TaskBoardSummaryClock({
|
|
8320
|
-
at: () => summaryTime(this.fleetConfig?.defaults?.task_board?.summary_at) ?? null,
|
|
8321
|
-
zone: () => this.usageZone(),
|
|
8322
|
-
now: () => new Date(),
|
|
8323
|
-
mark: summaryMarkFile(join(this.dataDir, "task-board-summary.json")),
|
|
8324
|
-
send: (since, until) => this.postTaskBoardSummary(since, until),
|
|
8325
|
-
failed: (err) => this.logger.warn({ err }, "The task board's daily summary failed; the next minute tries again"),
|
|
8326
|
-
});
|
|
8327
|
-
this.taskBoardSummary.start();
|
|
8328
8329
|
// Rotate classic channel chat logs daily (piggyback on daily summary timer)
|
|
8329
8330
|
this.classicChannels?.rotateLogs();
|
|
8330
8331
|
this.rotateInboxes();
|
|
@@ -8413,7 +8414,7 @@ export class FleetManager {
|
|
|
8413
8414
|
fleet.instances[name] = autoGeneralInstanceConfig(fleet.defaults, generalDir, need.adapterId); // #2598: its kiro_ui is the fleet's
|
|
8414
8415
|
generalsCreated = true;
|
|
8415
8416
|
}
|
|
8416
|
-
if (generalsCreated)
|
|
8417
|
+
if (generalsCreated && !this.configHistory?.isFallback(this.configPath))
|
|
8417
8418
|
this.saveFleetConfig();
|
|
8418
8419
|
if (topicMode && (fleet.channel || fleet.channels?.length)) {
|
|
8419
8420
|
const schedulerConfig = {
|
|
@@ -8589,7 +8590,7 @@ export class FleetManager {
|
|
|
8589
8590
|
fixedGeneral = true;
|
|
8590
8591
|
}
|
|
8591
8592
|
}
|
|
8592
|
-
if (fixedGeneral)
|
|
8593
|
+
if (fixedGeneral && !this.configHistory?.isFallback(this.configPath))
|
|
8593
8594
|
this.saveFleetConfig();
|
|
8594
8595
|
// Auto-create topics AFTER adapter is ready (needs adapter.createTopic)
|
|
8595
8596
|
await this.topicCommands.autoCreateTopics();
|
|
@@ -12616,6 +12617,7 @@ export class FleetManager {
|
|
|
12616
12617
|
},
|
|
12617
12618
|
});
|
|
12618
12619
|
this.rawFleetConfig = loadRawFleetConfig(this.configPath);
|
|
12620
|
+
this.configHistory?.accepted("fleet.yaml", this.configPath, { source: output, fallback: false });
|
|
12619
12621
|
// The instance map is defaults-expanded. A defaults edit changes that view even when it writes no instance
|
|
12620
12622
|
// leaves. Re-resolve from the committed YAML so inherited values cannot become stale overrides on the next save.
|
|
12621
12623
|
// Keep the FleetConfig object itself: Settings and other request contexts hold it.
|
|
@@ -12694,6 +12696,8 @@ export class FleetManager {
|
|
|
12694
12696
|
}
|
|
12695
12697
|
/** One-time upgrade migration; invalid YAML is never rewritten. */
|
|
12696
12698
|
migrateNoticeDeliveryStorage() {
|
|
12699
|
+
if (this.configHistory?.isFallback(this.configPath))
|
|
12700
|
+
return;
|
|
12697
12701
|
if (this.fleetConfig && this.configPath && (this.fleetConfig.web?.notify_login === "dm" || this.fleetConfig.defaults.tips === "dm"
|
|
12698
12702
|
|| this.fleetConfig.web?.notice_delivery !== undefined)
|
|
12699
12703
|
&& validateFleetConfig(this.rawFleetConfig).valid) {
|
|
@@ -12708,6 +12712,8 @@ export class FleetManager {
|
|
|
12708
12712
|
}
|
|
12709
12713
|
}
|
|
12710
12714
|
migrateOfficeStorage() {
|
|
12715
|
+
if (this.configHistory?.isFallback(this.configPath))
|
|
12716
|
+
return;
|
|
12711
12717
|
if (!this.fleetConfig || !this.configPath || !hasLegacyOffice(this.rawFleetConfig) || !validateFleetConfig(this.rawFleetConfig).valid)
|
|
12712
12718
|
return;
|
|
12713
12719
|
try {
|
|
@@ -12731,6 +12737,8 @@ export class FleetManager {
|
|
|
12731
12737
|
}
|
|
12732
12738
|
}
|
|
12733
12739
|
slimFleetConfigAtStartup() {
|
|
12740
|
+
if (this.configHistory?.isFallback(this.configPath))
|
|
12741
|
+
return;
|
|
12734
12742
|
this.migrateOfficeStorage();
|
|
12735
12743
|
this.migrateNoticeDeliveryStorage();
|
|
12736
12744
|
const redundantPaths = collectRedundantInstanceDefaultPaths(this.rawFleetConfig);
|
|
@@ -14312,51 +14320,6 @@ export class FleetManager {
|
|
|
14312
14320
|
}
|
|
14313
14321
|
return { chatId, opts: {} };
|
|
14314
14322
|
}
|
|
14315
|
-
/**
|
|
14316
|
-
* #2246 step 3: the task board's daily summary of [since, until) (task-board-summary.ts). Each chat world's General
|
|
14317
|
-
* gets its own instances' cards — a card that no world's instance holds (nobody's, a person's) goes to the primary
|
|
14318
|
-
* one — and each of that world's Generals shows the same text in the web chat. A world with nothing to say gets
|
|
14319
|
-
* nothing. A web-only fleet: everything, in the web chat.
|
|
14320
|
-
*/
|
|
14321
|
-
postTaskBoardSummary(since, until) {
|
|
14322
|
-
const board = this.scheduler?.db.board;
|
|
14323
|
-
if (!board)
|
|
14324
|
-
return;
|
|
14325
|
-
const read = board.summaryRead(since.toISOString(), until.toISOString(), this.dispatcherGone);
|
|
14326
|
-
const projects = board.listProjects(true);
|
|
14327
|
-
const stats = board.projectStats(this.dispatcherGone);
|
|
14328
|
-
const names = new Map(projects.map((p) => [p.id, p.name]));
|
|
14329
|
-
const configured = this.getPrimaryAdapterId();
|
|
14330
|
-
const primary = configured !== undefined && this.worlds.has(configured) ? configured : this.worlds.keys().next().value;
|
|
14331
|
-
const worldOf = (assignee) => {
|
|
14332
|
-
const owner = assignee ? this.getInstanceAdapterId(assignee) : undefined;
|
|
14333
|
-
return owner !== undefined && this.worlds.has(owner) ? owner : primary;
|
|
14334
|
-
};
|
|
14335
|
-
const zone = this.usageZone();
|
|
14336
|
-
for (const world of this.worlds.size ? [...this.worlds.keys()] : [undefined]) {
|
|
14337
|
-
const summary = buildSummary(read, since, until, { inChat: (assignee) => worldOf(assignee) === world, projects, stats });
|
|
14338
|
-
if (isQuiet(summary))
|
|
14339
|
-
continue;
|
|
14340
|
-
const text = renderSummary(summary, { zone, t: (key, ...args) => t(key, ...args), projectOf: (c) => (c.projectId === null ? null : names.get(c.projectId) ?? null) });
|
|
14341
|
-
const cards = [...new Set([...summary.review, ...summary.blocked, ...summary.done, ...summary.created].map((c) => c.code))];
|
|
14342
|
-
if (world !== undefined) {
|
|
14343
|
-
const adapter = this.worlds.get(world)?.adapter;
|
|
14344
|
-
const target = this.fleetNoticeTarget(world);
|
|
14345
|
-
if (adapter && target) {
|
|
14346
|
-
adapter.sendText(target.chatId, text, target.opts).catch(e => this.logger.warn({ err: e, world }, "Failed to send the task board's daily summary"));
|
|
14347
|
-
}
|
|
14348
|
-
else {
|
|
14349
|
-
this.logger.warn({ world }, "The task board's daily summary has no postable target — set a General topic or channel.options.general_channel_id");
|
|
14350
|
-
}
|
|
14351
|
-
}
|
|
14352
|
-
for (const [name, cfg] of Object.entries(this.fleetConfig?.instances ?? {})) {
|
|
14353
|
-
if (cfg.general_topic !== true || (world !== undefined && this.getInstanceAdapterId(name) !== world))
|
|
14354
|
-
continue;
|
|
14355
|
-
// #2341 review: with the cards it names, so the public link withholds it while one of them is hidden there.
|
|
14356
|
-
this.emitSseEvent("message", { instance: name, sender: name, role: "status", text, ts: new Date().toISOString(), cards });
|
|
14357
|
-
}
|
|
14358
|
-
}
|
|
14359
|
-
}
|
|
14360
14323
|
/**
|
|
14361
14324
|
* Post the daily summary where fleet-wide notices go.
|
|
14362
14325
|
*
|
|
@@ -15066,6 +15029,7 @@ export class FleetManager {
|
|
|
15066
15029
|
pauseReason, pausedAt,
|
|
15067
15030
|
crashedAt: crashed ? (this.needsCrashedAt.get(name) ?? null) : null,
|
|
15068
15031
|
signIn: pauseReason === "auth" ? this.signInFor(name) : null,
|
|
15032
|
+
usageHold: this.daemons.get(name)?.usageHold?.() ?? null,
|
|
15069
15033
|
});
|
|
15070
15034
|
}
|
|
15071
15035
|
return out;
|
|
@@ -15139,6 +15103,9 @@ export class FleetManager {
|
|
|
15139
15103
|
},
|
|
15140
15104
|
tasks: () => this.needsYouTaskItems(),
|
|
15141
15105
|
backendSignIns: () => this.needsYouBackendSignIns(),
|
|
15106
|
+
configIssues: () => (this.configHistory?.currentIssues() ?? []).map(issue => ({
|
|
15107
|
+
...issue, detail: configIssueText(issue, t), owner: this.worlds.keys().next().value,
|
|
15108
|
+
})),
|
|
15142
15109
|
taskAction: action => this.taskActionFromChat(action),
|
|
15143
15110
|
settings: () => ({
|
|
15144
15111
|
liveMessage: this.fleetConfig?.needs_you?.live_message !== false,
|
|
@@ -21278,6 +21245,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21278
21245
|
const sharesStopped = this.officeSharesInstance?.close(); // #2636: every Office share ends, and its tunnel
|
|
21279
21246
|
// #1386: every live "Needs you" message says the fleet stopped (capabilities revoked first) — while the
|
|
21280
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;
|
|
21281
21251
|
const needsStopped = this.stopNeedsYou();
|
|
21282
21252
|
// #1490 P3: stop() now returns a Promise; await it so the final
|
|
21283
21253
|
// ledger flush completes before process.exit(0) in doStopAll's caller.
|
|
@@ -21333,7 +21303,6 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21333
21303
|
this.clearStatuslineWatchers();
|
|
21334
21304
|
this.costGuard?.stop();
|
|
21335
21305
|
this.dailySummary?.stop();
|
|
21336
|
-
this.taskBoardSummary?.stop();
|
|
21337
21306
|
this.dailyTipScheduler?.stop();
|
|
21338
21307
|
this.dailyTipScheduler = null;
|
|
21339
21308
|
if (this.updateCheckTimer) {
|
|
@@ -21624,14 +21593,16 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21624
21593
|
const previousRawConfig = this.rawFleetConfig;
|
|
21625
21594
|
const previousRawDocument = this.rawFleetDocument;
|
|
21626
21595
|
const previousSavedSnapshot = this.savedFleetConfigSnapshot;
|
|
21596
|
+
const previousSource = this.loadedFleetSource;
|
|
21627
21597
|
try {
|
|
21628
|
-
this.loadConfig(this.configPath);
|
|
21598
|
+
this.loadConfig(this.configPath, { remember: false });
|
|
21629
21599
|
}
|
|
21630
21600
|
catch (err) {
|
|
21631
21601
|
this.fleetConfig = oldConfig;
|
|
21632
21602
|
this.rawFleetConfig = previousRawConfig;
|
|
21633
21603
|
this.rawFleetDocument = previousRawDocument;
|
|
21634
21604
|
this.savedFleetConfigSnapshot = previousSavedSnapshot;
|
|
21605
|
+
this.loadedFleetSource = previousSource;
|
|
21635
21606
|
throw err;
|
|
21636
21607
|
}
|
|
21637
21608
|
const validation = validateFleetConfig(this.rawFleetConfig);
|
|
@@ -21647,6 +21618,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21647
21618
|
this.rawFleetConfig = previousRawConfig;
|
|
21648
21619
|
this.rawFleetDocument = previousRawDocument;
|
|
21649
21620
|
this.savedFleetConfigSnapshot = previousSavedSnapshot;
|
|
21621
|
+
this.loadedFleetSource = previousSource;
|
|
21650
21622
|
this.observeWebAuthority(); // #2453 review: the configuration back in force is the one seen
|
|
21651
21623
|
this.logger.error({
|
|
21652
21624
|
oldCount,
|
|
@@ -21667,6 +21639,8 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21667
21639
|
// mark every row done and tell the user "changes applied".
|
|
21668
21640
|
return { rejected: why };
|
|
21669
21641
|
}
|
|
21642
|
+
if (this.loadedFleetSource)
|
|
21643
|
+
this.getConfigHistory().accepted("fleet.yaml", this.configPath, this.loadedFleetSource);
|
|
21670
21644
|
this.migrateOfficeStorage();
|
|
21671
21645
|
this.migrateNoticeDeliveryStorage();
|
|
21672
21646
|
if (canvasWorkerEnabled(oldConfig?.web) !== this.canvasWorkerEnabled()) {
|
|
@@ -21881,7 +21855,7 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
21881
21855
|
* the panel shows the mismatch instead of a button.
|
|
21882
21856
|
*/
|
|
21883
21857
|
checkStartupSignatureConsistency() {
|
|
21884
|
-
if (!this.configPath) {
|
|
21858
|
+
if (!this.configPath || this.configHistory?.isFallback(this.configPath)) {
|
|
21885
21859
|
this.fleetSignatureMismatch = null;
|
|
21886
21860
|
return;
|
|
21887
21861
|
}
|