@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,97 +1,258 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
import path from "node:path";
|
|
3
1
|
import { resolveQueueSettings } from "../auto-reply/reply/queue.js";
|
|
2
|
+
import { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
|
|
4
3
|
import { loadConfig } from "../config/config.js";
|
|
5
4
|
import { loadSessionStore, resolveAgentIdFromSessionKey, resolveMainSessionKey, resolveStorePath, } from "../config/sessions.js";
|
|
6
5
|
import { callGateway } from "../gateway/call.js";
|
|
7
|
-
import { formatDurationCompact } from "../infra/format-time/format-duration.js";
|
|
8
6
|
import { normalizeMainKey } from "../routing/session-key.js";
|
|
9
7
|
import { defaultRuntime } from "../runtime.js";
|
|
8
|
+
import { extractTextFromChatContent } from "../shared/chat-content.js";
|
|
10
9
|
import { deliveryContextFromSession, mergeDeliveryContext, normalizeDeliveryContext, } from "../utils/delivery-context.js";
|
|
10
|
+
import { isDeliverableMessageChannel } from "../utils/message-channel.js";
|
|
11
|
+
import { buildAnnounceIdFromChildRun, buildAnnounceIdempotencyKey, resolveQueueAnnounceId, } from "./announce-idempotency.js";
|
|
11
12
|
import { isEmbeddedPiRunActive, queueEmbeddedPiMessage, waitForEmbeddedPiRunEnd, } from "./pi-embedded.js";
|
|
12
13
|
import { enqueueAnnounce } from "./subagent-announce-queue.js";
|
|
13
|
-
import {
|
|
14
|
+
import { getSubagentDepthFromSessionStore } from "./subagent-depth.js";
|
|
15
|
+
import { sanitizeTextContent, extractAssistantText } from "./tools/sessions-helpers.js";
|
|
16
|
+
function buildCompletionDeliveryMessage(params) {
|
|
17
|
+
const findingsText = params.findings.trim();
|
|
18
|
+
const hasFindings = findingsText.length > 0 && findingsText !== "(no output)";
|
|
19
|
+
const header = `✅ Subagent ${params.subagentName} finished`;
|
|
20
|
+
if (!hasFindings) {
|
|
21
|
+
return header;
|
|
22
|
+
}
|
|
23
|
+
return `${header}\n\n${findingsText}`;
|
|
24
|
+
}
|
|
25
|
+
function summarizeDeliveryError(error) {
|
|
26
|
+
if (error instanceof Error) {
|
|
27
|
+
return error.message || "error";
|
|
28
|
+
}
|
|
29
|
+
if (typeof error === "string") {
|
|
30
|
+
return error;
|
|
31
|
+
}
|
|
32
|
+
if (error === undefined || error === null) {
|
|
33
|
+
return "unknown error";
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return JSON.stringify(error);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
return "error";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
function extractToolResultText(content) {
|
|
43
|
+
if (typeof content === "string") {
|
|
44
|
+
return sanitizeTextContent(content);
|
|
45
|
+
}
|
|
46
|
+
if (content && typeof content === "object" && !Array.isArray(content)) {
|
|
47
|
+
const obj = content;
|
|
48
|
+
if (typeof obj.text === "string") {
|
|
49
|
+
return sanitizeTextContent(obj.text);
|
|
50
|
+
}
|
|
51
|
+
if (typeof obj.output === "string") {
|
|
52
|
+
return sanitizeTextContent(obj.output);
|
|
53
|
+
}
|
|
54
|
+
if (typeof obj.content === "string") {
|
|
55
|
+
return sanitizeTextContent(obj.content);
|
|
56
|
+
}
|
|
57
|
+
if (typeof obj.result === "string") {
|
|
58
|
+
return sanitizeTextContent(obj.result);
|
|
59
|
+
}
|
|
60
|
+
if (typeof obj.error === "string") {
|
|
61
|
+
return sanitizeTextContent(obj.error);
|
|
62
|
+
}
|
|
63
|
+
if (typeof obj.summary === "string") {
|
|
64
|
+
return sanitizeTextContent(obj.summary);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (!Array.isArray(content)) {
|
|
68
|
+
return "";
|
|
69
|
+
}
|
|
70
|
+
const joined = extractTextFromChatContent(content, {
|
|
71
|
+
sanitizeText: sanitizeTextContent,
|
|
72
|
+
normalizeText: (text) => text,
|
|
73
|
+
joinWith: "\n",
|
|
74
|
+
});
|
|
75
|
+
return joined?.trim() ?? "";
|
|
76
|
+
}
|
|
77
|
+
function extractSubagentOutputText(message) {
|
|
78
|
+
if (!message || typeof message !== "object") {
|
|
79
|
+
return "";
|
|
80
|
+
}
|
|
81
|
+
const role = message.role;
|
|
82
|
+
const content = message.content;
|
|
83
|
+
if (role === "assistant") {
|
|
84
|
+
const assistantText = extractAssistantText(message);
|
|
85
|
+
if (assistantText) {
|
|
86
|
+
return assistantText;
|
|
87
|
+
}
|
|
88
|
+
if (typeof content === "string") {
|
|
89
|
+
return sanitizeTextContent(content);
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(content)) {
|
|
92
|
+
return (extractTextFromChatContent(content, {
|
|
93
|
+
sanitizeText: sanitizeTextContent,
|
|
94
|
+
normalizeText: (text) => text.trim(),
|
|
95
|
+
joinWith: "",
|
|
96
|
+
}) ?? "");
|
|
97
|
+
}
|
|
98
|
+
return "";
|
|
99
|
+
}
|
|
100
|
+
if (role === "toolResult" || role === "tool") {
|
|
101
|
+
return extractToolResultText(message.content);
|
|
102
|
+
}
|
|
103
|
+
if (typeof content === "string") {
|
|
104
|
+
return sanitizeTextContent(content);
|
|
105
|
+
}
|
|
106
|
+
if (Array.isArray(content)) {
|
|
107
|
+
return (extractTextFromChatContent(content, {
|
|
108
|
+
sanitizeText: sanitizeTextContent,
|
|
109
|
+
normalizeText: (text) => text.trim(),
|
|
110
|
+
joinWith: "",
|
|
111
|
+
}) ?? "");
|
|
112
|
+
}
|
|
113
|
+
return "";
|
|
114
|
+
}
|
|
115
|
+
async function readLatestSubagentOutput(sessionKey) {
|
|
116
|
+
const history = await callGateway({
|
|
117
|
+
method: "chat.history",
|
|
118
|
+
params: { sessionKey, limit: 50 },
|
|
119
|
+
});
|
|
120
|
+
const messages = Array.isArray(history?.messages) ? history.messages : [];
|
|
121
|
+
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
122
|
+
const msg = messages[i];
|
|
123
|
+
const text = extractSubagentOutputText(msg);
|
|
124
|
+
if (text) {
|
|
125
|
+
return text;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return undefined;
|
|
129
|
+
}
|
|
130
|
+
async function readLatestSubagentOutputWithRetry(params) {
|
|
131
|
+
const RETRY_INTERVAL_MS = 100;
|
|
132
|
+
const deadline = Date.now() + Math.max(0, Math.min(params.maxWaitMs, 15_000));
|
|
133
|
+
let result;
|
|
134
|
+
while (Date.now() < deadline) {
|
|
135
|
+
result = await readLatestSubagentOutput(params.sessionKey);
|
|
136
|
+
if (result?.trim()) {
|
|
137
|
+
return result;
|
|
138
|
+
}
|
|
139
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_INTERVAL_MS));
|
|
140
|
+
}
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
function formatDurationShort(valueMs) {
|
|
144
|
+
if (!valueMs || !Number.isFinite(valueMs) || valueMs <= 0) {
|
|
145
|
+
return "n/a";
|
|
146
|
+
}
|
|
147
|
+
const totalSeconds = Math.round(valueMs / 1000);
|
|
148
|
+
const hours = Math.floor(totalSeconds / 3600);
|
|
149
|
+
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
|
150
|
+
const seconds = totalSeconds % 60;
|
|
151
|
+
if (hours > 0) {
|
|
152
|
+
return `${hours}h${minutes}m`;
|
|
153
|
+
}
|
|
154
|
+
if (minutes > 0) {
|
|
155
|
+
return `${minutes}m${seconds}s`;
|
|
156
|
+
}
|
|
157
|
+
return `${seconds}s`;
|
|
158
|
+
}
|
|
14
159
|
function formatTokenCount(value) {
|
|
15
|
-
if (
|
|
160
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
16
161
|
return "0";
|
|
17
|
-
|
|
162
|
+
}
|
|
163
|
+
if (value >= 1_000_000) {
|
|
18
164
|
return `${(value / 1_000_000).toFixed(1)}m`;
|
|
19
|
-
|
|
165
|
+
}
|
|
166
|
+
if (value >= 1_000) {
|
|
20
167
|
return `${(value / 1_000).toFixed(1)}k`;
|
|
168
|
+
}
|
|
21
169
|
return String(Math.round(value));
|
|
22
170
|
}
|
|
23
|
-
function
|
|
24
|
-
if (value === undefined || !Number.isFinite(value))
|
|
25
|
-
return undefined;
|
|
26
|
-
if (value >= 1)
|
|
27
|
-
return `$${value.toFixed(2)}`;
|
|
28
|
-
if (value >= 0.01)
|
|
29
|
-
return `$${value.toFixed(2)}`;
|
|
30
|
-
return `$${value.toFixed(4)}`;
|
|
31
|
-
}
|
|
32
|
-
function resolveModelCost(params) {
|
|
33
|
-
const provider = params.provider?.trim();
|
|
34
|
-
const model = params.model?.trim();
|
|
35
|
-
if (!provider || !model)
|
|
36
|
-
return undefined;
|
|
37
|
-
const models = params.config.models?.providers?.[provider]?.models ?? [];
|
|
38
|
-
const entry = models.find((candidate) => candidate.id === model);
|
|
39
|
-
return entry?.cost;
|
|
40
|
-
}
|
|
41
|
-
async function waitForSessionUsage(params) {
|
|
171
|
+
async function buildCompactAnnounceStatsLine(params) {
|
|
42
172
|
const cfg = loadConfig();
|
|
43
173
|
const agentId = resolveAgentIdFromSessionKey(params.sessionKey);
|
|
44
174
|
const storePath = resolveStorePath(cfg.session?.store, { agentId });
|
|
45
175
|
let entry = loadSessionStore(storePath)[params.sessionKey];
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
typeof entry.outputTokens === "number");
|
|
52
|
-
if (hasTokens())
|
|
53
|
-
return { entry, storePath };
|
|
54
|
-
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
55
|
-
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
56
|
-
entry = loadSessionStore(storePath)[params.sessionKey];
|
|
57
|
-
if (hasTokens())
|
|
176
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
177
|
+
const hasTokenData = typeof entry?.inputTokens === "number" ||
|
|
178
|
+
typeof entry?.outputTokens === "number" ||
|
|
179
|
+
typeof entry?.totalTokens === "number";
|
|
180
|
+
if (hasTokenData) {
|
|
58
181
|
break;
|
|
182
|
+
}
|
|
183
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
184
|
+
entry = loadSessionStore(storePath)[params.sessionKey];
|
|
185
|
+
}
|
|
186
|
+
const input = typeof entry?.inputTokens === "number" ? entry.inputTokens : 0;
|
|
187
|
+
const output = typeof entry?.outputTokens === "number" ? entry.outputTokens : 0;
|
|
188
|
+
const ioTotal = input + output;
|
|
189
|
+
const promptCache = typeof entry?.totalTokens === "number" ? entry.totalTokens : undefined;
|
|
190
|
+
const runtimeMs = typeof params.startedAt === "number" && typeof params.endedAt === "number"
|
|
191
|
+
? Math.max(0, params.endedAt - params.startedAt)
|
|
192
|
+
: undefined;
|
|
193
|
+
const parts = [
|
|
194
|
+
`runtime ${formatDurationShort(runtimeMs)}`,
|
|
195
|
+
`tokens ${formatTokenCount(ioTotal)} (in ${formatTokenCount(input)} / out ${formatTokenCount(output)})`,
|
|
196
|
+
];
|
|
197
|
+
if (typeof promptCache === "number" && promptCache > ioTotal) {
|
|
198
|
+
parts.push(`prompt/cache ${formatTokenCount(promptCache)}`);
|
|
59
199
|
}
|
|
60
|
-
return {
|
|
200
|
+
return `Stats: ${parts.join(" • ")}`;
|
|
61
201
|
}
|
|
62
202
|
function resolveAnnounceOrigin(entry, requesterOrigin) {
|
|
203
|
+
const normalizedRequester = normalizeDeliveryContext(requesterOrigin);
|
|
204
|
+
const normalizedEntry = deliveryContextFromSession(entry);
|
|
205
|
+
if (normalizedRequester?.channel && !isDeliverableMessageChannel(normalizedRequester.channel)) {
|
|
206
|
+
// Ignore internal/non-deliverable channel hints (for example webchat)
|
|
207
|
+
// so a valid persisted route can still be used for outbound delivery.
|
|
208
|
+
return mergeDeliveryContext({
|
|
209
|
+
accountId: normalizedRequester.accountId,
|
|
210
|
+
threadId: normalizedRequester.threadId,
|
|
211
|
+
}, normalizedEntry);
|
|
212
|
+
}
|
|
63
213
|
// requesterOrigin (captured at spawn time) reflects the channel the user is
|
|
64
214
|
// actually on and must take priority over the session entry, which may carry
|
|
65
215
|
// stale lastChannel / lastTo values from a previous channel interaction.
|
|
66
|
-
return mergeDeliveryContext(
|
|
216
|
+
return mergeDeliveryContext(normalizedRequester, normalizedEntry);
|
|
67
217
|
}
|
|
68
218
|
async function sendAnnounce(item) {
|
|
219
|
+
const requesterDepth = getSubagentDepthFromSessionStore(item.sessionKey);
|
|
220
|
+
const requesterIsSubagent = requesterDepth >= 1;
|
|
69
221
|
const origin = item.origin;
|
|
70
222
|
const threadId = origin?.threadId != null && origin.threadId !== "" ? String(origin.threadId) : undefined;
|
|
223
|
+
// Share one announce identity across direct and queued delivery paths so
|
|
224
|
+
// gateway dedupe suppresses true retries without collapsing distinct events.
|
|
225
|
+
const idempotencyKey = buildAnnounceIdempotencyKey(resolveQueueAnnounceId({
|
|
226
|
+
announceId: item.announceId,
|
|
227
|
+
sessionKey: item.sessionKey,
|
|
228
|
+
enqueuedAt: item.enqueuedAt,
|
|
229
|
+
}));
|
|
71
230
|
await callGateway({
|
|
72
231
|
method: "agent",
|
|
73
232
|
params: {
|
|
74
233
|
sessionKey: item.sessionKey,
|
|
75
234
|
message: item.prompt,
|
|
76
|
-
channel: origin?.channel,
|
|
77
|
-
accountId: origin?.accountId,
|
|
78
|
-
to: origin?.to,
|
|
79
|
-
threadId,
|
|
80
|
-
deliver:
|
|
81
|
-
idempotencyKey
|
|
235
|
+
channel: requesterIsSubagent ? undefined : origin?.channel,
|
|
236
|
+
accountId: requesterIsSubagent ? undefined : origin?.accountId,
|
|
237
|
+
to: requesterIsSubagent ? undefined : origin?.to,
|
|
238
|
+
threadId: requesterIsSubagent ? undefined : threadId,
|
|
239
|
+
deliver: !requesterIsSubagent,
|
|
240
|
+
idempotencyKey,
|
|
82
241
|
},
|
|
83
|
-
|
|
84
|
-
timeoutMs: 60_000,
|
|
242
|
+
timeoutMs: 15_000,
|
|
85
243
|
});
|
|
86
244
|
}
|
|
87
245
|
function resolveRequesterStoreKey(cfg, requesterSessionKey) {
|
|
88
246
|
const raw = requesterSessionKey.trim();
|
|
89
|
-
if (!raw)
|
|
247
|
+
if (!raw) {
|
|
90
248
|
return raw;
|
|
91
|
-
|
|
249
|
+
}
|
|
250
|
+
if (raw === "global" || raw === "unknown") {
|
|
92
251
|
return raw;
|
|
93
|
-
|
|
252
|
+
}
|
|
253
|
+
if (raw.startsWith("agent:")) {
|
|
94
254
|
return raw;
|
|
255
|
+
}
|
|
95
256
|
const mainKey = normalizeMainKey(cfg.session?.mainKey);
|
|
96
257
|
if (raw === "main" || raw === mainKey) {
|
|
97
258
|
return resolveMainSessionKey(cfg);
|
|
@@ -112,8 +273,9 @@ async function maybeQueueSubagentAnnounce(params) {
|
|
|
112
273
|
const { cfg, entry } = loadRequesterSessionEntry(params.requesterSessionKey);
|
|
113
274
|
const canonicalKey = resolveRequesterStoreKey(cfg, params.requesterSessionKey);
|
|
114
275
|
const sessionId = entry?.sessionId;
|
|
115
|
-
if (!sessionId)
|
|
276
|
+
if (!sessionId) {
|
|
116
277
|
return "none";
|
|
278
|
+
}
|
|
117
279
|
const queueSettings = resolveQueueSettings({
|
|
118
280
|
cfg,
|
|
119
281
|
channel: entry?.channel ?? entry?.lastChannel,
|
|
@@ -123,8 +285,9 @@ async function maybeQueueSubagentAnnounce(params) {
|
|
|
123
285
|
const shouldSteer = queueSettings.mode === "steer" || queueSettings.mode === "steer-backlog";
|
|
124
286
|
if (shouldSteer) {
|
|
125
287
|
const steered = queueEmbeddedPiMessage(sessionId, params.triggerMessage);
|
|
126
|
-
if (steered)
|
|
288
|
+
if (steered) {
|
|
127
289
|
return "steered";
|
|
290
|
+
}
|
|
128
291
|
}
|
|
129
292
|
const shouldFollowup = queueSettings.mode === "followup" ||
|
|
130
293
|
queueSettings.mode === "collect" ||
|
|
@@ -135,6 +298,7 @@ async function maybeQueueSubagentAnnounce(params) {
|
|
|
135
298
|
enqueueAnnounce({
|
|
136
299
|
key: canonicalKey,
|
|
137
300
|
item: {
|
|
301
|
+
announceId: params.announceId,
|
|
138
302
|
prompt: params.triggerMessage,
|
|
139
303
|
summaryLine: params.summaryLine,
|
|
140
304
|
enqueuedAt: Date.now(),
|
|
@@ -148,47 +312,137 @@ async function maybeQueueSubagentAnnounce(params) {
|
|
|
148
312
|
}
|
|
149
313
|
return "none";
|
|
150
314
|
}
|
|
151
|
-
|
|
315
|
+
function queueOutcomeToDeliveryResult(outcome) {
|
|
316
|
+
if (outcome === "steered") {
|
|
317
|
+
return {
|
|
318
|
+
delivered: true,
|
|
319
|
+
path: "steered",
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
if (outcome === "queued") {
|
|
323
|
+
return {
|
|
324
|
+
delivered: true,
|
|
325
|
+
path: "queued",
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
return {
|
|
329
|
+
delivered: false,
|
|
330
|
+
path: "none",
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
async function sendSubagentAnnounceDirectly(params) {
|
|
152
334
|
const cfg = loadConfig();
|
|
153
|
-
const
|
|
154
|
-
|
|
335
|
+
const canonicalRequesterSessionKey = resolveRequesterStoreKey(cfg, params.targetRequesterSessionKey);
|
|
336
|
+
try {
|
|
337
|
+
const completionDirectOrigin = normalizeDeliveryContext(params.completionDirectOrigin);
|
|
338
|
+
const completionChannelRaw = typeof completionDirectOrigin?.channel === "string"
|
|
339
|
+
? completionDirectOrigin.channel.trim()
|
|
340
|
+
: "";
|
|
341
|
+
const completionChannel = completionChannelRaw && isDeliverableMessageChannel(completionChannelRaw)
|
|
342
|
+
? completionChannelRaw
|
|
343
|
+
: "";
|
|
344
|
+
const completionTo = typeof completionDirectOrigin?.to === "string" ? completionDirectOrigin.to.trim() : "";
|
|
345
|
+
const hasCompletionDirectTarget = !params.requesterIsSubagent && Boolean(completionChannel) && Boolean(completionTo);
|
|
346
|
+
if (params.expectsCompletionMessage &&
|
|
347
|
+
hasCompletionDirectTarget &&
|
|
348
|
+
params.completionMessage?.trim()) {
|
|
349
|
+
const completionThreadId = completionDirectOrigin?.threadId != null && completionDirectOrigin.threadId !== ""
|
|
350
|
+
? String(completionDirectOrigin.threadId)
|
|
351
|
+
: undefined;
|
|
352
|
+
await callGateway({
|
|
353
|
+
method: "send",
|
|
354
|
+
params: {
|
|
355
|
+
channel: completionChannel,
|
|
356
|
+
to: completionTo,
|
|
357
|
+
accountId: completionDirectOrigin?.accountId,
|
|
358
|
+
threadId: completionThreadId,
|
|
359
|
+
sessionKey: canonicalRequesterSessionKey,
|
|
360
|
+
message: params.completionMessage,
|
|
361
|
+
idempotencyKey: params.directIdempotencyKey,
|
|
362
|
+
},
|
|
363
|
+
timeoutMs: 15_000,
|
|
364
|
+
});
|
|
365
|
+
return {
|
|
366
|
+
delivered: true,
|
|
367
|
+
path: "direct",
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
const directOrigin = normalizeDeliveryContext(params.directOrigin);
|
|
371
|
+
const threadId = directOrigin?.threadId != null && directOrigin.threadId !== ""
|
|
372
|
+
? String(directOrigin.threadId)
|
|
373
|
+
: undefined;
|
|
374
|
+
await callGateway({
|
|
375
|
+
method: "agent",
|
|
376
|
+
params: {
|
|
377
|
+
sessionKey: canonicalRequesterSessionKey,
|
|
378
|
+
message: params.triggerMessage,
|
|
379
|
+
deliver: !params.requesterIsSubagent,
|
|
380
|
+
channel: params.requesterIsSubagent ? undefined : directOrigin?.channel,
|
|
381
|
+
accountId: params.requesterIsSubagent ? undefined : directOrigin?.accountId,
|
|
382
|
+
to: params.requesterIsSubagent ? undefined : directOrigin?.to,
|
|
383
|
+
threadId: params.requesterIsSubagent ? undefined : threadId,
|
|
384
|
+
idempotencyKey: params.directIdempotencyKey,
|
|
385
|
+
},
|
|
386
|
+
expectFinal: true,
|
|
387
|
+
timeoutMs: 15_000,
|
|
388
|
+
});
|
|
389
|
+
return {
|
|
390
|
+
delivered: true,
|
|
391
|
+
path: "direct",
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
catch (err) {
|
|
395
|
+
return {
|
|
396
|
+
delivered: false,
|
|
397
|
+
path: "direct",
|
|
398
|
+
error: summarizeDeliveryError(err),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async function deliverSubagentAnnouncement(params) {
|
|
403
|
+
// Non-completion mode mirrors historical behavior: try queued/steered delivery first,
|
|
404
|
+
// then (only if not queued) attempt direct delivery.
|
|
405
|
+
if (!params.expectsCompletionMessage) {
|
|
406
|
+
const queueOutcome = await maybeQueueSubagentAnnounce({
|
|
407
|
+
requesterSessionKey: params.requesterSessionKey,
|
|
408
|
+
announceId: params.announceId,
|
|
409
|
+
triggerMessage: params.triggerMessage,
|
|
410
|
+
summaryLine: params.summaryLine,
|
|
411
|
+
requesterOrigin: params.requesterOrigin,
|
|
412
|
+
});
|
|
413
|
+
const queued = queueOutcomeToDeliveryResult(queueOutcome);
|
|
414
|
+
if (queued.delivered) {
|
|
415
|
+
return queued;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
// Completion-mode uses direct send first so manual spawns can return immediately
|
|
419
|
+
// in the common ready-to-deliver case.
|
|
420
|
+
const direct = await sendSubagentAnnounceDirectly({
|
|
421
|
+
targetRequesterSessionKey: params.targetRequesterSessionKey,
|
|
422
|
+
triggerMessage: params.triggerMessage,
|
|
423
|
+
completionMessage: params.completionMessage,
|
|
424
|
+
directIdempotencyKey: params.directIdempotencyKey,
|
|
425
|
+
completionDirectOrigin: params.completionDirectOrigin,
|
|
426
|
+
directOrigin: params.directOrigin,
|
|
427
|
+
requesterIsSubagent: params.requesterIsSubagent,
|
|
428
|
+
expectsCompletionMessage: params.expectsCompletionMessage,
|
|
155
429
|
});
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const runtime = formatDurationCompact(runtimeMs);
|
|
173
|
-
parts.push(`runtime ${runtime ?? "n/a"}`);
|
|
174
|
-
if (typeof total === "number") {
|
|
175
|
-
const inputText = typeof input === "number" ? formatTokenCount(input) : "n/a";
|
|
176
|
-
const outputText = typeof output === "number" ? formatTokenCount(output) : "n/a";
|
|
177
|
-
const totalText = formatTokenCount(total);
|
|
178
|
-
parts.push(`tokens ${totalText} (in ${inputText} / out ${outputText})`);
|
|
179
|
-
}
|
|
180
|
-
else {
|
|
181
|
-
parts.push("tokens n/a");
|
|
182
|
-
}
|
|
183
|
-
const costText = formatUsd(cost);
|
|
184
|
-
if (costText)
|
|
185
|
-
parts.push(`est ${costText}`);
|
|
186
|
-
parts.push(`sessionKey ${params.sessionKey}`);
|
|
187
|
-
if (sessionId)
|
|
188
|
-
parts.push(`sessionId ${sessionId}`);
|
|
189
|
-
if (transcriptPath)
|
|
190
|
-
parts.push(`transcript ${transcriptPath}`);
|
|
191
|
-
return `Stats: ${parts.join(" \u2022 ")}`;
|
|
430
|
+
if (direct.delivered || !params.expectsCompletionMessage) {
|
|
431
|
+
return direct;
|
|
432
|
+
}
|
|
433
|
+
// If completion path failed direct delivery, try queueing as a fallback so the
|
|
434
|
+
// report can still be delivered once the requester session is idle.
|
|
435
|
+
const queueOutcome = await maybeQueueSubagentAnnounce({
|
|
436
|
+
requesterSessionKey: params.requesterSessionKey,
|
|
437
|
+
announceId: params.announceId,
|
|
438
|
+
triggerMessage: params.triggerMessage,
|
|
439
|
+
summaryLine: params.summaryLine,
|
|
440
|
+
requesterOrigin: params.requesterOrigin,
|
|
441
|
+
});
|
|
442
|
+
if (queueOutcome === "steered" || queueOutcome === "queued") {
|
|
443
|
+
return queueOutcomeToDeliveryResult(queueOutcome);
|
|
444
|
+
}
|
|
445
|
+
return direct;
|
|
192
446
|
}
|
|
193
447
|
function loadSessionEntryByKey(sessionKey) {
|
|
194
448
|
const cfg = loadConfig();
|
|
@@ -197,70 +451,84 @@ function loadSessionEntryByKey(sessionKey) {
|
|
|
197
451
|
const store = loadSessionStore(storePath);
|
|
198
452
|
return store[sessionKey];
|
|
199
453
|
}
|
|
200
|
-
async function readLatestAssistantReplyWithRetry(params) {
|
|
201
|
-
let reply = params.initialReply?.trim() ? params.initialReply : undefined;
|
|
202
|
-
if (reply) {
|
|
203
|
-
return reply;
|
|
204
|
-
}
|
|
205
|
-
const deadline = Date.now() + Math.max(0, Math.min(params.maxWaitMs, 15_000));
|
|
206
|
-
while (Date.now() < deadline) {
|
|
207
|
-
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
208
|
-
const latest = await readLatestAssistantReply({ sessionKey: params.sessionKey });
|
|
209
|
-
if (latest?.trim()) {
|
|
210
|
-
return latest;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return reply;
|
|
214
|
-
}
|
|
215
454
|
export function buildSubagentSystemPrompt(params) {
|
|
216
455
|
const taskText = typeof params.task === "string" && params.task.trim()
|
|
217
456
|
? params.task.replace(/\s+/g, " ").trim()
|
|
218
457
|
: "{{TASK_DESCRIPTION}}";
|
|
458
|
+
const childDepth = typeof params.childDepth === "number" ? params.childDepth : 1;
|
|
459
|
+
const maxSpawnDepth = typeof params.maxSpawnDepth === "number" ? params.maxSpawnDepth : 1;
|
|
460
|
+
const canSpawn = childDepth < maxSpawnDepth;
|
|
461
|
+
const parentLabel = childDepth >= 2 ? "parent orchestrator" : "main agent";
|
|
219
462
|
const lines = [
|
|
220
463
|
"# Subagent Context",
|
|
221
464
|
"",
|
|
222
|
-
|
|
465
|
+
`You are a **subagent** spawned by the ${parentLabel} for a specific task.`,
|
|
223
466
|
"",
|
|
224
467
|
"## Your Role",
|
|
225
468
|
`- You were created to handle: ${taskText}`,
|
|
226
469
|
"- Complete this task. That's your entire purpose.",
|
|
227
|
-
|
|
470
|
+
`- You are NOT the ${parentLabel}. Don't try to be.`,
|
|
228
471
|
"",
|
|
229
472
|
"## Rules",
|
|
230
473
|
"1. **Stay focused** - Do your assigned task, nothing else",
|
|
231
|
-
|
|
474
|
+
`2. **Complete the task** - Your final message will be automatically reported to the ${parentLabel}`,
|
|
232
475
|
"3. **Don't initiate** - No heartbeats, no proactive actions, no side quests",
|
|
233
476
|
"4. **Be ephemeral** - You may be terminated after task completion. That's fine.",
|
|
477
|
+
"5. **Trust push-based completion** - Descendant results are auto-announced back to you; do not busy-poll for status.",
|
|
478
|
+
"6. **Recover from compacted/truncated tool output** - If you see `[compacted: tool output removed to free context]` or `[truncated: output exceeded context limit]`, assume prior output was reduced. Re-read only what you need using smaller chunks (`read` with offset/limit, or targeted `rg`/`head`/`tail`) instead of full-file `cat`.",
|
|
234
479
|
"",
|
|
235
480
|
"## Output Format",
|
|
236
481
|
"When complete, your final response should include:",
|
|
237
|
-
|
|
238
|
-
|
|
482
|
+
`- What you accomplished or found`,
|
|
483
|
+
`- Any relevant details the ${parentLabel} should know`,
|
|
239
484
|
"- Keep it concise but informative",
|
|
240
485
|
"",
|
|
241
486
|
"## What You DON'T Do",
|
|
242
|
-
|
|
487
|
+
`- NO user conversations (that's ${parentLabel}'s job)`,
|
|
243
488
|
"- NO external messages (email, tweets, etc.) unless explicitly tasked with a specific recipient/channel",
|
|
244
489
|
"- NO cron jobs or persistent state",
|
|
245
|
-
|
|
246
|
-
|
|
490
|
+
`- NO pretending to be the ${parentLabel}`,
|
|
491
|
+
`- Only use the \`message\` tool when explicitly instructed to contact a specific external recipient; otherwise return plain text and let the ${parentLabel} deliver it`,
|
|
247
492
|
"",
|
|
248
|
-
|
|
493
|
+
];
|
|
494
|
+
if (canSpawn) {
|
|
495
|
+
lines.push("## Sub-Agent Spawning", "You CAN spawn your own sub-agents for parallel or complex work using `sessions_spawn`.", "Use the `subagents` tool to steer, kill, or do an on-demand status check for your spawned sub-agents.", "Your sub-agents will announce their results back to you automatically (not to the main agent).", "Default workflow: spawn work, continue orchestrating, and wait for auto-announced completions.", "Do NOT repeatedly poll `subagents list` in a loop unless you are actively debugging or intervening.", "Coordinate their work and synthesize results before reporting back.", "");
|
|
496
|
+
}
|
|
497
|
+
else if (childDepth >= 2) {
|
|
498
|
+
lines.push("## Sub-Agent Spawning", "You are a leaf worker and CANNOT spawn further sub-agents. Focus on your assigned task.", "");
|
|
499
|
+
}
|
|
500
|
+
lines.push("## Session Context", ...[
|
|
249
501
|
params.label ? `- Label: ${params.label}` : undefined,
|
|
250
|
-
params.requesterSessionKey
|
|
502
|
+
params.requesterSessionKey
|
|
503
|
+
? `- Requester session: ${params.requesterSessionKey}.`
|
|
504
|
+
: undefined,
|
|
251
505
|
params.requesterOrigin?.channel
|
|
252
506
|
? `- Requester channel: ${params.requesterOrigin.channel}.`
|
|
253
507
|
: undefined,
|
|
254
508
|
`- Your session: ${params.childSessionKey}.`,
|
|
255
|
-
|
|
256
|
-
].filter((line) => line !== undefined);
|
|
509
|
+
].filter((line) => line !== undefined), "");
|
|
257
510
|
return lines.join("\n");
|
|
258
511
|
}
|
|
512
|
+
function buildAnnounceReplyInstruction(params) {
|
|
513
|
+
if (params.expectsCompletionMessage) {
|
|
514
|
+
return `A completed ${params.announceType} is ready for user delivery. Convert the result above into your normal assistant voice and send that user-facing update now. Keep this internal context private (don't mention system/log/stats/session details or announce type).`;
|
|
515
|
+
}
|
|
516
|
+
if (params.remainingActiveSubagentRuns > 0) {
|
|
517
|
+
const activeRunsLabel = params.remainingActiveSubagentRuns === 1 ? "run" : "runs";
|
|
518
|
+
return `There are still ${params.remainingActiveSubagentRuns} active subagent ${activeRunsLabel} for this session. If they are part of the same workflow, wait for the remaining results before sending a user update. If they are unrelated, respond normally using only the result above.`;
|
|
519
|
+
}
|
|
520
|
+
if (params.requesterIsSubagent) {
|
|
521
|
+
return `Convert this completion into a concise internal orchestration update for your parent agent in your own words. Keep this internal context private (don't mention system/log/stats/session details or announce type). If this result is duplicate or no update is needed, reply ONLY: ${SILENT_REPLY_TOKEN}.`;
|
|
522
|
+
}
|
|
523
|
+
return `A completed ${params.announceType} is ready for user delivery. Convert the result above into your normal assistant voice and send that user-facing update now. Keep this internal context private (don't mention system/log/stats/session details or announce type), and do not copy the system message verbatim. Reply ONLY: ${SILENT_REPLY_TOKEN} if this exact result was already delivered to the user in this same turn.`;
|
|
524
|
+
}
|
|
259
525
|
export async function runSubagentAnnounceFlow(params) {
|
|
260
526
|
let didAnnounce = false;
|
|
527
|
+
const expectsCompletionMessage = params.expectsCompletionMessage === true;
|
|
261
528
|
let shouldDeleteChildSession = params.cleanup === "delete";
|
|
262
529
|
try {
|
|
263
|
-
|
|
530
|
+
let targetRequesterSessionKey = params.requesterSessionKey;
|
|
531
|
+
let targetRequesterOrigin = normalizeDeliveryContext(params.requesterOrigin);
|
|
264
532
|
const childSessionId = (() => {
|
|
265
533
|
const entry = loadSessionEntryByKey(params.childSessionKey);
|
|
266
534
|
return typeof entry?.sessionId === "string" && entry.sessionId.trim()
|
|
@@ -272,7 +540,7 @@ export async function runSubagentAnnounceFlow(params) {
|
|
|
272
540
|
let outcome = params.outcome;
|
|
273
541
|
// Lifecycle "end" can arrive before auto-compaction retries finish. If the
|
|
274
542
|
// subagent is still active, wait for the embedded run to fully settle.
|
|
275
|
-
if (childSessionId && isEmbeddedPiRunActive(childSessionId)) {
|
|
543
|
+
if (!expectsCompletionMessage && childSessionId && isEmbeddedPiRunActive(childSessionId)) {
|
|
276
544
|
const settled = await waitForEmbeddedPiRunEnd(childSessionId, settleTimeoutMs);
|
|
277
545
|
if (!settled && isEmbeddedPiRunActive(childSessionId)) {
|
|
278
546
|
// The child run is still active (e.g., compaction retry still in progress).
|
|
@@ -313,19 +581,21 @@ export async function runSubagentAnnounceFlow(params) {
|
|
|
313
581
|
outcome = { status: "timeout" };
|
|
314
582
|
}
|
|
315
583
|
}
|
|
316
|
-
reply = await
|
|
584
|
+
reply = await readLatestSubagentOutput(params.childSessionKey);
|
|
317
585
|
}
|
|
318
586
|
if (!reply) {
|
|
319
|
-
reply = await
|
|
587
|
+
reply = await readLatestSubagentOutput(params.childSessionKey);
|
|
320
588
|
}
|
|
321
589
|
if (!reply?.trim()) {
|
|
322
|
-
reply = await
|
|
590
|
+
reply = await readLatestSubagentOutputWithRetry({
|
|
323
591
|
sessionKey: params.childSessionKey,
|
|
324
|
-
initialReply: reply,
|
|
325
592
|
maxWaitMs: params.timeoutMs,
|
|
326
593
|
});
|
|
327
594
|
}
|
|
328
|
-
if (!
|
|
595
|
+
if (!expectsCompletionMessage &&
|
|
596
|
+
!reply?.trim() &&
|
|
597
|
+
childSessionId &&
|
|
598
|
+
isEmbeddedPiRunActive(childSessionId)) {
|
|
329
599
|
// Avoid announcing "(no output)" while the child run is still producing output.
|
|
330
600
|
shouldDeleteChildSession = false;
|
|
331
601
|
return false;
|
|
@@ -333,12 +603,20 @@ export async function runSubagentAnnounceFlow(params) {
|
|
|
333
603
|
if (!outcome) {
|
|
334
604
|
outcome = { status: "unknown" };
|
|
335
605
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
606
|
+
let activeChildDescendantRuns = 0;
|
|
607
|
+
try {
|
|
608
|
+
const { countActiveDescendantRuns } = await import("./subagent-registry.js");
|
|
609
|
+
activeChildDescendantRuns = Math.max(0, countActiveDescendantRuns(params.childSessionKey));
|
|
610
|
+
}
|
|
611
|
+
catch {
|
|
612
|
+
// Best-effort only; fall back to direct announce behavior when unavailable.
|
|
613
|
+
}
|
|
614
|
+
if (!expectsCompletionMessage && activeChildDescendantRuns > 0) {
|
|
615
|
+
// The finished run still has active descendant subagents. Defer announcing
|
|
616
|
+
// this run until descendants settle so we avoid posting in-progress updates.
|
|
617
|
+
shouldDeleteChildSession = false;
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
342
620
|
// Build status label
|
|
343
621
|
const statusLabel = outcome.status === "ok"
|
|
344
622
|
? "completed successfully"
|
|
@@ -350,56 +628,112 @@ export async function runSubagentAnnounceFlow(params) {
|
|
|
350
628
|
// Build instructional message for main agent
|
|
351
629
|
const announceType = params.announceType ?? "subagent task";
|
|
352
630
|
const taskLabel = params.label || params.task || "task";
|
|
353
|
-
const
|
|
354
|
-
|
|
631
|
+
const subagentName = resolveAgentIdFromSessionKey(params.childSessionKey);
|
|
632
|
+
const announceSessionId = childSessionId || "unknown";
|
|
633
|
+
const findings = reply || "(no output)";
|
|
634
|
+
let completionMessage = "";
|
|
635
|
+
let triggerMessage = "";
|
|
636
|
+
let requesterDepth = getSubagentDepthFromSessionStore(targetRequesterSessionKey);
|
|
637
|
+
let requesterIsSubagent = !expectsCompletionMessage && requesterDepth >= 1;
|
|
638
|
+
// If the requester subagent has already finished, bubble the announce to its
|
|
639
|
+
// requester (typically main) so descendant completion is not silently lost.
|
|
640
|
+
// BUT: only fallback if the parent SESSION is deleted, not just if the current
|
|
641
|
+
// run ended. A parent waiting for child results has no active run but should
|
|
642
|
+
// still receive the announce — injecting will start a new agent turn.
|
|
643
|
+
if (requesterIsSubagent) {
|
|
644
|
+
const { isSubagentSessionRunActive, resolveRequesterForChildSession } = await import("./subagent-registry.js");
|
|
645
|
+
if (!isSubagentSessionRunActive(targetRequesterSessionKey)) {
|
|
646
|
+
// Parent run has ended. Check if parent SESSION still exists.
|
|
647
|
+
// If it does, the parent may be waiting for child results — inject there.
|
|
648
|
+
const parentSessionEntry = loadSessionEntryByKey(targetRequesterSessionKey);
|
|
649
|
+
const parentSessionAlive = parentSessionEntry &&
|
|
650
|
+
typeof parentSessionEntry.sessionId === "string" &&
|
|
651
|
+
parentSessionEntry.sessionId.trim();
|
|
652
|
+
if (!parentSessionAlive) {
|
|
653
|
+
// Parent session is truly gone — fallback to grandparent
|
|
654
|
+
const fallback = resolveRequesterForChildSession(targetRequesterSessionKey);
|
|
655
|
+
if (!fallback?.requesterSessionKey) {
|
|
656
|
+
// Without a requester fallback we cannot safely deliver this nested
|
|
657
|
+
// completion. Keep cleanup retryable so a later registry restore can
|
|
658
|
+
// recover and re-announce instead of silently dropping the result.
|
|
659
|
+
shouldDeleteChildSession = false;
|
|
660
|
+
return false;
|
|
661
|
+
}
|
|
662
|
+
targetRequesterSessionKey = fallback.requesterSessionKey;
|
|
663
|
+
targetRequesterOrigin =
|
|
664
|
+
normalizeDeliveryContext(fallback.requesterOrigin) ?? targetRequesterOrigin;
|
|
665
|
+
requesterDepth = getSubagentDepthFromSessionStore(targetRequesterSessionKey);
|
|
666
|
+
requesterIsSubagent = requesterDepth >= 1;
|
|
667
|
+
}
|
|
668
|
+
// If parent session is alive (just has no active run), continue with parent
|
|
669
|
+
// as target. Injecting the announce will start a new agent turn for processing.
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
let remainingActiveSubagentRuns = 0;
|
|
673
|
+
try {
|
|
674
|
+
const { countActiveDescendantRuns } = await import("./subagent-registry.js");
|
|
675
|
+
remainingActiveSubagentRuns = Math.max(0, countActiveDescendantRuns(targetRequesterSessionKey));
|
|
676
|
+
}
|
|
677
|
+
catch {
|
|
678
|
+
// Best-effort only; fall back to default announce instructions when unavailable.
|
|
679
|
+
}
|
|
680
|
+
const replyInstruction = buildAnnounceReplyInstruction({
|
|
681
|
+
remainingActiveSubagentRuns,
|
|
682
|
+
requesterIsSubagent,
|
|
683
|
+
announceType,
|
|
684
|
+
expectsCompletionMessage,
|
|
685
|
+
});
|
|
686
|
+
const statsLine = await buildCompactAnnounceStatsLine({
|
|
687
|
+
sessionKey: params.childSessionKey,
|
|
688
|
+
startedAt: params.startedAt,
|
|
689
|
+
endedAt: params.endedAt,
|
|
690
|
+
});
|
|
691
|
+
completionMessage = buildCompletionDeliveryMessage({
|
|
692
|
+
findings,
|
|
693
|
+
subagentName,
|
|
694
|
+
});
|
|
695
|
+
const internalSummaryMessage = [
|
|
696
|
+
`[System Message] [sessionId: ${announceSessionId}] A ${announceType} "${taskLabel}" just ${statusLabel}.`,
|
|
355
697
|
"",
|
|
356
|
-
"
|
|
357
|
-
|
|
698
|
+
"Result:",
|
|
699
|
+
findings,
|
|
358
700
|
"",
|
|
359
701
|
statsLine,
|
|
360
|
-
"",
|
|
361
|
-
"Summarize this naturally for the user. Keep it brief (1-2 sentences). Flow it into the conversation naturally.",
|
|
362
|
-
`Do not mention technical details like tokens, stats, or that this was a ${announceType}.`,
|
|
363
|
-
"You can respond with NO_REPLY if no announcement is needed (e.g., internal task with no user-facing result).",
|
|
364
702
|
].join("\n");
|
|
365
|
-
|
|
366
|
-
|
|
703
|
+
triggerMessage = [internalSummaryMessage, "", replyInstruction].join("\n");
|
|
704
|
+
const announceId = buildAnnounceIdFromChildRun({
|
|
705
|
+
childSessionKey: params.childSessionKey,
|
|
706
|
+
childRunId: params.childRunId,
|
|
707
|
+
});
|
|
708
|
+
// Send to the requester session. For nested subagents this is an internal
|
|
709
|
+
// follow-up injection (deliver=false) so the orchestrator receives it.
|
|
710
|
+
let directOrigin = targetRequesterOrigin;
|
|
711
|
+
if (!requesterIsSubagent) {
|
|
712
|
+
const { entry } = loadRequesterSessionEntry(targetRequesterSessionKey);
|
|
713
|
+
directOrigin = resolveAnnounceOrigin(entry, targetRequesterOrigin);
|
|
714
|
+
}
|
|
715
|
+
// Use a deterministic idempotency key so the gateway dedup cache
|
|
716
|
+
// catches duplicates if this announce is also queued by the gateway-
|
|
717
|
+
// level message queue while the main session is busy (#17122).
|
|
718
|
+
const directIdempotencyKey = buildAnnounceIdempotencyKey(announceId);
|
|
719
|
+
const delivery = await deliverSubagentAnnouncement({
|
|
720
|
+
requesterSessionKey: targetRequesterSessionKey,
|
|
721
|
+
announceId,
|
|
367
722
|
triggerMessage,
|
|
723
|
+
completionMessage,
|
|
368
724
|
summaryLine: taskLabel,
|
|
369
|
-
requesterOrigin,
|
|
725
|
+
requesterOrigin: targetRequesterOrigin,
|
|
726
|
+
completionDirectOrigin: targetRequesterOrigin,
|
|
727
|
+
directOrigin,
|
|
728
|
+
targetRequesterSessionKey,
|
|
729
|
+
requesterIsSubagent,
|
|
730
|
+
expectsCompletionMessage: expectsCompletionMessage,
|
|
731
|
+
directIdempotencyKey,
|
|
370
732
|
});
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
733
|
+
didAnnounce = delivery.delivered;
|
|
734
|
+
if (!delivery.delivered && delivery.path === "direct" && delivery.error) {
|
|
735
|
+
defaultRuntime.error?.(`Subagent completion direct announce failed for run ${params.childRunId}: ${delivery.error}`);
|
|
374
736
|
}
|
|
375
|
-
if (queued === "queued") {
|
|
376
|
-
didAnnounce = true;
|
|
377
|
-
return true;
|
|
378
|
-
}
|
|
379
|
-
// Send to main agent - it will respond in its own voice
|
|
380
|
-
let directOrigin = requesterOrigin;
|
|
381
|
-
if (!directOrigin) {
|
|
382
|
-
const { entry } = loadRequesterSessionEntry(params.requesterSessionKey);
|
|
383
|
-
directOrigin = deliveryContextFromSession(entry);
|
|
384
|
-
}
|
|
385
|
-
await callGateway({
|
|
386
|
-
method: "agent",
|
|
387
|
-
params: {
|
|
388
|
-
sessionKey: params.requesterSessionKey,
|
|
389
|
-
message: triggerMessage,
|
|
390
|
-
deliver: true,
|
|
391
|
-
channel: directOrigin?.channel,
|
|
392
|
-
accountId: directOrigin?.accountId,
|
|
393
|
-
to: directOrigin?.to,
|
|
394
|
-
threadId: directOrigin?.threadId != null && directOrigin.threadId !== ""
|
|
395
|
-
? String(directOrigin.threadId)
|
|
396
|
-
: undefined,
|
|
397
|
-
idempotencyKey: crypto.randomUUID(),
|
|
398
|
-
},
|
|
399
|
-
expectFinal: true,
|
|
400
|
-
timeoutMs: 60_000,
|
|
401
|
-
});
|
|
402
|
-
didAnnounce = true;
|
|
403
737
|
}
|
|
404
738
|
catch (err) {
|
|
405
739
|
defaultRuntime.error?.(`Subagent announce failed: ${String(err)}`);
|