@songsid/agend 2.1.4-beta.9 → 2.1.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.
- package/README.md +1 -1
- package/README.zh-TW.md +1 -1
- package/dist/agent-cli-instructions.md +2 -2
- package/dist/agent-endpoint.js +1 -1
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/backend/antigravity.d.ts +12 -8
- package/dist/backend/antigravity.js +189 -42
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-code.d.ts +87 -23
- package/dist/backend/claude-code.js +361 -46
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +18 -0
- package/dist/backend/codex.js +139 -5
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/gemini-cli.js +2 -2
- package/dist/backend/gemini-cli.js.map +1 -1
- package/dist/backend/grok.js +2 -2
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro.d.ts +52 -2
- package/dist/backend/kiro.js +267 -12
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/opencode.js +17 -4
- package/dist/backend/opencode.js.map +1 -1
- package/dist/backend/types.d.ts +122 -3
- package/dist/backend/types.js +5 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-outage.d.ts +61 -0
- package/dist/backend-outage.js +71 -0
- package/dist/backend-outage.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +44 -2
- package/dist/channel/adapters/discord.js +523 -90
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +30 -0
- package/dist/channel/adapters/telegram.js +221 -19
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/agy-mcp-launcher.d.ts +2 -0
- package/dist/channel/agy-mcp-launcher.js +28 -0
- package/dist/channel/agy-mcp-launcher.js.map +1 -0
- package/dist/channel/ipc-bridge.d.ts +2 -1
- package/dist/channel/ipc-bridge.js +33 -4
- package/dist/channel/ipc-bridge.js.map +1 -1
- package/dist/channel/markdown-chunk.d.ts +42 -0
- package/dist/channel/markdown-chunk.js +205 -0
- package/dist/channel/markdown-chunk.js.map +1 -0
- package/dist/channel/mcp-server.js +34 -5
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.js +5 -3
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-queue.js +64 -8
- package/dist/channel/message-queue.js.map +1 -1
- package/dist/channel/types.d.ts +31 -1
- package/dist/classic-channel-manager.d.ts +65 -0
- package/dist/classic-channel-manager.js +172 -3
- package/dist/classic-channel-manager.js.map +1 -1
- package/dist/cli.js +124 -83
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +13 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/config.d.ts +4 -0
- package/dist/config.js +12 -1
- package/dist/config.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +6 -0
- package/dist/cross-instance-envelope.js +30 -0
- package/dist/cross-instance-envelope.js.map +1 -0
- package/dist/daemon.d.ts +491 -14
- package/dist/daemon.js +2121 -276
- package/dist/daemon.js.map +1 -1
- package/dist/doctor.d.ts +41 -0
- package/dist/doctor.js +267 -0
- package/dist/doctor.js.map +1 -0
- package/dist/fleet-context.d.ts +63 -0
- package/dist/fleet-context.js.map +1 -1
- package/dist/fleet-manager.d.ts +402 -11
- package/dist/fleet-manager.js +2594 -422
- package/dist/fleet-manager.js.map +1 -1
- package/dist/fleet-yaml-slim.d.ts +10 -0
- package/dist/fleet-yaml-slim.js +59 -0
- package/dist/fleet-yaml-slim.js.map +1 -0
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +1 -0
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +63 -16
- package/dist/general-knowledge/skills/session-management/SKILL.md +171 -8
- package/dist/instance-lifecycle.d.ts +114 -1
- package/dist/instance-lifecycle.js +404 -14
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.js +42 -4
- package/dist/instructions.js.map +1 -1
- package/dist/locale.js +1024 -320
- package/dist/locale.js.map +1 -1
- package/dist/login-flows.d.ts +91 -0
- package/dist/login-flows.js +170 -0
- package/dist/login-flows.js.map +1 -0
- package/dist/login-manager.d.ts +63 -0
- package/dist/login-manager.js +134 -0
- package/dist/login-manager.js.map +1 -0
- package/dist/network-family.d.ts +18 -0
- package/dist/network-family.js +20 -0
- package/dist/network-family.js.map +1 -0
- package/dist/outbound-handlers.d.ts +16 -1
- package/dist/outbound-handlers.js +174 -40
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +10 -3
- package/dist/outbound-schemas.js +10 -0
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/pane-input-residue.d.ts +52 -0
- package/dist/pane-input-residue.js +107 -0
- package/dist/pane-input-residue.js.map +1 -0
- package/dist/process-memory.d.ts +6 -0
- package/dist/process-memory.js +45 -0
- package/dist/process-memory.js.map +1 -1
- package/dist/reply-dedup.d.ts +7 -8
- package/dist/reply-dedup.js +0 -0
- package/dist/reply-dedup.js.map +1 -1
- package/dist/restart-progress.d.ts +2 -0
- package/dist/restart-progress.js +3 -0
- package/dist/restart-progress.js.map +1 -1
- package/dist/scheduler/db.d.ts +12 -0
- package/dist/scheduler/db.js +59 -0
- package/dist/scheduler/db.js.map +1 -1
- package/dist/service-installer.d.ts +11 -0
- package/dist/service-installer.js +84 -18
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.js +1 -1
- package/dist/settings-api.js.map +1 -1
- package/dist/setup-wizard.js +2 -2
- package/dist/setup-wizard.js.map +1 -1
- package/dist/spawn-gate.d.ts +30 -0
- package/dist/spawn-gate.js +79 -0
- package/dist/spawn-gate.js.map +1 -0
- package/dist/steer-capability.d.ts +1 -0
- package/dist/steer-capability.js +6 -0
- package/dist/steer-capability.js.map +1 -0
- package/dist/storm-window.d.ts +83 -0
- package/dist/storm-window.js +251 -0
- package/dist/storm-window.js.map +1 -0
- package/dist/tips.d.ts +44 -0
- package/dist/tips.js +355 -0
- package/dist/tips.js.map +1 -0
- package/dist/tmux-manager.d.ts +16 -0
- package/dist/tmux-manager.js +110 -25
- package/dist/tmux-manager.js.map +1 -1
- package/dist/topic-commands.d.ts +31 -7
- package/dist/topic-commands.js +342 -70
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.d.ts +35 -10
- package/dist/transcript-sources.js +190 -38
- package/dist/transcript-sources.js.map +1 -1
- package/dist/types.d.ts +6 -1
- package/dist/ui/dashboard.html +55 -32
- package/dist/ui/settings.html +200 -60
- package/dist/ui/view.html +147 -30
- package/dist/usage/format-rich.d.ts +1 -1
- package/dist/usage/format-rich.js +28 -24
- package/dist/usage/format-rich.js.map +1 -1
- package/dist/usage/i18n-keys.d.ts +7 -0
- package/dist/usage/i18n-keys.js +34 -0
- package/dist/usage/i18n-keys.js.map +1 -0
- package/dist/usage/i18n.d.ts +5 -0
- package/dist/usage/i18n.js +27 -0
- package/dist/usage/i18n.js.map +1 -0
- package/dist/usage/providers.d.ts +21 -0
- package/dist/usage/providers.js +153 -75
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.d.ts +11 -3
- package/dist/usage/usage-api.js +61 -24
- package/dist/usage/usage-api.js.map +1 -1
- package/package.json +2 -2
|
@@ -22,6 +22,8 @@ export interface OutboundContext {
|
|
|
22
22
|
name: string;
|
|
23
23
|
backend?: string;
|
|
24
24
|
model?: string;
|
|
25
|
+
displayName?: string;
|
|
26
|
+
description?: string;
|
|
25
27
|
channelId: string;
|
|
26
28
|
adapterId?: string;
|
|
27
29
|
}[];
|
|
@@ -34,7 +36,9 @@ export interface OutboundContext {
|
|
|
34
36
|
restartSingleInstance(name: string): Promise<void>;
|
|
35
37
|
connectIpcToInstance(name: string): Promise<void>;
|
|
36
38
|
/** FleetManager facade that wakes paused instances before delivery. */
|
|
37
|
-
deliverToInstance?(instanceName: string, payload: Record<string, unknown
|
|
39
|
+
deliverToInstance?(instanceName: string, payload: Record<string, unknown>, options?: OutboundDeliveryOptions): Promise<void>;
|
|
40
|
+
/** True while an earlier idle-gated delivery still owns this target's FIFO tail. */
|
|
41
|
+
hasPendingIdleGatedDelivery?(instanceName: string): boolean;
|
|
38
42
|
saveFleetConfig(): void;
|
|
39
43
|
queueMirrorMessage?(text: string): void;
|
|
40
44
|
getAdapterForInstance?(name: string): ChannelAdapter | null;
|
|
@@ -50,6 +54,8 @@ export interface OutboundContext {
|
|
|
50
54
|
clearCancelButton?(instanceName: string): void;
|
|
51
55
|
clearCancelButtonByCorrelation?(correlationId: string): void;
|
|
52
56
|
getInstanceExecutionState?(name: string): "idle" | "working" | "stuck" | "paused" | null;
|
|
57
|
+
/** Subscription provider IDs used by running or paused fleet/Classic instances. */
|
|
58
|
+
getActiveUsageProviderIds?(): ReadonlySet<string>;
|
|
53
59
|
/** Effective process state used to annotate cross-instance delivery results. */
|
|
54
60
|
getInstanceStatus?(name: string): "running" | "paused" | "stopped" | "crashed";
|
|
55
61
|
resolveInstanceModel?(name: string): {
|
|
@@ -58,6 +64,15 @@ export interface OutboundContext {
|
|
|
58
64
|
display: string;
|
|
59
65
|
reason?: string;
|
|
60
66
|
};
|
|
67
|
+
listModelCatalog?(opts: {
|
|
68
|
+
backend?: string;
|
|
69
|
+
instanceName?: string;
|
|
70
|
+
}): Promise<import("./fleet-manager.js").ModelCatalog>;
|
|
71
|
+
}
|
|
72
|
+
interface OutboundDeliveryOptions {
|
|
73
|
+
isCrossInstance?: boolean;
|
|
74
|
+
waitForIdle?: boolean;
|
|
75
|
+
idleTimeoutMs?: number;
|
|
61
76
|
}
|
|
62
77
|
/** Metadata extracted from the raw outbound message. */
|
|
63
78
|
export interface OutboundMeta {
|
|
@@ -1,8 +1,36 @@
|
|
|
1
1
|
import { resolve as pathResolve, isAbsolute } from "node:path";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { GENERAL_PAUSE_ERROR, isGeneralInstance } from "./general-instance.js";
|
|
4
|
-
import {
|
|
4
|
+
import { DEFAULT_MAX_CROSS_INSTANCE_MESSAGE_BYTES } from "./config.js";
|
|
5
|
+
import { t } from "./locale.js";
|
|
6
|
+
import { truncatePreview } from "./channel/markdown-chunk.js";
|
|
7
|
+
import { backendSupportsSteer } from "./steer-capability.js";
|
|
8
|
+
import { formatCrossInstanceInboundMessage, MAX_ASSEMBLED_CROSS_INSTANCE_MESSAGE_BYTES, } from "./cross-instance-envelope.js";
|
|
9
|
+
import { BroadcastArgs, CreateInstanceArgs, CreateTeamArgs, DelegateTaskArgs, DeleteInstanceArgs, DeleteTeamArgs, DeployTemplateArgs, DescribeInstanceArgs, ListDeploymentsArgs, ListInstancesArgs, ListTeamsArgs, ReplaceInstanceArgs, ReportResultArgs, RequestInformationArgs, RestartInstanceArgs, SendToInstanceArgs, PauseInstanceArgs, WakeInstanceArgs, StopInstanceArgs, GetInstanceLogsArgs, GetEffortArgs, GetUsageArgs, ListModelsArgs, GetFleetConfigArgs, UpdateInstanceConfigArgs, UpdateFleetDefaultsArgs, StartInstanceArgs, TeardownDeploymentArgs, UpdateTeamArgs, validateArgs, } from "./outbound-schemas.js";
|
|
5
10
|
const HOME_DIR = homedir();
|
|
11
|
+
function formatByteSize(bytes) {
|
|
12
|
+
if (bytes < 1024)
|
|
13
|
+
return `${bytes} B`;
|
|
14
|
+
const kib = bytes / 1024;
|
|
15
|
+
return `${Number.isInteger(kib) ? kib : kib.toFixed(1)} KiB`;
|
|
16
|
+
}
|
|
17
|
+
/** Hard rejects must happen while the originating MCP request can still fail. */
|
|
18
|
+
function rejectOversizedCrossInstanceMessage(ctx, message, respond) {
|
|
19
|
+
const limit = ctx.fleetConfig?.defaults?.max_cross_instance_message_bytes
|
|
20
|
+
?? DEFAULT_MAX_CROSS_INSTANCE_MESSAGE_BYTES;
|
|
21
|
+
const actual = Buffer.byteLength(message, "utf8");
|
|
22
|
+
if (actual <= limit)
|
|
23
|
+
return false;
|
|
24
|
+
respond(null, t("cross_instance.message_too_large", formatByteSize(actual), formatByteSize(limit)));
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
function rejectOversizedCrossInstanceEnvelope(message, meta, respond) {
|
|
28
|
+
const actual = Buffer.byteLength(formatCrossInstanceInboundMessage(message, meta), "utf8");
|
|
29
|
+
if (actual <= MAX_ASSEMBLED_CROSS_INSTANCE_MESSAGE_BYTES)
|
|
30
|
+
return false;
|
|
31
|
+
respond(null, t("cross_instance.envelope_too_large", formatByteSize(actual), formatByteSize(MAX_ASSEMBLED_CROSS_INSTANCE_MESSAGE_BYTES)));
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
6
34
|
/**
|
|
7
35
|
* Sanitize an error for inclusion in outbound responses sent to agents.
|
|
8
36
|
* Logs the full error server-side, then returns a redacted message that
|
|
@@ -24,9 +52,12 @@ function sanitizeError(err, ctx, operation) {
|
|
|
24
52
|
* The fallback keeps standalone handler harnesses/backward-compatible embedders
|
|
25
53
|
* working; FleetManager always supplies the facade in production.
|
|
26
54
|
*/
|
|
27
|
-
async function deliverToInstance(ctx, instanceName, payload) {
|
|
55
|
+
async function deliverToInstance(ctx, instanceName, payload, options) {
|
|
28
56
|
if (ctx.deliverToInstance) {
|
|
29
|
-
|
|
57
|
+
if (options)
|
|
58
|
+
await ctx.deliverToInstance(instanceName, payload, options);
|
|
59
|
+
else
|
|
60
|
+
await ctx.deliverToInstance(instanceName, payload);
|
|
30
61
|
return;
|
|
31
62
|
}
|
|
32
63
|
const ipc = ctx.instanceIpcClients.get(instanceName);
|
|
@@ -73,10 +104,10 @@ export function setCrossInstanceRetryForTests(cfg) {
|
|
|
73
104
|
* sender's and the target's topic so the agent that sent it and any watching human
|
|
74
105
|
* both find out.
|
|
75
106
|
*/
|
|
76
|
-
async function deliverCrossInstanceWithRetry(ctx, targetInstanceName, targetLabel, senderLabel, correlationId, payload) {
|
|
107
|
+
async function deliverCrossInstanceWithRetry(ctx, targetInstanceName, targetLabel, senderLabel, correlationId, payload, deliveryOptions) {
|
|
77
108
|
const attempt = async () => {
|
|
78
109
|
try {
|
|
79
|
-
await deliverToInstance(ctx, targetInstanceName, payload);
|
|
110
|
+
await deliverToInstance(ctx, targetInstanceName, payload, deliveryOptions);
|
|
80
111
|
return null;
|
|
81
112
|
}
|
|
82
113
|
catch (err) {
|
|
@@ -106,6 +137,21 @@ async function deliverCrossInstanceWithRetry(ctx, targetInstanceName, targetLabe
|
|
|
106
137
|
if (targetInstanceName !== senderLabel)
|
|
107
138
|
ctx.notifyInstanceTopic?.(targetInstanceName, notice);
|
|
108
139
|
}
|
|
140
|
+
function effectiveBackendForTarget(ctx, instanceName) {
|
|
141
|
+
const configured = ctx.fleetConfig?.instances?.[instanceName]?.backend;
|
|
142
|
+
if (configured)
|
|
143
|
+
return configured;
|
|
144
|
+
const classic = ctx.classicChannels?.getAll().find(channel => channel.instanceName === instanceName);
|
|
145
|
+
if (classic) {
|
|
146
|
+
if (classic.backend)
|
|
147
|
+
return classic.backend;
|
|
148
|
+
return ctx.classicChannels?.getBackendByInstance?.(instanceName, ctx.fleetConfig?.defaults?.backend) ?? (ctx.fleetConfig ? ctx.fleetConfig.defaults?.backend ?? "claude-code" : undefined);
|
|
149
|
+
}
|
|
150
|
+
if (ctx.fleetConfig?.instances?.[instanceName]) {
|
|
151
|
+
return ctx.fleetConfig.defaults?.backend ?? "claude-code";
|
|
152
|
+
}
|
|
153
|
+
return undefined;
|
|
154
|
+
}
|
|
109
155
|
// ── Handler implementations ─────────────────────────────────────────────
|
|
110
156
|
const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
111
157
|
const v = validateArgs(SendToInstanceArgs, rawArgs, "send_to_instance");
|
|
@@ -113,7 +159,9 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
113
159
|
respond(null, v.error);
|
|
114
160
|
return;
|
|
115
161
|
}
|
|
116
|
-
const { instance_name: targetName, message, request_kind: reqKind, requires_reply, task_summary, working_directory, branch, correlation_id: parsedCorrelationId } = v.data;
|
|
162
|
+
const { instance_name: targetName, message, steer, request_kind: reqKind, requires_reply, task_summary, working_directory, branch, correlation_id: parsedCorrelationId } = v.data;
|
|
163
|
+
if (rejectOversizedCrossInstanceMessage(ctx, message, respond))
|
|
164
|
+
return;
|
|
117
165
|
const senderLabel = meta.senderSessionName ?? meta.instanceName;
|
|
118
166
|
const isExternalSender = meta.senderSessionName != null && meta.senderSessionName !== meta.instanceName;
|
|
119
167
|
let targetIpc = ctx.instanceIpcClients.get(targetName);
|
|
@@ -145,6 +193,33 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
145
193
|
respond(null, `Instance '${targetName}' is stopped (target_state=stopped). Use start_instance('${targetName}') to start it first.`);
|
|
146
194
|
return;
|
|
147
195
|
}
|
|
196
|
+
// Pane steering can target only the hosting instance's own CLI. External MCP
|
|
197
|
+
// sessions share that daemon but are separate conversations, so steering the
|
|
198
|
+
// host pane would deliver to the wrong recipient; safely keep their ordinary
|
|
199
|
+
// targetSession-aware idle queue instead.
|
|
200
|
+
const isExternalSession = targetSession !== targetInstanceName;
|
|
201
|
+
const targetBackend = steer === true
|
|
202
|
+
? effectiveBackendForTarget(ctx, targetInstanceName)
|
|
203
|
+
: undefined;
|
|
204
|
+
const steerCapable = steer === true
|
|
205
|
+
&& !isExternalSession
|
|
206
|
+
&& targetBackend !== undefined
|
|
207
|
+
&& backendSupportsSteer(targetBackend);
|
|
208
|
+
// A supplement must not jump ahead of the work it is meant to amend. The
|
|
209
|
+
// facade publishes its idle-gated tail synchronously, so a send immediately
|
|
210
|
+
// followed by steer observes the predecessor and joins the same FIFO queue.
|
|
211
|
+
const queuedPredecessor = steerCapable
|
|
212
|
+
&& ctx.hasPendingIdleGatedDelivery?.(targetInstanceName) === true;
|
|
213
|
+
const useSteer = steerCapable && !queuedPredecessor;
|
|
214
|
+
const steerFallbackWarning = steer === true && !useSteer
|
|
215
|
+
? isExternalSession
|
|
216
|
+
? "Steer was not applied because external sessions cannot be targeted safely at pane level. The message was safely queued for idle delivery instead."
|
|
217
|
+
: queuedPredecessor
|
|
218
|
+
? "Steer was not applied because a previous message to this target is still queued and steering would overtake it. The supplement was safely queued behind it for idle delivery instead."
|
|
219
|
+
: targetBackend === undefined
|
|
220
|
+
? "Steer was not applied because the target backend could not be confirmed. The message was safely queued for idle delivery instead."
|
|
221
|
+
: `Steer was not applied because the '${targetBackend}' backend cannot accept mid-turn input. The message was safely queued for idle delivery instead.`
|
|
222
|
+
: undefined;
|
|
148
223
|
const correlationId = parsedCorrelationId || `cid-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
149
224
|
const ipcMeta = {
|
|
150
225
|
chat_id: "",
|
|
@@ -171,6 +246,8 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
171
246
|
const senderDisplay = ctx.fleetConfig?.instances[senderLabel]?.display_name;
|
|
172
247
|
if (senderDisplay && senderDisplay !== senderLabel)
|
|
173
248
|
ipcMeta.from_display = senderDisplay;
|
|
249
|
+
if (rejectOversizedCrossInstanceEnvelope(message, ipcMeta, respond))
|
|
250
|
+
return;
|
|
174
251
|
// Fire-and-queue: hand the message to the fleet and respond immediately.
|
|
175
252
|
// deliverToInstance owns waking a paused target, the idle gate and retries;
|
|
176
253
|
// awaiting it here blocked this call for as long as the target stayed busy
|
|
@@ -180,7 +257,7 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
180
257
|
// failing past this point is a delivery problem to log, not a caller error.
|
|
181
258
|
// Retries and, on final failure, tells both agents' topics — see
|
|
182
259
|
// deliverCrossInstanceWithRetry. It never rejects, so nothing escapes here.
|
|
183
|
-
void deliverCrossInstanceWithRetry(ctx, targetInstanceName, targetName, senderLabel, correlationId, { type: "fleet_inbound", targetSession, content: message, meta: ipcMeta });
|
|
260
|
+
void deliverCrossInstanceWithRetry(ctx, targetInstanceName, targetName, senderLabel, correlationId, { type: useSteer ? "steer" : "fleet_inbound", targetSession, content: message, meta: ipcMeta }, useSteer ? { isCrossInstance: true, waitForIdle: false } : undefined);
|
|
184
261
|
// Show a cancel button on the target's topic so a watching user can interrupt
|
|
185
262
|
// work started by another instance — but only for messages that actually put
|
|
186
263
|
// the recipient to work (task/query). Informational report/update messages
|
|
@@ -219,7 +296,7 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
219
296
|
const showFull = requestKind === "task" || requestKind === "query";
|
|
220
297
|
const text = showFull
|
|
221
298
|
? `${notificationLabel}:\n${message}`
|
|
222
|
-
: `${notificationLabel}: ${ipcMeta.task_summary ??
|
|
299
|
+
: `${notificationLabel}: ${ipcMeta.task_summary ?? truncatePreview(message, 100)}`;
|
|
223
300
|
targetAdapter.sendText(String(targetGroupId), text, { threadId: String(targetTopicId) })
|
|
224
301
|
.catch(e => ctx.logger.warn({ err: e }, "Failed to post target topic notification"));
|
|
225
302
|
}
|
|
@@ -238,22 +315,23 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
238
315
|
ctx.logger.info(`✉ ${senderLabel} → ${targetName}: ${(message ?? "").slice(0, 100)}`);
|
|
239
316
|
const taskSummary = ipcMeta.task_summary || (message ?? "").slice(0, 200);
|
|
240
317
|
ctx.eventLog?.logActivity("message", senderLabel, taskSummary, targetName, ipcMeta.request_kind);
|
|
241
|
-
ctx.queueMirrorMessage?.(`${senderLabel} → ${targetName}: ${(message ?? ""
|
|
318
|
+
ctx.queueMirrorMessage?.(`${senderLabel} → ${targetName}: ${truncatePreview(message ?? "", 500)}`);
|
|
319
|
+
const targetDaemon = ctx.lifecycle.daemons.get(targetInstanceName);
|
|
320
|
+
const targetStateWarning = targetDaemon?.isErrorState
|
|
321
|
+
? targetDaemon.isCrashLoop
|
|
322
|
+
? `${targetName} is in a crash loop — restart or replace required. Message delivered but may not be processed.`
|
|
323
|
+
: targetDaemon.lastErrorType === "rate_limit" || targetDaemon.lastErrorType === "timeout"
|
|
324
|
+
? `${targetName} is rate-limited by provider — paused. Message delivered but may be delayed.`
|
|
325
|
+
: targetDaemon.lastErrorType === "auth_error"
|
|
326
|
+
? `${targetName} has an authentication error — paused. Check credentials. Message delivered but may not be processed.`
|
|
327
|
+
: `${targetName} is in an error state (paused due to repeated errors). Message delivered but may not be processed.`
|
|
328
|
+
: undefined;
|
|
329
|
+
const warnings = [steerFallbackWarning, targetStateWarning].filter((value) => !!value);
|
|
242
330
|
respond({ sent: true, queued: true, target: targetName, target_state: state,
|
|
243
331
|
...(state === "paused" ? { waking: true } : {}), correlation_id: correlationId,
|
|
244
|
-
...(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
if (daemon.isCrashLoop)
|
|
248
|
-
return `${targetName} is in a crash loop — restart or replace required. Message delivered but may not be processed.`;
|
|
249
|
-
const errType = daemon.lastErrorType;
|
|
250
|
-
if (errType === "rate_limit" || errType === "timeout")
|
|
251
|
-
return `${targetName} is rate-limited by provider — paused. Message delivered but may be delayed.`;
|
|
252
|
-
if (errType === "auth_error")
|
|
253
|
-
return `${targetName} has an authentication error — paused. Check credentials. Message delivered but may not be processed.`;
|
|
254
|
-
return `${targetName} is in an error state (paused due to repeated errors). Message delivered but may not be processed.`;
|
|
255
|
-
})(),
|
|
256
|
-
}),
|
|
332
|
+
...(useSteer ? { delivery_mode: "steer" } : {}),
|
|
333
|
+
...(steerFallbackWarning ? { delivery_mode: "idle_queue" } : {}),
|
|
334
|
+
...(warnings.length > 0 ? { warning: warnings.join(" ") } : {}),
|
|
257
335
|
});
|
|
258
336
|
};
|
|
259
337
|
const listInstances = (ctx, rawArgs, respond, meta) => {
|
|
@@ -297,8 +375,8 @@ const listInstances = (ctx, rawArgs, respond, meta) => {
|
|
|
297
375
|
instance_state: ctx.getInstanceExecutionState?.(ch.instanceName) ?? null,
|
|
298
376
|
working_directory: "",
|
|
299
377
|
topic_id: ch.channelId,
|
|
300
|
-
display_name: `classic: ${ch.name}`,
|
|
301
|
-
description: `ClassicBot channel (${ch.name})`,
|
|
378
|
+
display_name: ch.displayName ?? `classic: ${ch.name}`,
|
|
379
|
+
description: ch.description ?? `ClassicBot channel (${ch.name})`,
|
|
302
380
|
backend: ctx.classicChannels.getBackendByInstance?.(ch.instanceName, ctx.fleetConfig?.defaults?.backend) ?? ch.backend ?? ctx.fleetConfig?.defaults?.backend ?? "claude-code",
|
|
303
381
|
model: ctx.resolveInstanceModel?.(ch.instanceName).model
|
|
304
382
|
?? ctx.classicChannels.getModel?.(ch.channelId, ch.adapterId, ctx.fleetConfig?.defaults?.model)
|
|
@@ -558,12 +636,46 @@ const getUsage = async (ctx, rawArgs, respond) => {
|
|
|
558
636
|
try {
|
|
559
637
|
// Same structure as GET /api/ai-usage, same 5-minute cache behind it.
|
|
560
638
|
const { getUsageSnapshot } = await import("./usage/usage-api.js");
|
|
561
|
-
respond(await getUsageSnapshot(v.data.force === true));
|
|
639
|
+
respond(await getUsageSnapshot(v.data.force === true, ctx.getActiveUsageProviderIds?.()));
|
|
562
640
|
}
|
|
563
641
|
catch (err) {
|
|
564
642
|
respond(null, `Usage fetch failed: ${err.message}`);
|
|
565
643
|
}
|
|
566
644
|
};
|
|
645
|
+
/**
|
|
646
|
+
* Enumerate a backend's models. Read-only and best-effort by design: the
|
|
647
|
+
* catalog is an aid for picking a name, and AgEnD passes model strings through
|
|
648
|
+
* to the CLI regardless, so an empty list is a reportable answer rather than an
|
|
649
|
+
* error. The one real error is naming an instance that does not exist — that is
|
|
650
|
+
* a mistake in the call, and silently answering with the fleet default backend
|
|
651
|
+
* would hide it.
|
|
652
|
+
*/
|
|
653
|
+
const listModels = async (ctx, rawArgs, respond) => {
|
|
654
|
+
const v = validateArgs(ListModelsArgs, rawArgs, "list_models");
|
|
655
|
+
if (!v.ok) {
|
|
656
|
+
respond(null, v.error);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
const { backend, instance_name } = v.data;
|
|
660
|
+
if (!ctx.listModelCatalog) {
|
|
661
|
+
respond(null, "list_models is unavailable on this fleet");
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
if (instance_name) {
|
|
665
|
+
const known = Boolean(ctx.fleetConfig?.instances?.[instance_name])
|
|
666
|
+
|| (ctx.classicChannels?.getAll() ?? []).some(c => c.instanceName === instance_name);
|
|
667
|
+
if (!known) {
|
|
668
|
+
respond(null, `Instance not found: ${instance_name}`);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
try {
|
|
673
|
+
respond(await ctx.listModelCatalog({ backend, instanceName: instance_name }));
|
|
674
|
+
}
|
|
675
|
+
catch (err) {
|
|
676
|
+
respond(null, `Model listing failed: ${err.message}`);
|
|
677
|
+
}
|
|
678
|
+
};
|
|
567
679
|
const getInstanceLogs = async (ctx, rawArgs, respond) => {
|
|
568
680
|
const v = validateArgs(GetInstanceLogsArgs, rawArgs, "get_instance_logs");
|
|
569
681
|
if (!v.ok) {
|
|
@@ -614,7 +726,11 @@ const updateInstanceConfig = (ctx, rawArgs, respond) => {
|
|
|
614
726
|
}
|
|
615
727
|
const inst = ctx.fleetConfig?.instances[v.data.name];
|
|
616
728
|
if (!inst) {
|
|
617
|
-
|
|
729
|
+
const isClassic = (ctx.classicChannels?.getAll() ?? [])
|
|
730
|
+
.some(channel => channel.instanceName === v.data.name);
|
|
731
|
+
respond(null, isClassic
|
|
732
|
+
? "This is a ClassicBot instance (managed in classicBot.yaml). Use set_display_name or set_description to configure it."
|
|
733
|
+
: `Instance '${v.data.name}' not found in fleet config`);
|
|
618
734
|
return;
|
|
619
735
|
}
|
|
620
736
|
const patch = v.data.config;
|
|
@@ -654,6 +770,9 @@ const updateFleetDefaults = (ctx, rawArgs, respond) => {
|
|
|
654
770
|
defaults.model = patch.model;
|
|
655
771
|
if (patch.auto_pause_after !== undefined)
|
|
656
772
|
defaults.auto_pause_after = patch.auto_pause_after;
|
|
773
|
+
if (patch.max_cross_instance_message_bytes !== undefined) {
|
|
774
|
+
defaults.max_cross_instance_message_bytes = patch.max_cross_instance_message_bytes;
|
|
775
|
+
}
|
|
657
776
|
ctx.fleetConfig.defaults = defaults;
|
|
658
777
|
try {
|
|
659
778
|
ctx.saveFleetConfig();
|
|
@@ -682,6 +801,9 @@ function wrapAsSend(schema, toolName, buildArgs, warnMissing) {
|
|
|
682
801
|
...extra,
|
|
683
802
|
instance_name: targetName,
|
|
684
803
|
message: body,
|
|
804
|
+
...(v.data.steer !== undefined
|
|
805
|
+
? { steer: v.data.steer }
|
|
806
|
+
: {}),
|
|
685
807
|
request_kind: kind,
|
|
686
808
|
requires_reply: reply,
|
|
687
809
|
task_summary: summary,
|
|
@@ -765,8 +887,31 @@ const broadcast = async (ctx, rawArgs, respond, meta) => {
|
|
|
765
887
|
return;
|
|
766
888
|
}
|
|
767
889
|
const { message, targets, team: teamName, tags: filterTags, task_summary, request_kind, requires_reply } = v.data;
|
|
890
|
+
if (rejectOversizedCrossInstanceMessage(ctx, message, respond))
|
|
891
|
+
return;
|
|
768
892
|
const senderLabel = meta.senderSessionName ?? meta.instanceName;
|
|
769
893
|
const senderDisplay = ctx.fleetConfig?.instances[senderLabel]?.display_name;
|
|
894
|
+
const makeBroadcastMeta = (correlationId, messageId = `bcast-${Date.now()}`) => {
|
|
895
|
+
const ipcMeta = {
|
|
896
|
+
chat_id: "", message_id: messageId, user: `instance:${senderLabel}`,
|
|
897
|
+
user_id: `instance:${senderLabel}`, ts: new Date().toISOString(), thread_id: "",
|
|
898
|
+
from_instance: senderLabel, correlation_id: correlationId,
|
|
899
|
+
};
|
|
900
|
+
if (request_kind)
|
|
901
|
+
ipcMeta.request_kind = request_kind;
|
|
902
|
+
if (requires_reply != null)
|
|
903
|
+
ipcMeta.requires_reply = String(requires_reply);
|
|
904
|
+
if (task_summary)
|
|
905
|
+
ipcMeta.task_summary = task_summary;
|
|
906
|
+
if (senderDisplay && senderDisplay !== senderLabel)
|
|
907
|
+
ipcMeta.from_display = senderDisplay;
|
|
908
|
+
return ipcMeta;
|
|
909
|
+
};
|
|
910
|
+
// Validate once before dispatching any target. The placeholders are the
|
|
911
|
+
// longest values generated below, so every real per-target envelope fits if
|
|
912
|
+
// this one does. Keeping this outside the loop prevents partial broadcasts.
|
|
913
|
+
if (rejectOversizedCrossInstanceEnvelope(message, makeBroadcastMeta("bcast-9999999999999-zzzzzz", "bcast-9999999999999"), respond))
|
|
914
|
+
return;
|
|
770
915
|
// Resolve target list: team, explicit targets, tag filter, or all running
|
|
771
916
|
let targetNames;
|
|
772
917
|
if (teamName) {
|
|
@@ -803,19 +948,7 @@ const broadcast = async (ctx, rawArgs, respond, meta) => {
|
|
|
803
948
|
continue;
|
|
804
949
|
}
|
|
805
950
|
const correlationId = `bcast-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
806
|
-
const ipcMeta =
|
|
807
|
-
chat_id: "", message_id: `bcast-${Date.now()}`, user: `instance:${senderLabel}`,
|
|
808
|
-
user_id: `instance:${senderLabel}`, ts: new Date().toISOString(), thread_id: "",
|
|
809
|
-
from_instance: senderLabel, correlation_id: correlationId,
|
|
810
|
-
};
|
|
811
|
-
if (request_kind)
|
|
812
|
-
ipcMeta.request_kind = request_kind;
|
|
813
|
-
if (requires_reply != null)
|
|
814
|
-
ipcMeta.requires_reply = String(requires_reply);
|
|
815
|
-
if (task_summary)
|
|
816
|
-
ipcMeta.task_summary = task_summary;
|
|
817
|
-
if (senderDisplay && senderDisplay !== senderLabel)
|
|
818
|
-
ipcMeta.from_display = senderDisplay;
|
|
951
|
+
const ipcMeta = makeBroadcastMeta(correlationId);
|
|
819
952
|
// Same at-least-once treatment as send_to_instance: retry, then surface a
|
|
820
953
|
// final failure on both topics instead of dying in a warn-level log line.
|
|
821
954
|
void deliverCrossInstanceWithRetry(ctx, hostInstance, targetName, senderLabel, correlationId, { type: "fleet_inbound", targetSession: targetName, content: message, meta: ipcMeta });
|
|
@@ -826,7 +959,7 @@ const broadcast = async (ctx, rawArgs, respond, meta) => {
|
|
|
826
959
|
for (const target of sentTo) {
|
|
827
960
|
ctx.eventLog?.logActivity("message", senderLabel, summary, target);
|
|
828
961
|
}
|
|
829
|
-
ctx.queueMirrorMessage?.(`📢 ${senderLabel} → [${sentTo.join(", ")}]: ${message
|
|
962
|
+
ctx.queueMirrorMessage?.(`📢 ${senderLabel} → [${sentTo.join(", ")}]: ${truncatePreview(message, 500)}`);
|
|
830
963
|
respond({ sent_to: sentTo, failed, count: sentTo.length, queued: true });
|
|
831
964
|
};
|
|
832
965
|
// ── Teams ────────────────────────────────────────────────────────────────
|
|
@@ -1173,6 +1306,7 @@ export const outboundHandlers = new Map([
|
|
|
1173
1306
|
["get_fleet_status", getFleetStatus],
|
|
1174
1307
|
["get_effort", getEffort],
|
|
1175
1308
|
["get_usage", getUsage],
|
|
1309
|
+
["list_models", listModels],
|
|
1176
1310
|
["get_instance_logs", getInstanceLogs],
|
|
1177
1311
|
["get_fleet_config", getFleetConfig],
|
|
1178
1312
|
["update_instance_config", updateInstanceConfig],
|