@poolzin/pool-bot 2026.2.21 → 2026.2.22
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/CHANGELOG.md +17 -0
- package/dist/agents/api-key-rotation.js +47 -0
- package/dist/agents/apply-patch-update.js +19 -9
- package/dist/agents/apply-patch.js +72 -47
- package/dist/agents/bash-tools.exec.js +141 -559
- package/dist/agents/cli-backends.js +49 -6
- package/dist/agents/cli-runner/helpers.js +69 -152
- package/dist/agents/cli-runner.js +70 -19
- package/dist/agents/identity.js +20 -1
- package/dist/agents/image-sanitization.js +9 -0
- package/dist/agents/live-auth-keys.js +123 -26
- package/dist/agents/live-model-filter.js +13 -4
- package/dist/agents/model-catalog.js +40 -9
- package/dist/agents/model-forward-compat.js +60 -23
- package/dist/agents/model-selection.js +134 -41
- package/dist/agents/pi-auth-json.js +2 -2
- package/dist/agents/pi-embedded-helpers/bootstrap.js +65 -15
- package/dist/agents/pi-embedded-helpers/errors.js +140 -15
- package/dist/agents/pi-embedded-helpers/images.js +22 -12
- package/dist/agents/pi-embedded-helpers.js +2 -2
- package/dist/agents/pi-embedded-runner/abort.js +10 -3
- package/dist/agents/pi-embedded-runner/compact.js +230 -32
- package/dist/agents/pi-embedded-runner/extra-params.js +203 -12
- package/dist/agents/pi-embedded-runner/google.js +109 -19
- package/dist/agents/pi-embedded-runner/history.js +35 -17
- package/dist/agents/pi-embedded-runner/run/attempt.js +386 -95
- package/dist/agents/pi-embedded-runner/run/images.js +81 -55
- package/dist/agents/pi-embedded-runner/run/payloads.js +89 -39
- package/dist/agents/pi-embedded-runner/run.js +193 -25
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +2 -2
- package/dist/agents/pi-embedded-runner/runs.js +17 -8
- package/dist/agents/pi-embedded-runner/tool-result-context-guard.js +262 -0
- package/dist/agents/pi-embedded-runner.js +1 -1
- package/dist/agents/pi-embedded-subscribe.handlers.tools.js +180 -10
- package/dist/agents/pi-embedded-subscribe.js +37 -0
- package/dist/agents/pi-embedded-subscribe.tools.js +127 -30
- package/dist/agents/pi-model-discovery.js +9 -2
- package/dist/agents/pi-tool-definition-adapter.js +60 -8
- package/dist/agents/pi-tools.before-tool-call.js +1 -1
- package/dist/agents/pi-tools.js +113 -94
- package/dist/agents/pi-tools.read.js +337 -38
- package/dist/agents/poolbot-tools.js +14 -5
- package/dist/agents/sandbox/docker.js +10 -5
- package/dist/agents/sandbox/registry.js +96 -46
- package/dist/agents/sandbox/sanitize-env-vars.js +82 -0
- package/dist/agents/sandbox-paths.js +43 -10
- package/dist/agents/session-tool-result-guard-wrapper.js +23 -11
- package/dist/agents/session-tool-result-guard.js +39 -39
- package/dist/agents/session-transcript-repair.js +36 -33
- package/dist/agents/session-write-lock.js +62 -44
- package/dist/agents/skills/frontmatter.js +49 -88
- package/dist/agents/skills/workspace.js +335 -28
- package/dist/agents/subagent-announce.js +508 -174
- package/dist/agents/subagent-registry.js +45 -4
- package/dist/agents/subagent-spawn.js +16 -33
- package/dist/agents/system-prompt-report.js +27 -10
- package/dist/agents/system-prompt.js +26 -32
- package/dist/agents/tool-call-id.js +69 -17
- package/dist/agents/tool-display-common.js +1 -1
- package/dist/agents/tool-images.js +64 -31
- package/dist/agents/tools/canvas-tool.js +17 -11
- package/dist/agents/tools/common.js +37 -19
- package/dist/agents/tools/cron-tool.js +40 -38
- package/dist/agents/tools/gateway.js +70 -2
- package/dist/agents/tools/message-tool.js +181 -40
- package/dist/agents/tools/nodes-tool.js +128 -36
- package/dist/agents/tools/nodes-utils.js +12 -38
- package/dist/agents/tools/session-status-tool.js +24 -71
- package/dist/agents/tools/sessions-helpers.js +38 -210
- package/dist/agents/tools/sessions-spawn-tool.js +28 -198
- package/dist/agents/tools/telegram-actions.js +58 -7
- package/dist/agents/tools/web-fetch-utils.js +112 -7
- package/dist/agents/tools/web-fetch.js +279 -175
- package/dist/agents/tools/web-shared.js +71 -8
- package/dist/agents/usage.js +25 -16
- package/dist/auto-reply/commands-registry.data.js +85 -11
- package/dist/auto-reply/dispatch.js +40 -21
- package/dist/auto-reply/reply/abort.js +102 -33
- package/dist/auto-reply/reply/commands-core.js +82 -33
- package/dist/auto-reply/reply/commands-export-session.js +1 -1
- package/dist/auto-reply/reply/commands-info.js +41 -12
- package/dist/auto-reply/reply/commands-subagents.js +352 -100
- package/dist/auto-reply/reply/commands-system-prompt.js +2 -2
- package/dist/auto-reply/reply/dispatch-from-config.js +100 -29
- package/dist/auto-reply/reply/elevated-unavailable.js +1 -1
- package/dist/auto-reply/reply/inbound-meta.js +12 -1
- package/dist/auto-reply/reply/mentions.js +18 -11
- package/dist/auto-reply/reply/normalize-reply.js +17 -8
- package/dist/auto-reply/reply/reply-dispatcher.js +62 -10
- package/dist/auto-reply/reply/session.js +102 -21
- package/dist/auto-reply/reply/streaming-directives.js +16 -5
- package/dist/auto-reply/status.js +73 -50
- package/dist/browser/extension-relay.js +3 -3
- package/dist/browser/http-auth.js +1 -1
- package/dist/browser/paths.js +2 -2
- package/dist/build-info.json +3 -3
- package/dist/channels/allowlist-match.js +20 -0
- package/dist/channels/allowlists/resolve-utils.js +65 -2
- package/dist/channels/chat-type.js +8 -4
- package/dist/channels/dock.js +127 -35
- package/dist/channels/draft-stream-loop.js +6 -2
- package/dist/channels/plugins/actions/telegram.js +42 -18
- package/dist/channels/plugins/allowlist-match.js +1 -1
- package/dist/channels/plugins/group-mentions.js +51 -41
- package/dist/channels/plugins/message-action-names.js +2 -0
- package/dist/channels/plugins/message-actions.js +24 -5
- package/dist/channels/plugins/normalize/discord.js +26 -4
- package/dist/channels/plugins/normalize/signal.js +35 -22
- package/dist/channels/plugins/onboarding/helpers.js +8 -26
- package/dist/channels/plugins/outbound/imessage.js +15 -14
- package/dist/channels/registry.js +20 -7
- package/dist/cli/acp-cli.js +7 -5
- package/dist/cli/browser-cli-extension.js +25 -12
- package/dist/cli/browser-cli-state.cookies-storage.js +25 -6
- package/dist/cli/browser-cli-state.js +101 -145
- package/dist/cli/command-options.js +28 -0
- package/dist/cli/completion-cli.js +6 -6
- package/dist/cli/cron-cli/register.cron-add.js +25 -1
- package/dist/cli/cron-cli/register.cron-edit.js +44 -0
- package/dist/cli/cron-cli/shared.js +7 -1
- package/dist/cli/daemon-cli/lifecycle-core.js +23 -21
- package/dist/cli/daemon-cli/lifecycle.js +23 -247
- package/dist/cli/daemon-cli/register-service-commands.js +25 -4
- package/dist/cli/daemon-cli.js +1 -0
- package/dist/cli/devices-cli.js +33 -20
- package/dist/cli/gateway-cli/register.js +37 -105
- package/dist/cli/gateway-cli/run.js +49 -11
- package/dist/cli/nodes-camera.js +59 -4
- package/dist/cli/nodes-cli/register.camera.js +27 -24
- package/dist/cli/nodes-cli/rpc.js +21 -38
- package/dist/cli/qr-cli.js +2 -2
- package/dist/cli/skills-cli.format.js +2 -2
- package/dist/cli/update-cli/progress.js +2 -2
- package/dist/cli/update-cli/restart-helper.js +28 -7
- package/dist/cli/update-cli/shared.js +7 -7
- package/dist/cli/update-cli/status.js +1 -1
- package/dist/cli/update-cli/update-command.js +14 -8
- package/dist/cli/update-cli/wizard.js +2 -2
- package/dist/cli/update-cli.js +21 -1027
- package/dist/commands/auth-choice.apply.anthropic.js +10 -2
- package/dist/commands/channels/add-mutators.js +3 -35
- package/dist/commands/channels/add.js +39 -51
- package/dist/commands/config-validation.js +1 -1
- package/dist/commands/configure.gateway-auth.js +52 -15
- package/dist/commands/configure.gateway.js +84 -40
- package/dist/commands/doctor-completion.js +3 -3
- package/dist/commands/doctor-config-flow.js +536 -16
- package/dist/commands/doctor-gateway-services.js +103 -79
- package/dist/commands/doctor-memory-search.js +9 -9
- package/dist/commands/doctor-platform-notes.js +57 -30
- package/dist/commands/doctor-prompter.js +26 -15
- package/dist/commands/doctor-session-locks.js +1 -1
- package/dist/commands/doctor.js +21 -9
- package/dist/commands/model-picker.js +120 -95
- package/dist/commands/models/set.js +2 -21
- package/dist/commands/models/shared.js +65 -37
- package/dist/commands/onboard-helpers.js +81 -39
- package/dist/commands/openai-codex-oauth.js +1 -1
- package/dist/commands/sessions.js +52 -53
- package/dist/commands/status.summary.js +52 -34
- package/dist/commands/test-wizard-helpers.js +2 -2
- package/dist/config/defaults.js +79 -42
- package/dist/config/group-policy.js +50 -18
- package/dist/config/includes.js +37 -10
- package/dist/config/schema.help.js +5 -4
- package/dist/config/schema.hints.js +2 -2
- package/dist/config/schema.labels.js +1 -0
- package/dist/config/sessions/group.js +12 -11
- package/dist/config/sessions/paths.js +137 -11
- package/dist/config/sessions/store.js +185 -65
- package/dist/config/sessions/types.js +15 -1
- package/dist/config/sessions.js +1 -0
- package/dist/config/telegram-custom-commands.js +3 -2
- package/dist/config/types.js +2 -0
- package/dist/config/zod-schema.agent-defaults.js +6 -27
- package/dist/config/zod-schema.agent-runtime.js +171 -79
- package/dist/config/zod-schema.providers-core.js +138 -65
- package/dist/config/zod-schema.session.js +49 -22
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -1
- package/dist/cron/isolated-agent/run.js +224 -57
- package/dist/cron/normalize.js +48 -45
- package/dist/cron/run-log.js +14 -0
- package/dist/cron/service/jobs.js +190 -28
- package/dist/cron/service/normalize.js +29 -11
- package/dist/cron/service/store.js +30 -44
- package/dist/cron/service/timer.js +182 -96
- package/dist/cron/service.js +3 -0
- package/dist/cron/stagger.js +37 -0
- package/dist/daemon/inspect.js +132 -92
- package/dist/daemon/runtime-paths.js +25 -4
- package/dist/daemon/service-audit.js +47 -16
- package/dist/discord/accounts.js +23 -20
- package/dist/discord/monitor/agent-components.js +1115 -219
- package/dist/discord/monitor/allow-list.js +114 -34
- package/dist/discord/monitor/listeners.js +204 -97
- package/dist/discord/monitor/message-handler.js +21 -10
- package/dist/discord/monitor/message-handler.preflight.js +195 -101
- package/dist/discord/monitor/message-handler.process.js +384 -123
- package/dist/discord/monitor/message-utils.js +86 -23
- package/dist/discord/monitor/native-command.js +77 -57
- package/dist/discord/monitor/provider.js +122 -117
- package/dist/discord/monitor/reply-context.js +20 -16
- package/dist/discord/monitor/reply-delivery.js +40 -8
- package/dist/discord/monitor/rest-fetch.js +22 -0
- package/dist/discord/monitor/threading.js +117 -24
- package/dist/discord/send.js +2 -1
- package/dist/discord/send.outbound.js +124 -11
- package/dist/discord/send.shared.js +112 -72
- package/dist/discord/voice-message.js +3 -3
- package/dist/gateway/auth.js +119 -44
- package/dist/gateway/call.js +76 -34
- package/dist/gateway/channel-health-monitor.js +57 -50
- package/dist/gateway/client.js +63 -29
- package/dist/gateway/control-ui-contract.js +1 -1
- package/dist/gateway/gateway-config-prompts.shared.js +2 -2
- package/dist/gateway/net.js +109 -1
- package/dist/gateway/protocol/index.js +5 -8
- package/dist/gateway/protocol/schema/agent.js +19 -1
- package/dist/gateway/protocol/schema/channels.js +21 -0
- package/dist/gateway/protocol/schema/cron.js +43 -30
- package/dist/gateway/protocol/schema/protocol-schemas.js +6 -11
- package/dist/gateway/protocol/schema/sessions.js +5 -1
- package/dist/gateway/protocol/schema.js +0 -1
- package/dist/gateway/server/presence-events.js +12 -0
- package/dist/gateway/server/ws-connection/message-handler.js +203 -212
- package/dist/gateway/server/ws-connection.js +58 -21
- package/dist/gateway/server-broadcast.js +18 -13
- package/dist/gateway/server-cron.js +177 -10
- package/dist/gateway/server-methods/agent-job.js +131 -38
- package/dist/gateway/server-methods/send.js +60 -14
- package/dist/gateway/server-methods/sessions.js +160 -96
- package/dist/gateway/server-methods/system.js +5 -7
- package/dist/gateway/server-methods-list.js +8 -0
- package/dist/gateway/server-methods.js +24 -8
- package/dist/gateway/server-node-events.js +278 -68
- package/dist/gateway/session-utils.fs.js +316 -75
- package/dist/gateway/session-utils.js +224 -70
- package/dist/gateway/sessions-patch.js +63 -20
- package/dist/gateway/test-temp-config.js +1 -1
- package/dist/gateway/tools-invoke-http.js +118 -70
- package/dist/gateway/ws-log.js +135 -107
- package/dist/hooks/frontmatter.js +36 -82
- package/dist/hooks/install.js +149 -139
- package/dist/hooks/internal-hooks.js +29 -4
- package/dist/hooks/plugin-hooks.js +2 -1
- package/dist/imessage/monitor/deliver.js +10 -4
- package/dist/imessage/monitor/monitor-provider.js +138 -375
- package/dist/imessage/monitor/runtime.js +4 -8
- package/dist/imessage/send.js +65 -19
- package/dist/infra/exec-approvals-allowlist.js +7 -0
- package/dist/infra/exec-approvals.js +35 -920
- package/dist/infra/exec-safe-bin-trust.js +64 -0
- package/dist/infra/heartbeat-runner.js +207 -134
- package/dist/infra/heartbeat-wake.js +183 -22
- package/dist/infra/install-source-utils.js +47 -0
- package/dist/infra/net/ssrf.js +170 -36
- package/dist/infra/outbound/deliver.js +224 -58
- package/dist/infra/outbound/message-action-spec.js +12 -5
- package/dist/infra/outbound/outbound-session.js +27 -25
- package/dist/infra/poolbot-root.js +32 -22
- package/dist/infra/ports.js +14 -11
- package/dist/infra/skills-remote.js +48 -37
- package/dist/infra/system-events.js +25 -11
- package/dist/infra/system-presence.js +26 -33
- package/dist/infra/tmp-poolbot-dir.js +81 -2
- package/dist/infra/wsl.js +37 -1
- package/dist/line/bot-message-context.js +163 -191
- package/dist/logging/subsystem.js +59 -22
- package/dist/markdown/ir.js +124 -50
- package/dist/media/store.js +1 -1
- package/dist/media-understanding/runner.entries.js +42 -25
- package/dist/media-understanding/runner.js +53 -488
- package/dist/memory/embeddings-gemini.js +53 -38
- package/dist/memory/manager-embedding-ops.js +48 -69
- package/dist/pairing/pairing-store.js +178 -119
- package/dist/plugin-sdk/index.js +34 -6
- package/dist/plugins/hooks.js +135 -14
- package/dist/plugins/install.js +190 -152
- package/dist/polls.js +11 -0
- package/dist/routing/resolve-route.js +190 -56
- package/dist/routing/session-key.js +38 -22
- package/dist/runtime.js +35 -9
- package/dist/security/audit-channel.js +1 -1
- package/dist/sessions/session-key-utils.js +29 -11
- package/dist/shared/frontmatter.js +5 -5
- package/dist/shared/node-list-types.js +1 -0
- package/dist/shared/string-normalization.js +15 -0
- package/dist/signal/monitor/event-handler.js +68 -36
- package/dist/signal/send.js +29 -37
- package/dist/slack/monitor/allow-list.js +10 -11
- package/dist/slack/monitor/commands.js +14 -3
- package/dist/slack/monitor/events/interactions.js +4 -4
- package/dist/slack/monitor/media.js +224 -16
- package/dist/slack/monitor/message-handler/dispatch.js +247 -13
- package/dist/slack/monitor/message-handler/prepare.js +128 -45
- package/dist/slack/monitor/slash.js +357 -144
- package/dist/slack/streaming.js +77 -0
- package/dist/telegram/accounts.js +40 -13
- package/dist/telegram/allowed-updates.js +3 -0
- package/dist/telegram/bot/delivery.js +129 -66
- package/dist/telegram/bot/helpers.js +136 -122
- package/dist/telegram/bot-handlers.js +600 -339
- package/dist/telegram/bot-message-context.js +115 -73
- package/dist/telegram/bot-message-dispatch.js +235 -104
- package/dist/telegram/bot-native-command-menu.js +3 -1
- package/dist/telegram/bot-native-commands.js +213 -193
- package/dist/telegram/bot.js +24 -132
- package/dist/telegram/draft-stream.js +84 -75
- package/dist/telegram/format.js +150 -6
- package/dist/telegram/send.js +415 -255
- package/dist/telegram/targets.js +21 -2
- package/dist/telegram/update-offset-store.js +19 -3
- package/dist/terminal/restore.js +5 -2
- package/dist/test-utils/fetch-mock.js +5 -0
- package/dist/version.js +18 -5
- package/dist/web/auto-reply/monitor/broadcast.js +7 -3
- package/dist/web/auto-reply/monitor/on-message.js +6 -3
- package/dist/web/inbound/media.js +34 -8
- package/dist/web/inbound/monitor.js +34 -17
- package/dist/web/inbound/send-api.js +18 -17
- package/dist/web/outbound.js +12 -5
- package/dist/wizard/clack-prompter.js +40 -7
- package/extensions/bluebubbles/package.json +1 -1
- package/extensions/copilot-proxy/package.json +1 -1
- package/extensions/diagnostics-otel/package.json +1 -1
- package/extensions/discord/package.json +1 -1
- package/extensions/feishu/package.json +1 -1
- package/extensions/google-antigravity-auth/package.json +1 -1
- package/extensions/google-gemini-cli-auth/package.json +1 -1
- package/extensions/googlechat/package.json +1 -1
- package/extensions/imessage/package.json +1 -1
- package/extensions/irc/package.json +1 -1
- package/extensions/line/package.json +1 -1
- package/extensions/llm-task/package.json +1 -1
- package/extensions/lobster/package.json +1 -1
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/mattermost/package.json +1 -1
- package/extensions/memory-core/package.json +1 -1
- package/extensions/memory-lancedb/package.json +1 -1
- package/extensions/minimax-portal-auth/package.json +1 -1
- package/extensions/msteams/CHANGELOG.md +5 -0
- package/extensions/msteams/package.json +1 -1
- package/extensions/nextcloud-talk/package.json +1 -1
- package/extensions/nostr/CHANGELOG.md +5 -0
- package/extensions/nostr/package.json +1 -1
- package/extensions/open-prose/package.json +1 -1
- package/extensions/openai-codex-auth/package.json +1 -1
- package/extensions/signal/package.json +1 -1
- package/extensions/slack/package.json +1 -1
- package/extensions/telegram/package.json +1 -1
- package/extensions/tlon/package.json +1 -1
- package/extensions/twitch/CHANGELOG.md +5 -0
- package/extensions/twitch/package.json +1 -1
- package/extensions/voice-call/CHANGELOG.md +5 -0
- package/extensions/voice-call/package.json +1 -1
- package/extensions/whatsapp/package.json +1 -1
- package/extensions/zalo/CHANGELOG.md +5 -0
- package/extensions/zalo/package.json +1 -1
- package/extensions/zalouser/CHANGELOG.md +5 -0
- package/extensions/zalouser/package.json +1 -1
- package/package.json +1 -1
- package/skills/apple-reminders/SKILL.md +100 -49
- package/skills/coding-agent/SKILL.md +34 -28
- package/skills/github/SKILL.md +131 -16
- package/skills/imsg/SKILL.md +112 -15
- package/skills/openhue/SKILL.md +101 -19
- package/skills/tmux/SKILL.md +111 -79
- package/skills/weather/SKILL.md +88 -25
|
@@ -1,58 +1,134 @@
|
|
|
1
1
|
import crypto from "node:crypto";
|
|
2
|
-
import { abortEmbeddedPiRun } from "../../agents/pi-embedded.js";
|
|
3
2
|
import { AGENT_LANE_SUBAGENT } from "../../agents/lanes.js";
|
|
4
|
-
import {
|
|
3
|
+
import { abortEmbeddedPiRun } from "../../agents/pi-embedded.js";
|
|
4
|
+
import { clearSubagentRunSteerRestart, listSubagentRunsForRequester, markSubagentRunTerminated, markSubagentRunForSteerRestart, replaceSubagentRunAfterSteer, } from "../../agents/subagent-registry.js";
|
|
5
|
+
import { spawnSubagentDirect } from "../../agents/subagent-spawn.js";
|
|
5
6
|
import { extractAssistantText, resolveInternalSessionKey, resolveMainSessionAlias, sanitizeTextContent, stripToolMessages, } from "../../agents/tools/sessions-helpers.js";
|
|
6
|
-
import { loadSessionStore, resolveStorePath, updateSessionStore } from "../../config/sessions.js";
|
|
7
|
+
import { loadSessionStore, resolveStorePath, updateSessionStore, } from "../../config/sessions.js";
|
|
7
8
|
import { callGateway } from "../../gateway/call.js";
|
|
8
9
|
import { logVerbose } from "../../globals.js";
|
|
10
|
+
import { formatTimeAgo } from "../../infra/format-time/format-relative.js";
|
|
9
11
|
import { parseAgentSessionKey } from "../../routing/session-key.js";
|
|
12
|
+
import { extractTextFromChatContent } from "../../shared/chat-content.js";
|
|
13
|
+
import { formatDurationCompact, formatTokenUsageDisplay, truncateLine, } from "../../shared/subagents-format.js";
|
|
10
14
|
import { INTERNAL_MESSAGE_CHANNEL } from "../../utils/message-channel.js";
|
|
11
|
-
import { formatAgeShort, formatDurationShort, formatRunLabel, formatRunStatus, sortSubagentRuns, } from "./subagents-utils.js";
|
|
12
15
|
import { stopSubagentsForRequester } from "./abort.js";
|
|
13
16
|
import { clearSessionQueues } from "./queue.js";
|
|
17
|
+
import { formatRunLabel, formatRunStatus, sortSubagentRuns } from "./subagents-utils.js";
|
|
14
18
|
const COMMAND = "/subagents";
|
|
15
|
-
const
|
|
19
|
+
const COMMAND_KILL = "/kill";
|
|
20
|
+
const COMMAND_STEER = "/steer";
|
|
21
|
+
const COMMAND_TELL = "/tell";
|
|
22
|
+
const ACTIONS = new Set(["list", "kill", "log", "send", "steer", "info", "spawn", "help"]);
|
|
23
|
+
const RECENT_WINDOW_MINUTES = 30;
|
|
24
|
+
const SUBAGENT_TASK_PREVIEW_MAX = 110;
|
|
25
|
+
const STEER_ABORT_SETTLE_TIMEOUT_MS = 5_000;
|
|
26
|
+
function compactLine(value) {
|
|
27
|
+
return value.replace(/\s+/g, " ").trim();
|
|
28
|
+
}
|
|
29
|
+
function formatTaskPreview(value) {
|
|
30
|
+
return truncateLine(compactLine(value), SUBAGENT_TASK_PREVIEW_MAX);
|
|
31
|
+
}
|
|
32
|
+
function resolveModelDisplay(entry, fallbackModel) {
|
|
33
|
+
const model = typeof entry?.model === "string" ? entry.model.trim() : "";
|
|
34
|
+
const provider = typeof entry?.modelProvider === "string" ? entry.modelProvider.trim() : "";
|
|
35
|
+
let combined = model.includes("/") ? model : model && provider ? `${provider}/${model}` : model;
|
|
36
|
+
if (!combined) {
|
|
37
|
+
// Fall back to override fields which are populated at spawn time,
|
|
38
|
+
// before the first run completes and writes model/modelProvider.
|
|
39
|
+
const overrideModel = typeof entry?.modelOverride === "string" ? entry.modelOverride.trim() : "";
|
|
40
|
+
const overrideProvider = typeof entry?.providerOverride === "string" ? entry.providerOverride.trim() : "";
|
|
41
|
+
combined = overrideModel.includes("/")
|
|
42
|
+
? overrideModel
|
|
43
|
+
: overrideModel && overrideProvider
|
|
44
|
+
? `${overrideProvider}/${overrideModel}`
|
|
45
|
+
: overrideModel;
|
|
46
|
+
}
|
|
47
|
+
if (!combined) {
|
|
48
|
+
combined = fallbackModel?.trim() || "";
|
|
49
|
+
}
|
|
50
|
+
if (!combined) {
|
|
51
|
+
return "model n/a";
|
|
52
|
+
}
|
|
53
|
+
const slash = combined.lastIndexOf("/");
|
|
54
|
+
if (slash >= 0 && slash < combined.length - 1) {
|
|
55
|
+
return combined.slice(slash + 1);
|
|
56
|
+
}
|
|
57
|
+
return combined;
|
|
58
|
+
}
|
|
59
|
+
function resolveDisplayStatus(entry) {
|
|
60
|
+
const status = formatRunStatus(entry);
|
|
61
|
+
return status === "error" ? "failed" : status;
|
|
62
|
+
}
|
|
16
63
|
function formatTimestamp(valueMs) {
|
|
17
|
-
if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0)
|
|
64
|
+
if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) {
|
|
18
65
|
return "n/a";
|
|
66
|
+
}
|
|
19
67
|
return new Date(valueMs).toISOString();
|
|
20
68
|
}
|
|
21
69
|
function formatTimestampWithAge(valueMs) {
|
|
22
|
-
if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0)
|
|
70
|
+
if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) {
|
|
23
71
|
return "n/a";
|
|
24
|
-
|
|
72
|
+
}
|
|
73
|
+
return `${formatTimestamp(valueMs)} (${formatTimeAgo(Date.now() - valueMs, { fallback: "n/a" })})`;
|
|
25
74
|
}
|
|
26
|
-
function resolveRequesterSessionKey(params) {
|
|
27
|
-
const
|
|
28
|
-
|
|
75
|
+
function resolveRequesterSessionKey(params, opts) {
|
|
76
|
+
const commandTarget = params.ctx.CommandTargetSessionKey?.trim();
|
|
77
|
+
const commandSession = params.sessionKey?.trim();
|
|
78
|
+
const raw = opts?.preferCommandTarget
|
|
79
|
+
? commandTarget || commandSession
|
|
80
|
+
: commandSession || commandTarget;
|
|
81
|
+
if (!raw) {
|
|
29
82
|
return undefined;
|
|
83
|
+
}
|
|
30
84
|
const { mainKey, alias } = resolveMainSessionAlias(params.cfg);
|
|
31
85
|
return resolveInternalSessionKey({ key: raw, alias, mainKey });
|
|
32
86
|
}
|
|
33
87
|
function resolveSubagentTarget(runs, token) {
|
|
34
88
|
const trimmed = token?.trim();
|
|
35
|
-
if (!trimmed)
|
|
89
|
+
if (!trimmed) {
|
|
36
90
|
return { error: "Missing subagent id." };
|
|
91
|
+
}
|
|
37
92
|
if (trimmed === "last") {
|
|
38
93
|
const sorted = sortSubagentRuns(runs);
|
|
39
94
|
return { entry: sorted[0] };
|
|
40
95
|
}
|
|
41
96
|
const sorted = sortSubagentRuns(runs);
|
|
97
|
+
const recentCutoff = Date.now() - RECENT_WINDOW_MINUTES * 60_000;
|
|
98
|
+
const numericOrder = [
|
|
99
|
+
...sorted.filter((entry) => !entry.endedAt),
|
|
100
|
+
...sorted.filter((entry) => !!entry.endedAt && (entry.endedAt ?? 0) >= recentCutoff),
|
|
101
|
+
];
|
|
42
102
|
if (/^\d+$/.test(trimmed)) {
|
|
43
103
|
const idx = Number.parseInt(trimmed, 10);
|
|
44
|
-
if (!Number.isFinite(idx) || idx <= 0 || idx >
|
|
104
|
+
if (!Number.isFinite(idx) || idx <= 0 || idx > numericOrder.length) {
|
|
45
105
|
return { error: `Invalid subagent index: ${trimmed}` };
|
|
46
106
|
}
|
|
47
|
-
return { entry:
|
|
107
|
+
return { entry: numericOrder[idx - 1] };
|
|
48
108
|
}
|
|
49
109
|
if (trimmed.includes(":")) {
|
|
50
110
|
const match = runs.find((entry) => entry.childSessionKey === trimmed);
|
|
51
111
|
return match ? { entry: match } : { error: `Unknown subagent session: ${trimmed}` };
|
|
52
112
|
}
|
|
113
|
+
const lowered = trimmed.toLowerCase();
|
|
114
|
+
const byLabel = runs.filter((entry) => formatRunLabel(entry).toLowerCase() === lowered);
|
|
115
|
+
if (byLabel.length === 1) {
|
|
116
|
+
return { entry: byLabel[0] };
|
|
117
|
+
}
|
|
118
|
+
if (byLabel.length > 1) {
|
|
119
|
+
return { error: `Ambiguous subagent label: ${trimmed}` };
|
|
120
|
+
}
|
|
121
|
+
const byLabelPrefix = runs.filter((entry) => formatRunLabel(entry).toLowerCase().startsWith(lowered));
|
|
122
|
+
if (byLabelPrefix.length === 1) {
|
|
123
|
+
return { entry: byLabelPrefix[0] };
|
|
124
|
+
}
|
|
125
|
+
if (byLabelPrefix.length > 1) {
|
|
126
|
+
return { error: `Ambiguous subagent label prefix: ${trimmed}` };
|
|
127
|
+
}
|
|
53
128
|
const byRunId = runs.filter((entry) => entry.runId.startsWith(trimmed));
|
|
54
|
-
if (byRunId.length === 1)
|
|
129
|
+
if (byRunId.length === 1) {
|
|
55
130
|
return { entry: byRunId[0] };
|
|
131
|
+
}
|
|
56
132
|
if (byRunId.length > 1) {
|
|
57
133
|
return { error: `Ambiguous run id prefix: ${trimmed}` };
|
|
58
134
|
}
|
|
@@ -60,81 +136,93 @@ function resolveSubagentTarget(runs, token) {
|
|
|
60
136
|
}
|
|
61
137
|
function buildSubagentsHelp() {
|
|
62
138
|
return [
|
|
63
|
-
"
|
|
139
|
+
"Subagents",
|
|
64
140
|
"Usage:",
|
|
65
141
|
"- /subagents list",
|
|
66
|
-
"- /subagents
|
|
142
|
+
"- /subagents kill <id|#|all>",
|
|
67
143
|
"- /subagents log <id|#> [limit] [tools]",
|
|
68
144
|
"- /subagents info <id|#>",
|
|
69
145
|
"- /subagents send <id|#> <message>",
|
|
146
|
+
"- /subagents steer <id|#> <message>",
|
|
147
|
+
"- /subagents spawn <agentId> <task> [--model <model>] [--thinking <level>]",
|
|
148
|
+
"- /kill <id|#|all>",
|
|
149
|
+
"- /steer <id|#> <message>",
|
|
150
|
+
"- /tell <id|#> <message>",
|
|
70
151
|
"",
|
|
71
|
-
"Ids: use the list index (#), runId prefix, or full session key.",
|
|
152
|
+
"Ids: use the list index (#), runId/session prefix, label, or full session key.",
|
|
72
153
|
].join("\n");
|
|
73
154
|
}
|
|
74
|
-
function normalizeMessageText(text) {
|
|
75
|
-
return text.replace(/\s+/g, " ").trim();
|
|
76
|
-
}
|
|
77
155
|
export function extractMessageText(message) {
|
|
78
156
|
const role = typeof message.role === "string" ? message.role : "";
|
|
79
157
|
const shouldSanitize = role === "assistant";
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
if (!Array.isArray(content))
|
|
86
|
-
return null;
|
|
87
|
-
const chunks = [];
|
|
88
|
-
for (const block of content) {
|
|
89
|
-
if (!block || typeof block !== "object")
|
|
90
|
-
continue;
|
|
91
|
-
if (block.type !== "text")
|
|
92
|
-
continue;
|
|
93
|
-
const text = block.text;
|
|
94
|
-
if (typeof text === "string") {
|
|
95
|
-
const value = shouldSanitize ? sanitizeTextContent(text) : text;
|
|
96
|
-
if (value.trim()) {
|
|
97
|
-
chunks.push(value);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
const joined = normalizeMessageText(chunks.join(" "));
|
|
102
|
-
return joined ? { role, text: joined } : null;
|
|
158
|
+
const text = extractTextFromChatContent(message.content, {
|
|
159
|
+
sanitizeText: shouldSanitize ? sanitizeTextContent : undefined,
|
|
160
|
+
});
|
|
161
|
+
return text ? { role, text } : null;
|
|
103
162
|
}
|
|
104
163
|
function formatLogLines(messages) {
|
|
105
164
|
const lines = [];
|
|
106
165
|
for (const msg of messages) {
|
|
107
166
|
const extracted = extractMessageText(msg);
|
|
108
|
-
if (!extracted)
|
|
167
|
+
if (!extracted) {
|
|
109
168
|
continue;
|
|
169
|
+
}
|
|
110
170
|
const label = extracted.role === "assistant" ? "Assistant" : "User";
|
|
111
171
|
lines.push(`${label}: ${extracted.text}`);
|
|
112
172
|
}
|
|
113
173
|
return lines;
|
|
114
174
|
}
|
|
115
|
-
function loadSubagentSessionEntry(params, childKey) {
|
|
175
|
+
function loadSubagentSessionEntry(params, childKey, storeCache) {
|
|
116
176
|
const parsed = parseAgentSessionKey(childKey);
|
|
117
177
|
const storePath = resolveStorePath(params.cfg.session?.store, { agentId: parsed?.agentId });
|
|
118
|
-
|
|
178
|
+
let store = storeCache?.get(storePath);
|
|
179
|
+
if (!store) {
|
|
180
|
+
store = loadSessionStore(storePath);
|
|
181
|
+
storeCache?.set(storePath, store);
|
|
182
|
+
}
|
|
119
183
|
return { storePath, store, entry: store[childKey] };
|
|
120
184
|
}
|
|
121
185
|
export const handleSubagentsCommand = async (params, allowTextCommands) => {
|
|
122
|
-
if (!allowTextCommands)
|
|
186
|
+
if (!allowTextCommands) {
|
|
123
187
|
return null;
|
|
188
|
+
}
|
|
124
189
|
const normalized = params.command.commandBodyNormalized;
|
|
125
|
-
|
|
190
|
+
const handledPrefix = normalized.startsWith(COMMAND)
|
|
191
|
+
? COMMAND
|
|
192
|
+
: normalized.startsWith(COMMAND_KILL)
|
|
193
|
+
? COMMAND_KILL
|
|
194
|
+
: normalized.startsWith(COMMAND_STEER)
|
|
195
|
+
? COMMAND_STEER
|
|
196
|
+
: normalized.startsWith(COMMAND_TELL)
|
|
197
|
+
? COMMAND_TELL
|
|
198
|
+
: null;
|
|
199
|
+
if (!handledPrefix) {
|
|
126
200
|
return null;
|
|
201
|
+
}
|
|
127
202
|
if (!params.command.isAuthorizedSender) {
|
|
128
|
-
logVerbose(`Ignoring
|
|
203
|
+
logVerbose(`Ignoring ${handledPrefix} from unauthorized sender: ${params.command.senderId || "<unknown>"}`);
|
|
129
204
|
return { shouldContinue: false };
|
|
130
205
|
}
|
|
131
|
-
const rest = normalized.slice(
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
if (
|
|
135
|
-
|
|
206
|
+
const rest = normalized.slice(handledPrefix.length).trim();
|
|
207
|
+
const restTokens = rest.split(/\s+/).filter(Boolean);
|
|
208
|
+
let action = "list";
|
|
209
|
+
if (handledPrefix === COMMAND) {
|
|
210
|
+
const [actionRaw] = restTokens;
|
|
211
|
+
action = actionRaw?.toLowerCase() || "list";
|
|
212
|
+
if (!ACTIONS.has(action)) {
|
|
213
|
+
return { shouldContinue: false, reply: { text: buildSubagentsHelp() } };
|
|
214
|
+
}
|
|
215
|
+
restTokens.splice(0, 1);
|
|
216
|
+
}
|
|
217
|
+
else if (handledPrefix === COMMAND_KILL) {
|
|
218
|
+
action = "kill";
|
|
219
|
+
}
|
|
220
|
+
else {
|
|
221
|
+
action = "steer";
|
|
136
222
|
}
|
|
137
|
-
const requesterKey = resolveRequesterSessionKey(params
|
|
223
|
+
const requesterKey = resolveRequesterSessionKey(params, {
|
|
224
|
+
preferCommandTarget: action === "spawn",
|
|
225
|
+
});
|
|
138
226
|
if (!requesterKey) {
|
|
139
227
|
return { shouldContinue: false, reply: { text: "⚠️ Missing session key." } };
|
|
140
228
|
}
|
|
@@ -143,39 +231,71 @@ export const handleSubagentsCommand = async (params, allowTextCommands) => {
|
|
|
143
231
|
return { shouldContinue: false, reply: { text: buildSubagentsHelp() } };
|
|
144
232
|
}
|
|
145
233
|
if (action === "list") {
|
|
146
|
-
if (runs.length === 0) {
|
|
147
|
-
return { shouldContinue: false, reply: { text: "🧭 Subagents: none for this session." } };
|
|
148
|
-
}
|
|
149
234
|
const sorted = sortSubagentRuns(runs);
|
|
150
|
-
const
|
|
151
|
-
const
|
|
152
|
-
const
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const
|
|
160
|
-
|
|
235
|
+
const now = Date.now();
|
|
236
|
+
const recentCutoff = now - RECENT_WINDOW_MINUTES * 60_000;
|
|
237
|
+
const storeCache = new Map();
|
|
238
|
+
let index = 1;
|
|
239
|
+
const activeLines = sorted
|
|
240
|
+
.filter((entry) => !entry.endedAt)
|
|
241
|
+
.map((entry) => {
|
|
242
|
+
const { entry: sessionEntry } = loadSubagentSessionEntry(params, entry.childSessionKey, storeCache);
|
|
243
|
+
const usageText = formatTokenUsageDisplay(sessionEntry);
|
|
244
|
+
const label = truncateLine(formatRunLabel(entry, { maxLength: 48 }), 48);
|
|
245
|
+
const task = formatTaskPreview(entry.task);
|
|
246
|
+
const runtime = formatDurationCompact(now - (entry.startedAt ?? entry.createdAt));
|
|
247
|
+
const status = resolveDisplayStatus(entry);
|
|
248
|
+
const line = `${index}. ${label} (${resolveModelDisplay(sessionEntry, entry.model)}, ${runtime}${usageText ? `, ${usageText}` : ""}) ${status}${task.toLowerCase() !== label.toLowerCase() ? ` - ${task}` : ""}`;
|
|
249
|
+
index += 1;
|
|
250
|
+
return line;
|
|
251
|
+
});
|
|
252
|
+
const recentLines = sorted
|
|
253
|
+
.filter((entry) => !!entry.endedAt && (entry.endedAt ?? 0) >= recentCutoff)
|
|
254
|
+
.map((entry) => {
|
|
255
|
+
const { entry: sessionEntry } = loadSubagentSessionEntry(params, entry.childSessionKey, storeCache);
|
|
256
|
+
const usageText = formatTokenUsageDisplay(sessionEntry);
|
|
257
|
+
const label = truncateLine(formatRunLabel(entry, { maxLength: 48 }), 48);
|
|
258
|
+
const task = formatTaskPreview(entry.task);
|
|
259
|
+
const runtime = formatDurationCompact((entry.endedAt ?? now) - (entry.startedAt ?? entry.createdAt));
|
|
260
|
+
const status = resolveDisplayStatus(entry);
|
|
261
|
+
const line = `${index}. ${label} (${resolveModelDisplay(sessionEntry, entry.model)}, ${runtime}${usageText ? `, ${usageText}` : ""}) ${status}${task.toLowerCase() !== label.toLowerCase() ? ` - ${task}` : ""}`;
|
|
262
|
+
index += 1;
|
|
263
|
+
return line;
|
|
161
264
|
});
|
|
265
|
+
const lines = ["active subagents:", "-----"];
|
|
266
|
+
if (activeLines.length === 0) {
|
|
267
|
+
lines.push("(none)");
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
lines.push(activeLines.join("\n"));
|
|
271
|
+
}
|
|
272
|
+
lines.push("", `recent subagents (last ${RECENT_WINDOW_MINUTES}m):`, "-----");
|
|
273
|
+
if (recentLines.length === 0) {
|
|
274
|
+
lines.push("(none)");
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
lines.push(recentLines.join("\n"));
|
|
278
|
+
}
|
|
162
279
|
return { shouldContinue: false, reply: { text: lines.join("\n") } };
|
|
163
280
|
}
|
|
164
|
-
if (action === "
|
|
281
|
+
if (action === "kill") {
|
|
165
282
|
const target = restTokens[0];
|
|
166
283
|
if (!target) {
|
|
167
|
-
return {
|
|
284
|
+
return {
|
|
285
|
+
shouldContinue: false,
|
|
286
|
+
reply: {
|
|
287
|
+
text: handledPrefix === COMMAND
|
|
288
|
+
? "Usage: /subagents kill <id|#|all>"
|
|
289
|
+
: "Usage: /kill <id|#|all>",
|
|
290
|
+
},
|
|
291
|
+
};
|
|
168
292
|
}
|
|
169
293
|
if (target === "all" || target === "*") {
|
|
170
|
-
|
|
294
|
+
stopSubagentsForRequester({
|
|
171
295
|
cfg: params.cfg,
|
|
172
296
|
requesterSessionKey: requesterKey,
|
|
173
297
|
});
|
|
174
|
-
|
|
175
|
-
return {
|
|
176
|
-
shouldContinue: false,
|
|
177
|
-
reply: { text: `⚙️ Stopped ${stopped} ${label}.` },
|
|
178
|
-
};
|
|
298
|
+
return { shouldContinue: false };
|
|
179
299
|
}
|
|
180
300
|
const resolved = resolveSubagentTarget(runs, target);
|
|
181
301
|
if (!resolved.entry) {
|
|
@@ -187,7 +307,7 @@ export const handleSubagentsCommand = async (params, allowTextCommands) => {
|
|
|
187
307
|
if (resolved.entry.endedAt) {
|
|
188
308
|
return {
|
|
189
309
|
shouldContinue: false,
|
|
190
|
-
reply: { text:
|
|
310
|
+
reply: { text: `${formatRunLabel(resolved.entry)} is already finished.` },
|
|
191
311
|
};
|
|
192
312
|
}
|
|
193
313
|
const childKey = resolved.entry.childSessionKey;
|
|
@@ -198,7 +318,7 @@ export const handleSubagentsCommand = async (params, allowTextCommands) => {
|
|
|
198
318
|
}
|
|
199
319
|
const cleared = clearSessionQueues([childKey, sessionId]);
|
|
200
320
|
if (cleared.followupCleared > 0 || cleared.laneCleared > 0) {
|
|
201
|
-
logVerbose(`subagents
|
|
321
|
+
logVerbose(`subagents kill: cleared followups=${cleared.followupCleared} lane=${cleared.laneCleared} keys=${cleared.keys.join(",")}`);
|
|
202
322
|
}
|
|
203
323
|
if (entry) {
|
|
204
324
|
entry.abortedLastRun = true;
|
|
@@ -208,10 +328,17 @@ export const handleSubagentsCommand = async (params, allowTextCommands) => {
|
|
|
208
328
|
nextStore[childKey] = entry;
|
|
209
329
|
});
|
|
210
330
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
331
|
+
markSubagentRunTerminated({
|
|
332
|
+
runId: resolved.entry.runId,
|
|
333
|
+
childSessionKey: childKey,
|
|
334
|
+
reason: "killed",
|
|
335
|
+
});
|
|
336
|
+
// Cascade: also stop any sub-sub-agents spawned by this child.
|
|
337
|
+
stopSubagentsForRequester({
|
|
338
|
+
cfg: params.cfg,
|
|
339
|
+
requesterSessionKey: childKey,
|
|
340
|
+
});
|
|
341
|
+
return { shouldContinue: false };
|
|
215
342
|
}
|
|
216
343
|
if (action === "info") {
|
|
217
344
|
const target = restTokens[0];
|
|
@@ -228,14 +355,14 @@ export const handleSubagentsCommand = async (params, allowTextCommands) => {
|
|
|
228
355
|
const run = resolved.entry;
|
|
229
356
|
const { entry: sessionEntry } = loadSubagentSessionEntry(params, run.childSessionKey);
|
|
230
357
|
const runtime = run.startedAt && Number.isFinite(run.startedAt)
|
|
231
|
-
?
|
|
358
|
+
? (formatDurationCompact((run.endedAt ?? Date.now()) - run.startedAt) ?? "n/a")
|
|
232
359
|
: "n/a";
|
|
233
360
|
const outcome = run.outcome
|
|
234
361
|
? `${run.outcome.status}${run.outcome.error ? ` (${run.outcome.error})` : ""}`
|
|
235
362
|
: "n/a";
|
|
236
363
|
const lines = [
|
|
237
364
|
"ℹ️ Subagent info",
|
|
238
|
-
`Status: ${
|
|
365
|
+
`Status: ${resolveDisplayStatus(run)}`,
|
|
239
366
|
`Label: ${formatRunLabel(run)}`,
|
|
240
367
|
`Task: ${run.task}`,
|
|
241
368
|
`Run: ${run.runId}`,
|
|
@@ -268,10 +395,10 @@ export const handleSubagentsCommand = async (params, allowTextCommands) => {
|
|
|
268
395
|
reply: { text: `⚠️ ${resolved.error ?? "Unknown subagent."}` },
|
|
269
396
|
};
|
|
270
397
|
}
|
|
271
|
-
const history =
|
|
398
|
+
const history = await callGateway({
|
|
272
399
|
method: "chat.history",
|
|
273
400
|
params: { sessionKey: resolved.entry.childSessionKey, limit },
|
|
274
|
-
})
|
|
401
|
+
});
|
|
275
402
|
const rawMessages = Array.isArray(history?.messages) ? history.messages : [];
|
|
276
403
|
const filtered = includeTools ? rawMessages : stripToolMessages(rawMessages);
|
|
277
404
|
const lines = formatLogLines(filtered);
|
|
@@ -281,13 +408,20 @@ export const handleSubagentsCommand = async (params, allowTextCommands) => {
|
|
|
281
408
|
}
|
|
282
409
|
return { shouldContinue: false, reply: { text: [header, ...lines].join("\n") } };
|
|
283
410
|
}
|
|
284
|
-
if (action === "send") {
|
|
411
|
+
if (action === "send" || action === "steer") {
|
|
412
|
+
const steerRequested = action === "steer";
|
|
285
413
|
const target = restTokens[0];
|
|
286
414
|
const message = restTokens.slice(1).join(" ").trim();
|
|
287
415
|
if (!target || !message) {
|
|
288
416
|
return {
|
|
289
417
|
shouldContinue: false,
|
|
290
|
-
reply: {
|
|
418
|
+
reply: {
|
|
419
|
+
text: steerRequested
|
|
420
|
+
? handledPrefix === COMMAND
|
|
421
|
+
? "Usage: /subagents steer <id|#> <message>"
|
|
422
|
+
: `Usage: ${handledPrefix} <id|#> <message>`
|
|
423
|
+
: "Usage: /subagents send <id|#> <message>",
|
|
424
|
+
},
|
|
291
425
|
};
|
|
292
426
|
}
|
|
293
427
|
const resolved = resolveSubagentTarget(runs, target);
|
|
@@ -297,34 +431,94 @@ export const handleSubagentsCommand = async (params, allowTextCommands) => {
|
|
|
297
431
|
reply: { text: `⚠️ ${resolved.error ?? "Unknown subagent."}` },
|
|
298
432
|
};
|
|
299
433
|
}
|
|
434
|
+
if (steerRequested && resolved.entry.endedAt) {
|
|
435
|
+
return {
|
|
436
|
+
shouldContinue: false,
|
|
437
|
+
reply: { text: `${formatRunLabel(resolved.entry)} is already finished.` },
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const { entry: targetSessionEntry } = loadSubagentSessionEntry(params, resolved.entry.childSessionKey);
|
|
441
|
+
const targetSessionId = typeof targetSessionEntry?.sessionId === "string" && targetSessionEntry.sessionId.trim()
|
|
442
|
+
? targetSessionEntry.sessionId.trim()
|
|
443
|
+
: undefined;
|
|
444
|
+
if (steerRequested) {
|
|
445
|
+
// Suppress stale announce before interrupting the in-flight run.
|
|
446
|
+
markSubagentRunForSteerRestart(resolved.entry.runId);
|
|
447
|
+
// Force an immediate interruption and make steer the next run.
|
|
448
|
+
if (targetSessionId) {
|
|
449
|
+
abortEmbeddedPiRun(targetSessionId);
|
|
450
|
+
}
|
|
451
|
+
const cleared = clearSessionQueues([resolved.entry.childSessionKey, targetSessionId]);
|
|
452
|
+
if (cleared.followupCleared > 0 || cleared.laneCleared > 0) {
|
|
453
|
+
logVerbose(`subagents steer: cleared followups=${cleared.followupCleared} lane=${cleared.laneCleared} keys=${cleared.keys.join(",")}`);
|
|
454
|
+
}
|
|
455
|
+
// Best effort: wait for the interrupted run to settle so the steer
|
|
456
|
+
// message is appended on the existing conversation state.
|
|
457
|
+
try {
|
|
458
|
+
await callGateway({
|
|
459
|
+
method: "agent.wait",
|
|
460
|
+
params: {
|
|
461
|
+
runId: resolved.entry.runId,
|
|
462
|
+
timeoutMs: STEER_ABORT_SETTLE_TIMEOUT_MS,
|
|
463
|
+
},
|
|
464
|
+
timeoutMs: STEER_ABORT_SETTLE_TIMEOUT_MS + 2_000,
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
catch {
|
|
468
|
+
// Continue even if wait fails; steer should still be attempted.
|
|
469
|
+
}
|
|
470
|
+
}
|
|
300
471
|
const idempotencyKey = crypto.randomUUID();
|
|
301
472
|
let runId = idempotencyKey;
|
|
302
473
|
try {
|
|
303
|
-
const response =
|
|
474
|
+
const response = await callGateway({
|
|
304
475
|
method: "agent",
|
|
305
476
|
params: {
|
|
306
477
|
message,
|
|
307
478
|
sessionKey: resolved.entry.childSessionKey,
|
|
479
|
+
sessionId: targetSessionId,
|
|
308
480
|
idempotencyKey,
|
|
309
481
|
deliver: false,
|
|
310
482
|
channel: INTERNAL_MESSAGE_CHANNEL,
|
|
311
483
|
lane: AGENT_LANE_SUBAGENT,
|
|
484
|
+
timeout: 0,
|
|
312
485
|
},
|
|
313
486
|
timeoutMs: 10_000,
|
|
314
|
-
})
|
|
315
|
-
|
|
316
|
-
|
|
487
|
+
});
|
|
488
|
+
const responseRunId = typeof response?.runId === "string" ? response.runId : undefined;
|
|
489
|
+
if (responseRunId) {
|
|
490
|
+
runId = responseRunId;
|
|
491
|
+
}
|
|
317
492
|
}
|
|
318
493
|
catch (err) {
|
|
494
|
+
if (steerRequested) {
|
|
495
|
+
// Replacement launch failed; restore announce behavior for the
|
|
496
|
+
// original run so completion is not silently suppressed.
|
|
497
|
+
clearSubagentRunSteerRestart(resolved.entry.runId);
|
|
498
|
+
}
|
|
319
499
|
const messageText = err instanceof Error ? err.message : typeof err === "string" ? err : "error";
|
|
320
|
-
return { shouldContinue: false, reply: { text:
|
|
500
|
+
return { shouldContinue: false, reply: { text: `send failed: ${messageText}` } };
|
|
501
|
+
}
|
|
502
|
+
if (steerRequested) {
|
|
503
|
+
replaceSubagentRunAfterSteer({
|
|
504
|
+
previousRunId: resolved.entry.runId,
|
|
505
|
+
nextRunId: runId,
|
|
506
|
+
fallback: resolved.entry,
|
|
507
|
+
runTimeoutSeconds: resolved.entry.runTimeoutSeconds ?? 0,
|
|
508
|
+
});
|
|
509
|
+
return {
|
|
510
|
+
shouldContinue: false,
|
|
511
|
+
reply: {
|
|
512
|
+
text: `steered ${formatRunLabel(resolved.entry)} (run ${runId.slice(0, 8)}).`,
|
|
513
|
+
},
|
|
514
|
+
};
|
|
321
515
|
}
|
|
322
516
|
const waitMs = 30_000;
|
|
323
|
-
const wait =
|
|
517
|
+
const wait = await callGateway({
|
|
324
518
|
method: "agent.wait",
|
|
325
519
|
params: { runId, timeoutMs: waitMs },
|
|
326
520
|
timeoutMs: waitMs + 2000,
|
|
327
|
-
})
|
|
521
|
+
});
|
|
328
522
|
if (wait?.status === "timeout") {
|
|
329
523
|
return {
|
|
330
524
|
shouldContinue: false,
|
|
@@ -332,17 +526,18 @@ export const handleSubagentsCommand = async (params, allowTextCommands) => {
|
|
|
332
526
|
};
|
|
333
527
|
}
|
|
334
528
|
if (wait?.status === "error") {
|
|
529
|
+
const waitError = typeof wait.error === "string" ? wait.error : "unknown error";
|
|
335
530
|
return {
|
|
336
531
|
shouldContinue: false,
|
|
337
532
|
reply: {
|
|
338
|
-
text: `⚠️ Subagent error: ${
|
|
533
|
+
text: `⚠️ Subagent error: ${waitError} (run ${runId.slice(0, 8)}).`,
|
|
339
534
|
},
|
|
340
535
|
};
|
|
341
536
|
}
|
|
342
|
-
const history =
|
|
537
|
+
const history = await callGateway({
|
|
343
538
|
method: "chat.history",
|
|
344
539
|
params: { sessionKey: resolved.entry.childSessionKey, limit: 50 },
|
|
345
|
-
})
|
|
540
|
+
});
|
|
346
541
|
const filtered = stripToolMessages(Array.isArray(history?.messages) ? history.messages : []);
|
|
347
542
|
const last = filtered.length > 0 ? filtered[filtered.length - 1] : undefined;
|
|
348
543
|
const replyText = last ? extractAssistantText(last) : undefined;
|
|
@@ -353,5 +548,62 @@ export const handleSubagentsCommand = async (params, allowTextCommands) => {
|
|
|
353
548
|
},
|
|
354
549
|
};
|
|
355
550
|
}
|
|
551
|
+
if (action === "spawn") {
|
|
552
|
+
const agentId = restTokens[0];
|
|
553
|
+
// Parse remaining tokens: task text with optional --model and --thinking flags.
|
|
554
|
+
const taskParts = [];
|
|
555
|
+
let model;
|
|
556
|
+
let thinking;
|
|
557
|
+
for (let i = 1; i < restTokens.length; i++) {
|
|
558
|
+
if (restTokens[i] === "--model" && i + 1 < restTokens.length) {
|
|
559
|
+
i += 1;
|
|
560
|
+
model = restTokens[i];
|
|
561
|
+
}
|
|
562
|
+
else if (restTokens[i] === "--thinking" && i + 1 < restTokens.length) {
|
|
563
|
+
i += 1;
|
|
564
|
+
thinking = restTokens[i];
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
taskParts.push(restTokens[i]);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
const task = taskParts.join(" ").trim();
|
|
571
|
+
if (!agentId || !task) {
|
|
572
|
+
return {
|
|
573
|
+
shouldContinue: false,
|
|
574
|
+
reply: {
|
|
575
|
+
text: "Usage: /subagents spawn <agentId> <task> [--model <model>] [--thinking <level>]",
|
|
576
|
+
},
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
const commandTo = typeof params.command.to === "string" ? params.command.to.trim() : "";
|
|
580
|
+
const originatingTo = typeof params.ctx.OriginatingTo === "string" ? params.ctx.OriginatingTo.trim() : "";
|
|
581
|
+
const fallbackTo = typeof params.ctx.To === "string" ? params.ctx.To.trim() : "";
|
|
582
|
+
// OriginatingTo reflects the active conversation target and is safer than
|
|
583
|
+
// command.to for cross-surface command dispatch.
|
|
584
|
+
const normalizedTo = originatingTo || commandTo || fallbackTo || undefined;
|
|
585
|
+
const result = await spawnSubagentDirect({ task, agentId, model, thinking, cleanup: "keep", expectsCompletionMessage: true }, {
|
|
586
|
+
agentSessionKey: requesterKey,
|
|
587
|
+
agentChannel: params.ctx.OriginatingChannel ?? params.command.channel,
|
|
588
|
+
agentAccountId: params.ctx.AccountId,
|
|
589
|
+
agentTo: normalizedTo,
|
|
590
|
+
agentThreadId: params.ctx.MessageThreadId,
|
|
591
|
+
agentGroupId: params.sessionEntry?.groupId ?? null,
|
|
592
|
+
agentGroupChannel: params.sessionEntry?.groupChannel ?? null,
|
|
593
|
+
agentGroupSpace: params.sessionEntry?.space ?? null,
|
|
594
|
+
});
|
|
595
|
+
if (result.status === "accepted") {
|
|
596
|
+
return {
|
|
597
|
+
shouldContinue: false,
|
|
598
|
+
reply: {
|
|
599
|
+
text: `Spawned subagent ${agentId} (session ${result.childSessionKey}, run ${result.runId?.slice(0, 8)}).`,
|
|
600
|
+
},
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
return {
|
|
604
|
+
shouldContinue: false,
|
|
605
|
+
reply: { text: `Spawn failed: ${result.error ?? result.status}` },
|
|
606
|
+
};
|
|
607
|
+
}
|
|
356
608
|
return { shouldContinue: false, reply: { text: buildSubagentsHelp() } };
|
|
357
609
|
};
|