@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
|
@@ -1,20 +1,8 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
-
import os from "node:os";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import { splitShellArgs } from "../utils/shell-argv.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
if (!value) {
|
|
8
|
-
return value;
|
|
9
|
-
}
|
|
10
|
-
if (value === "~") {
|
|
11
|
-
return os.homedir();
|
|
12
|
-
}
|
|
13
|
-
if (value.startsWith("~/")) {
|
|
14
|
-
return path.join(os.homedir(), value.slice(2));
|
|
15
|
-
}
|
|
16
|
-
return value;
|
|
17
|
-
}
|
|
4
|
+
import { expandHomePrefix } from "./home-dir.js";
|
|
5
|
+
export const DEFAULT_SAFE_BINS = ["jq", "cut", "uniq", "head", "tail", "tr", "wc"];
|
|
18
6
|
function isExecutableFile(filePath) {
|
|
19
7
|
try {
|
|
20
8
|
const stat = fs.statSync(filePath);
|
|
@@ -47,7 +35,7 @@ function parseFirstToken(command) {
|
|
|
47
35
|
return match ? match[0] : null;
|
|
48
36
|
}
|
|
49
37
|
function resolveExecutablePath(rawExecutable, cwd, env) {
|
|
50
|
-
const expanded = rawExecutable.startsWith("~") ?
|
|
38
|
+
const expanded = rawExecutable.startsWith("~") ? expandHomePrefix(rawExecutable) : rawExecutable;
|
|
51
39
|
if (expanded.includes("/") || expanded.includes("\\")) {
|
|
52
40
|
if (path.isAbsolute(expanded)) {
|
|
53
41
|
return isExecutableFile(expanded) ? expanded : undefined;
|
|
@@ -145,7 +133,7 @@ function matchesPattern(pattern, target) {
|
|
|
145
133
|
if (!trimmed) {
|
|
146
134
|
return false;
|
|
147
135
|
}
|
|
148
|
-
const expanded = trimmed.startsWith("~") ?
|
|
136
|
+
const expanded = trimmed.startsWith("~") ? expandHomePrefix(trimmed) : trimmed;
|
|
149
137
|
const hasWildcard = /[*?]/.test(expanded);
|
|
150
138
|
let normalizedPattern = expanded;
|
|
151
139
|
let normalizedTarget = target;
|
|
@@ -169,7 +157,7 @@ export function resolveAllowlistCandidatePath(resolution, cwd) {
|
|
|
169
157
|
if (!raw) {
|
|
170
158
|
return undefined;
|
|
171
159
|
}
|
|
172
|
-
const expanded = raw.startsWith("~") ?
|
|
160
|
+
const expanded = raw.startsWith("~") ? expandHomePrefix(raw) : raw;
|
|
173
161
|
if (!expanded.includes("/") && !expanded.includes("\\")) {
|
|
174
162
|
return undefined;
|
|
175
163
|
}
|
|
@@ -199,6 +187,45 @@ export function matchAllowlist(entries, resolution) {
|
|
|
199
187
|
}
|
|
200
188
|
return null;
|
|
201
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Tokenizes a single argv entry into a normalized option/positional model.
|
|
192
|
+
* Consumers can share this model to keep argv parsing behavior consistent.
|
|
193
|
+
*/
|
|
194
|
+
export function parseExecArgvToken(raw) {
|
|
195
|
+
if (!raw) {
|
|
196
|
+
return { kind: "empty", raw };
|
|
197
|
+
}
|
|
198
|
+
if (raw === "--") {
|
|
199
|
+
return { kind: "terminator", raw };
|
|
200
|
+
}
|
|
201
|
+
if (raw === "-") {
|
|
202
|
+
return { kind: "stdin", raw };
|
|
203
|
+
}
|
|
204
|
+
if (!raw.startsWith("-")) {
|
|
205
|
+
return { kind: "positional", raw };
|
|
206
|
+
}
|
|
207
|
+
if (raw.startsWith("--")) {
|
|
208
|
+
const eqIndex = raw.indexOf("=");
|
|
209
|
+
if (eqIndex > 0) {
|
|
210
|
+
return {
|
|
211
|
+
kind: "option",
|
|
212
|
+
raw,
|
|
213
|
+
style: "long",
|
|
214
|
+
flag: raw.slice(0, eqIndex),
|
|
215
|
+
inlineValue: raw.slice(eqIndex + 1),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
return { kind: "option", raw, style: "long", flag: raw };
|
|
219
|
+
}
|
|
220
|
+
const cluster = raw.slice(1);
|
|
221
|
+
return {
|
|
222
|
+
kind: "option",
|
|
223
|
+
raw,
|
|
224
|
+
style: "short-cluster",
|
|
225
|
+
cluster,
|
|
226
|
+
flags: cluster.split("").map((entry) => `-${entry}`),
|
|
227
|
+
};
|
|
228
|
+
}
|
|
202
229
|
const DISALLOWED_PIPELINE_TOKENS = new Set([">", "<", "`", "\n", "\r", "(", ")"]);
|
|
203
230
|
const DOUBLE_QUOTE_ESCAPES = new Set(["\\", '"', "$", "`", "\n", "\r"]);
|
|
204
231
|
const WINDOWS_UNSUPPORTED_TOKENS = new Set([
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { parseExecArgvToken } from "./exec-approvals-analysis.js";
|
|
2
|
+
function isPathLikeToken(value) {
|
|
3
|
+
const trimmed = value.trim();
|
|
4
|
+
if (!trimmed) {
|
|
5
|
+
return false;
|
|
6
|
+
}
|
|
7
|
+
if (trimmed === "-") {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
if (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed.startsWith("~")) {
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
if (trimmed.startsWith("/")) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
return /^[A-Za-z]:[\\/]/.test(trimmed);
|
|
17
|
+
}
|
|
18
|
+
function hasGlobToken(value) {
|
|
19
|
+
// Safe bins are stdin-only; globbing is both surprising and a historical bypass vector.
|
|
20
|
+
// Note: we still harden execution-time expansion separately.
|
|
21
|
+
return /[*?[\]]/.test(value);
|
|
22
|
+
}
|
|
23
|
+
const NO_FLAGS = new Set();
|
|
24
|
+
const toFlagSet = (flags) => {
|
|
25
|
+
if (!flags || flags.length === 0) {
|
|
26
|
+
return NO_FLAGS;
|
|
27
|
+
}
|
|
28
|
+
return new Set(flags);
|
|
29
|
+
};
|
|
30
|
+
function compileSafeBinProfile(fixture) {
|
|
31
|
+
return {
|
|
32
|
+
minPositional: fixture.minPositional,
|
|
33
|
+
maxPositional: fixture.maxPositional,
|
|
34
|
+
valueFlags: toFlagSet(fixture.valueFlags),
|
|
35
|
+
blockedFlags: toFlagSet(fixture.blockedFlags),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function compileSafeBinProfiles(fixtures) {
|
|
39
|
+
return Object.fromEntries(Object.entries(fixtures).map(([name, fixture]) => [name, compileSafeBinProfile(fixture)]));
|
|
40
|
+
}
|
|
41
|
+
export const SAFE_BIN_GENERIC_PROFILE_FIXTURE = {};
|
|
42
|
+
export const SAFE_BIN_PROFILE_FIXTURES = {
|
|
43
|
+
jq: {
|
|
44
|
+
maxPositional: 1,
|
|
45
|
+
valueFlags: [
|
|
46
|
+
"--arg",
|
|
47
|
+
"--argjson",
|
|
48
|
+
"--argstr",
|
|
49
|
+
"--argfile",
|
|
50
|
+
"--rawfile",
|
|
51
|
+
"--slurpfile",
|
|
52
|
+
"--from-file",
|
|
53
|
+
"--library-path",
|
|
54
|
+
"-L",
|
|
55
|
+
"-f",
|
|
56
|
+
],
|
|
57
|
+
blockedFlags: [
|
|
58
|
+
"--argfile",
|
|
59
|
+
"--rawfile",
|
|
60
|
+
"--slurpfile",
|
|
61
|
+
"--from-file",
|
|
62
|
+
"--library-path",
|
|
63
|
+
"-L",
|
|
64
|
+
"-f",
|
|
65
|
+
],
|
|
66
|
+
},
|
|
67
|
+
grep: {
|
|
68
|
+
maxPositional: 1,
|
|
69
|
+
valueFlags: [
|
|
70
|
+
"--regexp",
|
|
71
|
+
"--file",
|
|
72
|
+
"--max-count",
|
|
73
|
+
"--after-context",
|
|
74
|
+
"--before-context",
|
|
75
|
+
"--context",
|
|
76
|
+
"--devices",
|
|
77
|
+
"--directories",
|
|
78
|
+
"--binary-files",
|
|
79
|
+
"--exclude",
|
|
80
|
+
"--exclude-from",
|
|
81
|
+
"--include",
|
|
82
|
+
"--label",
|
|
83
|
+
"-e",
|
|
84
|
+
"-f",
|
|
85
|
+
"-m",
|
|
86
|
+
"-A",
|
|
87
|
+
"-B",
|
|
88
|
+
"-C",
|
|
89
|
+
"-D",
|
|
90
|
+
"-d",
|
|
91
|
+
],
|
|
92
|
+
blockedFlags: [
|
|
93
|
+
"--file",
|
|
94
|
+
"--exclude-from",
|
|
95
|
+
"--dereference-recursive",
|
|
96
|
+
"--directories",
|
|
97
|
+
"--recursive",
|
|
98
|
+
"-f",
|
|
99
|
+
"-d",
|
|
100
|
+
"-r",
|
|
101
|
+
"-R",
|
|
102
|
+
],
|
|
103
|
+
},
|
|
104
|
+
cut: {
|
|
105
|
+
maxPositional: 0,
|
|
106
|
+
valueFlags: [
|
|
107
|
+
"--bytes",
|
|
108
|
+
"--characters",
|
|
109
|
+
"--fields",
|
|
110
|
+
"--delimiter",
|
|
111
|
+
"--output-delimiter",
|
|
112
|
+
"-b",
|
|
113
|
+
"-c",
|
|
114
|
+
"-f",
|
|
115
|
+
"-d",
|
|
116
|
+
],
|
|
117
|
+
},
|
|
118
|
+
sort: {
|
|
119
|
+
maxPositional: 0,
|
|
120
|
+
valueFlags: [
|
|
121
|
+
"--key",
|
|
122
|
+
"--field-separator",
|
|
123
|
+
"--buffer-size",
|
|
124
|
+
"--temporary-directory",
|
|
125
|
+
"--compress-program",
|
|
126
|
+
"--parallel",
|
|
127
|
+
"--batch-size",
|
|
128
|
+
"--random-source",
|
|
129
|
+
"--files0-from",
|
|
130
|
+
"--output",
|
|
131
|
+
"-k",
|
|
132
|
+
"-t",
|
|
133
|
+
"-S",
|
|
134
|
+
"-T",
|
|
135
|
+
"-o",
|
|
136
|
+
],
|
|
137
|
+
blockedFlags: ["--files0-from", "--output", "-o"],
|
|
138
|
+
},
|
|
139
|
+
uniq: {
|
|
140
|
+
maxPositional: 0,
|
|
141
|
+
valueFlags: ["--skip-fields", "--skip-chars", "--check-chars", "--group", "-f", "-s", "-w"],
|
|
142
|
+
},
|
|
143
|
+
head: {
|
|
144
|
+
maxPositional: 0,
|
|
145
|
+
valueFlags: ["--lines", "--bytes", "-n", "-c"],
|
|
146
|
+
},
|
|
147
|
+
tail: {
|
|
148
|
+
maxPositional: 0,
|
|
149
|
+
valueFlags: [
|
|
150
|
+
"--lines",
|
|
151
|
+
"--bytes",
|
|
152
|
+
"--sleep-interval",
|
|
153
|
+
"--max-unchanged-stats",
|
|
154
|
+
"--pid",
|
|
155
|
+
"-n",
|
|
156
|
+
"-c",
|
|
157
|
+
],
|
|
158
|
+
},
|
|
159
|
+
tr: {
|
|
160
|
+
minPositional: 1,
|
|
161
|
+
maxPositional: 2,
|
|
162
|
+
},
|
|
163
|
+
wc: {
|
|
164
|
+
maxPositional: 0,
|
|
165
|
+
valueFlags: ["--files0-from"],
|
|
166
|
+
blockedFlags: ["--files0-from"],
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
export const SAFE_BIN_GENERIC_PROFILE = compileSafeBinProfile(SAFE_BIN_GENERIC_PROFILE_FIXTURE);
|
|
170
|
+
export const SAFE_BIN_PROFILES = compileSafeBinProfiles(SAFE_BIN_PROFILE_FIXTURES);
|
|
171
|
+
function isSafeLiteralToken(value) {
|
|
172
|
+
if (!value || value === "-") {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return !hasGlobToken(value) && !isPathLikeToken(value);
|
|
176
|
+
}
|
|
177
|
+
function isInvalidValueToken(value) {
|
|
178
|
+
return !value || !isSafeLiteralToken(value);
|
|
179
|
+
}
|
|
180
|
+
function consumeLongOptionToken(args, index, flag, inlineValue, valueFlags, blockedFlags) {
|
|
181
|
+
if (blockedFlags.has(flag)) {
|
|
182
|
+
return -1;
|
|
183
|
+
}
|
|
184
|
+
if (inlineValue !== undefined) {
|
|
185
|
+
return isSafeLiteralToken(inlineValue) ? index + 1 : -1;
|
|
186
|
+
}
|
|
187
|
+
if (!valueFlags.has(flag)) {
|
|
188
|
+
return index + 1;
|
|
189
|
+
}
|
|
190
|
+
return isInvalidValueToken(args[index + 1]) ? -1 : index + 2;
|
|
191
|
+
}
|
|
192
|
+
function consumeShortOptionClusterToken(args, index, raw, cluster, flags, valueFlags, blockedFlags) {
|
|
193
|
+
for (let j = 0; j < flags.length; j += 1) {
|
|
194
|
+
const flag = flags[j];
|
|
195
|
+
if (blockedFlags.has(flag)) {
|
|
196
|
+
return -1;
|
|
197
|
+
}
|
|
198
|
+
if (!valueFlags.has(flag)) {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const inlineValue = cluster.slice(j + 1);
|
|
202
|
+
if (inlineValue) {
|
|
203
|
+
return isSafeLiteralToken(inlineValue) ? index + 1 : -1;
|
|
204
|
+
}
|
|
205
|
+
return isInvalidValueToken(args[index + 1]) ? -1 : index + 2;
|
|
206
|
+
}
|
|
207
|
+
return hasGlobToken(raw) ? -1 : index + 1;
|
|
208
|
+
}
|
|
209
|
+
function consumePositionalToken(token, positional) {
|
|
210
|
+
if (!isSafeLiteralToken(token)) {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
positional.push(token);
|
|
214
|
+
return true;
|
|
215
|
+
}
|
|
216
|
+
function validatePositionalCount(positional, profile) {
|
|
217
|
+
const minPositional = profile.minPositional ?? 0;
|
|
218
|
+
if (positional.length < minPositional) {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
return typeof profile.maxPositional !== "number" || positional.length <= profile.maxPositional;
|
|
222
|
+
}
|
|
223
|
+
export function validateSafeBinArgv(args, profile) {
|
|
224
|
+
const valueFlags = profile.valueFlags ?? NO_FLAGS;
|
|
225
|
+
const blockedFlags = profile.blockedFlags ?? NO_FLAGS;
|
|
226
|
+
const positional = [];
|
|
227
|
+
let i = 0;
|
|
228
|
+
while (i < args.length) {
|
|
229
|
+
const rawToken = args[i] ?? "";
|
|
230
|
+
const token = parseExecArgvToken(rawToken);
|
|
231
|
+
if (token.kind === "empty" || token.kind === "stdin") {
|
|
232
|
+
i += 1;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (token.kind === "terminator") {
|
|
236
|
+
for (let j = i + 1; j < args.length; j += 1) {
|
|
237
|
+
const rest = args[j];
|
|
238
|
+
if (!rest || rest === "-") {
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (!consumePositionalToken(rest, positional)) {
|
|
242
|
+
return false;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
break;
|
|
246
|
+
}
|
|
247
|
+
if (token.kind === "positional") {
|
|
248
|
+
if (!consumePositionalToken(token.raw, positional)) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
i += 1;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (token.style === "long") {
|
|
255
|
+
const nextIndex = consumeLongOptionToken(args, i, token.flag, token.inlineValue, valueFlags, blockedFlags);
|
|
256
|
+
if (nextIndex < 0) {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
i = nextIndex;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
const nextIndex = consumeShortOptionClusterToken(args, i, token.raw, token.cluster, token.flags, valueFlags, blockedFlags);
|
|
263
|
+
if (nextIndex < 0) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
i = nextIndex;
|
|
267
|
+
}
|
|
268
|
+
return validatePositionalCount(positional, profile);
|
|
269
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export function createFixedWindowRateLimiter(params) {
|
|
2
|
+
const maxRequests = Math.max(1, Math.floor(params.maxRequests));
|
|
3
|
+
const windowMs = Math.max(1, Math.floor(params.windowMs));
|
|
4
|
+
const now = params.now ?? Date.now;
|
|
5
|
+
let count = 0;
|
|
6
|
+
let windowStartMs = 0;
|
|
7
|
+
return {
|
|
8
|
+
consume() {
|
|
9
|
+
const nowMs = now();
|
|
10
|
+
if (nowMs - windowStartMs >= windowMs) {
|
|
11
|
+
windowStartMs = nowMs;
|
|
12
|
+
count = 0;
|
|
13
|
+
}
|
|
14
|
+
if (count >= maxRequests) {
|
|
15
|
+
return {
|
|
16
|
+
allowed: false,
|
|
17
|
+
retryAfterMs: Math.max(0, windowStartMs + windowMs - nowMs),
|
|
18
|
+
remaining: 0,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
count += 1;
|
|
22
|
+
return {
|
|
23
|
+
allowed: true,
|
|
24
|
+
retryAfterMs: 0,
|
|
25
|
+
remaining: Math.max(0, maxRequests - count),
|
|
26
|
+
};
|
|
27
|
+
},
|
|
28
|
+
reset() {
|
|
29
|
+
count = 0;
|
|
30
|
+
windowStartMs = 0;
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
export const DEFAULT_GIT_DISCOVERY_MAX_DEPTH = 12;
|
|
4
|
+
function walkUpFrom(startDir, opts, resolveAtDir) {
|
|
5
|
+
let current = path.resolve(startDir);
|
|
6
|
+
const maxDepth = opts.maxDepth ?? DEFAULT_GIT_DISCOVERY_MAX_DEPTH;
|
|
7
|
+
for (let i = 0; i < maxDepth; i += 1) {
|
|
8
|
+
const resolved = resolveAtDir(current);
|
|
9
|
+
if (resolved !== null && resolved !== undefined) {
|
|
10
|
+
return resolved;
|
|
11
|
+
}
|
|
12
|
+
const parent = path.dirname(current);
|
|
13
|
+
if (parent === current) {
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
current = parent;
|
|
17
|
+
}
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
function hasGitMarker(repoRoot) {
|
|
21
|
+
const gitPath = path.join(repoRoot, ".git");
|
|
22
|
+
try {
|
|
23
|
+
const stat = fs.statSync(gitPath);
|
|
24
|
+
return stat.isDirectory() || stat.isFile();
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function findGitRoot(startDir, opts = {}) {
|
|
31
|
+
// A `.git` file counts as a repo marker even if it is not a valid gitdir pointer.
|
|
32
|
+
return walkUpFrom(startDir, opts, (repoRoot) => (hasGitMarker(repoRoot) ? repoRoot : null));
|
|
33
|
+
}
|
|
34
|
+
function resolveGitDirFromMarker(repoRoot) {
|
|
35
|
+
const gitPath = path.join(repoRoot, ".git");
|
|
36
|
+
try {
|
|
37
|
+
const stat = fs.statSync(gitPath);
|
|
38
|
+
if (stat.isDirectory()) {
|
|
39
|
+
return gitPath;
|
|
40
|
+
}
|
|
41
|
+
if (!stat.isFile()) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const raw = fs.readFileSync(gitPath, "utf-8");
|
|
45
|
+
const match = raw.match(/gitdir:\s*(.+)/i);
|
|
46
|
+
if (!match?.[1]) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
return path.resolve(repoRoot, match[1].trim());
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function resolveGitHeadPath(startDir, opts = {}) {
|
|
56
|
+
// Stricter than findGitRoot: keep walking until a resolvable git dir is found.
|
|
57
|
+
return walkUpFrom(startDir, opts, (repoRoot) => {
|
|
58
|
+
const gitDir = resolveGitDirFromMarker(repoRoot);
|
|
59
|
+
return gitDir ? path.join(gitDir, "HEAD") : null;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveUserTimezone } from "../agents/date-time.js";
|
|
2
|
-
const ACTIVE_HOURS_TIME_PATTERN = /^([01]\d|2[0-3]
|
|
2
|
+
const ACTIVE_HOURS_TIME_PATTERN = /^(?:([01]\d|2[0-3]):([0-5]\d)|24:00)$/;
|
|
3
3
|
function resolveActiveHoursTimezone(cfg, raw) {
|
|
4
4
|
const trimmed = raw?.trim();
|
|
5
5
|
if (!trimmed || trimmed === "user") {
|
|
@@ -71,7 +71,7 @@ export function isWithinActiveHours(cfg, heartbeat, nowMs) {
|
|
|
71
71
|
return true;
|
|
72
72
|
}
|
|
73
73
|
if (startMin === endMin) {
|
|
74
|
-
return
|
|
74
|
+
return false;
|
|
75
75
|
}
|
|
76
76
|
const timeZone = resolveActiveHoursTimezone(cfg, active.timezone);
|
|
77
77
|
const currentMin = resolveMinutesInTimeZone(nowMs ?? Date.now(), timeZone);
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
function trimReason(reason) {
|
|
2
|
+
return typeof reason === "string" ? reason.trim() : "";
|
|
3
|
+
}
|
|
4
|
+
export function normalizeHeartbeatWakeReason(reason) {
|
|
5
|
+
const trimmed = trimReason(reason);
|
|
6
|
+
return trimmed.length > 0 ? trimmed : "requested";
|
|
7
|
+
}
|
|
8
|
+
export function resolveHeartbeatReasonKind(reason) {
|
|
9
|
+
const trimmed = trimReason(reason);
|
|
10
|
+
if (trimmed === "retry") {
|
|
11
|
+
return "retry";
|
|
12
|
+
}
|
|
13
|
+
if (trimmed === "interval") {
|
|
14
|
+
return "interval";
|
|
15
|
+
}
|
|
16
|
+
if (trimmed === "manual") {
|
|
17
|
+
return "manual";
|
|
18
|
+
}
|
|
19
|
+
if (trimmed === "exec-event") {
|
|
20
|
+
return "exec-event";
|
|
21
|
+
}
|
|
22
|
+
if (trimmed === "wake") {
|
|
23
|
+
return "wake";
|
|
24
|
+
}
|
|
25
|
+
if (trimmed.startsWith("cron:")) {
|
|
26
|
+
return "cron";
|
|
27
|
+
}
|
|
28
|
+
if (trimmed.startsWith("hook:")) {
|
|
29
|
+
return "hook";
|
|
30
|
+
}
|
|
31
|
+
return "other";
|
|
32
|
+
}
|
|
33
|
+
export function isHeartbeatEventDrivenReason(reason) {
|
|
34
|
+
const kind = resolveHeartbeatReasonKind(reason);
|
|
35
|
+
return kind === "exec-event" || kind === "cron" || kind === "wake" || kind === "hook";
|
|
36
|
+
}
|
|
37
|
+
export function isHeartbeatActionWakeReason(reason) {
|
|
38
|
+
const kind = resolveHeartbeatReasonKind(reason);
|
|
39
|
+
return kind === "manual" || kind === "exec-event" || kind === "hook";
|
|
40
|
+
}
|
|
@@ -17,10 +17,11 @@ import { getQueueSize } from "../process/command-queue.js";
|
|
|
17
17
|
import { normalizeAgentId, toAgentStoreSessionKey } from "../routing/session-key.js";
|
|
18
18
|
import { defaultRuntime } from "../runtime.js";
|
|
19
19
|
import { escapeRegExp } from "../utils.js";
|
|
20
|
-
import { formatErrorMessage } from "./errors.js";
|
|
20
|
+
import { formatErrorMessage, hasErrnoCode } from "./errors.js";
|
|
21
21
|
import { isWithinActiveHours } from "./heartbeat-active-hours.js";
|
|
22
22
|
import { buildCronEventPrompt, isCronSystemEvent, isExecCompletionEvent, } from "./heartbeat-events-filter.js";
|
|
23
23
|
import { emitHeartbeatEvent, resolveIndicatorType } from "./heartbeat-events.js";
|
|
24
|
+
import { resolveHeartbeatReasonKind } from "./heartbeat-reason.js";
|
|
24
25
|
import { resolveHeartbeatVisibility } from "./heartbeat-visibility.js";
|
|
25
26
|
import { requestHeartbeatNow, setHeartbeatWakeHandler, } from "./heartbeat-wake.js";
|
|
26
27
|
import { deliverOutboundPayloads } from "./outbound/deliver.js";
|
|
@@ -322,6 +323,56 @@ function normalizeHeartbeatReply(payload, responsePrefix, ackMaxChars) {
|
|
|
322
323
|
}
|
|
323
324
|
return { shouldSkip: false, text: finalText, hasMedia };
|
|
324
325
|
}
|
|
326
|
+
function resolveHeartbeatReasonFlags(reason) {
|
|
327
|
+
const reasonKind = resolveHeartbeatReasonKind(reason);
|
|
328
|
+
return {
|
|
329
|
+
isExecEventReason: reasonKind === "exec-event",
|
|
330
|
+
isCronEventReason: reasonKind === "cron",
|
|
331
|
+
isWakeReason: reasonKind === "wake" || reasonKind === "hook",
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
async function resolveHeartbeatPreflight(params) {
|
|
335
|
+
const reasonFlags = resolveHeartbeatReasonFlags(params.reason);
|
|
336
|
+
const session = resolveHeartbeatSession(params.cfg, params.agentId, params.heartbeat, params.forcedSessionKey);
|
|
337
|
+
const pendingEventEntries = peekSystemEventEntries(session.sessionKey);
|
|
338
|
+
const hasTaggedCronEvents = pendingEventEntries.some((event) => event.contextKey?.startsWith("cron:"));
|
|
339
|
+
const shouldInspectPendingEvents = reasonFlags.isExecEventReason || reasonFlags.isCronEventReason || hasTaggedCronEvents;
|
|
340
|
+
const shouldBypassFileGates = reasonFlags.isExecEventReason ||
|
|
341
|
+
reasonFlags.isCronEventReason ||
|
|
342
|
+
reasonFlags.isWakeReason ||
|
|
343
|
+
hasTaggedCronEvents;
|
|
344
|
+
const basePreflight = {
|
|
345
|
+
...reasonFlags,
|
|
346
|
+
session,
|
|
347
|
+
pendingEventEntries,
|
|
348
|
+
hasTaggedCronEvents,
|
|
349
|
+
shouldInspectPendingEvents,
|
|
350
|
+
};
|
|
351
|
+
if (shouldBypassFileGates) {
|
|
352
|
+
return basePreflight;
|
|
353
|
+
}
|
|
354
|
+
const workspaceDir = resolveAgentWorkspaceDir(params.cfg, params.agentId);
|
|
355
|
+
const heartbeatFilePath = path.join(workspaceDir, DEFAULT_HEARTBEAT_FILENAME);
|
|
356
|
+
try {
|
|
357
|
+
const heartbeatFileContent = await fs.readFile(heartbeatFilePath, "utf-8");
|
|
358
|
+
if (isHeartbeatContentEffectivelyEmpty(heartbeatFileContent)) {
|
|
359
|
+
return {
|
|
360
|
+
...basePreflight,
|
|
361
|
+
skipReason: "empty-heartbeat-file",
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
catch (err) {
|
|
366
|
+
if (hasErrnoCode(err, "ENOENT")) {
|
|
367
|
+
// Missing HEARTBEAT.md is intentional in some setups (for example, when
|
|
368
|
+
// heartbeat instructions live outside the file), so keep the run active.
|
|
369
|
+
// The heartbeat prompt already says "if it exists".
|
|
370
|
+
return basePreflight;
|
|
371
|
+
}
|
|
372
|
+
// For other read errors, proceed with heartbeat as before.
|
|
373
|
+
}
|
|
374
|
+
return basePreflight;
|
|
375
|
+
}
|
|
325
376
|
export async function runHeartbeatOnce(opts) {
|
|
326
377
|
const cfg = opts.cfg ?? loadConfig();
|
|
327
378
|
const agentId = normalizeAgentId(opts.agentId ?? resolveDefaultAgentId(cfg));
|
|
@@ -343,34 +394,24 @@ export async function runHeartbeatOnce(opts) {
|
|
|
343
394
|
if (queueSize > 0) {
|
|
344
395
|
return { status: "skipped", reason: "requests-in-flight" };
|
|
345
396
|
}
|
|
346
|
-
//
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
emitHeartbeatEvent({
|
|
362
|
-
status: "skipped",
|
|
363
|
-
reason: "empty-heartbeat-file",
|
|
364
|
-
durationMs: Date.now() - startedAt,
|
|
365
|
-
});
|
|
366
|
-
return { status: "skipped", reason: "empty-heartbeat-file" };
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
catch {
|
|
370
|
-
// File doesn't exist or can't be read - proceed with heartbeat.
|
|
371
|
-
// The LLM prompt says "if it exists" so this is expected behavior.
|
|
397
|
+
// Preflight centralizes trigger classification, event inspection, and HEARTBEAT.md gating.
|
|
398
|
+
const preflight = await resolveHeartbeatPreflight({
|
|
399
|
+
cfg,
|
|
400
|
+
agentId,
|
|
401
|
+
heartbeat,
|
|
402
|
+
forcedSessionKey: opts.sessionKey,
|
|
403
|
+
reason: opts.reason,
|
|
404
|
+
});
|
|
405
|
+
if (preflight.skipReason) {
|
|
406
|
+
emitHeartbeatEvent({
|
|
407
|
+
status: "skipped",
|
|
408
|
+
reason: preflight.skipReason,
|
|
409
|
+
durationMs: Date.now() - startedAt,
|
|
410
|
+
});
|
|
411
|
+
return { status: "skipped", reason: preflight.skipReason };
|
|
372
412
|
}
|
|
373
|
-
const { entry, sessionKey, storePath } =
|
|
413
|
+
const { entry, sessionKey, storePath } = preflight.session;
|
|
414
|
+
const { isCronEventReason, pendingEventEntries } = preflight;
|
|
374
415
|
const previousUpdatedAt = entry?.updatedAt;
|
|
375
416
|
const delivery = resolveHeartbeatDeliveryTarget({ cfg, entry, heartbeat });
|
|
376
417
|
const heartbeatAccountId = heartbeat?.accountId?.trim();
|
|
@@ -402,10 +443,7 @@ export async function runHeartbeatOnce(opts) {
|
|
|
402
443
|
// Check if this is an exec event or cron event with pending system events.
|
|
403
444
|
// If so, use a specialized prompt that instructs the model to relay the result
|
|
404
445
|
// instead of the standard heartbeat prompt with "reply HEARTBEAT_OK".
|
|
405
|
-
const
|
|
406
|
-
const pendingEventEntries = peekSystemEventEntries(sessionKey);
|
|
407
|
-
const hasTaggedCronEvents = pendingEventEntries.some((event) => event.contextKey?.startsWith("cron:"));
|
|
408
|
-
const shouldInspectPendingEvents = isExecEvent || isCronEventReason || hasTaggedCronEvents;
|
|
446
|
+
const shouldInspectPendingEvents = preflight.shouldInspectPendingEvents;
|
|
409
447
|
const pendingEvents = shouldInspectPendingEvents
|
|
410
448
|
? pendingEventEntries.map((event) => event.text)
|
|
411
449
|
: [];
|
|
@@ -459,6 +497,7 @@ export async function runHeartbeatOnce(opts) {
|
|
|
459
497
|
channel: delivery.channel,
|
|
460
498
|
to: delivery.to,
|
|
461
499
|
accountId: delivery.accountId,
|
|
500
|
+
threadId: delivery.threadId,
|
|
462
501
|
payloads: [{ text: heartbeatOkText }],
|
|
463
502
|
agentId,
|
|
464
503
|
deps: opts.deps,
|
|
@@ -635,6 +674,7 @@ export async function runHeartbeatOnce(opts) {
|
|
|
635
674
|
to: delivery.to,
|
|
636
675
|
accountId: deliveryAccountId,
|
|
637
676
|
agentId,
|
|
677
|
+
threadId: delivery.threadId,
|
|
638
678
|
payloads: [
|
|
639
679
|
...reasoningPayloads,
|
|
640
680
|
...(shouldSkipMain
|