@songsid/agend 2.1.12-beta.5 → 2.1.12-beta.7
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/agent-cli.js +13 -2
- package/dist/agent-cli.js.map +1 -1
- package/dist/backend/antigravity.d.ts +51 -10
- package/dist/backend/antigravity.js +142 -18
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/codex-metadata.js +4 -0
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +9 -0
- package/dist/backend/effort-metadata.js +29 -0
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/kiro.js +6 -2
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.d.ts +5 -0
- package/dist/backend/muse.js +29 -0
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +8 -0
- package/dist/backend/types.js +9 -3
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +13 -0
- package/dist/channel/adapters/discord.js +39 -0
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/mcp-tools.js +1 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/cli.js +12 -1
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +4 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/cpu-profile.d.ts +32 -0
- package/dist/cpu-profile.js +221 -0
- package/dist/cpu-profile.js.map +1 -0
- package/dist/delivery-outbox.d.ts +24 -0
- package/dist/delivery-outbox.js +118 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/event-log.d.ts +1 -0
- package/dist/event-log.js +5 -1
- package/dist/event-log.js.map +1 -1
- package/dist/event-loop-watch.d.ts +18 -0
- package/dist/event-loop-watch.js +32 -3
- package/dist/event-loop-watch.js.map +1 -1
- package/dist/fleet-manager.d.ts +37 -0
- package/dist/fleet-manager.js +219 -61
- package/dist/fleet-manager.js.map +1 -1
- package/dist/instance-lifecycle.js +7 -0
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +2 -0
- package/dist/locale.js.map +1 -1
- package/dist/login-flows.js +5 -1
- package/dist/login-flows.js.map +1 -1
- package/dist/outbound-handlers.d.ts +2 -0
- package/dist/outbound-handlers.js +8 -0
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +15 -1
- package/dist/outbound-schemas.js +9 -3
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/scheduler/db.d.ts +29 -4
- package/dist/scheduler/db.js +96 -6
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/types.d.ts +33 -0
- package/dist/statusline-watcher.js +2 -1
- package/dist/statusline-watcher.js.map +1 -1
- package/dist/sync-work-attribution.d.ts +9 -0
- package/dist/sync-work-attribution.js +25 -0
- package/dist/sync-work-attribution.js.map +1 -0
- package/dist/topic-commands.js +10 -3
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.d.ts +1 -27
- package/dist/transcript-sources.js +9 -0
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { measureSyncWork } from "./sync-work-attribution.js";
|
|
1
2
|
import { existsSync, readFileSync, mkdirSync, writeFileSync, unlinkSync, rmSync, readdirSync, renameSync, copyFileSync, chmodSync, statSync, accessSync, constants as fsConstants } from "node:fs";
|
|
2
3
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
3
4
|
import { spawnSync } from "node:child_process";
|
|
@@ -34,7 +35,7 @@ import { createAdapter } from "./channel/factory.js";
|
|
|
34
35
|
import { TelegramAdapter } from "./channel/adapters/telegram.js";
|
|
35
36
|
import { createBackend } from "./backend/factory.js";
|
|
36
37
|
import { readEffortMetadata } from "./backend/effort-metadata.js";
|
|
37
|
-
import { isModelCompatible, SYSINFO_BACKEND_IDS, UnsupportedCliError } from "./backend/types.js";
|
|
38
|
+
import { CLI_ENV_TTL_MS, isModelCompatible, SYSINFO_BACKEND_IDS, UnsupportedCliError } from "./backend/types.js";
|
|
38
39
|
import { createLogger, rotateLogIfNeeded } from "./logger.js";
|
|
39
40
|
import { processAttachments } from "./channel/attachment-handler.js";
|
|
40
41
|
import { routeToolCall } from "./channel/tool-router.js";
|
|
@@ -56,6 +57,51 @@ import { DeliveryOutbox } from "./delivery-outbox.js";
|
|
|
56
57
|
// active manager/target generation pair owns the lane until the daemon reports
|
|
57
58
|
// a state transition or that generation is replaced.
|
|
58
59
|
export const DURABLE_DELIVERY_LANE_ALERT_MS = 35 * 60_000;
|
|
60
|
+
/**
|
|
61
|
+
* #1335: Cap an unfiltered task list at 100 rows (most recently updated first).
|
|
62
|
+
* Exported so the production branch can be tested directly without starting a fleet.
|
|
63
|
+
* Filtered calls pass-through unchanged. Empty strings count as "not set" (P3).
|
|
64
|
+
* #1336: generic over the row shape so it works on both full Task and compact rows.
|
|
65
|
+
*/
|
|
66
|
+
export const TASK_LIST_CAP = 100;
|
|
67
|
+
/**
|
|
68
|
+
* #1336: the non-terminal statuses a bare `list` returns by default. `done`
|
|
69
|
+
* and `cancelled` are excluded unless the caller passes `filter_status`.
|
|
70
|
+
*/
|
|
71
|
+
export const LIVE_TASK_STATUSES = ["open", "claimed", "blocked"];
|
|
72
|
+
/**
|
|
73
|
+
* #1336 P2: shared normalization for filter_status across both task handlers
|
|
74
|
+
* and the cap helper. Trims whitespace before dropping empties, so
|
|
75
|
+
* " \t " and [" ", "\t"] both normalize to undefined (not treated as explicit
|
|
76
|
+
* filters). The same result drives the live-only default and the cap decision.
|
|
77
|
+
*/
|
|
78
|
+
export function normalizeStatusFilter(v) {
|
|
79
|
+
if (typeof v === "string") {
|
|
80
|
+
const s = v.trim();
|
|
81
|
+
return s || undefined;
|
|
82
|
+
}
|
|
83
|
+
if (Array.isArray(v) && v.every(x => typeof x === "string")) {
|
|
84
|
+
const arr = [...new Set(v.map(s => s.trim()).filter(s => s.length > 0))];
|
|
85
|
+
return arr.length > 0 ? arr : undefined;
|
|
86
|
+
}
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
export function applyTaskListCap(tasks, filterAssignee, filterStatus) {
|
|
90
|
+
// Use the shared normalizer so " \t " is treated identically to undefined.
|
|
91
|
+
const normalized = normalizeStatusFilter(filterStatus);
|
|
92
|
+
const hasStatusFilter = Array.isArray(normalized) ? normalized.length > 0 : !!normalized;
|
|
93
|
+
const isFiltered = !!filterAssignee || hasStatusFilter;
|
|
94
|
+
if (!isFiltered && tasks.length > TASK_LIST_CAP) {
|
|
95
|
+
const omitted = tasks.length - TASK_LIST_CAP;
|
|
96
|
+
tasks.sort((a, b) => b.updated_at.localeCompare(a.updated_at));
|
|
97
|
+
return {
|
|
98
|
+
tasks: tasks.slice(0, TASK_LIST_CAP),
|
|
99
|
+
omitted,
|
|
100
|
+
hint: `${omitted} older task(s) omitted — use filter_assignee or filter_status to narrow results`,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return tasks;
|
|
104
|
+
}
|
|
59
105
|
import { handleWebRequest, broadcastSseEvent } from "./web-api.js";
|
|
60
106
|
import { handleViewRequest, isViewPath } from "./view-api.js";
|
|
61
107
|
import { filterUsageProviders, formatDiscordUsageActivity, getUsageSnapshot, handleUsageRequest, isUsagePath, usageProviderIdForBackend } from "./usage/usage-api.js";
|
|
@@ -263,7 +309,6 @@ const NONCE_BUTTON_TIMEOUT_MS = 15 * 60_000;
|
|
|
263
309
|
const TIP_BUTTON_TIMEOUT_MS = 24 * 60 * 60_000;
|
|
264
310
|
/** How long shutdown will spend retiring still-armed button prompts. */
|
|
265
311
|
const NONCE_RETIRE_BUDGET_MS = 5_000;
|
|
266
|
-
const CLI_ENV_TTL_MS = 24 * 60 * 60 * 1000; // hard validity bound for the cached CLI env
|
|
267
312
|
/**
|
|
268
313
|
* How old the cached CLI env may be before `/model` re-probes it live.
|
|
269
314
|
*
|
|
@@ -571,6 +616,7 @@ export class FleetManager {
|
|
|
571
616
|
/** Injectable only to keep the chat→CLI reload hand-off deterministic in tests. */
|
|
572
617
|
fullRestartLauncher = launchFullRestartHelper;
|
|
573
618
|
eventLogPruneTimer = null;
|
|
619
|
+
outboxPruneTimer = null;
|
|
574
620
|
logRotateTimer = null;
|
|
575
621
|
discordPresenceTimer = null;
|
|
576
622
|
discordPresenceEagerTimer = null;
|
|
@@ -2766,7 +2812,7 @@ export class FleetManager {
|
|
|
2766
2812
|
oldToolProgress.set(ch.instanceName, this.classicChannels.getToolProgress(ch.channelId, ch.adapterId, this.fleetConfig?.defaults?.tool_progress));
|
|
2767
2813
|
oldReplyGuard.set(ch.instanceName, this.classicChannels.getReplyCompletionGuard(ch.channelId, ch.adapterId, this.fleetConfig?.defaults?.reply_completion_guard));
|
|
2768
2814
|
}
|
|
2769
|
-
if (!this.classicChannels.checkReload())
|
|
2815
|
+
if (!measureSyncWork("fleet.classicConfigReload", () => this.classicChannels.checkReload()))
|
|
2770
2816
|
return;
|
|
2771
2817
|
// A reload can introduce a bad id (hand edit) or clear one; the
|
|
2772
2818
|
// throttle keeps a repeated report from flooding the topic.
|
|
@@ -2870,7 +2916,7 @@ export class FleetManager {
|
|
|
2870
2916
|
// managed independently from fleet-topic workers.
|
|
2871
2917
|
try {
|
|
2872
2918
|
const skillsWorkDir = this.resolveKnowledgeWorkDir(config.working_directory, backend, name);
|
|
2873
|
-
this.syncRoleSkills(skillsWorkDir, backend, "worker");
|
|
2919
|
+
measureSyncWork("fleet.workerSkills", () => this.syncRoleSkills(skillsWorkDir, backend, "worker"));
|
|
2874
2920
|
}
|
|
2875
2921
|
catch (err) {
|
|
2876
2922
|
// Skill publishing is additive. A read-only or temporarily unavailable
|
|
@@ -4116,6 +4162,11 @@ export class FleetManager {
|
|
|
4116
4162
|
this.pruneEventLog();
|
|
4117
4163
|
this.eventLogPruneTimer = setInterval(() => this.pruneEventLog(), 24 * 60 * 60_000);
|
|
4118
4164
|
this.eventLogPruneTimer.unref?.();
|
|
4165
|
+
// #1335: delivery-outbox.db and Task Board retention. Same once-at-startup
|
|
4166
|
+
// + daily-timer pattern; chunked DELETEs so one run cannot hold the loop.
|
|
4167
|
+
void this.pruneOutboxAndTasks();
|
|
4168
|
+
this.outboxPruneTimer = setInterval(() => { void this.pruneOutboxAndTasks(); }, 24 * 60 * 60_000);
|
|
4169
|
+
this.outboxPruneTimer.unref?.();
|
|
4119
4170
|
// Same shape for logs, and for the same reason: the only sweep that
|
|
4120
4171
|
// covered them lived inside the daily-summary callback, so it did not run at
|
|
4121
4172
|
// all when summaries were off.
|
|
@@ -6636,45 +6687,74 @@ export class FleetManager {
|
|
|
6636
6687
|
const asStatus = (v) => {
|
|
6637
6688
|
return (v === "open" || v === "claimed" || v === "done" || v === "blocked" || v === "cancelled") ? v : undefined;
|
|
6638
6689
|
};
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
|
|
6663
|
-
|
|
6664
|
-
|
|
6665
|
-
|
|
6666
|
-
|
|
6667
|
-
|
|
6668
|
-
|
|
6669
|
-
|
|
6670
|
-
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6690
|
+
// #1336 P2: use the shared normalizer — trims whitespace AND drops empties,
|
|
6691
|
+
// so " \t " / [" ","\t"] are treated identically to undefined.
|
|
6692
|
+
const asStatusFilter = normalizeStatusFilter;
|
|
6693
|
+
// #1336: small write ack — identifying fields only.
|
|
6694
|
+
const ack = (t) => ({ id: t.id, status: t.status, updated_at: t.updated_at });
|
|
6695
|
+
try {
|
|
6696
|
+
switch (action) {
|
|
6697
|
+
case "create": {
|
|
6698
|
+
const title = asStr(args.title);
|
|
6699
|
+
if (!title)
|
|
6700
|
+
return { error: "title is required" };
|
|
6701
|
+
return ack(db.createTask({
|
|
6702
|
+
title,
|
|
6703
|
+
description: asStr(args.description),
|
|
6704
|
+
priority: asPriority(args.priority),
|
|
6705
|
+
assignee: asStr(args.assignee),
|
|
6706
|
+
depends_on: asStrArr(args.depends_on),
|
|
6707
|
+
created_by: instance,
|
|
6708
|
+
}));
|
|
6709
|
+
}
|
|
6710
|
+
case "list": {
|
|
6711
|
+
const filterAssignee = asStr(args.filter_assignee) || undefined;
|
|
6712
|
+
const explicitStatus = asStatusFilter(args.filter_status);
|
|
6713
|
+
// #1336 item 1: default to live-only (no done/cancelled) unless the
|
|
6714
|
+
// caller explicitly asked for a status.
|
|
6715
|
+
const effectiveStatus = explicitStatus ?? LIVE_TASK_STATUSES;
|
|
6716
|
+
const verbose = args.verbose === true;
|
|
6717
|
+
const tasks = verbose
|
|
6718
|
+
? db.listTasks({ assignee: filterAssignee, status: effectiveStatus, verbose: true })
|
|
6719
|
+
: db.listTasks({ assignee: filterAssignee, status: effectiveStatus });
|
|
6720
|
+
// #1335: cap unfiltered list. The default live-only filter does not
|
|
6721
|
+
// count as an explicit filter for the cap decision.
|
|
6722
|
+
return applyTaskListCap(tasks, filterAssignee, explicitStatus);
|
|
6723
|
+
}
|
|
6724
|
+
case "get": {
|
|
6725
|
+
const id = asStr(args.id);
|
|
6726
|
+
if (!id)
|
|
6727
|
+
return { error: "id is required" };
|
|
6728
|
+
return db.getTaskByPrefix(id);
|
|
6729
|
+
}
|
|
6730
|
+
case "claim": {
|
|
6731
|
+
const id = asStr(args.id);
|
|
6732
|
+
if (!id)
|
|
6733
|
+
return { error: "id is required" };
|
|
6734
|
+
return ack(db.claimTask(db.getTaskByPrefix(id).id, instance));
|
|
6735
|
+
}
|
|
6736
|
+
case "done": {
|
|
6737
|
+
const id = asStr(args.id);
|
|
6738
|
+
if (!id)
|
|
6739
|
+
return { error: "id is required" };
|
|
6740
|
+
return ack(db.completeTask(db.getTaskByPrefix(id).id, asStr(args.result)));
|
|
6741
|
+
}
|
|
6742
|
+
case "update": {
|
|
6743
|
+
const id = asStr(args.id);
|
|
6744
|
+
if (!id)
|
|
6745
|
+
return { error: "id is required" };
|
|
6746
|
+
return ack(db.updateTask(db.getTaskByPrefix(id).id, {
|
|
6747
|
+
status: asStatus(args.status),
|
|
6748
|
+
assignee: asStr(args.assignee),
|
|
6749
|
+
result: asStr(args.result),
|
|
6750
|
+
priority: asPriority(args.priority),
|
|
6751
|
+
}));
|
|
6752
|
+
}
|
|
6753
|
+
default: return { error: `Unknown task action: ${action}` };
|
|
6676
6754
|
}
|
|
6677
|
-
|
|
6755
|
+
}
|
|
6756
|
+
catch (err) {
|
|
6757
|
+
return { error: err.message };
|
|
6678
6758
|
}
|
|
6679
6759
|
}
|
|
6680
6760
|
async handleSetDisplayNameHttp(instance, name) {
|
|
@@ -6745,11 +6825,17 @@ export class FleetManager {
|
|
|
6745
6825
|
}
|
|
6746
6826
|
const db = this.scheduler.db;
|
|
6747
6827
|
const action = payload.action;
|
|
6828
|
+
// #1336 P2: use the shared normalizer — trims whitespace AND drops empties,
|
|
6829
|
+
// so " \t " / [" ","\t"] are treated identically to undefined.
|
|
6830
|
+
const asStatusFilter = normalizeStatusFilter;
|
|
6831
|
+
const ack = (t) => ({ id: t.id, status: t.status, updated_at: t.updated_at });
|
|
6748
6832
|
try {
|
|
6749
6833
|
let result;
|
|
6834
|
+
// Full task kept for activity logging; the IPC reply uses the compact ack.
|
|
6835
|
+
let logTask;
|
|
6750
6836
|
switch (action) {
|
|
6751
6837
|
case "create":
|
|
6752
|
-
|
|
6838
|
+
logTask = db.createTask({
|
|
6753
6839
|
title: payload.title,
|
|
6754
6840
|
description: payload.description,
|
|
6755
6841
|
priority: payload.priority,
|
|
@@ -6757,43 +6843,57 @@ export class FleetManager {
|
|
|
6757
6843
|
depends_on: payload.depends_on,
|
|
6758
6844
|
created_by: meta.instance_name || instanceName,
|
|
6759
6845
|
});
|
|
6846
|
+
result = ack(logTask);
|
|
6760
6847
|
break;
|
|
6761
|
-
case "list":
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6848
|
+
case "list": {
|
|
6849
|
+
// P3: normalize empty strings to undefined — SchedulerDb.listTasks
|
|
6850
|
+
// ignores them but the cap logic must treat them as "not filtered".
|
|
6851
|
+
const filterAssignee = payload.filter_assignee || undefined;
|
|
6852
|
+
const explicitStatus = asStatusFilter(payload.filter_status);
|
|
6853
|
+
// #1336 item 1: default to live-only (no done/cancelled).
|
|
6854
|
+
const effectiveStatus = explicitStatus ?? LIVE_TASK_STATUSES;
|
|
6855
|
+
const verbose = payload.verbose === true;
|
|
6856
|
+
const tasks = verbose
|
|
6857
|
+
? db.listTasks({ assignee: filterAssignee, status: effectiveStatus, verbose: true })
|
|
6858
|
+
: db.listTasks({ assignee: filterAssignee, status: effectiveStatus });
|
|
6859
|
+
// #1335: cap unfiltered list — the default live-only filter does not
|
|
6860
|
+
// count as an explicit filter.
|
|
6861
|
+
result = applyTaskListCap(tasks, filterAssignee, explicitStatus);
|
|
6862
|
+
break;
|
|
6863
|
+
}
|
|
6864
|
+
case "get":
|
|
6865
|
+
result = db.getTaskByPrefix(payload.id);
|
|
6766
6866
|
break;
|
|
6767
6867
|
case "claim":
|
|
6768
|
-
|
|
6868
|
+
logTask = db.claimTask(db.getTaskByPrefix(payload.id).id, meta.instance_name || instanceName);
|
|
6869
|
+
result = ack(logTask);
|
|
6769
6870
|
break;
|
|
6770
6871
|
case "done":
|
|
6771
|
-
|
|
6872
|
+
logTask = db.completeTask(db.getTaskByPrefix(payload.id).id, payload.result);
|
|
6873
|
+
result = ack(logTask);
|
|
6772
6874
|
break;
|
|
6773
6875
|
case "update":
|
|
6774
|
-
|
|
6876
|
+
logTask = db.updateTask(db.getTaskByPrefix(payload.id).id, {
|
|
6775
6877
|
status: payload.status,
|
|
6776
6878
|
assignee: payload.assignee,
|
|
6777
6879
|
result: payload.result,
|
|
6778
6880
|
priority: payload.priority,
|
|
6779
6881
|
});
|
|
6882
|
+
result = ack(logTask);
|
|
6780
6883
|
break;
|
|
6781
6884
|
default:
|
|
6782
6885
|
throw new Error(`Unknown task action: ${action}`);
|
|
6783
6886
|
}
|
|
6784
6887
|
ipc.send({ type: "fleet_task_response", fleetRequestId, result });
|
|
6785
6888
|
// Activity log for task lifecycle events
|
|
6786
|
-
if (action === "create") {
|
|
6787
|
-
|
|
6788
|
-
this.eventLog?.logActivity("task_update", instanceName, `created task: ${t.title}`, t.assignee ?? undefined);
|
|
6889
|
+
if (action === "create" && logTask) {
|
|
6890
|
+
this.eventLog?.logActivity("task_update", instanceName, `created task: ${logTask.title}`, logTask.assignee ?? undefined);
|
|
6789
6891
|
}
|
|
6790
|
-
else if (action === "claim") {
|
|
6791
|
-
|
|
6792
|
-
this.eventLog?.logActivity("task_update", instanceName, `claimed: ${t.title}`);
|
|
6892
|
+
else if (action === "claim" && logTask) {
|
|
6893
|
+
this.eventLog?.logActivity("task_update", instanceName, `claimed: ${logTask.title}`);
|
|
6793
6894
|
}
|
|
6794
|
-
else if (action === "done") {
|
|
6795
|
-
|
|
6796
|
-
this.eventLog?.logActivity("task_update", instanceName, `completed: ${t.title}`, undefined, t.result ?? undefined);
|
|
6895
|
+
else if (action === "done" && logTask) {
|
|
6896
|
+
this.eventLog?.logActivity("task_update", instanceName, `completed: ${logTask.title}`, undefined, logTask.result ?? undefined);
|
|
6797
6897
|
}
|
|
6798
6898
|
}
|
|
6799
6899
|
catch (err) {
|
|
@@ -8165,6 +8265,42 @@ export class FleetManager {
|
|
|
8165
8265
|
this.logger.warn({ err }, "Event log prune failed");
|
|
8166
8266
|
}
|
|
8167
8267
|
}
|
|
8268
|
+
/** #1335: Prune delivery-outbox.db and Task Board in chunked async passes. */
|
|
8269
|
+
async pruneOutboxAndTasks() {
|
|
8270
|
+
const days = this.fleetConfig?.defaults?.retention_days ?? 30;
|
|
8271
|
+
// Outbox prune
|
|
8272
|
+
const outbox = this.deliveryOutbox;
|
|
8273
|
+
if (outbox?.isOpen) {
|
|
8274
|
+
try {
|
|
8275
|
+
const { pruned, durationMs } = await outbox.prune(days);
|
|
8276
|
+
if (pruned > 0) {
|
|
8277
|
+
this.logger.info({ pruned, durationMs: Math.round(durationMs) }, "Delivery outbox pruned");
|
|
8278
|
+
}
|
|
8279
|
+
}
|
|
8280
|
+
catch (err) {
|
|
8281
|
+
this.logger.warn({ err }, "Delivery outbox prune failed");
|
|
8282
|
+
}
|
|
8283
|
+
}
|
|
8284
|
+
// Task Board prune
|
|
8285
|
+
if (this.scheduler?.db) {
|
|
8286
|
+
try {
|
|
8287
|
+
const pruned = await this.scheduler.db.pruneOldTasks(days);
|
|
8288
|
+
if (pruned > 0) {
|
|
8289
|
+
this.logger.info({ pruned }, "Task board pruned");
|
|
8290
|
+
}
|
|
8291
|
+
}
|
|
8292
|
+
catch (err) {
|
|
8293
|
+
this.logger.warn({ err }, "Task board prune failed");
|
|
8294
|
+
}
|
|
8295
|
+
}
|
|
8296
|
+
}
|
|
8297
|
+
/**
|
|
8298
|
+
* #1335: True when the delivery_id was in the outbox, was pruned by
|
|
8299
|
+
* retention, and the caller is the original source or target (#1340 P2 🔒).
|
|
8300
|
+
*/
|
|
8301
|
+
wasDeliveryIdPrunedForCaller(deliveryId, callerInstance) {
|
|
8302
|
+
return this.deliveryOutbox?.wasDeliveryIdPrunedForCaller(deliveryId, callerInstance) ?? false;
|
|
8303
|
+
}
|
|
8168
8304
|
openEventLog() {
|
|
8169
8305
|
const dbPath = join(this.dataDir, "events.db");
|
|
8170
8306
|
try {
|
|
@@ -10419,8 +10555,11 @@ export class FleetManager {
|
|
|
10419
10555
|
* definition or a function name, neither of which a spawn could run.
|
|
10420
10556
|
*/
|
|
10421
10557
|
locateBinaryOnLoginShell(binary) {
|
|
10558
|
+
return measureSyncWork("fleet.installLookup", () => this.locateBinaryOnLoginShellSync(binary));
|
|
10559
|
+
}
|
|
10560
|
+
locateBinaryOnLoginShellSync(binary) {
|
|
10422
10561
|
try {
|
|
10423
|
-
const result = spawnSync("bash", ["-lc", `command -v ${binary}`], { timeout: 10_000, stdio: "pipe", encoding: "utf8" });
|
|
10562
|
+
const result = measureSyncWork("fleet.installLoginShell", () => spawnSync("bash", ["-lc", `command -v ${binary}`], { timeout: 10_000, stdio: "pipe", encoding: "utf8" }));
|
|
10424
10563
|
if (result.status !== 0)
|
|
10425
10564
|
return null;
|
|
10426
10565
|
const path = String(result.stdout ?? "").trim().split("\n").pop()?.trim() ?? "";
|
|
@@ -10640,6 +10779,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
10640
10779
|
`;
|
|
10641
10780
|
/** Ensure the general instance has its project instructions file + knowledge */
|
|
10642
10781
|
ensureGeneralInstructions(workDir, backendName, instanceName) {
|
|
10782
|
+
measureSyncWork("fleet.generalInstructions", () => this.ensureGeneralInstructionsSync(workDir, backendName, instanceName));
|
|
10783
|
+
}
|
|
10784
|
+
ensureGeneralInstructionsSync(workDir, backendName, instanceName) {
|
|
10643
10785
|
const backend = backendName ?? "claude-code";
|
|
10644
10786
|
workDir = this.resolveKnowledgeWorkDir(workDir, backend, instanceName);
|
|
10645
10787
|
const filename = FleetManager.INSTRUCTIONS_FILENAME[backend] ?? "CLAUDE.md";
|
|
@@ -11460,6 +11602,15 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
11460
11602
|
if (previous?.apiModels?.length)
|
|
11461
11603
|
env.apiModels = previous.apiModels;
|
|
11462
11604
|
}
|
|
11605
|
+
// Effort levels read from --help (#1328) are a capability of one binary. A help that could not be read (absent)
|
|
11606
|
+
// keeps the cached levels only for that same binary: both versions known and equal, cache still valid. A help
|
|
11607
|
+
// that was read and lists none ([]) is an answer and is written as is, so the fallback applies.
|
|
11608
|
+
if (env.effortLevels === undefined) {
|
|
11609
|
+
const previous = this.readCliEnv(backend);
|
|
11610
|
+
if (previous?.effortLevels && previous.version && env.version && previous.version === env.version) {
|
|
11611
|
+
env.effortLevels = previous.effortLevels;
|
|
11612
|
+
}
|
|
11613
|
+
}
|
|
11463
11614
|
const path = this.cliEnvPath(backend);
|
|
11464
11615
|
mkdirSync(dirname(path), { recursive: true });
|
|
11465
11616
|
writeFileSync(path, JSON.stringify(env, null, 2));
|
|
@@ -12589,6 +12740,10 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
12589
12740
|
clearInterval(this.eventLogPruneTimer);
|
|
12590
12741
|
this.eventLogPruneTimer = null;
|
|
12591
12742
|
}
|
|
12743
|
+
if (this.outboxPruneTimer) {
|
|
12744
|
+
clearInterval(this.outboxPruneTimer);
|
|
12745
|
+
this.outboxPruneTimer = null;
|
|
12746
|
+
}
|
|
12592
12747
|
if (this.replyObligationTimer) {
|
|
12593
12748
|
clearInterval(this.replyObligationTimer);
|
|
12594
12749
|
this.replyObligationTimer = null;
|
|
@@ -14804,6 +14959,9 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
14804
14959
|
this.healthServer.listen(port, "127.0.0.1", () => markListening());
|
|
14805
14960
|
}
|
|
14806
14961
|
getUiStatus() {
|
|
14962
|
+
return measureSyncWork("fleet.uiStatus", () => this.getUiStatusSync());
|
|
14963
|
+
}
|
|
14964
|
+
getUiStatusSync() {
|
|
14807
14965
|
const fleetNames = Object.keys(this.fleetConfig?.instances ?? {});
|
|
14808
14966
|
// Classic rooms live only in classicBot.yaml — /api/profiles merges them into
|
|
14809
14967
|
// the View roster, but previously getUiStatus skipped them so context_pct was
|