@poolzin/pool-bot 2026.2.23 → 2026.2.25
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 +29 -0
- package/dist/acp/client.js +207 -18
- package/dist/acp/secret-file.js +22 -0
- package/dist/agents/agent-scope.js +10 -0
- package/dist/agents/bash-process-registry.test-helpers.js +29 -0
- package/dist/agents/bash-tools.exec-approval-request.js +20 -0
- package/dist/agents/bash-tools.exec-host-gateway.js +230 -0
- package/dist/agents/bash-tools.exec-host-node.js +235 -0
- package/dist/agents/bash-tools.exec-types.js +1 -0
- package/dist/agents/bash-tools.process.js +224 -218
- package/dist/agents/content-blocks.js +16 -0
- package/dist/agents/model-fallback.js +96 -101
- package/dist/agents/models-config.providers.js +299 -182
- package/dist/agents/pi-embedded-payloads.js +1 -0
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +34 -0
- package/dist/agents/skills.test-helpers.js +13 -0
- package/dist/agents/stable-stringify.js +12 -0
- package/dist/agents/subagent-registry.mocks.shared.js +12 -0
- package/dist/agents/test-helpers/assistant-message-fixtures.js +29 -0
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +27 -0
- package/dist/agents/tool-policy-shared.js +108 -0
- package/dist/agents/tools/browser-tool.js +160 -54
- package/dist/agents/tools/cron-tool.test-helpers.js +12 -0
- package/dist/agents/tools/discord-actions-moderation-shared.js +27 -0
- package/dist/agents/tools/image-tool.js +214 -99
- package/dist/agents/tools/sessions-history-tool.js +140 -108
- package/dist/agents/workspace.js +222 -46
- package/dist/auto-reply/commands-registry.js +15 -18
- package/dist/auto-reply/fallback-state.js +114 -0
- package/dist/auto-reply/model-runtime.js +68 -0
- package/dist/auto-reply/reply/agent-runner-execution.js +36 -4
- package/dist/auto-reply/reply/agent-runner.js +165 -39
- package/dist/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/browser/config.js +26 -0
- package/dist/browser/navigation-guard.js +31 -0
- package/dist/browser/routes/agent.act.js +431 -424
- package/dist/browser/routes/agent.shared.js +47 -3
- package/dist/browser/routes/agent.snapshot.js +122 -116
- package/dist/browser/routes/agent.storage.js +303 -297
- package/dist/browser/routes/tabs.js +154 -100
- package/dist/browser/server-lifecycle.js +37 -0
- package/dist/build-info.json +3 -3
- package/dist/channels/allow-from.js +25 -0
- package/dist/channels/plugins/account-action-gate.js +13 -0
- package/dist/channels/plugins/message-actions.js +10 -0
- package/dist/channels/telegram/api.js +18 -0
- package/dist/cli/argv.js +84 -21
- package/dist/cli/banner.js +2 -1
- package/dist/cli/exec-approvals-cli.js +92 -124
- package/dist/cli/memory-cli.js +158 -61
- package/dist/cli/nodes-cli/register.push.js +63 -0
- package/dist/cli/nodes-media-utils.js +21 -0
- package/dist/cli/plugins-cli.js +245 -61
- package/dist/cli/program/build-program.js +3 -1
- package/dist/cli/program/command-registry.js +223 -136
- package/dist/cli/program/help.js +43 -12
- package/dist/cli/route.js +1 -1
- package/dist/cli/test-runtime-capture.js +24 -0
- package/dist/commands/agent.js +163 -87
- package/dist/commands/channels.mock-harness.js +23 -0
- package/dist/commands/daemon-install-runtime-warning.js +11 -0
- package/dist/commands/onboard-helpers.js +4 -4
- package/dist/commands/sessions.test-helpers.js +61 -0
- package/dist/compat/legacy-names.js +2 -2
- package/dist/config/commands.js +3 -0
- package/dist/config/config.js +1 -1
- package/dist/config/env-substitution.js +62 -34
- package/dist/config/env-vars.js +9 -0
- package/dist/config/io.js +571 -171
- package/dist/config/merge-patch.js +50 -4
- package/dist/config/redact-snapshot.js +404 -76
- package/dist/config/schema.js +58 -570
- package/dist/config/validation.js +140 -85
- package/dist/config/zod-schema.hooks.js +40 -11
- package/dist/config/zod-schema.installs.js +20 -0
- package/dist/config/zod-schema.js +8 -7
- package/dist/control-ui/assets/{index-HRr1grwl.js → index-Dvkl4Xlx.js} +2 -1
- package/dist/control-ui/assets/{index-HRr1grwl.js.map → index-Dvkl4Xlx.js.map} +1 -1
- package/dist/control-ui/index.html +1 -1
- package/dist/daemon/cmd-argv.js +21 -0
- package/dist/daemon/cmd-set.js +58 -0
- package/dist/daemon/service-types.js +1 -0
- package/dist/discord/monitor/exec-approvals.js +357 -162
- package/dist/gateway/auth.js +38 -3
- package/dist/gateway/call.js +149 -68
- package/dist/gateway/canvas-capability.js +75 -0
- package/dist/gateway/control-plane-audit.js +28 -0
- package/dist/gateway/control-plane-rate-limit.js +53 -0
- package/dist/gateway/events.js +1 -0
- package/dist/gateway/hooks.js +109 -54
- package/dist/gateway/http-common.js +22 -0
- package/dist/gateway/method-scopes.js +169 -0
- package/dist/gateway/net.js +23 -0
- package/dist/gateway/openresponses-http.js +120 -110
- package/dist/gateway/probe-auth.js +2 -0
- package/dist/gateway/protocol/index.js +3 -2
- package/dist/gateway/protocol/schema/protocol-schemas.js +2 -0
- package/dist/gateway/protocol/schema/push.js +18 -0
- package/dist/gateway/protocol/schema.js +1 -0
- package/dist/gateway/server-http.js +236 -52
- package/dist/gateway/server-methods/agent.js +162 -24
- package/dist/gateway/server-methods/chat.js +461 -130
- package/dist/gateway/server-methods/config.js +193 -150
- package/dist/gateway/server-methods/nodes.helpers.js +12 -0
- package/dist/gateway/server-methods/nodes.js +251 -69
- package/dist/gateway/server-methods/push.js +53 -0
- package/dist/gateway/server-reload-handlers.js +2 -3
- package/dist/gateway/server-runtime-config.js +5 -0
- package/dist/gateway/server-runtime-state.js +2 -0
- package/dist/gateway/server-ws-runtime.js +1 -0
- package/dist/gateway/server.impl.js +296 -139
- package/dist/gateway/session-preview.test-helpers.js +11 -0
- package/dist/gateway/startup-auth.js +126 -0
- package/dist/gateway/test-helpers.agent-results.js +15 -0
- package/dist/gateway/test-helpers.mocks.js +37 -14
- package/dist/gateway/test-helpers.server.js +161 -77
- package/dist/hooks/bundled/session-memory/handler.js +165 -34
- package/dist/hooks/gmail-watcher-lifecycle.js +23 -0
- package/dist/infra/archive-path.js +49 -0
- package/dist/infra/device-pairing.js +148 -167
- package/dist/infra/exec-approvals-allowlist.js +19 -70
- package/dist/infra/exec-approvals-analysis.js +44 -17
- package/dist/infra/exec-safe-bin-policy.js +269 -0
- package/dist/infra/fixed-window-rate-limit.js +33 -0
- package/dist/infra/git-root.js +61 -0
- package/dist/infra/heartbeat-active-hours.js +2 -2
- package/dist/infra/heartbeat-reason.js +40 -0
- package/dist/infra/heartbeat-runner.js +72 -32
- package/dist/infra/install-source-utils.js +91 -7
- package/dist/infra/node-pairing.js +50 -105
- package/dist/infra/npm-integrity.js +45 -0
- package/dist/infra/npm-pack-install.js +40 -0
- package/dist/infra/outbound/channel-adapters.js +20 -7
- package/dist/infra/outbound/message-action-runner.js +107 -327
- package/dist/infra/outbound/message.js +59 -36
- package/dist/infra/outbound/outbound-policy.js +52 -25
- package/dist/infra/outbound/outbound-send-service.js +58 -71
- package/dist/infra/pairing-files.js +10 -0
- package/dist/infra/plain-object.js +9 -0
- package/dist/infra/push-apns.js +365 -0
- package/dist/infra/restart-sentinel.js +16 -1
- package/dist/infra/restart.js +229 -26
- package/dist/infra/scp-host.js +54 -0
- package/dist/infra/update-startup.js +86 -9
- package/dist/media/inbound-path-policy.js +114 -0
- package/dist/media/input-files.js +16 -0
- package/dist/memory/test-manager.js +8 -0
- package/dist/plugin-sdk/temp-path.js +47 -0
- package/dist/plugins/discovery.js +217 -23
- package/dist/plugins/hook-runner-global.js +16 -0
- package/dist/plugins/loader.js +192 -26
- package/dist/plugins/logger.js +8 -0
- package/dist/plugins/manifest-registry.js +3 -0
- package/dist/plugins/path-safety.js +34 -0
- package/dist/plugins/registry.js +5 -2
- package/dist/plugins/runtime/index.js +271 -206
- package/dist/providers/github-copilot-models.js +4 -1
- package/dist/security/audit-channel.js +8 -19
- package/dist/security/audit-extra.async.js +354 -182
- package/dist/security/audit-extra.js +11 -1
- package/dist/security/audit-extra.sync.js +340 -33
- package/dist/security/audit-fs.js +31 -13
- package/dist/security/audit.js +145 -371
- package/dist/security/dm-policy-shared.js +24 -0
- package/dist/security/external-content.js +20 -8
- package/dist/security/fix.js +49 -85
- package/dist/security/scan-paths.js +20 -0
- package/dist/security/secret-equal.js +3 -7
- package/dist/security/windows-acl.js +30 -15
- package/dist/shared/node-list-parse.js +13 -0
- package/dist/shared/operator-scope-compat.js +37 -0
- package/dist/shared/text-chunking.js +29 -0
- package/dist/slack/blocks.test-helpers.js +31 -0
- package/dist/slack/monitor/mrkdwn.js +8 -0
- package/dist/telegram/bot-message-dispatch.js +366 -164
- package/dist/telegram/draft-stream.js +30 -7
- package/dist/telegram/reasoning-lane-coordinator.js +128 -0
- package/dist/terminal/prompt-select-styled.js +9 -0
- package/dist/test-utils/command-runner.js +6 -0
- package/dist/test-utils/internal-hook-event-payload.js +10 -0
- package/dist/test-utils/model-auth-mock.js +12 -0
- package/dist/test-utils/provider-usage-fetch.js +14 -0
- package/dist/test-utils/temp-home.js +33 -0
- package/dist/tui/components/chat-log.js +9 -0
- package/dist/tui/tui-command-handlers.js +36 -27
- package/dist/tui/tui-event-handlers.js +122 -32
- package/dist/tui/tui.js +181 -45
- package/dist/utils/mask-api-key.js +10 -0
- package/dist/utils/run-with-concurrency.js +39 -0
- package/dist/web/media.js +4 -0
- package/docs/tools/slash-commands.md +5 -1
- 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/feishu/src/external-keys.ts +19 -0
- 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/lobster/src/windows-spawn.ts +193 -0
- package/extensions/matrix/CHANGELOG.md +5 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/matrix/src/matrix/actions/limits.ts +6 -0
- package/extensions/mattermost/package.json +1 -1
- package/extensions/mattermost/src/mattermost/reactions.test-helpers.ts +83 -0
- 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
|
@@ -6,7 +6,7 @@ import { getCliSessionId } from "../../agents/cli-session.js";
|
|
|
6
6
|
import { runWithModelFallback } from "../../agents/model-fallback.js";
|
|
7
7
|
import { isCliProvider } from "../../agents/model-selection.js";
|
|
8
8
|
import { runEmbeddedPiAgent } from "../../agents/pi-embedded.js";
|
|
9
|
-
import { isCompactionFailureError, isContextOverflowError, isLikelyContextOverflowError, sanitizeUserFacingText, } from "../../agents/pi-embedded-helpers.js";
|
|
9
|
+
import { isCompactionFailureError, isContextOverflowError, isLikelyContextOverflowError, isTransientHttpError, sanitizeUserFacingText, } from "../../agents/pi-embedded-helpers.js";
|
|
10
10
|
import { resolveAgentIdFromSessionKey, resolveGroupSessionKey, resolveSessionTranscriptPath, updateSessionStore, } from "../../config/sessions.js";
|
|
11
11
|
import { logVerbose } from "../../globals.js";
|
|
12
12
|
import { emitAgentEvent, registerAgentRunContext } from "../../infra/agent-events.js";
|
|
@@ -19,8 +19,10 @@ import { createBlockReplyPayloadKey } from "./block-reply-pipeline.js";
|
|
|
19
19
|
import { parseReplyDirectives } from "./reply-directives.js";
|
|
20
20
|
import { applyReplyTagsToPayload, isRenderablePayload } from "./reply-payloads.js";
|
|
21
21
|
export async function runAgentTurnWithFallback(params) {
|
|
22
|
+
const TRANSIENT_HTTP_RETRY_DELAY_MS = 2_500;
|
|
22
23
|
let didLogHeartbeatStrip = false;
|
|
23
24
|
let autoCompactionCompleted = false;
|
|
25
|
+
let didRetryTransientHttpError = false;
|
|
24
26
|
// Track payloads sent directly (not via pipeline) during tool flush to avoid duplicates.
|
|
25
27
|
const directlySentBlockKeys = new Set();
|
|
26
28
|
const runId = params.opts?.runId ?? crypto.randomUUID();
|
|
@@ -36,6 +38,7 @@ export async function runAgentTurnWithFallback(params) {
|
|
|
36
38
|
let fallbackProvider = params.followupRun.run.provider;
|
|
37
39
|
let fallbackModel = params.followupRun.run.model;
|
|
38
40
|
let didResetAfterCompactionFailure = false;
|
|
41
|
+
let fallbackAttempts = [];
|
|
39
42
|
while (true) {
|
|
40
43
|
try {
|
|
41
44
|
const allowPartialStream = !(params.followupRun.run.reasoningLevel === "stream" && params.opts?.onReasoningStream);
|
|
@@ -59,9 +62,13 @@ export async function runAgentTurnWithFallback(params) {
|
|
|
59
62
|
if (isSilentReplyText(text, SILENT_REPLY_TOKEN)) {
|
|
60
63
|
return { skip: true };
|
|
61
64
|
}
|
|
62
|
-
if (!text)
|
|
65
|
+
if (!text) {
|
|
66
|
+
// Allow media-only payloads through (no text but has media)
|
|
67
|
+
if (payload.mediaUrls?.length)
|
|
68
|
+
return { text: undefined, skip: false };
|
|
63
69
|
return { skip: true };
|
|
64
|
-
|
|
70
|
+
}
|
|
71
|
+
const sanitized = sanitizeUserFacingText(text, { errorContext: Boolean(payload.isError) });
|
|
65
72
|
if (!sanitized.trim())
|
|
66
73
|
return { skip: true };
|
|
67
74
|
return { text: sanitized, skip: false };
|
|
@@ -112,6 +119,7 @@ export async function runAgentTurnWithFallback(params) {
|
|
|
112
119
|
thinkLevel: params.followupRun.run.thinkLevel,
|
|
113
120
|
timeoutMs: params.followupRun.run.timeoutMs,
|
|
114
121
|
runId,
|
|
122
|
+
agentId: resolveAgentIdFromSessionKey(params.followupRun.run.sessionKey),
|
|
115
123
|
extraSystemPrompt: params.followupRun.run.extraSystemPrompt,
|
|
116
124
|
ownerNumbers: params.followupRun.run.ownerNumbers,
|
|
117
125
|
cliSessionId,
|
|
@@ -348,6 +356,16 @@ export async function runAgentTurnWithFallback(params) {
|
|
|
348
356
|
runResult = fallbackResult.result;
|
|
349
357
|
fallbackProvider = fallbackResult.provider;
|
|
350
358
|
fallbackModel = fallbackResult.model;
|
|
359
|
+
fallbackAttempts = Array.isArray(fallbackResult.attempts)
|
|
360
|
+
? fallbackResult.attempts.map((attempt) => ({
|
|
361
|
+
provider: String(attempt.provider ?? ""),
|
|
362
|
+
model: String(attempt.model ?? ""),
|
|
363
|
+
error: String(attempt.error ?? ""),
|
|
364
|
+
reason: attempt.reason ? String(attempt.reason) : undefined,
|
|
365
|
+
status: typeof attempt.status === "number" ? attempt.status : undefined,
|
|
366
|
+
code: attempt.code ? String(attempt.code) : undefined,
|
|
367
|
+
}))
|
|
368
|
+
: [];
|
|
351
369
|
// Some embedded runs surface context overflow as an error payload instead of throwing.
|
|
352
370
|
// Treat those as a session-level failure and auto-recover by starting a fresh session.
|
|
353
371
|
const embeddedError = runResult.meta?.error;
|
|
@@ -382,6 +400,7 @@ export async function runAgentTurnWithFallback(params) {
|
|
|
382
400
|
const isCompactionFailure = isCompactionFailureError(message);
|
|
383
401
|
const isSessionCorruption = /function call turn comes immediately after/i.test(message);
|
|
384
402
|
const isRoleOrderingError = /incorrect role information|roles must alternate/i.test(message);
|
|
403
|
+
const isTransientHttp = isTransientHttpError(message);
|
|
385
404
|
if (isCompactionFailure &&
|
|
386
405
|
!didResetAfterCompactionFailure &&
|
|
387
406
|
(await params.resetSessionAfterCompactionFailure(message))) {
|
|
@@ -404,6 +423,14 @@ export async function runAgentTurnWithFallback(params) {
|
|
|
404
423
|
};
|
|
405
424
|
}
|
|
406
425
|
}
|
|
426
|
+
if (isTransientHttp && !didRetryTransientHttpError) {
|
|
427
|
+
didRetryTransientHttpError = true;
|
|
428
|
+
defaultRuntime.error(`Transient HTTP provider error before reply (${message}). Retrying once in ${TRANSIENT_HTTP_RETRY_DELAY_MS}ms.`);
|
|
429
|
+
await new Promise((resolve) => {
|
|
430
|
+
setTimeout(resolve, TRANSIENT_HTTP_RETRY_DELAY_MS);
|
|
431
|
+
});
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
407
434
|
// Auto-recover from Gemini session corruption by resetting the session
|
|
408
435
|
if (isSessionCorruption &&
|
|
409
436
|
params.sessionKey &&
|
|
@@ -441,7 +468,10 @@ export async function runAgentTurnWithFallback(params) {
|
|
|
441
468
|
};
|
|
442
469
|
}
|
|
443
470
|
defaultRuntime.error(`Embedded agent failed before reply: ${message}`);
|
|
444
|
-
const
|
|
471
|
+
const safeMessage = isTransientHttp
|
|
472
|
+
? sanitizeUserFacingText(message, { errorContext: true })
|
|
473
|
+
: message;
|
|
474
|
+
const trimmedMessage = safeMessage.replace(/\.\s*$/, "");
|
|
445
475
|
const fallbackText = isContextOverflow
|
|
446
476
|
? "⚠️ Context overflow — prompt too large for this model. Try a shorter message or a larger-context model."
|
|
447
477
|
: isRoleOrderingError
|
|
@@ -460,6 +490,8 @@ export async function runAgentTurnWithFallback(params) {
|
|
|
460
490
|
runResult,
|
|
461
491
|
fallbackProvider,
|
|
462
492
|
fallbackModel,
|
|
493
|
+
runId,
|
|
494
|
+
fallbackAttempts,
|
|
463
495
|
didLogHeartbeatStrip,
|
|
464
496
|
autoCompactionCompleted,
|
|
465
497
|
directlySentBlockKeys: directlySentBlockKeys.size > 0 ? directlySentBlockKeys : undefined,
|
|
@@ -7,8 +7,11 @@ import { isCliProvider } from "../../agents/model-selection.js";
|
|
|
7
7
|
import { queueEmbeddedPiMessage } from "../../agents/pi-embedded.js";
|
|
8
8
|
import { hasNonzeroUsage } from "../../agents/usage.js";
|
|
9
9
|
import { resolveAgentIdFromSessionKey, resolveSessionFilePath, resolveSessionTranscriptPath, updateSessionStore, updateSessionStoreEntry, } from "../../config/sessions.js";
|
|
10
|
+
import { emitAgentEvent } from "../../infra/agent-events.js";
|
|
11
|
+
import { emitDiagnosticEvent, isDiagnosticsEnabled } from "../../infra/diagnostic-events.js";
|
|
10
12
|
import { defaultRuntime } from "../../runtime.js";
|
|
11
13
|
import { estimateUsageCost, resolveModelCostConfig } from "../../utils/usage-format.js";
|
|
14
|
+
import { buildFallbackClearedNotice, buildFallbackNotice, resolveFallbackTransition, } from "../fallback-state.js";
|
|
12
15
|
import { resolveResponseUsageMode } from "../thinking.js";
|
|
13
16
|
import { runAgentTurnWithFallback } from "./agent-runner-execution.js";
|
|
14
17
|
import { createShouldEmitToolOutput, createShouldEmitToolResult, finalizeWithFollowup, isAudioPayload, signalTypingIfNeeded, } from "./agent-runner-helpers.js";
|
|
@@ -20,11 +23,41 @@ import { resolveBlockStreamingCoalescing } from "./block-streaming.js";
|
|
|
20
23
|
import { createFollowupRunner } from "./followup-runner.js";
|
|
21
24
|
import { enqueueFollowupRun } from "./queue.js";
|
|
22
25
|
import { createReplyToModeFilterForChannel, resolveReplyToMode } from "./reply-threading.js";
|
|
23
|
-
import {
|
|
24
|
-
import { incrementCompactionCount } from "./session-updates.js";
|
|
26
|
+
import { incrementRunCompactionCount, persistRunSessionUsage } from "./session-run-accounting.js";
|
|
25
27
|
import { createTypingSignaler } from "./typing-mode.js";
|
|
26
|
-
import { emitDiagnosticEvent, isDiagnosticsEnabled } from "../../infra/diagnostic-events.js";
|
|
27
28
|
const BLOCK_REPLY_SEND_TIMEOUT_MS = 15_000;
|
|
29
|
+
const UNSCHEDULED_REMINDER_NOTE = "Note: I did not schedule a reminder in this turn, so this will not trigger automatically.";
|
|
30
|
+
const REMINDER_COMMITMENT_PATTERNS = [
|
|
31
|
+
/\b(?:i\s*['']?ll|i will)\s+(?:make sure to\s+)?(?:remember|remind|ping|follow up|follow-up|check back|circle back)\b/i,
|
|
32
|
+
/\b(?:i\s*['']?ll|i will)\s+(?:set|create|schedule)\s+(?:a\s+)?reminder\b/i,
|
|
33
|
+
];
|
|
34
|
+
function hasUnbackedReminderCommitment(text) {
|
|
35
|
+
const normalized = text.toLowerCase();
|
|
36
|
+
if (!normalized.trim()) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (normalized.includes(UNSCHEDULED_REMINDER_NOTE.toLowerCase())) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return REMINDER_COMMITMENT_PATTERNS.some((pattern) => pattern.test(text));
|
|
43
|
+
}
|
|
44
|
+
function appendUnscheduledReminderNote(payloads) {
|
|
45
|
+
let appended = false;
|
|
46
|
+
return payloads.map((payload) => {
|
|
47
|
+
if (appended || payload.isError || typeof payload.text !== "string") {
|
|
48
|
+
return payload;
|
|
49
|
+
}
|
|
50
|
+
if (!hasUnbackedReminderCommitment(payload.text)) {
|
|
51
|
+
return payload;
|
|
52
|
+
}
|
|
53
|
+
appended = true;
|
|
54
|
+
const trimmed = payload.text.trimEnd();
|
|
55
|
+
return {
|
|
56
|
+
...payload,
|
|
57
|
+
text: `${trimmed}\n\n${UNSCHEDULED_REMINDER_NOTE}`,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
}
|
|
28
61
|
export async function runReplyAgent(params) {
|
|
29
62
|
const { commandBody, followupRun, queueKey, resolvedQueue, shouldSteer, shouldFollowup, isActive, isStreaming, opts, typing, sessionEntry, sessionStore, sessionKey, storePath, defaultModel, agentCfgContextTokens, resolvedVerboseLevel, isNewSession, blockStreamingEnabled, blockReplyChunking, resolvedBlockStreamingBreak, sessionCtx, shouldInjectGroupIntro, typingMode, } = params;
|
|
30
63
|
let activeSessionEntry = sessionEntry;
|
|
@@ -64,27 +97,7 @@ export async function runReplyAgent(params) {
|
|
|
64
97
|
buffer: createAudioAsVoiceBuffer({ isAudioPayload }),
|
|
65
98
|
})
|
|
66
99
|
: null;
|
|
67
|
-
|
|
68
|
-
const steered = queueEmbeddedPiMessage(followupRun.run.sessionId, followupRun.prompt);
|
|
69
|
-
if (steered && !shouldFollowup) {
|
|
70
|
-
if (activeSessionEntry && activeSessionStore && sessionKey) {
|
|
71
|
-
const updatedAt = Date.now();
|
|
72
|
-
activeSessionEntry.updatedAt = updatedAt;
|
|
73
|
-
activeSessionStore[sessionKey] = activeSessionEntry;
|
|
74
|
-
if (storePath) {
|
|
75
|
-
await updateSessionStoreEntry({
|
|
76
|
-
storePath,
|
|
77
|
-
sessionKey,
|
|
78
|
-
update: async () => ({ updatedAt }),
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
typing.cleanup();
|
|
83
|
-
return undefined;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
if (isActive && (shouldFollowup || resolvedQueue.mode === "steer")) {
|
|
87
|
-
enqueueFollowupRun(queueKey, followupRun, resolvedQueue);
|
|
100
|
+
const touchActiveSessionEntry = async () => {
|
|
88
101
|
if (activeSessionEntry && activeSessionStore && sessionKey) {
|
|
89
102
|
const updatedAt = Date.now();
|
|
90
103
|
activeSessionEntry.updatedAt = updatedAt;
|
|
@@ -97,6 +110,18 @@ export async function runReplyAgent(params) {
|
|
|
97
110
|
});
|
|
98
111
|
}
|
|
99
112
|
}
|
|
113
|
+
};
|
|
114
|
+
if (shouldSteer && isStreaming) {
|
|
115
|
+
const steered = queueEmbeddedPiMessage(followupRun.run.sessionId, followupRun.prompt);
|
|
116
|
+
if (steered && !shouldFollowup) {
|
|
117
|
+
await touchActiveSessionEntry();
|
|
118
|
+
typing.cleanup();
|
|
119
|
+
return undefined;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (isActive && (shouldFollowup || resolvedQueue.mode === "steer")) {
|
|
123
|
+
enqueueFollowupRun(queueKey, followupRun, resolvedQueue);
|
|
124
|
+
await touchActiveSessionEntry();
|
|
100
125
|
typing.cleanup();
|
|
101
126
|
return undefined;
|
|
102
127
|
}
|
|
@@ -141,6 +166,9 @@ export async function runReplyAgent(params) {
|
|
|
141
166
|
updatedAt: Date.now(),
|
|
142
167
|
systemSent: false,
|
|
143
168
|
abortedLastRun: false,
|
|
169
|
+
fallbackNoticeSelectedModel: undefined,
|
|
170
|
+
fallbackNoticeActiveModel: undefined,
|
|
171
|
+
fallbackNoticeReason: undefined,
|
|
144
172
|
};
|
|
145
173
|
const agentId = resolveAgentIdFromSessionKey(sessionKey);
|
|
146
174
|
const nextSessionFile = resolveSessionTranscriptPath(nextSessionId, agentId, sessionCtx.MessageThreadId);
|
|
@@ -213,7 +241,7 @@ export async function runReplyAgent(params) {
|
|
|
213
241
|
if (runOutcome.kind === "final") {
|
|
214
242
|
return finalizeWithFollowup(runOutcome.payload, queueKey, runFollowupTurn);
|
|
215
243
|
}
|
|
216
|
-
const { runResult, fallbackProvider, fallbackModel, directlySentBlockKeys } = runOutcome;
|
|
244
|
+
const { runId, runResult, fallbackProvider, fallbackModel, fallbackAttempts, directlySentBlockKeys, } = runOutcome;
|
|
217
245
|
let { didLogHeartbeatStrip, autoCompactionCompleted } = runOutcome;
|
|
218
246
|
if (shouldInjectGroupIntro &&
|
|
219
247
|
activeSessionEntry &&
|
|
@@ -243,24 +271,62 @@ export async function runReplyAgent(params) {
|
|
|
243
271
|
if (pendingToolTasks.size > 0) {
|
|
244
272
|
await Promise.allSettled(pendingToolTasks);
|
|
245
273
|
}
|
|
246
|
-
const usage = runResult.meta
|
|
247
|
-
const
|
|
248
|
-
const
|
|
274
|
+
const usage = runResult.meta?.agentMeta?.usage;
|
|
275
|
+
const promptTokens = runResult.meta?.agentMeta?.promptTokens;
|
|
276
|
+
const modelUsed = runResult.meta?.agentMeta?.model ?? fallbackModel ?? defaultModel;
|
|
277
|
+
const providerUsed = runResult.meta?.agentMeta?.provider ?? fallbackProvider ?? followupRun.run.provider;
|
|
278
|
+
const verboseEnabled = resolvedVerboseLevel !== "off";
|
|
279
|
+
const selectedProvider = followupRun.run.provider;
|
|
280
|
+
const selectedModel = followupRun.run.model;
|
|
281
|
+
const fallbackStateEntry = activeSessionEntry ?? (sessionKey ? activeSessionStore?.[sessionKey] : undefined);
|
|
282
|
+
const fallbackTransition = resolveFallbackTransition({
|
|
283
|
+
selectedProvider,
|
|
284
|
+
selectedModel,
|
|
285
|
+
activeProvider: providerUsed,
|
|
286
|
+
activeModel: modelUsed,
|
|
287
|
+
attempts: fallbackAttempts,
|
|
288
|
+
state: fallbackStateEntry,
|
|
289
|
+
});
|
|
290
|
+
if (fallbackTransition.stateChanged) {
|
|
291
|
+
if (fallbackStateEntry) {
|
|
292
|
+
fallbackStateEntry.fallbackNoticeSelectedModel = fallbackTransition.nextState.selectedModel;
|
|
293
|
+
fallbackStateEntry.fallbackNoticeActiveModel = fallbackTransition.nextState.activeModel;
|
|
294
|
+
fallbackStateEntry.fallbackNoticeReason = fallbackTransition.nextState.reason;
|
|
295
|
+
fallbackStateEntry.updatedAt = Date.now();
|
|
296
|
+
activeSessionEntry = fallbackStateEntry;
|
|
297
|
+
}
|
|
298
|
+
if (sessionKey && fallbackStateEntry && activeSessionStore) {
|
|
299
|
+
activeSessionStore[sessionKey] = fallbackStateEntry;
|
|
300
|
+
}
|
|
301
|
+
if (sessionKey && storePath) {
|
|
302
|
+
await updateSessionStoreEntry({
|
|
303
|
+
storePath,
|
|
304
|
+
sessionKey,
|
|
305
|
+
update: async () => ({
|
|
306
|
+
fallbackNoticeSelectedModel: fallbackTransition.nextState.selectedModel,
|
|
307
|
+
fallbackNoticeActiveModel: fallbackTransition.nextState.activeModel,
|
|
308
|
+
fallbackNoticeReason: fallbackTransition.nextState.reason,
|
|
309
|
+
}),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
}
|
|
249
313
|
const cliSessionId = isCliProvider(providerUsed, cfg)
|
|
250
|
-
? runResult.meta
|
|
314
|
+
? runResult.meta?.agentMeta?.sessionId?.trim()
|
|
251
315
|
: undefined;
|
|
252
316
|
const contextTokensUsed = agentCfgContextTokens ??
|
|
253
317
|
lookupContextTokens(modelUsed) ??
|
|
254
318
|
activeSessionEntry?.contextTokens ??
|
|
255
319
|
DEFAULT_CONTEXT_TOKENS;
|
|
256
|
-
await
|
|
320
|
+
await persistRunSessionUsage({
|
|
257
321
|
storePath,
|
|
258
322
|
sessionKey,
|
|
259
323
|
usage,
|
|
324
|
+
lastCallUsage: runResult.meta?.agentMeta?.lastCallUsage,
|
|
325
|
+
promptTokens,
|
|
260
326
|
modelUsed,
|
|
261
327
|
providerUsed,
|
|
262
328
|
contextTokensUsed,
|
|
263
|
-
systemPromptReport: runResult.meta
|
|
329
|
+
systemPromptReport: runResult.meta?.systemPromptReport,
|
|
264
330
|
cliSessionId,
|
|
265
331
|
});
|
|
266
332
|
// Drain any late tool/block deliveries before deciding there's "nothing to send".
|
|
@@ -288,7 +354,14 @@ export async function runReplyAgent(params) {
|
|
|
288
354
|
didLogHeartbeatStrip = payloadResult.didLogHeartbeatStrip;
|
|
289
355
|
if (replyPayloads.length === 0)
|
|
290
356
|
return finalizeWithFollowup(undefined, queueKey, runFollowupTurn);
|
|
291
|
-
|
|
357
|
+
const successfulCronAdds = runResult.successfulCronAdds ?? 0;
|
|
358
|
+
const hasReminderCommitment = replyPayloads.some((payload) => !payload.isError &&
|
|
359
|
+
typeof payload.text === "string" &&
|
|
360
|
+
hasUnbackedReminderCommitment(payload.text));
|
|
361
|
+
const guardedReplyPayloads = hasReminderCommitment && successfulCronAdds === 0
|
|
362
|
+
? appendUnscheduledReminderNote(replyPayloads)
|
|
363
|
+
: replyPayloads;
|
|
364
|
+
await signalTypingIfNeeded(guardedReplyPayloads, typingSignals);
|
|
292
365
|
if (isDiagnosticsEnabled(cfg) && hasNonzeroUsage(usage)) {
|
|
293
366
|
const input = usage.input ?? 0;
|
|
294
367
|
const output = usage.output ?? 0;
|
|
@@ -323,6 +396,7 @@ export async function runReplyAgent(params) {
|
|
|
323
396
|
},
|
|
324
397
|
costUsd,
|
|
325
398
|
durationMs: Date.now() - runStartedAt,
|
|
399
|
+
lastCallUsage: runResult.meta?.agentMeta?.lastCallUsage,
|
|
326
400
|
});
|
|
327
401
|
}
|
|
328
402
|
const responseUsageRaw = activeSessionEntry?.responseUsage ??
|
|
@@ -349,23 +423,75 @@ export async function runReplyAgent(params) {
|
|
|
349
423
|
if (formatted)
|
|
350
424
|
responseUsageLine = formatted;
|
|
351
425
|
}
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
426
|
+
let finalPayloads = guardedReplyPayloads;
|
|
427
|
+
const verboseNotices = [];
|
|
428
|
+
if (activeIsNewSession && verboseEnabled) {
|
|
429
|
+
verboseNotices.push(`🧭 New session: ${followupRun.run.sessionId}`);
|
|
430
|
+
}
|
|
431
|
+
if (fallbackTransition.fallbackTransitioned) {
|
|
432
|
+
const notice = buildFallbackNotice({
|
|
433
|
+
selectedProvider,
|
|
434
|
+
selectedModel,
|
|
435
|
+
activeProvider: providerUsed,
|
|
436
|
+
activeModel: modelUsed,
|
|
437
|
+
attempts: fallbackAttempts,
|
|
438
|
+
});
|
|
439
|
+
if (runId) {
|
|
440
|
+
emitAgentEvent({
|
|
441
|
+
runId,
|
|
442
|
+
stream: "lifecycle",
|
|
443
|
+
data: {
|
|
444
|
+
kind: "model.fallback.transitioned",
|
|
445
|
+
selected: fallbackTransition.selectedModelRef,
|
|
446
|
+
active: fallbackTransition.activeModelRef,
|
|
447
|
+
reason: fallbackTransition.reasonSummary,
|
|
448
|
+
attempts: fallbackTransition.attemptSummaries,
|
|
449
|
+
},
|
|
450
|
+
sessionKey,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
if (notice && verboseEnabled) {
|
|
454
|
+
verboseNotices.push(notice);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
if (fallbackTransition.fallbackCleared) {
|
|
458
|
+
const notice = buildFallbackClearedNotice({
|
|
459
|
+
selectedProvider,
|
|
460
|
+
selectedModel,
|
|
461
|
+
previousActiveModel: fallbackTransition.previousState.activeModel,
|
|
462
|
+
});
|
|
463
|
+
if (runId) {
|
|
464
|
+
emitAgentEvent({
|
|
465
|
+
runId,
|
|
466
|
+
stream: "lifecycle",
|
|
467
|
+
data: {
|
|
468
|
+
kind: "model.fallback.cleared",
|
|
469
|
+
selected: fallbackTransition.selectedModelRef,
|
|
470
|
+
previousActive: fallbackTransition.previousState.activeModel,
|
|
471
|
+
},
|
|
472
|
+
sessionKey,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
if (verboseEnabled) {
|
|
476
|
+
verboseNotices.push(notice);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
355
479
|
if (autoCompactionCompleted) {
|
|
356
|
-
const count = await
|
|
480
|
+
const count = await incrementRunCompactionCount({
|
|
357
481
|
sessionEntry: activeSessionEntry,
|
|
358
482
|
sessionStore: activeSessionStore,
|
|
359
483
|
sessionKey,
|
|
360
484
|
storePath,
|
|
485
|
+
lastCallUsage: runResult.meta?.agentMeta?.lastCallUsage,
|
|
486
|
+
contextTokensUsed,
|
|
361
487
|
});
|
|
362
488
|
if (verboseEnabled) {
|
|
363
489
|
const suffix = typeof count === "number" ? ` (count ${count})` : "";
|
|
364
|
-
|
|
490
|
+
verboseNotices.push(`🧹 Auto-compaction complete${suffix}.`);
|
|
365
491
|
}
|
|
366
492
|
}
|
|
367
|
-
if (
|
|
368
|
-
finalPayloads = [
|
|
493
|
+
if (verboseNotices.length > 0) {
|
|
494
|
+
finalPayloads = [...verboseNotices.map((text) => ({ text })), ...finalPayloads];
|
|
369
495
|
}
|
|
370
496
|
if (responseUsageLine) {
|
|
371
497
|
finalPayloads = appendUsageLine(finalPayloads, responseUsageLine);
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { parseSlashCommandWithSetUnset } from "./commands-setunset.js";
|
|
2
|
+
export function parseStandardSetUnsetSlashCommand(params) {
|
|
3
|
+
return parseSlashCommandWithSetUnset({
|
|
4
|
+
raw: params.raw,
|
|
5
|
+
slash: params.slash,
|
|
6
|
+
invalidMessage: params.invalidMessage,
|
|
7
|
+
usageMessage: params.usageMessage,
|
|
8
|
+
onKnownAction: params.onKnownAction,
|
|
9
|
+
onSet: params.onSet ?? ((path, value) => ({ action: "set", path, value })),
|
|
10
|
+
onUnset: params.onUnset ?? ((path) => ({ action: "unset", path })),
|
|
11
|
+
onError: params.onError ?? ((message) => ({ action: "error", message })),
|
|
12
|
+
});
|
|
13
|
+
}
|
package/dist/browser/config.js
CHANGED
|
@@ -16,6 +16,30 @@ function normalizeTimeoutMs(raw, fallback) {
|
|
|
16
16
|
const value = typeof raw === "number" && Number.isFinite(raw) ? Math.floor(raw) : fallback;
|
|
17
17
|
return value < 0 ? fallback : value;
|
|
18
18
|
}
|
|
19
|
+
function normalizeStringList(raw) {
|
|
20
|
+
if (!Array.isArray(raw) || raw.length === 0) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const values = raw
|
|
24
|
+
.map((value) => value.trim())
|
|
25
|
+
.filter((value) => value.length > 0);
|
|
26
|
+
return values.length > 0 ? values : undefined;
|
|
27
|
+
}
|
|
28
|
+
function resolveBrowserSsrFPolicy(cfg) {
|
|
29
|
+
const allowPrivateNetwork = cfg?.ssrfPolicy?.allowPrivateNetwork;
|
|
30
|
+
const allowedHostnames = normalizeStringList(cfg?.ssrfPolicy?.allowedHostnames);
|
|
31
|
+
const hostnameAllowlist = normalizeStringList(cfg?.ssrfPolicy?.hostnameAllowlist);
|
|
32
|
+
if (allowPrivateNetwork === undefined &&
|
|
33
|
+
allowedHostnames === undefined &&
|
|
34
|
+
hostnameAllowlist === undefined) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
...(allowPrivateNetwork === true ? { allowPrivateNetwork: true } : {}),
|
|
39
|
+
...(allowedHostnames ? { allowedHostnames } : {}),
|
|
40
|
+
...(hostnameAllowlist ? { hostnameAllowlist } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
19
43
|
export function parseHttpUrl(raw, label) {
|
|
20
44
|
const trimmed = raw.trim();
|
|
21
45
|
const parsed = new URL(trimmed);
|
|
@@ -113,6 +137,7 @@ export function resolveBrowserConfig(cfg, rootConfig) {
|
|
|
113
137
|
(profiles[DEFAULT_BROWSER_DEFAULT_PROFILE_NAME]
|
|
114
138
|
? DEFAULT_BROWSER_DEFAULT_PROFILE_NAME
|
|
115
139
|
: DEFAULT_CLAWD_BROWSER_PROFILE_NAME);
|
|
140
|
+
const ssrfPolicy = resolveBrowserSsrFPolicy(cfg);
|
|
116
141
|
return {
|
|
117
142
|
enabled,
|
|
118
143
|
evaluateEnabled,
|
|
@@ -129,6 +154,7 @@ export function resolveBrowserConfig(cfg, rootConfig) {
|
|
|
129
154
|
attachOnly,
|
|
130
155
|
defaultProfile,
|
|
131
156
|
profiles,
|
|
157
|
+
ssrfPolicy,
|
|
132
158
|
};
|
|
133
159
|
}
|
|
134
160
|
/**
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { resolvePinnedHostnameWithPolicy, } from "../infra/net/ssrf.js";
|
|
2
|
+
const NETWORK_NAVIGATION_PROTOCOLS = new Set(["http:", "https:"]);
|
|
3
|
+
export class InvalidBrowserNavigationUrlError extends Error {
|
|
4
|
+
constructor(message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "InvalidBrowserNavigationUrlError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export function withBrowserNavigationPolicy(ssrfPolicy) {
|
|
10
|
+
return ssrfPolicy ? { ssrfPolicy } : {};
|
|
11
|
+
}
|
|
12
|
+
export async function assertBrowserNavigationAllowed(opts) {
|
|
13
|
+
const rawUrl = String(opts.url ?? "").trim();
|
|
14
|
+
if (!rawUrl) {
|
|
15
|
+
throw new InvalidBrowserNavigationUrlError("url is required");
|
|
16
|
+
}
|
|
17
|
+
let parsed;
|
|
18
|
+
try {
|
|
19
|
+
parsed = new URL(rawUrl);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
throw new InvalidBrowserNavigationUrlError(`Invalid URL: ${rawUrl}`);
|
|
23
|
+
}
|
|
24
|
+
if (!NETWORK_NAVIGATION_PROTOCOLS.has(parsed.protocol)) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
await resolvePinnedHostnameWithPolicy(parsed.hostname, {
|
|
28
|
+
lookupFn: opts.lookupFn,
|
|
29
|
+
policy: opts.ssrfPolicy,
|
|
30
|
+
});
|
|
31
|
+
}
|