@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,7 +1,28 @@
|
|
|
1
1
|
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { formatDurationCompact } from "../infra/format-time/format-duration.js";
|
|
3
|
+
import { getDiagnosticSessionState } from "../logging/diagnostic-session-state.js";
|
|
4
|
+
import { killProcessTree } from "../process/kill-tree.js";
|
|
5
|
+
import { getProcessSupervisor } from "../process/supervisor/index.js";
|
|
2
6
|
import { deleteSession, drainSession, getFinishedSession, getSession, listFinishedSessions, listRunningSessions, markExited, setJobTtlMs, } from "./bash-process-registry.js";
|
|
3
|
-
import { deriveSessionName,
|
|
7
|
+
import { deriveSessionName, pad, sliceLogLines, truncateMiddle } from "./bash-tools.shared.js";
|
|
8
|
+
import { recordCommandPoll, resetCommandPollCount } from "./command-poll-backoff.js";
|
|
4
9
|
import { encodeKeySequence, encodePaste } from "./pty-keys.js";
|
|
10
|
+
const DEFAULT_LOG_TAIL_LINES = 200;
|
|
11
|
+
function resolveLogSliceWindow(offset, limit) {
|
|
12
|
+
const usingDefaultTail = offset === undefined && limit === undefined;
|
|
13
|
+
const effectiveLimit = typeof limit === "number" && Number.isFinite(limit)
|
|
14
|
+
? limit
|
|
15
|
+
: usingDefaultTail
|
|
16
|
+
? DEFAULT_LOG_TAIL_LINES
|
|
17
|
+
: undefined;
|
|
18
|
+
return { effectiveOffset: offset, effectiveLimit, usingDefaultTail };
|
|
19
|
+
}
|
|
20
|
+
function defaultTailNote(totalLines, usingDefaultTail) {
|
|
21
|
+
if (!usingDefaultTail || totalLines <= DEFAULT_LOG_TAIL_LINES) {
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
return `\n\n[showing last ${DEFAULT_LOG_TAIL_LINES} of ${totalLines} lines; pass offset/limit to page]`;
|
|
25
|
+
}
|
|
5
26
|
const processSchema = Type.Object({
|
|
6
27
|
action: Type.String({ description: "Process action" }),
|
|
7
28
|
sessionId: Type.Optional(Type.String({ description: "Session id for actions other than list" })),
|
|
@@ -14,13 +35,76 @@ const processSchema = Type.Object({
|
|
|
14
35
|
eof: Type.Optional(Type.Boolean({ description: "Close stdin after write" })),
|
|
15
36
|
offset: Type.Optional(Type.Number({ description: "Log offset" })),
|
|
16
37
|
limit: Type.Optional(Type.Number({ description: "Log length" })),
|
|
38
|
+
timeout: Type.Optional(Type.Number({
|
|
39
|
+
description: "For poll: wait up to this many milliseconds before returning",
|
|
40
|
+
minimum: 0,
|
|
41
|
+
})),
|
|
17
42
|
});
|
|
43
|
+
const MAX_POLL_WAIT_MS = 120_000;
|
|
44
|
+
function resolvePollWaitMs(value) {
|
|
45
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
46
|
+
return Math.max(0, Math.min(MAX_POLL_WAIT_MS, Math.floor(value)));
|
|
47
|
+
}
|
|
48
|
+
if (typeof value === "string") {
|
|
49
|
+
const parsed = Number.parseInt(value.trim(), 10);
|
|
50
|
+
if (Number.isFinite(parsed)) {
|
|
51
|
+
return Math.max(0, Math.min(MAX_POLL_WAIT_MS, parsed));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
function failText(text) {
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: "text",
|
|
61
|
+
text,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
details: { status: "failed" },
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function recordPollRetrySuggestion(sessionId, hasNewOutput) {
|
|
68
|
+
try {
|
|
69
|
+
const sessionState = getDiagnosticSessionState({ sessionId });
|
|
70
|
+
return recordCommandPoll(sessionState, sessionId, hasNewOutput);
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function resetPollRetrySuggestion(sessionId) {
|
|
77
|
+
try {
|
|
78
|
+
const sessionState = getDiagnosticSessionState({ sessionId });
|
|
79
|
+
resetCommandPollCount(sessionState, sessionId);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
// Ignore diagnostics state failures for process tool behavior.
|
|
83
|
+
}
|
|
84
|
+
}
|
|
18
85
|
export function createProcessTool(defaults) {
|
|
19
86
|
if (defaults?.cleanupMs !== undefined) {
|
|
20
87
|
setJobTtlMs(defaults.cleanupMs);
|
|
21
88
|
}
|
|
22
89
|
const scopeKey = defaults?.scopeKey;
|
|
90
|
+
const supervisor = getProcessSupervisor();
|
|
23
91
|
const isInScope = (session) => !scopeKey || session?.scopeKey === scopeKey;
|
|
92
|
+
const cancelManagedSession = (sessionId) => {
|
|
93
|
+
const record = supervisor.getRecord(sessionId);
|
|
94
|
+
if (!record || record.state === "exited") {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
supervisor.cancel(sessionId, "manual-cancel");
|
|
98
|
+
return true;
|
|
99
|
+
};
|
|
100
|
+
const terminateSessionFallback = (session) => {
|
|
101
|
+
const pid = session.pid ?? session.child?.pid;
|
|
102
|
+
if (typeof pid !== "number" || !Number.isFinite(pid) || pid <= 0) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
killProcessTree(pid);
|
|
106
|
+
return true;
|
|
107
|
+
};
|
|
24
108
|
return {
|
|
25
109
|
name: "process",
|
|
26
110
|
label: "process",
|
|
@@ -60,10 +144,10 @@ export function createProcessTool(defaults) {
|
|
|
60
144
|
exitSignal: s.exitSignal ?? undefined,
|
|
61
145
|
}));
|
|
62
146
|
const lines = [...running, ...finished]
|
|
63
|
-
.
|
|
147
|
+
.toSorted((a, b) => b.startedAt - a.startedAt)
|
|
64
148
|
.map((s) => {
|
|
65
149
|
const label = s.name ? truncateMiddle(s.name, 80) : truncateMiddle(s.command, 120);
|
|
66
|
-
return `${s.sessionId} ${pad(s.status, 9)} ${
|
|
150
|
+
return `${s.sessionId} ${pad(s.status, 9)} ${formatDurationCompact(s.runtimeMs) ?? "n/a"} :: ${label}`;
|
|
67
151
|
});
|
|
68
152
|
return {
|
|
69
153
|
content: [
|
|
@@ -85,10 +169,49 @@ export function createProcessTool(defaults) {
|
|
|
85
169
|
const finished = getFinishedSession(params.sessionId);
|
|
86
170
|
const scopedSession = isInScope(session) ? session : undefined;
|
|
87
171
|
const scopedFinished = isInScope(finished) ? finished : undefined;
|
|
172
|
+
const failedResult = (text) => ({
|
|
173
|
+
content: [{ type: "text", text }],
|
|
174
|
+
details: { status: "failed" },
|
|
175
|
+
});
|
|
176
|
+
const resolveBackgroundedWritableStdin = () => {
|
|
177
|
+
if (!scopedSession) {
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
result: failedResult(`No active session found for ${params.sessionId}`),
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
if (!scopedSession.backgrounded) {
|
|
184
|
+
return {
|
|
185
|
+
ok: false,
|
|
186
|
+
result: failedResult(`Session ${params.sessionId} is not backgrounded.`),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const stdin = scopedSession.stdin ?? scopedSession.child?.stdin;
|
|
190
|
+
if (!stdin || stdin.destroyed) {
|
|
191
|
+
return {
|
|
192
|
+
ok: false,
|
|
193
|
+
result: failedResult(`Session ${params.sessionId} stdin is not writable.`),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
return { ok: true, session: scopedSession, stdin: stdin };
|
|
197
|
+
};
|
|
198
|
+
const writeToStdin = async (stdin, data) => {
|
|
199
|
+
await new Promise((resolve, reject) => {
|
|
200
|
+
stdin.write(data, (err) => {
|
|
201
|
+
if (err) {
|
|
202
|
+
reject(err);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
resolve();
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
};
|
|
88
210
|
switch (params.action) {
|
|
89
211
|
case "poll": {
|
|
90
212
|
if (!scopedSession) {
|
|
91
213
|
if (scopedFinished) {
|
|
214
|
+
resetPollRetrySuggestion(params.sessionId);
|
|
92
215
|
return {
|
|
93
216
|
content: [
|
|
94
217
|
{
|
|
@@ -109,26 +232,18 @@ export function createProcessTool(defaults) {
|
|
|
109
232
|
},
|
|
110
233
|
};
|
|
111
234
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
{
|
|
115
|
-
type: "text",
|
|
116
|
-
text: `No session found for ${params.sessionId}`,
|
|
117
|
-
},
|
|
118
|
-
],
|
|
119
|
-
details: { status: "failed" },
|
|
120
|
-
};
|
|
235
|
+
resetPollRetrySuggestion(params.sessionId);
|
|
236
|
+
return failText(`No session found for ${params.sessionId}`);
|
|
121
237
|
}
|
|
122
238
|
if (!scopedSession.backgrounded) {
|
|
123
|
-
return {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
};
|
|
239
|
+
return failText(`Session ${params.sessionId} is not backgrounded.`);
|
|
240
|
+
}
|
|
241
|
+
const pollWaitMs = resolvePollWaitMs(params.timeout);
|
|
242
|
+
if (pollWaitMs > 0 && !scopedSession.exited) {
|
|
243
|
+
const deadline = Date.now() + pollWaitMs;
|
|
244
|
+
while (!scopedSession.exited && Date.now() < deadline) {
|
|
245
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(250, deadline - Date.now())));
|
|
246
|
+
}
|
|
132
247
|
}
|
|
133
248
|
const { stdout, stderr } = drainSession(scopedSession);
|
|
134
249
|
const exited = scopedSession.exited;
|
|
@@ -144,6 +259,13 @@ export function createProcessTool(defaults) {
|
|
|
144
259
|
: "failed"
|
|
145
260
|
: "running";
|
|
146
261
|
const output = [stdout.trimEnd(), stderr.trimEnd()].filter(Boolean).join("\n").trim();
|
|
262
|
+
const hasNewOutput = output.length > 0;
|
|
263
|
+
const retryInMs = exited
|
|
264
|
+
? undefined
|
|
265
|
+
: recordPollRetrySuggestion(params.sessionId, hasNewOutput);
|
|
266
|
+
if (exited) {
|
|
267
|
+
resetPollRetrySuggestion(params.sessionId);
|
|
268
|
+
}
|
|
147
269
|
return {
|
|
148
270
|
content: [
|
|
149
271
|
{
|
|
@@ -160,6 +282,7 @@ export function createProcessTool(defaults) {
|
|
|
160
282
|
exitCode: exited ? exitCode : undefined,
|
|
161
283
|
aggregated: scopedSession.aggregated,
|
|
162
284
|
name: deriveSessionName(scopedSession.command),
|
|
285
|
+
...(typeof retryInMs === "number" ? { retryInMs } : {}),
|
|
163
286
|
},
|
|
164
287
|
};
|
|
165
288
|
}
|
|
@@ -176,9 +299,13 @@ export function createProcessTool(defaults) {
|
|
|
176
299
|
details: { status: "failed" },
|
|
177
300
|
};
|
|
178
301
|
}
|
|
179
|
-
const
|
|
302
|
+
const window = resolveLogSliceWindow(params.offset, params.limit);
|
|
303
|
+
const { slice, totalLines, totalChars } = sliceLogLines(scopedSession.aggregated, window.effectiveOffset, window.effectiveLimit);
|
|
304
|
+
const logDefaultTailNote = defaultTailNote(totalLines, window.usingDefaultTail);
|
|
180
305
|
return {
|
|
181
|
-
content: [
|
|
306
|
+
content: [
|
|
307
|
+
{ type: "text", text: (slice || "(no output yet)") + logDefaultTailNote },
|
|
308
|
+
],
|
|
182
309
|
details: {
|
|
183
310
|
status: scopedSession.exited ? "completed" : "running",
|
|
184
311
|
sessionId: params.sessionId,
|
|
@@ -191,10 +318,17 @@ export function createProcessTool(defaults) {
|
|
|
191
318
|
};
|
|
192
319
|
}
|
|
193
320
|
if (scopedFinished) {
|
|
194
|
-
const
|
|
321
|
+
const window = resolveLogSliceWindow(params.offset, params.limit);
|
|
322
|
+
const { slice, totalLines, totalChars } = sliceLogLines(scopedFinished.aggregated, window.effectiveOffset, window.effectiveLimit);
|
|
195
323
|
const status = scopedFinished.status === "completed" ? "completed" : "failed";
|
|
324
|
+
const logDefaultTailNote = defaultTailNote(totalLines, window.usingDefaultTail);
|
|
196
325
|
return {
|
|
197
|
-
content: [
|
|
326
|
+
content: [
|
|
327
|
+
{
|
|
328
|
+
type: "text",
|
|
329
|
+
text: (slice || "(no output recorded)") + logDefaultTailNote,
|
|
330
|
+
},
|
|
331
|
+
],
|
|
198
332
|
details: {
|
|
199
333
|
status,
|
|
200
334
|
sessionId: params.sessionId,
|
|
@@ -219,50 +353,13 @@ export function createProcessTool(defaults) {
|
|
|
219
353
|
};
|
|
220
354
|
}
|
|
221
355
|
case "write": {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
{
|
|
226
|
-
type: "text",
|
|
227
|
-
text: `No active session found for ${params.sessionId}`,
|
|
228
|
-
},
|
|
229
|
-
],
|
|
230
|
-
details: { status: "failed" },
|
|
231
|
-
};
|
|
356
|
+
const resolved = resolveBackgroundedWritableStdin();
|
|
357
|
+
if (!resolved.ok) {
|
|
358
|
+
return resolved.result;
|
|
232
359
|
}
|
|
233
|
-
|
|
234
|
-
return {
|
|
235
|
-
content: [
|
|
236
|
-
{
|
|
237
|
-
type: "text",
|
|
238
|
-
text: `Session ${params.sessionId} is not backgrounded.`,
|
|
239
|
-
},
|
|
240
|
-
],
|
|
241
|
-
details: { status: "failed" },
|
|
242
|
-
};
|
|
243
|
-
}
|
|
244
|
-
const stdin = scopedSession.stdin ?? scopedSession.child?.stdin;
|
|
245
|
-
if (!stdin || stdin.destroyed) {
|
|
246
|
-
return {
|
|
247
|
-
content: [
|
|
248
|
-
{
|
|
249
|
-
type: "text",
|
|
250
|
-
text: `Session ${params.sessionId} stdin is not writable.`,
|
|
251
|
-
},
|
|
252
|
-
],
|
|
253
|
-
details: { status: "failed" },
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
await new Promise((resolve, reject) => {
|
|
257
|
-
stdin.write(params.data ?? "", (err) => {
|
|
258
|
-
if (err)
|
|
259
|
-
reject(err);
|
|
260
|
-
else
|
|
261
|
-
resolve();
|
|
262
|
-
});
|
|
263
|
-
});
|
|
360
|
+
await writeToStdin(resolved.stdin, params.data ?? "");
|
|
264
361
|
if (params.eof) {
|
|
265
|
-
stdin.end();
|
|
362
|
+
resolved.stdin.end();
|
|
266
363
|
}
|
|
267
364
|
return {
|
|
268
365
|
content: [
|
|
@@ -274,44 +371,14 @@ export function createProcessTool(defaults) {
|
|
|
274
371
|
details: {
|
|
275
372
|
status: "running",
|
|
276
373
|
sessionId: params.sessionId,
|
|
277
|
-
name:
|
|
374
|
+
name: deriveSessionName(resolved.session.command),
|
|
278
375
|
},
|
|
279
376
|
};
|
|
280
377
|
}
|
|
281
378
|
case "send-keys": {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
{
|
|
286
|
-
type: "text",
|
|
287
|
-
text: `No active session found for ${params.sessionId}`,
|
|
288
|
-
},
|
|
289
|
-
],
|
|
290
|
-
details: { status: "failed" },
|
|
291
|
-
};
|
|
292
|
-
}
|
|
293
|
-
if (!scopedSession.backgrounded) {
|
|
294
|
-
return {
|
|
295
|
-
content: [
|
|
296
|
-
{
|
|
297
|
-
type: "text",
|
|
298
|
-
text: `Session ${params.sessionId} is not backgrounded.`,
|
|
299
|
-
},
|
|
300
|
-
],
|
|
301
|
-
details: { status: "failed" },
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
const stdin = scopedSession.stdin ?? scopedSession.child?.stdin;
|
|
305
|
-
if (!stdin || stdin.destroyed) {
|
|
306
|
-
return {
|
|
307
|
-
content: [
|
|
308
|
-
{
|
|
309
|
-
type: "text",
|
|
310
|
-
text: `Session ${params.sessionId} stdin is not writable.`,
|
|
311
|
-
},
|
|
312
|
-
],
|
|
313
|
-
details: { status: "failed" },
|
|
314
|
-
};
|
|
379
|
+
const resolved = resolveBackgroundedWritableStdin();
|
|
380
|
+
if (!resolved.ok) {
|
|
381
|
+
return resolved.result;
|
|
315
382
|
}
|
|
316
383
|
const { data, warnings } = encodeKeySequence({
|
|
317
384
|
keys: params.keys,
|
|
@@ -329,14 +396,7 @@ export function createProcessTool(defaults) {
|
|
|
329
396
|
details: { status: "failed" },
|
|
330
397
|
};
|
|
331
398
|
}
|
|
332
|
-
await
|
|
333
|
-
stdin.write(data, (err) => {
|
|
334
|
-
if (err)
|
|
335
|
-
reject(err);
|
|
336
|
-
else
|
|
337
|
-
resolve();
|
|
338
|
-
});
|
|
339
|
-
});
|
|
399
|
+
await writeToStdin(resolved.stdin, data);
|
|
340
400
|
return {
|
|
341
401
|
content: [
|
|
342
402
|
{
|
|
@@ -348,53 +408,16 @@ export function createProcessTool(defaults) {
|
|
|
348
408
|
details: {
|
|
349
409
|
status: "running",
|
|
350
410
|
sessionId: params.sessionId,
|
|
351
|
-
name:
|
|
411
|
+
name: deriveSessionName(resolved.session.command),
|
|
352
412
|
},
|
|
353
413
|
};
|
|
354
414
|
}
|
|
355
415
|
case "submit": {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
{
|
|
360
|
-
type: "text",
|
|
361
|
-
text: `No active session found for ${params.sessionId}`,
|
|
362
|
-
},
|
|
363
|
-
],
|
|
364
|
-
details: { status: "failed" },
|
|
365
|
-
};
|
|
416
|
+
const resolved = resolveBackgroundedWritableStdin();
|
|
417
|
+
if (!resolved.ok) {
|
|
418
|
+
return resolved.result;
|
|
366
419
|
}
|
|
367
|
-
|
|
368
|
-
return {
|
|
369
|
-
content: [
|
|
370
|
-
{
|
|
371
|
-
type: "text",
|
|
372
|
-
text: `Session ${params.sessionId} is not backgrounded.`,
|
|
373
|
-
},
|
|
374
|
-
],
|
|
375
|
-
details: { status: "failed" },
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
const stdin = scopedSession.stdin ?? scopedSession.child?.stdin;
|
|
379
|
-
if (!stdin || stdin.destroyed) {
|
|
380
|
-
return {
|
|
381
|
-
content: [
|
|
382
|
-
{
|
|
383
|
-
type: "text",
|
|
384
|
-
text: `Session ${params.sessionId} stdin is not writable.`,
|
|
385
|
-
},
|
|
386
|
-
],
|
|
387
|
-
details: { status: "failed" },
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
await new Promise((resolve, reject) => {
|
|
391
|
-
stdin.write("\r", (err) => {
|
|
392
|
-
if (err)
|
|
393
|
-
reject(err);
|
|
394
|
-
else
|
|
395
|
-
resolve();
|
|
396
|
-
});
|
|
397
|
-
});
|
|
420
|
+
await writeToStdin(resolved.stdin, "\r");
|
|
398
421
|
return {
|
|
399
422
|
content: [
|
|
400
423
|
{
|
|
@@ -405,44 +428,14 @@ export function createProcessTool(defaults) {
|
|
|
405
428
|
details: {
|
|
406
429
|
status: "running",
|
|
407
430
|
sessionId: params.sessionId,
|
|
408
|
-
name:
|
|
431
|
+
name: deriveSessionName(resolved.session.command),
|
|
409
432
|
},
|
|
410
433
|
};
|
|
411
434
|
}
|
|
412
435
|
case "paste": {
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
{
|
|
417
|
-
type: "text",
|
|
418
|
-
text: `No active session found for ${params.sessionId}`,
|
|
419
|
-
},
|
|
420
|
-
],
|
|
421
|
-
details: { status: "failed" },
|
|
422
|
-
};
|
|
423
|
-
}
|
|
424
|
-
if (!scopedSession.backgrounded) {
|
|
425
|
-
return {
|
|
426
|
-
content: [
|
|
427
|
-
{
|
|
428
|
-
type: "text",
|
|
429
|
-
text: `Session ${params.sessionId} is not backgrounded.`,
|
|
430
|
-
},
|
|
431
|
-
],
|
|
432
|
-
details: { status: "failed" },
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
const stdin = scopedSession.stdin ?? scopedSession.child?.stdin;
|
|
436
|
-
if (!stdin || stdin.destroyed) {
|
|
437
|
-
return {
|
|
438
|
-
content: [
|
|
439
|
-
{
|
|
440
|
-
type: "text",
|
|
441
|
-
text: `Session ${params.sessionId} stdin is not writable.`,
|
|
442
|
-
},
|
|
443
|
-
],
|
|
444
|
-
details: { status: "failed" },
|
|
445
|
-
};
|
|
436
|
+
const resolved = resolveBackgroundedWritableStdin();
|
|
437
|
+
if (!resolved.ok) {
|
|
438
|
+
return resolved.result;
|
|
446
439
|
}
|
|
447
440
|
const payload = encodePaste(params.text ?? "", params.bracketed !== false);
|
|
448
441
|
if (!payload) {
|
|
@@ -456,14 +449,7 @@ export function createProcessTool(defaults) {
|
|
|
456
449
|
details: { status: "failed" },
|
|
457
450
|
};
|
|
458
451
|
}
|
|
459
|
-
await
|
|
460
|
-
stdin.write(payload, (err) => {
|
|
461
|
-
if (err)
|
|
462
|
-
reject(err);
|
|
463
|
-
else
|
|
464
|
-
resolve();
|
|
465
|
-
});
|
|
466
|
-
});
|
|
452
|
+
await writeToStdin(resolved.stdin, payload);
|
|
467
453
|
return {
|
|
468
454
|
content: [
|
|
469
455
|
{
|
|
@@ -474,37 +460,35 @@ export function createProcessTool(defaults) {
|
|
|
474
460
|
details: {
|
|
475
461
|
status: "running",
|
|
476
462
|
sessionId: params.sessionId,
|
|
477
|
-
name:
|
|
463
|
+
name: deriveSessionName(resolved.session.command),
|
|
478
464
|
},
|
|
479
465
|
};
|
|
480
466
|
}
|
|
481
467
|
case "kill": {
|
|
482
468
|
if (!scopedSession) {
|
|
483
|
-
return {
|
|
484
|
-
content: [
|
|
485
|
-
{
|
|
486
|
-
type: "text",
|
|
487
|
-
text: `No active session found for ${params.sessionId}`,
|
|
488
|
-
},
|
|
489
|
-
],
|
|
490
|
-
details: { status: "failed" },
|
|
491
|
-
};
|
|
469
|
+
return failText(`No active session found for ${params.sessionId}`);
|
|
492
470
|
}
|
|
493
471
|
if (!scopedSession.backgrounded) {
|
|
494
|
-
return {
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
472
|
+
return failText(`Session ${params.sessionId} is not backgrounded.`);
|
|
473
|
+
}
|
|
474
|
+
const canceled = cancelManagedSession(scopedSession.id);
|
|
475
|
+
if (!canceled) {
|
|
476
|
+
const terminated = terminateSessionFallback(scopedSession);
|
|
477
|
+
if (!terminated) {
|
|
478
|
+
return failText(`Unable to terminate session ${params.sessionId}: no active supervisor run or process id.`);
|
|
479
|
+
}
|
|
480
|
+
markExited(scopedSession, null, "SIGKILL", "failed");
|
|
503
481
|
}
|
|
504
|
-
|
|
505
|
-
markExited(scopedSession, null, "SIGKILL", "failed");
|
|
482
|
+
resetPollRetrySuggestion(params.sessionId);
|
|
506
483
|
return {
|
|
507
|
-
content: [
|
|
484
|
+
content: [
|
|
485
|
+
{
|
|
486
|
+
type: "text",
|
|
487
|
+
text: canceled
|
|
488
|
+
? `Termination requested for session ${params.sessionId}.`
|
|
489
|
+
: `Killed session ${params.sessionId}.`,
|
|
490
|
+
},
|
|
491
|
+
],
|
|
508
492
|
details: {
|
|
509
493
|
status: "failed",
|
|
510
494
|
name: scopedSession ? deriveSessionName(scopedSession.command) : undefined,
|
|
@@ -513,6 +497,7 @@ export function createProcessTool(defaults) {
|
|
|
513
497
|
}
|
|
514
498
|
case "clear": {
|
|
515
499
|
if (scopedFinished) {
|
|
500
|
+
resetPollRetrySuggestion(params.sessionId);
|
|
516
501
|
deleteSession(params.sessionId);
|
|
517
502
|
return {
|
|
518
503
|
content: [{ type: "text", text: `Cleared session ${params.sessionId}.` }],
|
|
@@ -531,10 +516,30 @@ export function createProcessTool(defaults) {
|
|
|
531
516
|
}
|
|
532
517
|
case "remove": {
|
|
533
518
|
if (scopedSession) {
|
|
534
|
-
|
|
535
|
-
|
|
519
|
+
const canceled = cancelManagedSession(scopedSession.id);
|
|
520
|
+
if (canceled) {
|
|
521
|
+
// Keep remove semantics deterministic: drop from process registry now.
|
|
522
|
+
scopedSession.backgrounded = false;
|
|
523
|
+
deleteSession(params.sessionId);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
const terminated = terminateSessionFallback(scopedSession);
|
|
527
|
+
if (!terminated) {
|
|
528
|
+
return failText(`Unable to remove session ${params.sessionId}: no active supervisor run or process id.`);
|
|
529
|
+
}
|
|
530
|
+
markExited(scopedSession, null, "SIGKILL", "failed");
|
|
531
|
+
deleteSession(params.sessionId);
|
|
532
|
+
}
|
|
533
|
+
resetPollRetrySuggestion(params.sessionId);
|
|
536
534
|
return {
|
|
537
|
-
content: [
|
|
535
|
+
content: [
|
|
536
|
+
{
|
|
537
|
+
type: "text",
|
|
538
|
+
text: canceled
|
|
539
|
+
? `Removed session ${params.sessionId} (termination requested).`
|
|
540
|
+
: `Removed session ${params.sessionId}.`,
|
|
541
|
+
},
|
|
542
|
+
],
|
|
538
543
|
details: {
|
|
539
544
|
status: "failed",
|
|
540
545
|
name: scopedSession ? deriveSessionName(scopedSession.command) : undefined,
|
|
@@ -542,6 +547,7 @@ export function createProcessTool(defaults) {
|
|
|
542
547
|
};
|
|
543
548
|
}
|
|
544
549
|
if (scopedFinished) {
|
|
550
|
+
resetPollRetrySuggestion(params.sessionId);
|
|
545
551
|
deleteSession(params.sessionId);
|
|
546
552
|
return {
|
|
547
553
|
content: [{ type: "text", text: `Removed session ${params.sessionId}.` }],
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function collectTextContentBlocks(content) {
|
|
2
|
+
if (!Array.isArray(content)) {
|
|
3
|
+
return [];
|
|
4
|
+
}
|
|
5
|
+
const parts = [];
|
|
6
|
+
for (const block of content) {
|
|
7
|
+
if (!block || typeof block !== "object") {
|
|
8
|
+
continue;
|
|
9
|
+
}
|
|
10
|
+
const rec = block;
|
|
11
|
+
if (rec.type === "text" && typeof rec.text === "string") {
|
|
12
|
+
parts.push(rec.text);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
return parts;
|
|
16
|
+
}
|