@songsid/agend 2.2.0-beta.1 → 2.2.0-beta.10
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 +16 -9
- package/dist/agent-cli.js +15 -7
- package/dist/agent-cli.js.map +1 -1
- package/dist/agent-endpoint.js +21 -9
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/agent-whats-new.d.ts +47 -0
- package/dist/agent-whats-new.js +147 -0
- package/dist/agent-whats-new.js.map +1 -0
- package/dist/auth-api.d.ts +2 -2
- package/dist/auth-api.js +4 -2
- package/dist/auth-api.js.map +1 -1
- package/dist/backend/antigravity.d.ts +3 -0
- package/dist/backend/antigravity.js +9 -0
- package/dist/backend/antigravity.js.map +1 -1
- package/dist/backend/claude-carry.d.ts +22 -0
- package/dist/backend/claude-carry.js +170 -0
- package/dist/backend/claude-carry.js.map +1 -0
- package/dist/backend/claude-code.d.ts +65 -2
- package/dist/backend/claude-code.js +226 -33
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex-metadata.d.ts +33 -0
- package/dist/backend/codex-metadata.js +122 -18
- package/dist/backend/codex-metadata.js.map +1 -1
- package/dist/backend/codex.d.ts +3 -0
- package/dist/backend/codex.js +17 -1
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +40 -3
- package/dist/backend/credential-profile.js +113 -5
- package/dist/backend/credential-profile.js.map +1 -1
- package/dist/backend/effort-metadata.d.ts +1 -1
- package/dist/backend/effort-metadata.js +3 -2
- package/dist/backend/effort-metadata.js.map +1 -1
- package/dist/backend/grok.d.ts +4 -0
- package/dist/backend/grok.js +14 -0
- package/dist/backend/grok.js.map +1 -1
- package/dist/backend/kiro-engine-ledger.d.ts +5 -0
- package/dist/backend/kiro-engine-ledger.js +20 -0
- package/dist/backend/kiro-engine-ledger.js.map +1 -1
- package/dist/backend/kiro-identity.d.ts +6 -0
- package/dist/backend/kiro-identity.js +19 -5
- package/dist/backend/kiro-identity.js.map +1 -1
- package/dist/backend/kiro.d.ts +39 -3
- package/dist/backend/kiro.js +143 -19
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/launch-effort.d.ts +33 -0
- package/dist/backend/launch-effort.js +123 -0
- package/dist/backend/launch-effort.js.map +1 -0
- package/dist/backend/muse.d.ts +84 -0
- package/dist/backend/muse.js +449 -53
- package/dist/backend/muse.js.map +1 -1
- package/dist/backend/types.d.ts +33 -4
- package/dist/backend/types.js +2 -1
- package/dist/backend/types.js.map +1 -1
- package/dist/backend-command.d.ts +91 -0
- package/dist/backend-command.js +122 -0
- package/dist/backend-command.js.map +1 -0
- package/dist/background-work-ended.d.ts +18 -0
- package/dist/background-work-ended.js +30 -0
- package/dist/background-work-ended.js.map +1 -0
- package/dist/cache-service.d.ts +2 -0
- package/dist/cache-service.js +2 -1
- package/dist/cache-service.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +24 -1
- package/dist/channel/adapters/discord.js +87 -2
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +24 -2
- package/dist/channel/adapters/telegram.js +118 -20
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/mcp-server.js +9 -1
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +11 -0
- package/dist/channel/mcp-tools.js +24 -13
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/message-edit-errors.d.ts +5 -0
- package/dist/channel/message-edit-errors.js +24 -0
- package/dist/channel/message-edit-errors.js.map +1 -0
- package/dist/channel/telegram-markdown.d.ts +6 -0
- package/dist/channel/telegram-markdown.js +104 -0
- package/dist/channel/telegram-markdown.js.map +1 -0
- package/dist/channel/tool-router.js +9 -3
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +39 -3
- package/dist/cli.js +133 -28
- package/dist/cli.js.map +1 -1
- package/dist/command-table.d.ts +2 -2
- package/dist/command-table.js +6 -3
- package/dist/command-table.js.map +1 -1
- package/dist/config-validator.js +59 -0
- package/dist/config-validator.js.map +1 -1
- package/dist/cross-instance-envelope.d.ts +15 -2
- package/dist/cross-instance-envelope.js +29 -5
- package/dist/cross-instance-envelope.js.map +1 -1
- package/dist/daemon.d.ts +156 -1
- package/dist/daemon.js +764 -89
- package/dist/daemon.js.map +1 -1
- package/dist/delivery-outbox.d.ts +62 -7
- package/dist/delivery-outbox.js +309 -22
- package/dist/delivery-outbox.js.map +1 -1
- package/dist/delivery-reconciliation.d.ts +18 -1
- package/dist/delivery-reconciliation.js +67 -2
- package/dist/delivery-reconciliation.js.map +1 -1
- package/dist/fleet-context.d.ts +10 -1
- package/dist/fleet-manager.d.ts +345 -10
- package/dist/fleet-manager.js +1868 -174
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/backend-providers/SKILL.md +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +92 -21
- package/dist/general-knowledge/skills/cross-instance-messaging/SKILL.md +27 -3
- package/dist/general-knowledge/skills/delegation-playbook/SKILL.md +8 -8
- package/dist/general-knowledge/skills/fleet-config/SKILL.md +5 -5
- package/dist/general-knowledge/skills/fleet-health/SKILL.md +4 -4
- package/dist/general-knowledge/skills/fleet-restart/SKILL.md +13 -13
- package/dist/general-knowledge/skills/instance-lifecycle/SKILL.md +3 -2
- package/dist/general-knowledge/skills/kiro-engine-migration/SKILL.md +6 -0
- package/dist/general-knowledge/skills/model-discovery/SKILL.md +9 -9
- package/dist/general-knowledge/skills/multi-channel/SKILL.md +8 -2
- package/dist/general-knowledge/skills/scheduling/SKILL.md +5 -3
- package/dist/general-knowledge/skills/session-management/SKILL.md +6 -5
- package/dist/general-knowledge/skills/tui-effort/SKILL.md +2 -2
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +8 -3
- package/dist/general-knowledge/steering/core-rules.md +8 -7
- package/dist/host-memory.d.ts +10 -0
- package/dist/host-memory.js +33 -1
- package/dist/host-memory.js.map +1 -1
- package/dist/human-delivery-receipts.d.ts +83 -0
- package/dist/human-delivery-receipts.js +194 -0
- package/dist/human-delivery-receipts.js.map +1 -0
- package/dist/instance-avatar.d.ts +36 -0
- package/dist/instance-avatar.js +57 -0
- package/dist/instance-avatar.js.map +1 -0
- package/dist/instance-config-impact.js +6 -0
- package/dist/instance-config-impact.js.map +1 -1
- package/dist/instance-lifecycle.d.ts +29 -6
- package/dist/instance-lifecycle.js +79 -28
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/instructions.d.ts +3 -1
- package/dist/instructions.js +11 -8
- package/dist/instructions.js.map +1 -1
- package/dist/kiro-log-gate.d.ts +42 -0
- package/dist/kiro-log-gate.js +39 -0
- package/dist/kiro-log-gate.js.map +1 -0
- package/dist/locale.js +200 -44
- package/dist/locale.js.map +1 -1
- package/dist/log-service.d.ts +99 -0
- package/dist/log-service.js +243 -0
- package/dist/log-service.js.map +1 -0
- package/dist/log-steps.d.ts +169 -0
- package/dist/log-steps.js +858 -0
- package/dist/log-steps.js.map +1 -0
- package/dist/login-controller.d.ts +20 -3
- package/dist/login-controller.js +71 -19
- package/dist/login-controller.js.map +1 -1
- package/dist/login-flows.d.ts +5 -2
- package/dist/login-flows.js +5 -4
- package/dist/login-flows.js.map +1 -1
- package/dist/memory-pressure.d.ts +13 -1
- package/dist/memory-pressure.js +50 -10
- package/dist/memory-pressure.js.map +1 -1
- package/dist/needs-you-hub.js +2 -0
- package/dist/needs-you-hub.js.map +1 -1
- package/dist/needs-you-live.d.ts +6 -0
- package/dist/needs-you-live.js +23 -3
- package/dist/needs-you-live.js.map +1 -1
- package/dist/next-steps.js +1 -1
- package/dist/next-steps.js.map +1 -1
- package/dist/outbound-handlers.d.ts +100 -0
- package/dist/outbound-handlers.js +263 -96
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +26 -15
- package/dist/outbound-schemas.js +24 -7
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/public-link-progress.d.ts +1 -1
- package/dist/public-link-progress.js +8 -2
- package/dist/public-link-progress.js.map +1 -1
- package/dist/public-preview.d.ts +101 -0
- package/dist/public-preview.js +236 -0
- package/dist/public-preview.js.map +1 -0
- package/dist/public-web-gateway.js +11 -4
- package/dist/public-web-gateway.js.map +1 -1
- package/dist/public-web-link.d.ts +24 -1
- package/dist/public-web-link.js +63 -9
- package/dist/public-web-link.js.map +1 -1
- package/dist/reply-buttons-controller.d.ts +3 -0
- package/dist/reply-buttons-controller.js +3 -2
- package/dist/reply-buttons-controller.js.map +1 -1
- package/dist/restart-guard.d.ts +6 -3
- package/dist/restart-guard.js +6 -3
- package/dist/restart-guard.js.map +1 -1
- package/dist/restart-progress.d.ts +33 -0
- package/dist/restart-progress.js +30 -0
- package/dist/restart-progress.js.map +1 -1
- package/dist/service-activation.d.ts +15 -7
- package/dist/service-activation.js +46 -29
- package/dist/service-activation.js.map +1 -1
- package/dist/service-installer.d.ts +31 -0
- package/dist/service-installer.js +41 -2
- package/dist/service-installer.js.map +1 -1
- package/dist/settings-api.js +85 -25
- package/dist/settings-api.js.map +1 -1
- package/dist/settings-change.js +72 -1
- package/dist/settings-change.js.map +1 -1
- package/dist/settings-effect.js +28 -4
- package/dist/settings-effect.js.map +1 -1
- package/dist/settings-http-confirmation.js +7 -0
- package/dist/settings-http-confirmation.js.map +1 -1
- package/dist/setup-form.d.ts +1 -1
- package/dist/setup-form.js +1 -1
- package/dist/spawn-gate.js +5 -1
- package/dist/spawn-gate.js.map +1 -1
- package/dist/spawn-hold-reply.d.ts +12 -0
- package/dist/spawn-hold-reply.js +39 -0
- package/dist/spawn-hold-reply.js.map +1 -0
- package/dist/status-emojis.d.ts +10 -3
- package/dist/status-emojis.js +11 -2
- package/dist/status-emojis.js.map +1 -1
- package/dist/systemd-peer-bus.d.ts +95 -0
- package/dist/systemd-peer-bus.js +430 -0
- package/dist/systemd-peer-bus.js.map +1 -0
- package/dist/systemd-runtime.d.ts +27 -1
- package/dist/systemd-runtime.js +69 -7
- package/dist/systemd-runtime.js.map +1 -1
- package/dist/tips.js +4 -4
- package/dist/tips.js.map +1 -1
- package/dist/tool-progress.js +3 -2
- package/dist/tool-progress.js.map +1 -1
- package/dist/topic-commands.d.ts +42 -2
- package/dist/topic-commands.js +92 -7
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-delta-reader.d.ts +3 -3
- package/dist/transcript-delta-reader.js +8 -8
- package/dist/transcript-delta-reader.js.map +1 -1
- package/dist/transcript-jsonl.d.ts +80 -3
- package/dist/transcript-jsonl.js +147 -10
- package/dist/transcript-jsonl.js.map +1 -1
- package/dist/transcript-monitor.d.ts +19 -0
- package/dist/transcript-monitor.js +87 -15
- package/dist/transcript-monitor.js.map +1 -1
- package/dist/transcript-sources.d.ts +78 -0
- package/dist/transcript-sources.js +375 -64
- package/dist/transcript-sources.js.map +1 -1
- package/dist/transcript-turns.d.ts +15 -2
- package/dist/transcript-turns.js +15 -2
- package/dist/transcript-turns.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +7 -15
- package/dist/tunnel/cloudflared.js +36 -1
- package/dist/tunnel/cloudflared.js.map +1 -1
- package/dist/tunnel/purpose-lane.d.ts +5 -1
- package/dist/tunnel/purpose-lane.js +14 -7
- package/dist/tunnel/purpose-lane.js.map +1 -1
- package/dist/tunnel/types.d.ts +5 -1
- package/dist/tunnel/types.js.map +1 -1
- package/dist/turn-resume.d.ts +2 -0
- package/dist/turn-resume.js +1 -1
- package/dist/turn-resume.js.map +1 -1
- package/dist/types.d.ts +25 -0
- package/dist/ui/chat-commands.js +1 -0
- package/dist/ui/chat-emojis.js +33 -0
- package/dist/ui/chat-mentions.js +63 -0
- package/dist/ui/chat-render.js +42 -2
- package/dist/ui/chat-store.js +2 -2
- package/dist/ui/chat-strings.js +38 -20
- package/dist/ui/chat-thread.js +69 -12
- package/dist/ui/chat-time.js +80 -0
- package/dist/ui/panel-chat.js +39 -10
- package/dist/ui/panel-details.js +2 -0
- package/dist/ui/panel-fleet.js +199 -57
- package/dist/ui/panel-log.js +428 -0
- package/dist/ui/panel-needs.js +6 -2
- package/dist/ui/panel-settings.js +137 -32
- package/dist/ui/preview-panel.js +55 -15
- package/dist/ui/preview-status.js +28 -0
- package/dist/ui/preview.js +44 -3
- package/dist/ui/settings-dialogs.js +7 -2
- package/dist/ui/settings-model.js +2 -1
- package/dist/ui/settings-strings.js +46 -4
- package/dist/ui/settings-token.js +1 -1
- package/dist/ui/shared/app-i18n.js +43 -17
- package/dist/ui/shared/app-prefs.js +250 -0
- package/dist/ui/shared/app-route.js +7 -2
- package/dist/ui/shared/app-shell.js +165 -8
- package/dist/ui/shared/app-store.js +5 -1
- package/dist/ui/shared/app-stream.js +23 -5
- package/dist/ui/shared/app.css +155 -22
- package/dist/ui/shared/app.js +11 -1
- package/dist/ui/shared/fonts/README.md +1 -1
- package/dist/ui/shared/header-tools.js +55 -5
- package/dist/ui/shared/instance-nav.js +241 -45
- package/dist/ui/shared/instance-switch.js +4 -4
- package/dist/ui/shared/panel-view.js +6 -0
- package/dist/ui/shared/signin.js +7 -7
- package/dist/ui/shared/theme.js +39 -7
- package/dist/ui/shared/tokens.css +1 -1
- package/dist/ui/shared/ui-icons.js +1 -0
- package/dist/ui/shared/usage-dialog.js +23 -5
- package/dist/ui/shared/view-strings.js +32 -4
- package/dist/ui/signin.html +1 -1
- package/dist/unrecognised-prompt.d.ts +23 -0
- package/dist/unrecognised-prompt.js +74 -0
- package/dist/unrecognised-prompt.js.map +1 -0
- package/dist/update-install.d.ts +28 -2
- package/dist/update-install.js +22 -4
- package/dist/update-install.js.map +1 -1
- package/dist/update-marker.d.ts +51 -0
- package/dist/update-marker.js +61 -0
- package/dist/update-marker.js.map +1 -1
- package/dist/update-preflight.d.ts +100 -0
- package/dist/update-preflight.js +264 -0
- package/dist/update-preflight.js.map +1 -0
- package/dist/updater-caller.d.ts +31 -0
- package/dist/updater-caller.js +109 -0
- package/dist/updater-caller.js.map +1 -0
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +1 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/providers.d.ts +34 -5
- package/dist/usage/providers.js +160 -27
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/statusline-usage.d.ts +6 -2
- package/dist/usage/statusline-usage.js +10 -4
- package/dist/usage/statusline-usage.js.map +1 -1
- package/dist/view-api.d.ts +1 -1
- package/dist/view-api.js +38 -8
- package/dist/view-api.js.map +1 -1
- package/dist/wake-coordinator.d.ts +11 -0
- package/dist/wake-coordinator.js +38 -4
- package/dist/wake-coordinator.js.map +1 -1
- package/dist/web-api.d.ts +49 -2
- package/dist/web-api.js +421 -31
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +2 -2
- package/dist/web-auth.js +9 -4
- package/dist/web-auth.js.map +1 -1
- package/dist/web-commands.d.ts +27 -4
- package/dist/web-commands.js +19 -4
- package/dist/web-commands.js.map +1 -1
- package/dist/web-host-guard.d.ts +3 -11
- package/dist/web-host-guard.js +3 -12
- package/dist/web-host-guard.js.map +1 -1
- package/dist/web-images.d.ts +47 -0
- package/dist/web-images.js +169 -0
- package/dist/web-images.js.map +1 -0
- package/dist/web-mentions.d.ts +44 -0
- package/dist/web-mentions.js +75 -0
- package/dist/web-mentions.js.map +1 -0
- package/dist/web-prefs.d.ts +63 -0
- package/dist/web-prefs.js +265 -0
- package/dist/web-prefs.js.map +1 -0
- package/dist/web-preview.d.ts +14 -1
- package/dist/web-preview.js +35 -13
- package/dist/web-preview.js.map +1 -1
- package/dist/web-reply-files.d.ts +45 -0
- package/dist/web-reply-files.js +223 -0
- package/dist/web-reply-files.js.map +1 -0
- package/dist/web-schedules.d.ts +12 -0
- package/dist/web-schedules.js +34 -0
- package/dist/web-schedules.js.map +1 -0
- package/dist/web-server-drain.d.ts +10 -0
- package/dist/web-server-drain.js +50 -0
- package/dist/web-server-drain.js.map +1 -0
- package/dist/web-session.d.ts +36 -1
- package/dist/web-session.js +53 -6
- package/dist/web-session.js.map +1 -1
- package/dist/web-settings-patch.d.ts +23 -0
- package/dist/web-settings-patch.js +77 -0
- package/dist/web-settings-patch.js.map +1 -0
- package/dist/web-shell-routes.d.ts +5 -1
- package/dist/web-shell-routes.js +10 -1
- package/dist/web-shell-routes.js.map +1 -1
- package/dist/web-upload.d.ts +17 -4
- package/dist/web-upload.js +64 -13
- package/dist/web-upload.js.map +1 -1
- package/dist/workflow-templates/default.md +1 -1
- package/package.json +1 -1
- package/templates/launchd.plist.ejs +3 -1
- package/templates/systemd.service.ejs +2 -1
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import { validateFleetConfig } from "./config-validator.js";
|
|
1
|
+
import { KNOWN_BACKENDS, validateFleetConfig } from "./config-validator.js";
|
|
2
2
|
import { resolve as pathResolve, isAbsolute } from "node:path";
|
|
3
3
|
import { homedir } from "node:os";
|
|
4
4
|
import { randomUUID } from "node:crypto";
|
|
5
5
|
import { deliveryContentDigest, deliveryStatusSelector } from "./delivery-outbox.js";
|
|
6
6
|
import { GENERAL_PAUSE_ERROR, isGeneralInstance } from "./general-instance.js";
|
|
7
|
-
import { DEFAULT_MAX_CROSS_INSTANCE_MESSAGE_BYTES, DEFAULT_LIST_INSTANCES_OUTPUT_BUDGET, MAX_INSTANCE_LOG_LINES } from "./config.js";
|
|
7
|
+
import { DEFAULT_MAX_CROSS_INSTANCE_MESSAGE_BYTES, DEFAULT_LIST_INSTANCES_OUTPUT_BUDGET, MAX_INSTANCE_LOG_LINES, getEffectiveInstanceDefaults } from "./config.js";
|
|
8
8
|
import { t } from "./locale.js";
|
|
9
9
|
import { presentationState, interactionSummary } from "./interaction-observation.js";
|
|
10
10
|
import { truncatePreview } from "./channel/markdown-chunk.js";
|
|
11
11
|
import { crossInstanceVisibility, senderTopicNotice, targetTopicNotice } from "./cross-instance-notice.js";
|
|
12
12
|
import { instanceSupportsSteer } from "./steer-capability.js";
|
|
13
13
|
import { assignDisplayLabels, displayInstanceName, readStatuslineModel } from "./topic-commands.js";
|
|
14
|
-
import { credentialProfileLogin, credentialSwitchStartsFresh, instanceCredentialProfile } from "./backend/credential-profile.js";
|
|
14
|
+
import { CHAT_PROFILE_LOGIN_BACKENDS, credentialProfileLogin, credentialSwitchCarriesConversation, credentialSwitchStartsFresh, instanceCredentialProfile } from "./backend/credential-profile.js";
|
|
15
15
|
import { kiroEngineCandidates, kiroEngineStatus } from "./kiro-engine-status.js";
|
|
16
16
|
import { formatCrossInstanceInboundMessage, MAX_ASSEMBLED_CROSS_INSTANCE_MESSAGE_BYTES, } from "./cross-instance-envelope.js";
|
|
17
17
|
import { BroadcastArgs, CreateInstanceArgs, CreateTeamArgs, DelegateTaskArgs, DeleteInstanceArgs, DeleteTeamArgs, DeployTemplateArgs, DescribeInstanceArgs, ListDeploymentsArgs, ListInstancesArgs, ListTeamsArgs, ReplaceInstanceArgs, ReportResultArgs, DeliveryStatusArgs, RequestInformationArgs, RestartInstanceArgs, SendToInstanceArgs, PauseInstanceArgs, WakeInstanceArgs, StopInstanceArgs, GetInstanceLogsArgs, GetEffortArgs, KiroEngineStatusArgs, GetUsageArgs, ListModelsArgs, GetFleetConfigArgs, UpdateInstanceConfigArgs, UpdateFleetDefaultsArgs, StartInstanceArgs, TeardownDeploymentArgs, UpdateTeamArgs, validateArgs, } from "./outbound-schemas.js";
|
|
@@ -32,8 +32,11 @@ function rejectOversizedCrossInstanceMessage(ctx, message, respond) {
|
|
|
32
32
|
respond(null, t("cross_instance.message_too_large", formatByteSize(actual), formatByteSize(limit)));
|
|
33
33
|
return true;
|
|
34
34
|
}
|
|
35
|
-
function rejectOversizedCrossInstanceEnvelope(message, meta, respond
|
|
36
|
-
|
|
35
|
+
function rejectOversizedCrossInstanceEnvelope(message, meta, respond,
|
|
36
|
+
/** #1650: the footer differs by the receiver's mode — its own, or the longer of the two when targets are not known yet. */
|
|
37
|
+
mode = "mcp") {
|
|
38
|
+
const size = (m) => Buffer.byteLength(formatCrossInstanceInboundMessage(message, meta, m), "utf8");
|
|
39
|
+
const actual = mode === "either" ? Math.max(size("mcp"), size("cli")) : size(mode);
|
|
37
40
|
if (actual <= MAX_ASSEMBLED_CROSS_INSTANCE_MESSAGE_BYTES)
|
|
38
41
|
return false;
|
|
39
42
|
respond(null, t("cross_instance.envelope_too_large", formatByteSize(actual), formatByteSize(MAX_ASSEMBLED_CROSS_INSTANCE_MESSAGE_BYTES)));
|
|
@@ -165,7 +168,7 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
165
168
|
respond(null, v.error);
|
|
166
169
|
return;
|
|
167
170
|
}
|
|
168
|
-
const { instance_name: targetName, message, steer, request_kind: reqKind, requires_reply, task_summary, working_directory, branch, correlation_id: parsedCorrelationId } = v.data;
|
|
171
|
+
const { instance_name: targetName, message, forwards, steer, request_kind: reqKind, requires_reply, task_summary, working_directory, branch, correlation_id: parsedCorrelationId } = v.data;
|
|
169
172
|
if (rejectOversizedCrossInstanceMessage(ctx, message, respond))
|
|
170
173
|
return;
|
|
171
174
|
const senderLabel = meta.senderSessionName ?? meta.instanceName;
|
|
@@ -221,7 +224,7 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
221
224
|
const queuedPredecessor = steerCapable
|
|
222
225
|
&& ctx.hasPendingIdleGatedDelivery?.(targetInstanceName) === true;
|
|
223
226
|
const useSteer = steerCapable && !queuedPredecessor;
|
|
224
|
-
|
|
227
|
+
let steerFallbackWarning = steer === true && !useSteer
|
|
225
228
|
? isExternalSession
|
|
226
229
|
? t("delivery.steer_external")
|
|
227
230
|
: queuedPredecessor
|
|
@@ -252,12 +255,14 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
252
255
|
ipcMeta.working_directory = working_directory;
|
|
253
256
|
if (branch)
|
|
254
257
|
ipcMeta.branch = branch;
|
|
258
|
+
if (forwards?.length)
|
|
259
|
+
ipcMeta.forwards = JSON.stringify(forwards);
|
|
255
260
|
// #77: surface the sender's display name in the recipient's [from:…] header
|
|
256
261
|
// (keeps from_instance as the machine name so the reply-back target stays valid).
|
|
257
262
|
const senderDisplay = ctx.fleetConfig?.instances[senderLabel]?.display_name;
|
|
258
263
|
if (senderDisplay && senderDisplay !== senderLabel)
|
|
259
264
|
ipcMeta.from_display = senderDisplay;
|
|
260
|
-
if (rejectOversizedCrossInstanceEnvelope(message, ipcMeta, respond))
|
|
265
|
+
if (rejectOversizedCrossInstanceEnvelope(message, ipcMeta, respond, ctx.fleetConfig?.instances[targetName]?.agent_mode === "cli" ? "cli" : "mcp"))
|
|
261
266
|
return;
|
|
262
267
|
// Fire-and-queue: hand the message to the fleet and respond immediately.
|
|
263
268
|
// deliverToInstance owns waking a paused target, the idle gate and retries;
|
|
@@ -275,6 +280,7 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
275
280
|
meta: ipcMeta,
|
|
276
281
|
};
|
|
277
282
|
let durableReceipt;
|
|
283
|
+
let effectiveUseSteer = useSteer;
|
|
278
284
|
if (ctx.admitDurableDelivery) {
|
|
279
285
|
try {
|
|
280
286
|
durableReceipt = ctx.admitDurableDelivery({
|
|
@@ -288,7 +294,13 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
288
294
|
kind: useSteer ? "steer" : "fleet_inbound",
|
|
289
295
|
correlationId,
|
|
290
296
|
payload,
|
|
297
|
+
forwards,
|
|
298
|
+
requestedKind: steer === true ? "steer" : "fleet_inbound",
|
|
291
299
|
});
|
|
300
|
+
if (durableReceipt.kind)
|
|
301
|
+
effectiveUseSteer = durableReceipt.kind === "steer";
|
|
302
|
+
if (durableReceipt.duplicate && durableReceipt.kind !== undefined && durableReceipt.kind !== payload.type)
|
|
303
|
+
steerFallbackWarning = undefined;
|
|
292
304
|
}
|
|
293
305
|
catch (err) {
|
|
294
306
|
ctx.logger.error({ err, source: meta.instanceName, target: targetInstanceName }, "Durable outbound admission failed closed");
|
|
@@ -297,6 +309,10 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
297
309
|
}
|
|
298
310
|
}
|
|
299
311
|
else {
|
|
312
|
+
if (forwards?.length) {
|
|
313
|
+
respond(null, "Forwarded message verification requires the durable delivery store");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
300
316
|
// Standalone handler harnesses retain the legacy path; production FleetManager
|
|
301
317
|
// always provides durable admission.
|
|
302
318
|
void deliverCrossInstanceWithRetry(ctx, targetInstanceName, targetName, senderLabel, correlationId, payload, useSteer ? { isCrossInstance: true, waitForIdle: false } : undefined);
|
|
@@ -333,33 +349,43 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
333
349
|
displayName: ctx.fleetConfig?.instances[name]?.display_name,
|
|
334
350
|
})));
|
|
335
351
|
const displayLabel = (name) => fleetLabels.get(name) ?? displayInstanceName(name, ctx.fleetConfig?.instances[name]?.display_name);
|
|
352
|
+
const instances = ctx.fleetConfig?.instances ?? {};
|
|
353
|
+
const notice = { label: `${displayLabel(senderLabel)} → ${displayLabel(targetName)}`, message, requestKind, taskSummary: ipcMeta.task_summary };
|
|
354
|
+
const targetInstance = instances[targetInstanceName];
|
|
355
|
+
const targetIsGeneral = targetInstance?.general_topic === true;
|
|
356
|
+
const targetText = targetTopicNotice(crossInstanceVisibility(ctx.fleetConfig, targetInstanceName), notice);
|
|
357
|
+
const targetShown = targetText !== null && !targetIsGeneral && !ctx.sessionRegistry.has(targetName);
|
|
358
|
+
const senderInstance = instances[meta.instanceName];
|
|
359
|
+
const senderIsGeneral = senderInstance?.general_topic === true;
|
|
360
|
+
const senderText = senderTopicNotice(crossInstanceVisibility(ctx.fleetConfig, meta.instanceName), notice);
|
|
361
|
+
const senderShown = senderText !== null && !senderIsGeneral;
|
|
336
362
|
if (groupId && ctx.adapter) {
|
|
337
|
-
const instances = ctx.fleetConfig?.instances ?? {};
|
|
338
|
-
const notificationLabel = `${displayLabel(senderLabel)} → ${displayLabel(targetName)}`;
|
|
339
|
-
const notice = { label: notificationLabel, message, requestKind, taskSummary: ipcMeta.task_summary };
|
|
340
363
|
// ── Target topic notification ──
|
|
341
|
-
const targetInstance = instances[targetInstanceName];
|
|
342
364
|
const targetTopicId = targetInstance?.topic_id;
|
|
343
|
-
|
|
344
|
-
const targetText = targetTopicNotice(crossInstanceVisibility(ctx.fleetConfig, targetInstanceName), notice);
|
|
345
|
-
if (targetText !== null && targetTopicId && !targetIsGeneral && !ctx.sessionRegistry.has(targetName)) {
|
|
365
|
+
if (targetShown && targetTopicId) {
|
|
346
366
|
const targetAdapter = ctx.getAdapterForInstance?.(targetInstanceName) ?? ctx.adapter;
|
|
347
367
|
const targetGroupId = ctx.getGroupIdForInstance?.(targetInstanceName) ?? String(groupId);
|
|
348
368
|
targetAdapter.sendText(String(targetGroupId), targetText, { threadId: String(targetTopicId) })
|
|
349
369
|
.catch(e => ctx.logger.warn({ err: e }, "Failed to post target topic notification"));
|
|
350
370
|
}
|
|
351
371
|
// ── Sender topic notification ──
|
|
352
|
-
const senderInstance = instances[meta.instanceName];
|
|
353
372
|
const senderTopicId = senderInstance?.topic_id;
|
|
354
|
-
|
|
355
|
-
const senderText = senderTopicNotice(crossInstanceVisibility(ctx.fleetConfig, meta.instanceName), notice);
|
|
356
|
-
if (senderText !== null && senderTopicId && !senderIsGeneral) {
|
|
373
|
+
if (senderShown && senderTopicId) {
|
|
357
374
|
const senderAdapter = ctx.getAdapterForInstance?.(meta.instanceName) ?? ctx.adapter;
|
|
358
375
|
const senderGroupId = ctx.getGroupIdForInstance?.(meta.instanceName) ?? String(groupId);
|
|
359
376
|
senderAdapter.sendText(senderGroupId, senderText, { threadId: String(senderTopicId) })
|
|
360
377
|
.catch(e => ctx.logger.warn({ err: e }, "Failed to post sender topic notification"));
|
|
361
378
|
}
|
|
362
379
|
}
|
|
380
|
+
// #1672: the same notices in each instance's web chat — a muted status line, never an agent's or a person's message.
|
|
381
|
+
// Not gated on a topic or a chat platform (a web-only fleet, a topic-less instance); recorded in its history (#1565).
|
|
382
|
+
const webNotice = (instance, text) => ctx.emitSseEvent?.("message", {
|
|
383
|
+
instance, sender: displayLabel(senderLabel), role: "status", text, ts: new Date().toISOString(),
|
|
384
|
+
});
|
|
385
|
+
if (targetShown)
|
|
386
|
+
webNotice(targetInstanceName, targetText);
|
|
387
|
+
if (senderShown)
|
|
388
|
+
webNotice(meta.instanceName, senderText);
|
|
363
389
|
// #856: the same line, now carrying the envelope's message_id and a short
|
|
364
390
|
// content digest, so a disputed message can be matched to its outbox row
|
|
365
391
|
// and paste digests without adding a log line per delivery.
|
|
@@ -379,6 +405,7 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
379
405
|
: undefined;
|
|
380
406
|
const warnings = [steerFallbackWarning, targetStateWarning].filter((value) => !!value);
|
|
381
407
|
respond({ sent: true, queued: true, target: targetName, target_state: state,
|
|
408
|
+
...(forwards?.length ? { forwards } : {}),
|
|
382
409
|
...(durableReceipt ? {
|
|
383
410
|
durable: true,
|
|
384
411
|
delivery_id: durableReceipt.deliveryId,
|
|
@@ -387,8 +414,8 @@ const sendToInstance = async (ctx, rawArgs, respond, meta) => {
|
|
|
387
414
|
duplicate: durableReceipt.duplicate,
|
|
388
415
|
} : {}),
|
|
389
416
|
...(state === "paused" ? { waking: true } : {}), correlation_id: correlationId,
|
|
390
|
-
...(
|
|
391
|
-
...(
|
|
417
|
+
...(effectiveUseSteer ? { delivery_mode: "steer" } : {}),
|
|
418
|
+
...(steer === true && !effectiveUseSteer ? { delivery_mode: "idle_queue" } : {}),
|
|
392
419
|
...(warnings.length > 0 ? { warning: warnings.join(" ") } : {}),
|
|
393
420
|
});
|
|
394
421
|
};
|
|
@@ -918,31 +945,86 @@ const updateInstanceConfig = (ctx, rawArgs, respond) => {
|
|
|
918
945
|
respond(null, v.error);
|
|
919
946
|
return;
|
|
920
947
|
}
|
|
921
|
-
|
|
948
|
+
applyInstanceConfigPatch(ctx, v.data.name, v.data.config, respond);
|
|
949
|
+
};
|
|
950
|
+
/**
|
|
951
|
+
* update_instance_config's write: one validated config write, rolled back whole on a refusal, then one relaunch when
|
|
952
|
+
* the backend (fresh, #1217), a credential profile or backend_options changed. Answers through `respond` like the tool.
|
|
953
|
+
*/
|
|
954
|
+
export function applyInstanceConfigPatch(ctx, name, patch, respond) {
|
|
955
|
+
const inst = ctx.fleetConfig?.instances[name];
|
|
922
956
|
if (!inst) {
|
|
923
957
|
const isClassic = (ctx.classicChannels?.getAll() ?? [])
|
|
924
|
-
.some(channel => channel.instanceName ===
|
|
958
|
+
.some(channel => channel.instanceName === name);
|
|
925
959
|
respond(null, isClassic
|
|
926
960
|
? "This is a ClassicBot instance (managed in classicBot.yaml). Use set_display_name or set_description to configure it."
|
|
927
|
-
: `Instance '${
|
|
961
|
+
: `Instance '${name}' not found in fleet config`);
|
|
928
962
|
return;
|
|
929
963
|
}
|
|
930
|
-
const patch = v.data.config;
|
|
931
964
|
// Read before anything is mutated: the switch is what decides whether the
|
|
932
965
|
// next launch may resume, and by then `inst` already holds the new value.
|
|
933
|
-
|
|
966
|
+
// The backend an instance runs: its own, else the fleet default (#1815 review: a Settings-created entry may omit it).
|
|
967
|
+
const effectiveBackend = () => inst.backend ?? ctx.fleetConfig?.defaults?.backend ?? "claude-code";
|
|
968
|
+
const backendBefore = effectiveBackend();
|
|
969
|
+
// #1808: a model and an effort name the backend they were chosen for. On a switch the new backend gets the ones
|
|
970
|
+
// given — if it takes them, refused here before anything is written — or none of the instance's own (the inherited
|
|
971
|
+
// value, else the CLI's default). Never the old backend's: codex launched with `--model opus` fails every turn.
|
|
972
|
+
if (patch.backend !== undefined && patch.backend !== backendBefore) {
|
|
973
|
+
const backendNew = patch.backend;
|
|
974
|
+
// Only a backend AgEnD knows gets these checks; any other name is refused by validation below, as before (and a
|
|
975
|
+
// "__proto__" or "toString" never reaches a lookup keyed by backend name).
|
|
976
|
+
const known = KNOWN_BACKENDS.includes(backendNew);
|
|
977
|
+
// The provider the new backend will run on (#1815 review): this patch's backend_options over the instance's, else
|
|
978
|
+
// the fleet defaults' — a custom provider has its own catalog, which the account's list says nothing about.
|
|
979
|
+
const options = mergeBackendOptions(inst.backend_options, patch.backend_options ?? {})[backendNew]
|
|
980
|
+
?? ctx.fleetConfig?.defaults?.backend_options?.[backendNew];
|
|
981
|
+
const rawProvider = options?.provider;
|
|
982
|
+
const provider = typeof rawProvider === "string" && rawProvider.trim() ? rawProvider.trim() : null;
|
|
983
|
+
if (known && typeof patch.model === "string") {
|
|
984
|
+
const verdict = ctx.backendModelVerdict?.(name, backendNew, patch.model, { provider });
|
|
985
|
+
if (verdict && !verdict.ok) {
|
|
986
|
+
respond(null, `Rejected: ${backendNew} does not take the model "${patch.model}". ${verdict.choices.length
|
|
987
|
+
? `Choose one of: ${verdict.choices.join(", ")}` : `No ${backendNew} model list is known yet`} — or leave model out for its default.`);
|
|
988
|
+
return;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
if (known && typeof patch.effort === "string") {
|
|
992
|
+
// The levels of the model that will run there (codex's differ per model): the one given, else the inherited one.
|
|
993
|
+
const inheritedModel = getEffectiveInstanceDefaults(ctx.fleetConfig?.defaults).model;
|
|
994
|
+
const runs = typeof patch.model === "string" ? patch.model : typeof inheritedModel === "string" ? inheritedModel : null;
|
|
995
|
+
const levels = ctx.backendEffortLevels?.(name, backendNew, runs);
|
|
996
|
+
if (levels && !levels.includes(patch.effort)) {
|
|
997
|
+
respond(null, levels.length
|
|
998
|
+
? `Rejected: ${backendNew} takes no effort "${patch.effort}" at launch on this host. Choose one of: ${levels.join(", ")} — or leave effort out for its default.`
|
|
999
|
+
: `Rejected: ${backendNew} takes no effort at launch on this host — leave effort out.`);
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
patch = { ...patch, model: patch.model === undefined ? null : patch.model, effort: patch.effort === undefined ? null : patch.effort };
|
|
1004
|
+
}
|
|
934
1005
|
const profileBefore = instanceCredentialProfile(inst, ctx.fleetConfig?.defaults, backendBefore);
|
|
935
1006
|
// Snapshot enough to undo the whole patch if validation refuses it.
|
|
936
1007
|
const beforeEdit = validateFleetConfig(ctx.fleetConfig);
|
|
937
1008
|
const beforeFields = {};
|
|
938
|
-
for (const key of ["backend", "model", "auto_pause_after", "display_name", "description"]) {
|
|
1009
|
+
for (const key of ["backend", "model", "effort", "auto_pause_after", "display_name", "description"]) {
|
|
939
1010
|
if (patch[key] !== undefined)
|
|
940
1011
|
beforeFields[key] = inst[key];
|
|
941
1012
|
}
|
|
942
1013
|
if (patch.backend !== undefined)
|
|
943
1014
|
inst.backend = patch.backend;
|
|
944
|
-
|
|
945
|
-
|
|
1015
|
+
// null: the value this instance would have with no override of its own — what loading fleet.yaml gives it.
|
|
1016
|
+
const inherited = getEffectiveInstanceDefaults(ctx.fleetConfig?.defaults);
|
|
1017
|
+
for (const key of ["model", "effort"]) {
|
|
1018
|
+
const value = patch[key];
|
|
1019
|
+
if (value === undefined)
|
|
1020
|
+
continue;
|
|
1021
|
+
if (value !== null)
|
|
1022
|
+
inst[key] = value;
|
|
1023
|
+
else if (inherited[key] !== undefined)
|
|
1024
|
+
inst[key] = inherited[key];
|
|
1025
|
+
else
|
|
1026
|
+
delete inst[key];
|
|
1027
|
+
}
|
|
946
1028
|
if (patch.auto_pause_after !== undefined)
|
|
947
1029
|
inst.auto_pause_after = patch.auto_pause_after;
|
|
948
1030
|
if (patch.display_name !== undefined)
|
|
@@ -956,20 +1038,7 @@ const updateInstanceConfig = (ctx, rawArgs, respond) => {
|
|
|
956
1038
|
let relaunchNeeded = false;
|
|
957
1039
|
if (patch.backend_options !== undefined) {
|
|
958
1040
|
const before = JSON.stringify(previousOptions ?? {});
|
|
959
|
-
const merged =
|
|
960
|
-
for (const [backendName, options] of Object.entries(patch.backend_options)) {
|
|
961
|
-
const namespace = { ...(merged[backendName] ?? {}), ...options };
|
|
962
|
-
// A merge cannot express removal, so null (or "") means "drop this key"
|
|
963
|
-
// — that is how an agent goes back to the fleet's default login.
|
|
964
|
-
for (const [key, value] of Object.entries(options)) {
|
|
965
|
-
if (value === null || value === "")
|
|
966
|
-
delete namespace[key];
|
|
967
|
-
}
|
|
968
|
-
if (Object.keys(namespace).length)
|
|
969
|
-
merged[backendName] = namespace;
|
|
970
|
-
else
|
|
971
|
-
delete merged[backendName];
|
|
972
|
-
}
|
|
1041
|
+
const merged = mergeBackendOptions(previousOptions, patch.backend_options);
|
|
973
1042
|
inst.backend_options = merged;
|
|
974
1043
|
relaunchNeeded = JSON.stringify(merged) !== before;
|
|
975
1044
|
}
|
|
@@ -997,7 +1066,7 @@ const updateInstanceConfig = (ctx, rawArgs, respond) => {
|
|
|
997
1066
|
respond(null, `Rejected: ${introduced.map(issue => `${issue.path}: ${issue.message}`).join("; ")}`);
|
|
998
1067
|
return;
|
|
999
1068
|
}
|
|
1000
|
-
const backendAfter =
|
|
1069
|
+
const backendAfter = effectiveBackend();
|
|
1001
1070
|
const profileAfter = instanceCredentialProfile(inst, ctx.fleetConfig?.defaults, backendAfter);
|
|
1002
1071
|
const profileSwitched = profileAfter !== profileBefore;
|
|
1003
1072
|
// #1217: a running CLI is the backend it was launched as. Saving a new
|
|
@@ -1020,13 +1089,17 @@ const updateInstanceConfig = (ctx, rawArgs, respond) => {
|
|
|
1020
1089
|
// the escape hatch from a bad switch and must never be the thing that is
|
|
1021
1090
|
// blocked, and an unlogged shared store means the CLI was never set up at
|
|
1022
1091
|
// all — a state that predates this feature.
|
|
1023
|
-
|
|
1092
|
+
// #1806 review: a profile is a login of ONE backend — the same name under another backend is another store, so a
|
|
1093
|
+
// backend switch onto a named profile is checked too (claude-code "personal" says nothing about kiro-cli "personal").
|
|
1094
|
+
if ((profileSwitched || backendSwitched) && profileAfter) {
|
|
1024
1095
|
// The same root the launcher resolves the profile under, so the store this
|
|
1025
1096
|
// probes is the store the CLI will be pointed at.
|
|
1026
1097
|
const login = credentialProfileLogin(ctx.dataDir, backendAfter, profileAfter);
|
|
1027
1098
|
if (login.state === "signed-out") {
|
|
1028
1099
|
rollback();
|
|
1029
|
-
|
|
1100
|
+
// #1699 part 2: a claude-code profile can also be signed in from chat, in the browser terminal.
|
|
1101
|
+
const fromChat = CHAT_PROFILE_LOGIN_BACKENDS.has(backendAfter) ? `\n or from chat: /login ${backendAfter} ${profileAfter}` : "";
|
|
1102
|
+
respond(null, `Rejected: credential profile "${profileAfter}" has no ${backendAfter} login yet, and switching to it would leave the agent waiting at a sign-in prompt. Log it in first:\n ${login.loginCommand}${fromChat}`);
|
|
1030
1103
|
return;
|
|
1031
1104
|
}
|
|
1032
1105
|
}
|
|
@@ -1039,18 +1112,18 @@ const updateInstanceConfig = (ctx, rawArgs, respond) => {
|
|
|
1039
1112
|
return;
|
|
1040
1113
|
}
|
|
1041
1114
|
if (!relaunchNeeded) {
|
|
1042
|
-
respond({ success: true, name
|
|
1115
|
+
respond({ success: true, name, applied: patch });
|
|
1043
1116
|
return;
|
|
1044
1117
|
}
|
|
1045
1118
|
// Only a running instance is restarted. Switching the subscription of a
|
|
1046
1119
|
// paused or stopped agent must not start it — restartSingleInstance is
|
|
1047
1120
|
// stop-then-start and would — so the new credentials are read whenever it
|
|
1048
1121
|
// next comes up on its own.
|
|
1049
|
-
const status = targetState(ctx,
|
|
1122
|
+
const status = targetState(ctx, name);
|
|
1050
1123
|
if (status !== "running") {
|
|
1051
1124
|
respond({
|
|
1052
1125
|
success: true,
|
|
1053
|
-
name
|
|
1126
|
+
name,
|
|
1054
1127
|
applied: patch,
|
|
1055
1128
|
restarted: false,
|
|
1056
1129
|
note: `Instance is ${status}; the new ${backendSwitched ? "backend" : "backend_options"} apply when it next starts.`,
|
|
@@ -1070,19 +1143,26 @@ const updateInstanceConfig = (ctx, rawArgs, respond) => {
|
|
|
1070
1143
|
// was — taking a handover there would be describing a loss that did not
|
|
1071
1144
|
// happen, and starting fresh would be causing one.
|
|
1072
1145
|
const switchLosesConversation = profileSwitched && credentialSwitchStartsFresh(backendAfter);
|
|
1146
|
+
// #1753: Claude Code's conversation can go along — copied into the new profile and resumed there, between the old
|
|
1147
|
+
// CLI's stop and the new one's start (the restart does it, and says how it went). The handover is still taken now,
|
|
1148
|
+
// while the daemon that holds it is alive: it is what the new session gets if the copy cannot be made.
|
|
1149
|
+
const carry = profileSwitched && !backendSwitched && credentialSwitchCarriesConversation(backendAfter)
|
|
1150
|
+
? { backend: backendAfter, from: profileBefore, to: profileAfter, result: null }
|
|
1151
|
+
: null;
|
|
1073
1152
|
const handover = switchLosesConversation
|
|
1074
|
-
? (ctx.lifecycle.daemons.get(
|
|
1153
|
+
? (ctx.lifecycle.daemons.get(name)?.collectHandoverContext() ?? "")
|
|
1075
1154
|
: "";
|
|
1076
1155
|
// Saving without restarting would report a subscription switch that has not
|
|
1077
1156
|
// happened — the running CLI keeps the credentials it was launched with.
|
|
1078
|
-
// A switch
|
|
1079
|
-
// directory, so resuming can only spend the
|
|
1080
|
-
// something that is not there.
|
|
1081
|
-
ctx.restartSingleInstance(
|
|
1082
|
-
const
|
|
1157
|
+
// A switch that cannot take the conversation along starts fresh: the new store
|
|
1158
|
+
// has no conversation for this directory, so resuming can only spend the
|
|
1159
|
+
// resume startup budget waiting for something that is not there.
|
|
1160
|
+
ctx.restartSingleInstance(name, carry ? { carry } : switchLosesConversation || backendSwitched ? { freshStart: true } : undefined).then(async () => {
|
|
1161
|
+
const carried = carry?.result?.carried === true;
|
|
1162
|
+
const handoverChars = carried ? 0 : await deliverProfileHandover(ctx, name, profileBefore, profileAfter, handover);
|
|
1083
1163
|
respond({
|
|
1084
1164
|
success: true,
|
|
1085
|
-
name
|
|
1165
|
+
name,
|
|
1086
1166
|
applied: patch,
|
|
1087
1167
|
restarted: true,
|
|
1088
1168
|
...(backendSwitched ? { backend_switched: true, conversation_carried_over: false } : {}),
|
|
@@ -1093,21 +1173,43 @@ const updateInstanceConfig = (ctx, rawArgs, respond) => {
|
|
|
1093
1173
|
// across accounts is the thing nobody has been able to test yet,
|
|
1094
1174
|
// so this says where the history lives rather than what will
|
|
1095
1175
|
// happen when the agent reaches for it.
|
|
1096
|
-
conversation_carried_over: !switchLosesConversation,
|
|
1097
|
-
...(
|
|
1098
|
-
? {
|
|
1099
|
-
:
|
|
1176
|
+
conversation_carried_over: carried || !switchLosesConversation,
|
|
1177
|
+
...(carried
|
|
1178
|
+
? { conversation_note: "The conversation was copied into the new profile and resumed there (the old profile keeps its own copy). The next reply re-reads it from a cold cache: one slower, full-price turn." }
|
|
1179
|
+
: switchLosesConversation
|
|
1180
|
+
? { handover_chars: handoverChars, ...(carry?.result && !carry.result.carried ? { carry_failed: carry.result.reason } : {}) }
|
|
1181
|
+
: { conversation_note: `${backendAfter} keeps its history outside the login, so it is shared across profiles; switching back to a profile returns to the same history.` }),
|
|
1100
1182
|
}
|
|
1101
1183
|
: {}),
|
|
1102
1184
|
});
|
|
1103
1185
|
}, (err) => respond({
|
|
1104
1186
|
success: true,
|
|
1105
|
-
name
|
|
1187
|
+
name,
|
|
1106
1188
|
applied: patch,
|
|
1107
1189
|
restarted: false,
|
|
1108
1190
|
warning: `Config saved, but the restart failed: ${err.message}. The new backend_options take effect on the next start.`,
|
|
1109
1191
|
}));
|
|
1110
|
-
}
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* backend_options merged per backend namespace, so setting a kiro option cannot silently drop a codex one. A merge
|
|
1195
|
+
* cannot express removal, so null (or "") means "drop this key" — that is how an agent goes back to the fleet's
|
|
1196
|
+
* default login.
|
|
1197
|
+
*/
|
|
1198
|
+
function mergeBackendOptions(previous, patch) {
|
|
1199
|
+
const merged = { ...(previous ?? {}) };
|
|
1200
|
+
for (const [backendName, options] of Object.entries(patch)) {
|
|
1201
|
+
const namespace = { ...(merged[backendName] ?? {}), ...options };
|
|
1202
|
+
for (const [key, value] of Object.entries(options)) {
|
|
1203
|
+
if (value === null || value === "")
|
|
1204
|
+
delete namespace[key];
|
|
1205
|
+
}
|
|
1206
|
+
if (Object.keys(namespace).length)
|
|
1207
|
+
merged[backendName] = namespace;
|
|
1208
|
+
else
|
|
1209
|
+
delete merged[backendName];
|
|
1210
|
+
}
|
|
1211
|
+
return merged;
|
|
1212
|
+
}
|
|
1111
1213
|
/** Long enough for the relaunched CLI to be listening; matches replace_instance. */
|
|
1112
1214
|
export const PROFILE_HANDOVER_SETTLE_MS = 3_000;
|
|
1113
1215
|
/**
|
|
@@ -1202,6 +1304,8 @@ function wrapAsSend(schema, toolName, buildArgs, warnMissing) {
|
|
|
1202
1304
|
request_kind: kind,
|
|
1203
1305
|
requires_reply: reply,
|
|
1204
1306
|
task_summary: summary,
|
|
1307
|
+
...(v.data.forwards !== undefined
|
|
1308
|
+
? { forwards: v.data.forwards } : {}),
|
|
1205
1309
|
};
|
|
1206
1310
|
// Re-dispatch through the handler map
|
|
1207
1311
|
return sendToInstance(ctx, newArgs, respond, meta);
|
|
@@ -1313,7 +1417,7 @@ const broadcast = async (ctx, rawArgs, respond, meta) => {
|
|
|
1313
1417
|
// Validate once before dispatching any target. The placeholders are the
|
|
1314
1418
|
// longest values generated below, so every real per-target envelope fits if
|
|
1315
1419
|
// this one does. Keeping this outside the loop prevents partial broadcasts.
|
|
1316
|
-
if (rejectOversizedCrossInstanceEnvelope(message, makeBroadcastMeta("bcast-9999999999999-zzzzzz", "bcast-9999999999999"), respond))
|
|
1420
|
+
if (rejectOversizedCrossInstanceEnvelope(message, makeBroadcastMeta("bcast-9999999999999-zzzzzz", "bcast-9999999999999"), respond, "either"))
|
|
1317
1421
|
return;
|
|
1318
1422
|
// Resolve target list: team, explicit targets, tag filter, or all running
|
|
1319
1423
|
let targetNames;
|
|
@@ -1401,30 +1505,50 @@ const broadcast = async (ctx, rawArgs, respond, meta) => {
|
|
|
1401
1505
|
});
|
|
1402
1506
|
};
|
|
1403
1507
|
// ── Teams ────────────────────────────────────────────────────────────────
|
|
1508
|
+
/**
|
|
1509
|
+
* #1692: the one creation of a team — create_team (agents) and POST /ui/teams (the web) both come here. Members must be
|
|
1510
|
+
* instances fleet.yaml defines (own keys; repeats dropped); an existing team is never overwritten (409); a name every
|
|
1511
|
+
* object already has (`__proto__`, `constructor`, `toString`…) is not a team name. The team and its save are one: a
|
|
1512
|
+
* save that throws removes it again (500).
|
|
1513
|
+
*/
|
|
1514
|
+
export function commitTeamCreate(fleetConfig, args, save) {
|
|
1515
|
+
if (!fleetConfig)
|
|
1516
|
+
return { ok: false, status: 500, error: "Fleet config not available" };
|
|
1517
|
+
if (args.name in Object.prototype)
|
|
1518
|
+
return { ok: false, status: 400, error: `Invalid team name: ${args.name}` };
|
|
1519
|
+
const invalid = args.members.filter(m => !Object.hasOwn(fleetConfig.instances, m));
|
|
1520
|
+
if (invalid.length)
|
|
1521
|
+
return { ok: false, status: 400, error: `Invalid instance names: ${invalid.join(", ")}` };
|
|
1522
|
+
if (fleetConfig.teams && Object.hasOwn(fleetConfig.teams, args.name))
|
|
1523
|
+
return { ok: false, status: 409, error: `Team already exists: ${args.name}` };
|
|
1524
|
+
const createdMap = !fleetConfig.teams;
|
|
1525
|
+
const teams = (fleetConfig.teams ??= {});
|
|
1526
|
+
const members = [...new Set(args.members)];
|
|
1527
|
+
const description = args.description?.trim();
|
|
1528
|
+
teams[args.name] = { members, ...(description ? { description } : {}) };
|
|
1529
|
+
try {
|
|
1530
|
+
save();
|
|
1531
|
+
}
|
|
1532
|
+
catch (err) {
|
|
1533
|
+
delete teams[args.name];
|
|
1534
|
+
if (createdMap)
|
|
1535
|
+
delete fleetConfig.teams;
|
|
1536
|
+
return { ok: false, status: 500, error: `Saving fleet.yaml failed; no team was created: ${err?.message ?? String(err)}` };
|
|
1537
|
+
}
|
|
1538
|
+
return { ok: true, result: { created: args.name, members } };
|
|
1539
|
+
}
|
|
1404
1540
|
const createTeam = (ctx, rawArgs, respond) => {
|
|
1405
1541
|
const v = validateArgs(CreateTeamArgs, rawArgs, "create_team");
|
|
1406
1542
|
if (!v.ok) {
|
|
1407
1543
|
respond(null, v.error);
|
|
1408
1544
|
return;
|
|
1409
1545
|
}
|
|
1410
|
-
const
|
|
1411
|
-
if (!
|
|
1412
|
-
respond(null,
|
|
1413
|
-
return;
|
|
1414
|
-
}
|
|
1415
|
-
const invalid = members.filter(m => !ctx.fleetConfig.instances[m]);
|
|
1416
|
-
if (invalid.length) {
|
|
1417
|
-
respond(null, `Invalid instance names: ${invalid.join(", ")}`);
|
|
1546
|
+
const r = commitTeamCreate(ctx.fleetConfig, v.data, () => ctx.saveFleetConfig());
|
|
1547
|
+
if (!r.ok) {
|
|
1548
|
+
respond(null, r.error);
|
|
1418
1549
|
return;
|
|
1419
1550
|
}
|
|
1420
|
-
|
|
1421
|
-
if (ctx.fleetConfig.teams[name]) {
|
|
1422
|
-
respond(null, `Team already exists: ${name}`);
|
|
1423
|
-
return;
|
|
1424
|
-
}
|
|
1425
|
-
ctx.fleetConfig.teams[name] = { members, ...(description ? { description } : {}) };
|
|
1426
|
-
ctx.saveFleetConfig();
|
|
1427
|
-
respond({ created: name, members });
|
|
1551
|
+
respond(r.result);
|
|
1428
1552
|
};
|
|
1429
1553
|
const deleteTeam = (ctx, rawArgs, respond) => {
|
|
1430
1554
|
const v = validateArgs(DeleteTeamArgs, rawArgs, "delete_team");
|
|
@@ -1458,31 +1582,74 @@ const listTeams = (ctx, rawArgs, respond) => {
|
|
|
1458
1582
|
}));
|
|
1459
1583
|
respond(result);
|
|
1460
1584
|
};
|
|
1585
|
+
/**
|
|
1586
|
+
* #1689: the one update of a team — update_team (agents) and PATCH /ui/teams/<name> (the web) both come here. Own keys
|
|
1587
|
+
* only: a team or an instance is one fleet.yaml defines (`__proto__` and `constructor` are not); nothing changes unless
|
|
1588
|
+
* everything is valid. `description`: "" clears it. Renaming is not an update (the org chart and agents name teams).
|
|
1589
|
+
* The caller saves fleet.yaml after an ok.
|
|
1590
|
+
*/
|
|
1591
|
+
export function applyTeamUpdate(fleetConfig, args) {
|
|
1592
|
+
if (!fleetConfig)
|
|
1593
|
+
return { ok: false, status: 500, error: "Fleet config not available" };
|
|
1594
|
+
const teams = fleetConfig.teams;
|
|
1595
|
+
if (!teams || !Object.hasOwn(teams, args.name))
|
|
1596
|
+
return { ok: false, status: 404, error: `Team not found: ${args.name}` };
|
|
1597
|
+
const team = teams[args.name];
|
|
1598
|
+
const invalid = (args.add ?? []).filter(m => !Object.hasOwn(fleetConfig.instances, m));
|
|
1599
|
+
if (invalid.length)
|
|
1600
|
+
return { ok: false, status: 400, error: `Invalid instance names: ${invalid.join(", ")}` };
|
|
1601
|
+
let members = [...team.members];
|
|
1602
|
+
if (args.add?.length)
|
|
1603
|
+
members = [...new Set([...members, ...args.add])];
|
|
1604
|
+
if (args.remove?.length)
|
|
1605
|
+
members = members.filter(m => !args.remove.includes(m));
|
|
1606
|
+
team.members = members;
|
|
1607
|
+
if (args.description !== undefined) {
|
|
1608
|
+
if (args.description.trim())
|
|
1609
|
+
team.description = args.description.trim();
|
|
1610
|
+
else
|
|
1611
|
+
delete team.description;
|
|
1612
|
+
}
|
|
1613
|
+
return { ok: true, result: { name: args.name, members: team.members, description: team.description ?? null } };
|
|
1614
|
+
}
|
|
1615
|
+
/**
|
|
1616
|
+
* #1689 review: the update and its save as one — a save that fails (an I/O error) puts the team back as it was, so the
|
|
1617
|
+
* live fleet never differs from fleet.yaml; that is a 500 (the input was fine), not a 400.
|
|
1618
|
+
*/
|
|
1619
|
+
export function commitTeamUpdate(fleetConfig, args, save) {
|
|
1620
|
+
const teams = fleetConfig?.teams;
|
|
1621
|
+
const team = teams && Object.hasOwn(teams, args.name) ? teams[args.name] : null;
|
|
1622
|
+
const before = team ? { members: [...team.members], hadDescription: Object.hasOwn(team, "description"), description: team.description } : null;
|
|
1623
|
+
const r = applyTeamUpdate(fleetConfig, args);
|
|
1624
|
+
if (!r.ok)
|
|
1625
|
+
return r;
|
|
1626
|
+
try {
|
|
1627
|
+
save();
|
|
1628
|
+
}
|
|
1629
|
+
catch (err) {
|
|
1630
|
+
if (team && before) {
|
|
1631
|
+
team.members = before.members;
|
|
1632
|
+
if (before.hadDescription)
|
|
1633
|
+
team.description = before.description;
|
|
1634
|
+
else
|
|
1635
|
+
delete team.description;
|
|
1636
|
+
}
|
|
1637
|
+
return { ok: false, status: 500, error: `Saving fleet.yaml failed; the team is unchanged: ${err?.message ?? String(err)}` };
|
|
1638
|
+
}
|
|
1639
|
+
return r;
|
|
1640
|
+
}
|
|
1461
1641
|
const updateTeam = (ctx, rawArgs, respond) => {
|
|
1462
1642
|
const v = validateArgs(UpdateTeamArgs, rawArgs, "update_team");
|
|
1463
1643
|
if (!v.ok) {
|
|
1464
1644
|
respond(null, v.error);
|
|
1465
1645
|
return;
|
|
1466
1646
|
}
|
|
1467
|
-
const
|
|
1468
|
-
if (!
|
|
1469
|
-
respond(null,
|
|
1647
|
+
const r = commitTeamUpdate(ctx.fleetConfig, v.data, () => ctx.saveFleetConfig());
|
|
1648
|
+
if (!r.ok) {
|
|
1649
|
+
respond(null, r.error);
|
|
1470
1650
|
return;
|
|
1471
1651
|
}
|
|
1472
|
-
|
|
1473
|
-
if (add?.length) {
|
|
1474
|
-
const invalid = add.filter(m => !ctx.fleetConfig.instances[m]);
|
|
1475
|
-
if (invalid.length) {
|
|
1476
|
-
respond(null, `Invalid instance names: ${invalid.join(", ")}`);
|
|
1477
|
-
return;
|
|
1478
|
-
}
|
|
1479
|
-
team.members = [...new Set([...team.members, ...add])];
|
|
1480
|
-
}
|
|
1481
|
-
if (remove?.length) {
|
|
1482
|
-
team.members = team.members.filter(m => !remove.includes(m));
|
|
1483
|
-
}
|
|
1484
|
-
ctx.saveFleetConfig();
|
|
1485
|
-
respond({ name, members: team.members });
|
|
1652
|
+
respond(r.result);
|
|
1486
1653
|
};
|
|
1487
1654
|
// ── Fleet Templates ────────────────────────────────────────────────────
|
|
1488
1655
|
const deployTemplate = async (ctx, rawArgs, respond) => {
|