@songsid/agend 2.3.0-beta.19 → 2.3.0-beta.20
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-instructions.md +4 -0
- package/dist/agent-cli.js +25 -0
- package/dist/agent-cli.js.map +1 -1
- package/dist/channel/mcp-tools.js +2 -1
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/config-validator.js +19 -1
- package/dist/config-validator.js.map +1 -1
- package/dist/cross-instance-notice.d.ts +7 -0
- package/dist/cross-instance-notice.js +10 -0
- package/dist/cross-instance-notice.js.map +1 -1
- package/dist/daemon.js +4 -0
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.js +5 -0
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/fleet-manager.d.ts +26 -0
- package/dist/fleet-manager.js +115 -14
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/scheduling/SKILL.md +4 -0
- package/dist/general-knowledge/skills/web-dashboard/SKILL.md +25 -1
- package/dist/instance-config-impact.js +4 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/log-steps.d.ts +1 -0
- package/dist/log-steps.js +15 -0
- package/dist/log-steps.js.map +1 -1
- package/dist/outbound-handlers.d.ts +3 -0
- package/dist/outbound-handlers.js +20 -4
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +33 -0
- package/dist/outbound-schemas.js +25 -8
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-web-gateway.js +5 -2
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/scheduler/db.d.ts +40 -13
- package/dist/scheduler/db.js +123 -89
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/task-board-text.d.ts +26 -0
- package/dist/scheduler/task-board-text.js +135 -0
- package/dist/scheduler/task-board-text.js.map +1 -0
- package/dist/scheduler/task-board.d.ts +650 -0
- package/dist/scheduler/task-board.js +1938 -0
- package/dist/scheduler/task-board.js.map +1 -0
- package/dist/scheduler/types.d.ts +6 -1
- package/dist/search/search-docs.d.ts +58 -0
- package/dist/search/search-docs.js +63 -0
- package/dist/search/search-docs.js.map +1 -0
- package/dist/search/search-index.d.ts +141 -0
- package/dist/search/search-index.js +440 -0
- package/dist/search/search-index.js.map +1 -0
- package/dist/search/search-store.d.ts +122 -0
- package/dist/search/search-store.js +236 -0
- package/dist/search/search-store.js.map +1 -0
- package/dist/search/search-worker.d.ts +1 -0
- package/dist/search/search-worker.js +163 -0
- package/dist/search/search-worker.js.map +1 -0
- package/dist/task-board-tool.d.ts +25 -0
- package/dist/task-board-tool.js +154 -0
- package/dist/task-board-tool.js.map +1 -0
- package/dist/task-board-waker.d.ts +44 -0
- package/dist/task-board-waker.js +128 -0
- package/dist/task-board-waker.js.map +1 -0
- package/dist/types.d.ts +15 -0
- package/dist/ui/fleet-task-card.js +436 -0
- package/dist/ui/fleet-task-common.js +448 -0
- package/dist/ui/fleet-tasks.js +535 -0
- package/dist/ui/panel-chat.js +8 -0
- package/dist/ui/panel-fleet.js +21 -65
- package/dist/ui/search-dialog.js +349 -0
- package/dist/ui/shared/app-i18n.js +3 -0
- package/dist/ui/shared/app-shell.js +43 -1
- package/dist/ui/shared/app-stream.js +2 -1
- package/dist/ui/shared/app.css +270 -2
- package/dist/ui/shared/app.js +3 -1
- package/dist/ui/shared/ui-toast.js +16 -3
- package/dist/web-api.d.ts +14 -4
- package/dist/web-api.js +408 -10
- package/dist/web-api.js.map +1 -1
- package/dist/web-chat-history.d.ts +2 -0
- package/dist/web-chat-history.js +2 -0
- package/dist/web-chat-history.js.map +1 -1
- package/dist/workflow-templates/default.md +3 -0
- package/package.json +1 -1
package/dist/fleet-manager.js
CHANGED
|
@@ -73,6 +73,8 @@ import { createLogger, rotateLogIfNeeded } from "./logger.js";
|
|
|
73
73
|
import { processAttachments } from "./channel/attachment-handler.js";
|
|
74
74
|
import { routeToolCall } from "./channel/tool-router.js";
|
|
75
75
|
import { Scheduler } from "./scheduler/index.js";
|
|
76
|
+
import { boardActivity, boardCardLimit, boardToolAction } from "./task-board-tool.js";
|
|
77
|
+
import { TaskBoardWaker } from "./task-board-waker.js";
|
|
76
78
|
import { escapeTelegramHtml, scheduleClock, scheduleRetryLabel } from "./scheduler/retry-label.js";
|
|
77
79
|
import { DEFAULT_SCHEDULER_CONFIG } from "./scheduler/index.js";
|
|
78
80
|
import { TopicCommands, saveCommandForBackend, parseSaveFilename, parsePauseWakeCommand, parseCompactCommand, SAVE_FILENAME_RE, resolveInstanceContext, forgetInstanceContext, readStatuslineJson, statuslineModel } from "./topic-commands.js";
|
|
@@ -103,7 +105,8 @@ export const TASK_LIST_CAP = 100;
|
|
|
103
105
|
* #1336: the non-terminal statuses a bare `list` returns by default. `done`
|
|
104
106
|
* and `cancelled` are excluded unless the caller passes `filter_status`.
|
|
105
107
|
*/
|
|
106
|
-
|
|
108
|
+
/** Not finished: `review` is done work waiting for the person (the task board), still live. */
|
|
109
|
+
export const LIVE_TASK_STATUSES = ["open", "claimed", "blocked", "review"];
|
|
107
110
|
/**
|
|
108
111
|
* #1336 P2: shared normalization for filter_status across both task handlers
|
|
109
112
|
* and the cap helper. Trims whitespace before dropping empties, so
|
|
@@ -181,7 +184,9 @@ import { runOnLoginShell } from "./login-shell.js";
|
|
|
181
184
|
import { tightenInstanceDirs } from "./private-dir.js";
|
|
182
185
|
import { decideSlash } from "./slash-authz.js";
|
|
183
186
|
import { canonicalCommand, commandSpec, decideCommand, ruleFor } from "./command-table.js";
|
|
184
|
-
import { crossInstanceVisibility, runVisibilityCommand } from "./cross-instance-notice.js";
|
|
187
|
+
import { crossInstanceVisibility, runVisibilityCommand, xmsgVisibility } from "./cross-instance-notice.js";
|
|
188
|
+
import { SEARCH_RETENTION_DAYS, SearchIndex } from "./search/search-index.js";
|
|
189
|
+
import { chatDoc, taskDoc, xmsgDoc } from "./search/search-docs.js";
|
|
185
190
|
import { WebSettingsTools } from "./web-settings-tools.js";
|
|
186
191
|
import { bindGuestRequest, guestRouteDecision } from "./web-guest-access.js";
|
|
187
192
|
import { GUEST_ROOM_TICK_MS, GuestRoomMembership, guestGroupRooms } from "./web-guest-rooms.js";
|
|
@@ -666,6 +671,8 @@ export class FleetManager {
|
|
|
666
671
|
statuslineWatcher;
|
|
667
672
|
dailySummary = null;
|
|
668
673
|
dailyTipScheduler = null;
|
|
674
|
+
/** The task board's wakes (step 2): an instance hears when someone else changes a card it owns or handed out. */
|
|
675
|
+
taskBoardWaker = null;
|
|
669
676
|
webhookEmitter = null;
|
|
670
677
|
// Topic icon + auto-archive state
|
|
671
678
|
topicIcons = {};
|
|
@@ -904,6 +911,8 @@ export class FleetManager {
|
|
|
904
911
|
webChatHistory = new WebChatHistory();
|
|
905
912
|
/** #1565: the web chat history's files (set at fleet start, once the home and the instances are known). */
|
|
906
913
|
webChatStore = null;
|
|
914
|
+
/** #2293: the dashboard's global search (null: web.search.enabled is false, or the fleet has not started). */
|
|
915
|
+
searchIndex = null;
|
|
907
916
|
/** Uploaded files and the files the dashboard may fetch back (uploads, reply attachments). */
|
|
908
917
|
// #1937: the upload limits are the config's, read on each use (agend reload applies a change to the next upload).
|
|
909
918
|
webFiles = new WebFileLedger({ limits: () => uploadLimits(this.fleetConfig?.web) });
|
|
@@ -1787,7 +1796,10 @@ export class FleetManager {
|
|
|
1787
1796
|
: t(`delivery.fleet_${row.state}`, row.targetInstance, row.operationId, row.deliveryId);
|
|
1788
1797
|
this.logger.error({ deliveryId: row.deliveryId, operationId: row.operationId, target: row.targetInstance, state: row.state,
|
|
1789
1798
|
cause: row.lastError, ...(silent ? { silentSchedule: true } : {}) }, notice);
|
|
1790
|
-
|
|
1799
|
+
// A self-scheduled silent heartbeat stays log-only, including the
|
|
1800
|
+
// operator alert; another source still receives the normal notice.
|
|
1801
|
+
if (!silent || row.sourceInstance !== row.targetInstance)
|
|
1802
|
+
this.notifyFleetError(notice);
|
|
1791
1803
|
}
|
|
1792
1804
|
}
|
|
1793
1805
|
});
|
|
@@ -7037,6 +7049,7 @@ export class FleetManager {
|
|
|
7037
7049
|
// #1565: the web chat as it was before this process — the configured instances' only (fleet.yaml and ClassicBot
|
|
7038
7050
|
// rooms), before the dashboard serves anything; from now on every change is written back (off the loop).
|
|
7039
7051
|
this.restoreWebChat(fleet);
|
|
7052
|
+
this.startSearchIndex(fleet);
|
|
7040
7053
|
// Poll classicBot.yaml for external changes every 30s
|
|
7041
7054
|
this.classicReloadTimer = setInterval(() => {
|
|
7042
7055
|
// Attributes the synchronous part, up to the reload's first await (#1235).
|
|
@@ -7181,7 +7194,9 @@ export class FleetManager {
|
|
|
7181
7194
|
};
|
|
7182
7195
|
this.scheduler = new Scheduler(join(this.dataDir, "scheduler.db"), (schedule, runId, retry) => this.handleScheduleTrigger(schedule, runId, retry), schedulerConfig, (name) => this.fleetConfig?.instances?.[name] != null || !!this.classicChannels?.getAll().some(ch => ch.instanceName === name), (schedule, retry, reason) => this.scheduleRetryDropped(schedule, retry, reason), this.logger);
|
|
7183
7196
|
this.scheduler.init();
|
|
7197
|
+
this.scheduler.db.onTaskChange = (task) => this.searchIndex?.record(taskDoc(task));
|
|
7184
7198
|
this.logger.info("Scheduler initialized");
|
|
7199
|
+
this.attachTaskBoard(this.scheduler.db);
|
|
7185
7200
|
// Tips share the daily-report clock but remain an independent internal
|
|
7186
7201
|
// job: disabling daily_summary must not disable tips (and vice versa).
|
|
7187
7202
|
// The callback checks defaults.tips at fire time, so /tips on|off is hot.
|
|
@@ -10650,14 +10665,20 @@ export class FleetManager {
|
|
|
10650
10665
|
return (v === "low" || v === "normal" || v === "high" || v === "urgent") ? v : undefined;
|
|
10651
10666
|
};
|
|
10652
10667
|
const asStatus = (v) => {
|
|
10653
|
-
return (v === "open" || v === "claimed" || v === "done" || v === "blocked" || v === "cancelled") ? v : undefined;
|
|
10668
|
+
return (v === "open" || v === "claimed" || v === "review" || v === "done" || v === "blocked" || v === "cancelled") ? v : undefined;
|
|
10654
10669
|
};
|
|
10655
10670
|
// #1336 P2: use the shared normalizer — trims whitespace AND drops empties,
|
|
10656
10671
|
// so " \t " / [" ","\t"] are treated identically to undefined.
|
|
10657
10672
|
const asStatusFilter = normalizeStatusFilter;
|
|
10658
10673
|
// #1336: small write ack — identifying fields only.
|
|
10659
10674
|
const ack = (t) => ({ id: t.id, status: t.status, updated_at: t.updated_at });
|
|
10675
|
+
// The board's notes for the write (a send-back reason, a comment the assignee had not seen): with the ack when any.
|
|
10676
|
+
const noted = (a) => { const notes = db.board.takeNotes(); return notes.length ? { ...a, notes } : a; };
|
|
10660
10677
|
try {
|
|
10678
|
+
// The task board's own actions and fields (task-board-tool.ts); the rest is the tool's plain task, below.
|
|
10679
|
+
const onBoard = boardToolAction(db, instance, args);
|
|
10680
|
+
if (onBoard !== undefined)
|
|
10681
|
+
return onBoard;
|
|
10661
10682
|
switch (action) {
|
|
10662
10683
|
case "create": {
|
|
10663
10684
|
const title = asStr(args.title);
|
|
@@ -10696,24 +10717,24 @@ export class FleetManager {
|
|
|
10696
10717
|
const id = asStr(args.id);
|
|
10697
10718
|
if (!id)
|
|
10698
10719
|
return { error: "id is required" };
|
|
10699
|
-
return ack(db.claimTask(db.getTaskByPrefix(id).id, instance));
|
|
10720
|
+
return noted(ack(db.claimTask(db.getTaskByPrefix(id).id, instance, { kind: "instance", name: instance })));
|
|
10700
10721
|
}
|
|
10701
10722
|
case "done": {
|
|
10702
10723
|
const id = asStr(args.id);
|
|
10703
10724
|
if (!id)
|
|
10704
10725
|
return { error: "id is required" };
|
|
10705
|
-
return ack(db.completeTask(db.getTaskByPrefix(id).id, asStr(args.result)));
|
|
10726
|
+
return noted(ack(db.completeTask(db.getTaskByPrefix(id).id, asStr(args.result), { kind: "instance", name: instance })));
|
|
10706
10727
|
}
|
|
10707
10728
|
case "update": {
|
|
10708
10729
|
const id = asStr(args.id);
|
|
10709
10730
|
if (!id)
|
|
10710
10731
|
return { error: "id is required" };
|
|
10711
|
-
return ack(db.updateTask(db.getTaskByPrefix(id).id, {
|
|
10732
|
+
return noted(ack(db.updateTask(db.getTaskByPrefix(id).id, {
|
|
10712
10733
|
status: asStatus(args.status),
|
|
10713
10734
|
assignee: asStr(args.assignee),
|
|
10714
10735
|
result: asStr(args.result),
|
|
10715
10736
|
priority: asPriority(args.priority),
|
|
10716
|
-
}));
|
|
10737
|
+
}, { kind: "instance", name: instance })));
|
|
10717
10738
|
}
|
|
10718
10739
|
default: return { error: `Unknown task action: ${action}` };
|
|
10719
10740
|
}
|
|
@@ -10807,7 +10828,18 @@ export class FleetManager {
|
|
|
10807
10828
|
// so " \t " / [" ","\t"] are treated identically to undefined.
|
|
10808
10829
|
const asStatusFilter = normalizeStatusFilter;
|
|
10809
10830
|
const ack = (t) => ({ id: t.id, status: t.status, updated_at: t.updated_at });
|
|
10831
|
+
const noted = (a) => { const notes = db.board.takeNotes(); return notes.length ? { ...a, notes } : a; };
|
|
10810
10832
|
try {
|
|
10833
|
+
// The task board's own actions and fields (task-board-tool.ts); the rest is the tool's plain task, below.
|
|
10834
|
+
const onBoard = boardToolAction(db, meta.instance_name || instanceName, payload);
|
|
10835
|
+
if (onBoard !== undefined) {
|
|
10836
|
+
ipc.send({ type: "fleet_task_response", fleetRequestId, result: onBoard });
|
|
10837
|
+
// The activity log hears of board work as it does of plain tasks.
|
|
10838
|
+
const what = boardActivity(payload);
|
|
10839
|
+
if (what)
|
|
10840
|
+
this.eventLog?.logActivity("task_update", instanceName, what);
|
|
10841
|
+
return;
|
|
10842
|
+
}
|
|
10811
10843
|
let result;
|
|
10812
10844
|
// Full task kept for activity logging; the IPC reply uses the compact ack.
|
|
10813
10845
|
let logTask;
|
|
@@ -10843,12 +10875,12 @@ export class FleetManager {
|
|
|
10843
10875
|
result = db.getTaskByPrefix(payload.id);
|
|
10844
10876
|
break;
|
|
10845
10877
|
case "claim":
|
|
10846
|
-
logTask = db.claimTask(db.getTaskByPrefix(payload.id).id, meta.instance_name || instanceName);
|
|
10847
|
-
result = ack(logTask);
|
|
10878
|
+
logTask = db.claimTask(db.getTaskByPrefix(payload.id).id, meta.instance_name || instanceName, { kind: "instance", name: meta.instance_name || instanceName });
|
|
10879
|
+
result = noted(ack(logTask));
|
|
10848
10880
|
break;
|
|
10849
10881
|
case "done":
|
|
10850
|
-
logTask = db.completeTask(db.getTaskByPrefix(payload.id).id, payload.result);
|
|
10851
|
-
result = ack(logTask);
|
|
10882
|
+
logTask = db.completeTask(db.getTaskByPrefix(payload.id).id, payload.result, { kind: "instance", name: meta.instance_name || instanceName });
|
|
10883
|
+
result = noted(ack(logTask));
|
|
10852
10884
|
break;
|
|
10853
10885
|
case "update":
|
|
10854
10886
|
logTask = db.updateTask(db.getTaskByPrefix(payload.id).id, {
|
|
@@ -10856,8 +10888,8 @@ export class FleetManager {
|
|
|
10856
10888
|
assignee: payload.assignee,
|
|
10857
10889
|
result: payload.result,
|
|
10858
10890
|
priority: payload.priority,
|
|
10859
|
-
});
|
|
10860
|
-
result = ack(logTask);
|
|
10891
|
+
}, { kind: "instance", name: meta.instance_name || instanceName });
|
|
10892
|
+
result = noted(ack(logTask));
|
|
10861
10893
|
break;
|
|
10862
10894
|
default:
|
|
10863
10895
|
throw new Error(`Unknown task action: ${action}`);
|
|
@@ -11480,6 +11512,7 @@ export class FleetManager {
|
|
|
11480
11512
|
// Its web chat goes with it (only after the removal succeeded): a later instance of the same name
|
|
11481
11513
|
// must not be shown the old one's conversation, and deleted names must not pile up.
|
|
11482
11514
|
this.webChatHistory.forget(name);
|
|
11515
|
+
this.searchIndex?.removeInstance(name);
|
|
11483
11516
|
this.webFiles.forget(name);
|
|
11484
11517
|
this.compactWatchService?.forget(name);
|
|
11485
11518
|
// #1664: and it is nobody's favorite any more (every device sees that at its next status frame).
|
|
@@ -16110,6 +16143,36 @@ export class FleetManager {
|
|
|
16110
16143
|
const failure = await this.promptLoginBackends(chat);
|
|
16111
16144
|
await data.respond(failure ?? t("login.chooser_posted"));
|
|
16112
16145
|
}
|
|
16146
|
+
/** #2293: whose cross-instance messages search leaves out now (web-api /ui/search). */
|
|
16147
|
+
searchVisibility() { return xmsgVisibility(this.fleetConfig); }
|
|
16148
|
+
/** #2293: a cross-instance message for the search index (outbound-handlers, after it was admitted). */
|
|
16149
|
+
indexCrossInstance(record) {
|
|
16150
|
+
this.searchIndex?.record(xmsgDoc(record));
|
|
16151
|
+
}
|
|
16152
|
+
/**
|
|
16153
|
+
* #2293: the global search index, at fleet start — after the web chat is restored (the backfill reads it). Its worker
|
|
16154
|
+
* owns search.db; see search-index.ts. Off with web.search.enabled: false.
|
|
16155
|
+
*/
|
|
16156
|
+
startSearchIndex(fleet) {
|
|
16157
|
+
const config = fleet.web?.search;
|
|
16158
|
+
if (config?.enabled === false || this.searchIndex)
|
|
16159
|
+
return;
|
|
16160
|
+
const history = this.webChatHistory;
|
|
16161
|
+
this.searchIndex = new SearchIndex({
|
|
16162
|
+
path: join(this.dataDir, "search.db"),
|
|
16163
|
+
retentionDays: config?.retention_days ?? SEARCH_RETENTION_DAYS,
|
|
16164
|
+
outboxPath: join(this.dataDir, "delivery-outbox.db"),
|
|
16165
|
+
schedulerPath: join(this.dataDir, "scheduler.db"),
|
|
16166
|
+
chatDocs: function* () { for (const name of history.instances())
|
|
16167
|
+
for (const m of history.list(name)) {
|
|
16168
|
+
const doc = chatDoc(m);
|
|
16169
|
+
if (doc)
|
|
16170
|
+
yield doc;
|
|
16171
|
+
} },
|
|
16172
|
+
log: (level, msg) => this.logger[level](msg),
|
|
16173
|
+
});
|
|
16174
|
+
this.searchIndex.start();
|
|
16175
|
+
}
|
|
16113
16176
|
queueMirrorMessage(text) {
|
|
16114
16177
|
const mirrorTopicId = this.fleetConfig?.channel?.mirror_topic_id;
|
|
16115
16178
|
if (mirrorTopicId == null || !this.adapter)
|
|
@@ -16207,6 +16270,7 @@ export class FleetManager {
|
|
|
16207
16270
|
replyTo: m.replyTo,
|
|
16208
16271
|
});
|
|
16209
16272
|
broadcastSseEvent(this.sseClients, event, recorded, onError, this.webChatHistory.cursorOf(recorded));
|
|
16273
|
+
this.searchIndex?.record(chatDoc(recorded));
|
|
16210
16274
|
// #1629: live-only receipt after the recorded agent reply. Never persist/replay it or give it an SSE cursor.
|
|
16211
16275
|
if (recorded.role === "agent")
|
|
16212
16276
|
broadcastSseEvent(this.sseClients, "agent_reply_live", { v: 1, boot: recorded.boot, id: recorded.id, instance: recorded.instance }, onError);
|
|
@@ -16219,6 +16283,38 @@ export class FleetManager {
|
|
|
16219
16283
|
}
|
|
16220
16284
|
broadcastSseEvent(this.sseClients, event, data, onError);
|
|
16221
16285
|
}
|
|
16286
|
+
/**
|
|
16287
|
+
* The task board's live side: every committed change goes to the open pages (never to a guest) — the board's new
|
|
16288
|
+
* revision and the card numbers it touched; not who touched them (the page does not need it, and a public-link page
|
|
16289
|
+
* must not learn a hidden instance's name from it). Its news goes to the instances whose cards someone else changed:
|
|
16290
|
+
* one "[Task board]" notice per instance, gathered for 10 s, held by the delivery outbox until it is taken
|
|
16291
|
+
* (task-board-waker.ts).
|
|
16292
|
+
*/
|
|
16293
|
+
attachTaskBoard(db) {
|
|
16294
|
+
this.taskBoardWaker?.stop();
|
|
16295
|
+
// #2303: each instance's cards at once, read at every claim (a fleet.yaml edit applies to the next one).
|
|
16296
|
+
db.board.setCardLimit((name) => boardCardLimit(this.fleetConfig, name));
|
|
16297
|
+
const waker = this.taskBoardWaker = new TaskBoardWaker({
|
|
16298
|
+
deliver: (instance, message, key) => this.admitTaskBoardNotice(instance, message, key),
|
|
16299
|
+
isInstance: (name) => this.fleetConfig?.instances?.[name] != null,
|
|
16300
|
+
holds: (wake) => db.board.wakeHolds(wake),
|
|
16301
|
+
card: (uuid) => db.board.findCard(uuid),
|
|
16302
|
+
log: (level, message, extra) => this.logger[level](extra ?? {}, message),
|
|
16303
|
+
});
|
|
16304
|
+
db.board.onChange(({ rev, ids, projects, wakes }) => {
|
|
16305
|
+
this.emitSseEvent("tasks", { rev, ids, projects });
|
|
16306
|
+
waker.note(wakes);
|
|
16307
|
+
});
|
|
16308
|
+
}
|
|
16309
|
+
/** A task board notice, as AgEnD's own (not a person's words): through the delivery outbox, held until delivered. */
|
|
16310
|
+
admitTaskBoardNotice(instance, message, key) {
|
|
16311
|
+
this.ensureDeliveryOutbox();
|
|
16312
|
+
const outbox = this.deliveryOutbox;
|
|
16313
|
+
if (!outbox)
|
|
16314
|
+
return false;
|
|
16315
|
+
outbox.admitSystemNotice(instance, key, message, new Date());
|
|
16316
|
+
return true;
|
|
16317
|
+
}
|
|
16222
16318
|
listClaimedTasks(assignee) {
|
|
16223
16319
|
try {
|
|
16224
16320
|
return this.scheduler?.db.listTasks({ assignee, status: "claimed" }) ?? [];
|
|
@@ -19188,7 +19284,12 @@ Plus the operational skills (fleet-health, instance-lifecycle, scheduling, sessi
|
|
|
19188
19284
|
// this is the last moment the prompts can be collapsed.
|
|
19189
19285
|
await this.retirePendingNoncePrompts();
|
|
19190
19286
|
this.topicArchiver.stop();
|
|
19287
|
+
this.taskBoardWaker?.stop();
|
|
19191
19288
|
this.scheduler?.shutdown();
|
|
19289
|
+
// What it was sent is written first (bounded: search-index.ts close()).
|
|
19290
|
+
const search = this.searchIndex;
|
|
19291
|
+
this.searchIndex = null;
|
|
19292
|
+
await search?.close();
|
|
19192
19293
|
// Stop instances in parallel batches to avoid long sequential waits.
|
|
19193
19294
|
// Concurrency scales with fleet size — larger fleets tolerate more parallel
|
|
19194
19295
|
// tmux ops, while small fleets stay conservative to avoid overwhelming the
|