@poolzin/pool-bot 2026.2.0 → 2026.2.2
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 +118 -0
- package/README-header.png +0 -0
- package/dist/agents/bash-tools.exec.js +76 -25
- package/dist/agents/cli-runner/helpers.js +9 -11
- package/dist/agents/context.js +1 -1
- package/dist/agents/identity.js +47 -7
- package/dist/agents/memory-search.js +25 -8
- package/dist/agents/model-catalog.js +1 -1
- package/dist/agents/model-selection.js +21 -0
- package/dist/agents/pi-embedded-block-chunker.js +117 -42
- package/dist/agents/pi-embedded-helpers/errors.js +183 -78
- package/dist/agents/pi-embedded-helpers.js +1 -1
- package/dist/agents/pi-embedded-runner/compact.js +8 -10
- package/dist/agents/pi-embedded-runner/model.js +62 -3
- package/dist/agents/pi-embedded-runner/run/attempt.js +21 -11
- package/dist/agents/pi-embedded-runner/run.js +199 -46
- package/dist/agents/pi-embedded-runner/system-prompt.js +10 -2
- package/dist/agents/pi-embedded-subscribe.js +118 -29
- package/dist/agents/pi-tools.js +10 -5
- package/dist/agents/poolbot-tools.js +15 -10
- package/dist/agents/sandbox-paths.js +31 -0
- package/dist/agents/session-tool-result-guard.js +94 -15
- package/dist/agents/shell-utils.js +51 -0
- package/dist/agents/skills/bundled-context.js +23 -0
- package/dist/agents/skills/bundled-dir.js +41 -7
- package/dist/agents/skills-install.js +60 -23
- package/dist/agents/subagent-announce.js +79 -34
- package/dist/agents/tool-policy.conformance.js +14 -0
- package/dist/agents/tool-policy.js +24 -0
- package/dist/agents/tools/cron-tool.js +166 -19
- package/dist/agents/tools/discord-actions-presence.js +78 -0
- package/dist/agents/tools/image-tool.js +1 -1
- package/dist/agents/tools/message-tool.js +56 -2
- package/dist/agents/tools/sessions-history-tool.js +69 -1
- package/dist/agents/tools/web-search.js +211 -42
- package/dist/agents/usage.js +23 -1
- package/dist/agents/workspace-run.js +67 -0
- package/dist/agents/workspace-templates.js +44 -0
- package/dist/auto-reply/command-auth.js +121 -6
- package/dist/auto-reply/envelope.js +74 -82
- package/dist/auto-reply/reply/commands-compact.js +1 -0
- package/dist/auto-reply/reply/commands-context-report.js +1 -0
- package/dist/auto-reply/reply/commands-context.js +1 -0
- package/dist/auto-reply/reply/commands-models.js +107 -60
- package/dist/auto-reply/reply/commands-ptt.js +171 -0
- package/dist/auto-reply/reply/get-reply-run.js +2 -1
- package/dist/auto-reply/reply/inbound-context.js +5 -1
- package/dist/auto-reply/reply/mentions.js +1 -1
- package/dist/auto-reply/reply/model-selection.js +3 -3
- package/dist/auto-reply/thinking.js +88 -43
- package/dist/browser/bridge-server.js +13 -0
- package/dist/browser/cdp.helpers.js +38 -24
- package/dist/browser/client-fetch.js +50 -7
- package/dist/browser/config.js +1 -10
- package/dist/browser/extension-relay.js +101 -40
- package/dist/browser/pw-ai.js +1 -1
- package/dist/browser/pw-session.js +143 -8
- package/dist/browser/pw-tools-core.interactions.js +125 -27
- package/dist/browser/pw-tools-core.responses.js +1 -1
- package/dist/browser/pw-tools-core.state.js +1 -1
- package/dist/browser/routes/agent.act.js +86 -41
- package/dist/browser/routes/dispatcher.js +4 -4
- package/dist/browser/screenshot.js +1 -1
- package/dist/browser/server.js +13 -0
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/index.html +28 -28
- package/dist/channels/reply-prefix.js +8 -1
- package/dist/cli/cron-cli/register.cron-add.js +61 -40
- package/dist/cli/cron-cli/register.cron-edit.js +60 -34
- package/dist/cli/cron-cli/shared.js +56 -41
- package/dist/cli/dns-cli.js +26 -14
- package/dist/cli/gateway-cli/register.js +37 -19
- package/dist/cli/memory-cli.js +5 -5
- package/dist/cli/parse-bytes.js +37 -0
- package/dist/cli/update-cli.js +173 -52
- package/dist/commands/agent.js +1 -0
- package/dist/commands/auth-choice.apply.oauth.js +1 -1
- package/dist/commands/doctor-config-flow.js +61 -5
- package/dist/commands/doctor-state-migrations.js +1 -1
- package/dist/commands/health.js +1 -1
- package/dist/commands/model-allowlist.js +29 -0
- package/dist/commands/model-picker.js +2 -1
- package/dist/commands/models/list.registry.js +1 -1
- package/dist/commands/models/list.status-command.js +43 -23
- package/dist/commands/models/shared.js +15 -0
- package/dist/commands/onboard-custom.js +384 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice-inference.js +35 -0
- package/dist/commands/onboard-non-interactive/local/auth-choice.js +6 -3
- package/dist/commands/onboard-skills.js +63 -38
- package/dist/commands/openai-model-default.js +41 -0
- package/dist/compat/legacy-names.js +2 -0
- package/dist/config/defaults.js +3 -2
- package/dist/config/paths.js +136 -35
- package/dist/config/plugin-auto-enable.js +21 -5
- package/dist/config/redact-snapshot.js +153 -0
- package/dist/config/schema.field-metadata.js +590 -0
- package/dist/config/schema.js +2 -2
- package/dist/config/sessions/store.js +291 -23
- package/dist/config/zod-schema.agent-defaults.js +3 -0
- package/dist/config/zod-schema.agent-runtime.js +13 -2
- package/dist/config/zod-schema.providers-core.js +142 -0
- package/dist/config/zod-schema.session.js +3 -0
- package/dist/control-ui/assets/{index-CIRDm-Lu.css → index-CSfXd2LO.css} +1 -1
- package/dist/control-ui/assets/{index-CmNMuoem.js → index-HRr1grwl.js} +446 -413
- package/dist/control-ui/assets/index-HRr1grwl.js.map +1 -0
- package/dist/control-ui/index.html +4 -4
- package/dist/cron/delivery.js +57 -0
- package/dist/cron/isolated-agent/delivery-target.js +18 -3
- package/dist/cron/isolated-agent/helpers.js +22 -5
- package/dist/cron/isolated-agent/run.js +172 -63
- package/dist/cron/isolated-agent/session.js +2 -0
- package/dist/cron/normalize.js +356 -28
- package/dist/cron/parse.js +10 -5
- package/dist/cron/run-log.js +35 -10
- package/dist/cron/schedule.js +41 -6
- package/dist/cron/service/jobs.js +208 -35
- package/dist/cron/service/ops.js +72 -16
- package/dist/cron/service/state.js +2 -0
- package/dist/cron/service/store.js +386 -14
- package/dist/cron/service/timer.js +390 -147
- package/dist/cron/session-reaper.js +86 -0
- package/dist/cron/store.js +23 -8
- package/dist/cron/validate-timestamp.js +43 -0
- package/dist/discord/monitor/agent-components.js +438 -0
- package/dist/discord/monitor/allow-list.js +28 -5
- package/dist/discord/monitor/gateway-registry.js +29 -0
- package/dist/discord/monitor/native-command.js +44 -23
- package/dist/discord/monitor/sender-identity.js +45 -0
- package/dist/discord/pluralkit.js +27 -0
- package/dist/discord/send.outbound.js +92 -5
- package/dist/discord/send.shared.js +60 -23
- package/dist/discord/targets.js +84 -1
- package/dist/entry.js +15 -9
- package/dist/extensionAPI.js +8 -0
- package/dist/gateway/control-ui.js +8 -1
- package/dist/gateway/hooks-mapping.js +3 -0
- package/dist/gateway/hooks.js +65 -0
- package/dist/gateway/net.js +96 -31
- package/dist/gateway/node-command-policy.js +50 -15
- package/dist/gateway/origin-check.js +56 -0
- package/dist/gateway/protocol/client-info.js +9 -0
- package/dist/gateway/protocol/index.js +9 -2
- package/dist/gateway/protocol/schema/agents-models-skills.js +71 -1
- package/dist/gateway/protocol/schema/cron.js +22 -10
- package/dist/gateway/protocol/schema/protocol-schemas.js +16 -2
- package/dist/gateway/protocol/schema/sessions.js +12 -0
- package/dist/gateway/server/hooks.js +1 -1
- package/dist/gateway/server-broadcast.js +26 -9
- package/dist/gateway/server-chat.js +112 -23
- package/dist/gateway/server-discovery-runtime.js +10 -2
- package/dist/gateway/server-http.js +109 -11
- package/dist/gateway/server-methods/agent-timestamp.js +60 -0
- package/dist/gateway/server-methods/agents.js +321 -2
- package/dist/gateway/server-methods/usage.js +559 -16
- package/dist/gateway/server-runtime-state.js +22 -8
- package/dist/gateway/server-startup-memory.js +16 -0
- package/dist/gateway/server.impl.js +5 -1
- package/dist/gateway/session-utils.fs.js +23 -25
- package/dist/gateway/session-utils.js +20 -10
- package/dist/gateway/sessions-patch.js +7 -22
- package/dist/gateway/test-helpers.mocks.js +11 -7
- package/dist/gateway/test-helpers.server.js +35 -2
- package/dist/imessage/constants.js +2 -0
- package/dist/imessage/monitor/deliver.js +4 -1
- package/dist/imessage/monitor/monitor-provider.js +51 -1
- package/dist/infra/bonjour-discovery.js +131 -70
- package/dist/infra/control-ui-assets.js +134 -12
- package/dist/infra/errors.js +12 -0
- package/dist/infra/exec-approvals.js +266 -57
- package/dist/infra/format-time/format-datetime.js +79 -0
- package/dist/infra/format-time/format-duration.js +81 -0
- package/dist/infra/format-time/format-relative.js +80 -0
- package/dist/infra/heartbeat-runner.js +140 -49
- package/dist/infra/home-dir.js +54 -0
- package/dist/infra/net/fetch-guard.js +122 -0
- package/dist/infra/net/ssrf.js +65 -29
- package/dist/infra/outbound/abort.js +14 -0
- package/dist/infra/outbound/message-action-runner.js +77 -13
- package/dist/infra/outbound/outbound-session.js +143 -37
- package/dist/infra/poolbot-root.js +43 -1
- package/dist/infra/session-cost-usage.js +631 -41
- package/dist/infra/state-migrations.js +317 -47
- package/dist/infra/update-global.js +35 -0
- package/dist/infra/update-runner.js +149 -43
- package/dist/infra/warning-filter.js +65 -0
- package/dist/infra/widearea-dns.js +30 -9
- package/dist/logging/redact-identifier.js +12 -0
- package/dist/media/fetch.js +81 -58
- package/dist/media/store.js +2 -0
- package/dist/media-understanding/apply.js +403 -3
- package/dist/media-understanding/attachments.js +38 -27
- package/dist/media-understanding/defaults.js +16 -0
- package/dist/media-understanding/providers/deepgram/audio.js +22 -14
- package/dist/media-understanding/providers/google/audio.js +24 -17
- package/dist/media-understanding/providers/google/video.js +24 -17
- package/dist/media-understanding/providers/image.js +3 -3
- package/dist/media-understanding/providers/index.js +4 -1
- package/dist/media-understanding/providers/openai/audio.js +22 -14
- package/dist/media-understanding/providers/shared.js +16 -11
- package/dist/media-understanding/providers/zai/index.js +6 -0
- package/dist/media-understanding/runner.js +158 -90
- package/dist/memory/batch-voyage.js +277 -0
- package/dist/memory/embeddings-voyage.js +75 -0
- package/dist/memory/embeddings.js +28 -16
- package/dist/memory/internal.js +101 -18
- package/dist/memory/manager.js +154 -48
- package/dist/memory/search-manager.js +173 -0
- package/dist/memory/session-files.js +9 -3
- package/dist/node-host/runner.js +34 -24
- package/dist/node-host/with-timeout.js +27 -0
- package/dist/plugins/commands.js +5 -1
- package/dist/plugins/config-state.js +86 -7
- package/dist/plugins/source-display.js +51 -0
- package/dist/process/exec.js +20 -2
- package/dist/routing/resolve-route.js +12 -0
- package/dist/routing/session-key.js +15 -0
- package/dist/runtime.js +2 -0
- package/dist/security/audit-extra.async.js +601 -0
- package/dist/security/audit-extra.js +2 -830
- package/dist/security/audit-extra.sync.js +505 -0
- package/dist/security/channel-metadata.js +34 -0
- package/dist/security/external-content.js +88 -6
- package/dist/security/skill-scanner.js +330 -0
- package/dist/sessions/session-key-utils.js +7 -0
- package/dist/signal/monitor/event-handler.js +80 -1
- package/dist/slack/monitor/media.js +85 -15
- package/dist/tailscale/detect.js +1 -2
- package/dist/telegram/bot/helpers.js +109 -28
- package/dist/telegram/bot-handlers.js +144 -3
- package/dist/telegram/bot-message-context.js +37 -10
- package/dist/telegram/bot-message-dispatch.js +54 -17
- package/dist/telegram/bot-native-commands.js +86 -29
- package/dist/telegram/bot.js +30 -29
- package/dist/telegram/model-buttons.js +163 -0
- package/dist/telegram/monitor.js +110 -85
- package/dist/telegram/send.js +129 -47
- package/dist/terminal/restore.js +45 -0
- package/dist/test-helpers/state-dir-env.js +16 -0
- package/dist/tts/tts.js +12 -6
- package/dist/tui/tui-session-actions.js +166 -54
- package/dist/utils/fetch-timeout.js +20 -0
- package/dist/utils/normalize-secret-input.js +19 -0
- package/dist/utils/transcript-tools.js +58 -0
- package/dist/utils.js +45 -14
- package/dist/version.js +42 -5
- package/dist/wizard/clack-prompter.js +9 -6
- package/extensions/googlechat/node_modules/.bin/poolbot +21 -0
- package/extensions/googlechat/package.json +2 -2
- package/extensions/line/node_modules/.bin/poolbot +21 -0
- package/extensions/line/package.json +1 -1
- package/extensions/matrix/node_modules/.bin/poolbot +21 -0
- package/extensions/matrix/package.json +1 -1
- package/extensions/memory-core/node_modules/.bin/poolbot +21 -0
- package/extensions/memory-core/package.json +4 -1
- package/extensions/twitch/node_modules/.bin/poolbot +21 -0
- package/extensions/twitch/package.json +1 -1
- package/package.json +183 -24
- package/dist/control-ui/assets/index-CmNMuoem.js.map +0 -1
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { createServer as createHttpServer, } from "node:http";
|
|
2
2
|
import { createServer as createHttpsServer } from "node:https";
|
|
3
|
-
import { handleA2uiHttpRequest } from "../canvas-host/a2ui.js";
|
|
3
|
+
import { A2UI_PATH, CANVAS_HOST_PATH, CANVAS_WS_PATH, handleA2uiHttpRequest, } from "../canvas-host/a2ui.js";
|
|
4
4
|
import { loadConfig } from "../config/config.js";
|
|
5
|
-
import { handleSlackHttpRequest } from "../slack/http/index.js";
|
|
6
5
|
import { resolveAgentAvatar } from "../agents/identity-avatar.js";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { handleSlackHttpRequest } from "../slack/http/index.js";
|
|
7
|
+
import { authorizeGatewayConnect, isLocalDirectRequest } from "./auth.js";
|
|
8
|
+
import { handleControlUiAvatarRequest, handleControlUiHttpRequest, } from "./control-ui.js";
|
|
9
|
+
import { extractHookToken, getHookAgentPolicyError, getHookChannelError, isHookAgentAllowed, normalizeAgentPayload, normalizeHookHeaders, normalizeWakePayload, readJsonBody, resolveHookTargetAgentId, resolveHookChannel, resolveHookDeliver, } from "./hooks.js";
|
|
9
10
|
import { applyHookMappings } from "./hooks-mapping.js";
|
|
11
|
+
import { sendUnauthorized } from "./http-common.js";
|
|
12
|
+
import { getBearerToken, getHeader } from "./http-utils.js";
|
|
13
|
+
import { resolveGatewayClientIp } from "./net.js";
|
|
10
14
|
import { handleOpenAiHttpRequest } from "./openai-http.js";
|
|
11
15
|
import { handleOpenResponsesHttpRequest } from "./openresponses-http.js";
|
|
12
16
|
import { handleToolsInvokeHttpRequest } from "./tools-invoke-http.js";
|
|
@@ -15,6 +19,49 @@ function sendJson(res, status, body) {
|
|
|
15
19
|
res.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
16
20
|
res.end(JSON.stringify(body));
|
|
17
21
|
}
|
|
22
|
+
function isCanvasPath(pathname) {
|
|
23
|
+
return (pathname === A2UI_PATH ||
|
|
24
|
+
pathname.startsWith(`${A2UI_PATH}/`) ||
|
|
25
|
+
pathname === CANVAS_HOST_PATH ||
|
|
26
|
+
pathname.startsWith(`${CANVAS_HOST_PATH}/`) ||
|
|
27
|
+
pathname === CANVAS_WS_PATH);
|
|
28
|
+
}
|
|
29
|
+
function hasAuthorizedWsClientForIp(clients, clientIp) {
|
|
30
|
+
for (const client of clients) {
|
|
31
|
+
if (client.clientIp && client.clientIp === clientIp) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
async function authorizeCanvasRequest(params) {
|
|
38
|
+
const { req, auth, trustedProxies, clients } = params;
|
|
39
|
+
if (isLocalDirectRequest(req, trustedProxies)) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
const token = getBearerToken(req);
|
|
43
|
+
if (token) {
|
|
44
|
+
const authResult = await authorizeGatewayConnect({
|
|
45
|
+
auth: { ...auth, allowTailscale: false },
|
|
46
|
+
connectAuth: { token, password: token },
|
|
47
|
+
req,
|
|
48
|
+
trustedProxies,
|
|
49
|
+
});
|
|
50
|
+
if (authResult.ok) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const clientIp = resolveGatewayClientIp({
|
|
55
|
+
remoteAddr: req.socket?.remoteAddress ?? "",
|
|
56
|
+
forwardedFor: getHeader(req, "x-forwarded-for"),
|
|
57
|
+
realIp: getHeader(req, "x-real-ip"),
|
|
58
|
+
trustedProxies,
|
|
59
|
+
});
|
|
60
|
+
if (!clientIp) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return hasAuthorizedWsClientForIp(clients, clientIp);
|
|
64
|
+
}
|
|
18
65
|
export function createHooksRequestHandler(opts) {
|
|
19
66
|
const { getHooksConfig, bindHost, port, logHooks, dispatchAgentHook, dispatchWakeHook } = opts;
|
|
20
67
|
return async (req, res) => {
|
|
@@ -26,6 +73,8 @@ export function createHooksRequestHandler(opts) {
|
|
|
26
73
|
if (url.pathname !== basePath && !url.pathname.startsWith(`${basePath}/`)) {
|
|
27
74
|
return false;
|
|
28
75
|
}
|
|
76
|
+
// pool-bot keeps the deprecation-warning approach for query-param tokens
|
|
77
|
+
// (upstream hard-rejects; we warn and allow for now)
|
|
29
78
|
const { token, fromQuery } = extractHookToken(req, url);
|
|
30
79
|
if (!token || token !== hooksConfig.token) {
|
|
31
80
|
res.statusCode = 401;
|
|
@@ -76,7 +125,14 @@ export function createHooksRequestHandler(opts) {
|
|
|
76
125
|
sendJson(res, 400, { ok: false, error: normalized.error });
|
|
77
126
|
return true;
|
|
78
127
|
}
|
|
79
|
-
|
|
128
|
+
if (!isHookAgentAllowed(hooksConfig, normalized.value.agentId)) {
|
|
129
|
+
sendJson(res, 400, { ok: false, error: getHookAgentPolicyError() });
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
const runId = dispatchAgentHook({
|
|
133
|
+
...normalized.value,
|
|
134
|
+
agentId: resolveHookTargetAgentId(hooksConfig, normalized.value.agentId),
|
|
135
|
+
});
|
|
80
136
|
sendJson(res, 202, { ok: true, runId });
|
|
81
137
|
return true;
|
|
82
138
|
}
|
|
@@ -111,9 +167,14 @@ export function createHooksRequestHandler(opts) {
|
|
|
111
167
|
sendJson(res, 400, { ok: false, error: getHookChannelError() });
|
|
112
168
|
return true;
|
|
113
169
|
}
|
|
170
|
+
if (!isHookAgentAllowed(hooksConfig, mapped.action.agentId)) {
|
|
171
|
+
sendJson(res, 400, { ok: false, error: getHookAgentPolicyError() });
|
|
172
|
+
return true;
|
|
173
|
+
}
|
|
114
174
|
const runId = dispatchAgentHook({
|
|
115
175
|
message: mapped.action.message,
|
|
116
176
|
name: mapped.action.name ?? "Hook",
|
|
177
|
+
agentId: resolveHookTargetAgentId(hooksConfig, mapped.action.agentId),
|
|
117
178
|
wakeMode: mapped.action.wakeMode,
|
|
118
179
|
sessionKey: mapped.action.sessionKey ?? "",
|
|
119
180
|
deliver: resolveHookDeliver(mapped.action.deliver),
|
|
@@ -141,7 +202,7 @@ export function createHooksRequestHandler(opts) {
|
|
|
141
202
|
};
|
|
142
203
|
}
|
|
143
204
|
export function createGatewayHttpServer(opts) {
|
|
144
|
-
const { canvasHost, controlUiEnabled, controlUiBasePath, openAiChatCompletionsEnabled, openResponsesEnabled, openResponsesConfig, handleHooksRequest, handlePluginRequest, resolvedAuth, } = opts;
|
|
205
|
+
const { canvasHost, clients, controlUiEnabled, controlUiBasePath, controlUiRoot, openAiChatCompletionsEnabled, openResponsesEnabled, openResponsesConfig, handleHooksRequest, handlePluginRequest, resolvedAuth, } = opts;
|
|
145
206
|
const httpServer = opts.tlsOptions
|
|
146
207
|
? createHttpsServer(opts.tlsOptions, (req, res) => {
|
|
147
208
|
void handleRequest(req, res);
|
|
@@ -183,6 +244,19 @@ export function createGatewayHttpServer(opts) {
|
|
|
183
244
|
return;
|
|
184
245
|
}
|
|
185
246
|
if (canvasHost) {
|
|
247
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
248
|
+
if (isCanvasPath(url.pathname)) {
|
|
249
|
+
const ok = await authorizeCanvasRequest({
|
|
250
|
+
req,
|
|
251
|
+
auth: resolvedAuth,
|
|
252
|
+
trustedProxies,
|
|
253
|
+
clients,
|
|
254
|
+
});
|
|
255
|
+
if (!ok) {
|
|
256
|
+
sendUnauthorized(res);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
186
260
|
if (await handleA2uiHttpRequest(req, res))
|
|
187
261
|
return;
|
|
188
262
|
if (await canvasHost.handleHttpRequest(req, res))
|
|
@@ -197,6 +271,7 @@ export function createGatewayHttpServer(opts) {
|
|
|
197
271
|
if (handleControlUiHttpRequest(req, res, {
|
|
198
272
|
basePath: controlUiBasePath,
|
|
199
273
|
config: configSnapshot,
|
|
274
|
+
root: controlUiRoot,
|
|
200
275
|
}))
|
|
201
276
|
return;
|
|
202
277
|
}
|
|
@@ -213,12 +288,35 @@ export function createGatewayHttpServer(opts) {
|
|
|
213
288
|
return httpServer;
|
|
214
289
|
}
|
|
215
290
|
export function attachGatewayUpgradeHandler(opts) {
|
|
216
|
-
const { httpServer, wss, canvasHost } = opts;
|
|
291
|
+
const { httpServer, wss, canvasHost, clients, resolvedAuth } = opts;
|
|
217
292
|
httpServer.on("upgrade", (req, socket, head) => {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
293
|
+
void (async () => {
|
|
294
|
+
if (canvasHost) {
|
|
295
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
296
|
+
if (url.pathname === CANVAS_WS_PATH) {
|
|
297
|
+
const configSnapshot = loadConfig();
|
|
298
|
+
const trustedProxies = configSnapshot.gateway?.trustedProxies ?? [];
|
|
299
|
+
const ok = await authorizeCanvasRequest({
|
|
300
|
+
req,
|
|
301
|
+
auth: resolvedAuth,
|
|
302
|
+
trustedProxies,
|
|
303
|
+
clients,
|
|
304
|
+
});
|
|
305
|
+
if (!ok) {
|
|
306
|
+
socket.write("HTTP/1.1 401 Unauthorized\r\nConnection: close\r\n\r\n");
|
|
307
|
+
socket.destroy();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (canvasHost.handleUpgrade(req, socket, head)) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
wss.handleUpgrade(req, socket, head, (ws) => {
|
|
316
|
+
wss.emit("connection", ws, req);
|
|
317
|
+
});
|
|
318
|
+
})().catch(() => {
|
|
319
|
+
socket.destroy();
|
|
222
320
|
});
|
|
223
321
|
});
|
|
224
322
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { resolveUserTimezone } from "../../agents/date-time.js";
|
|
2
|
+
import { formatZonedTimestamp } from "../../infra/format-time/format-datetime.js";
|
|
3
|
+
/**
|
|
4
|
+
* Cron jobs inject "Current time: ..." into their messages.
|
|
5
|
+
* Skip injection for those.
|
|
6
|
+
*/
|
|
7
|
+
const CRON_TIME_PATTERN = /Current time: /;
|
|
8
|
+
/**
|
|
9
|
+
* Matches a leading `[... YYYY-MM-DD HH:MM ...]` envelope -- either from
|
|
10
|
+
* channel plugins or from a previous injection. Uses the same YYYY-MM-DD
|
|
11
|
+
* HH:MM format as {@link formatZonedTimestamp}, so detection stays in sync
|
|
12
|
+
* with the formatting.
|
|
13
|
+
*/
|
|
14
|
+
const TIMESTAMP_ENVELOPE_PATTERN = /^\[.*\d{4}-\d{2}-\d{2} \d{2}:\d{2}/;
|
|
15
|
+
/**
|
|
16
|
+
* Injects a compact timestamp prefix into a message if one isn't already
|
|
17
|
+
* present. Uses the same `YYYY-MM-DD HH:MM TZ` format as channel envelope
|
|
18
|
+
* timestamps ({@link formatZonedTimestamp}), keeping token cost low (~7
|
|
19
|
+
* tokens) and format consistent across all agent contexts.
|
|
20
|
+
*
|
|
21
|
+
* Used by the gateway `agent` and `chat.send` handlers to give TUI, web,
|
|
22
|
+
* spawned subagents, `sessions_send`, and heartbeat wake events date/time
|
|
23
|
+
* awareness -- without modifying the system prompt (which is cached).
|
|
24
|
+
*
|
|
25
|
+
* Channel messages (Discord, Telegram, etc.) already have timestamps via
|
|
26
|
+
* envelope formatting and take a separate code path -- they never reach
|
|
27
|
+
* these handlers, so there is no double-stamping risk. The detection
|
|
28
|
+
* pattern is a safety net for edge cases.
|
|
29
|
+
*/
|
|
30
|
+
export function injectTimestamp(message, opts) {
|
|
31
|
+
if (!message.trim()) {
|
|
32
|
+
return message;
|
|
33
|
+
}
|
|
34
|
+
// Already has an envelope or injected timestamp
|
|
35
|
+
if (TIMESTAMP_ENVELOPE_PATTERN.test(message)) {
|
|
36
|
+
return message;
|
|
37
|
+
}
|
|
38
|
+
// Already has a cron-injected timestamp
|
|
39
|
+
if (CRON_TIME_PATTERN.test(message)) {
|
|
40
|
+
return message;
|
|
41
|
+
}
|
|
42
|
+
const now = opts?.now ?? new Date();
|
|
43
|
+
const timezone = opts?.timezone ?? "UTC";
|
|
44
|
+
const formatted = formatZonedTimestamp(now, { timeZone: timezone });
|
|
45
|
+
if (!formatted) {
|
|
46
|
+
return message;
|
|
47
|
+
}
|
|
48
|
+
// 3-letter DOW: small models (8B) can't reliably derive day-of-week from
|
|
49
|
+
// a date, and may treat a bare "Wed" as a typo. Costs ~1 token.
|
|
50
|
+
const dow = new Intl.DateTimeFormat("en-US", { timeZone: timezone, weekday: "short" }).format(now);
|
|
51
|
+
return `[${dow} ${formatted}] ${message}`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Build TimestampInjectionOptions from a PoolBotConfig.
|
|
55
|
+
*/
|
|
56
|
+
export function timestampOptsFromConfig(cfg) {
|
|
57
|
+
return {
|
|
58
|
+
timezone: resolveUserTimezone(cfg.agents?.defaults?.userTimezone),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -1,6 +1,119 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { listAgentIds, resolveAgentDir, resolveAgentWorkspaceDir, } from "../../agents/agent-scope.js";
|
|
4
|
+
import { DEFAULT_AGENTS_FILENAME, DEFAULT_BOOTSTRAP_FILENAME, DEFAULT_HEARTBEAT_FILENAME, DEFAULT_IDENTITY_FILENAME, DEFAULT_MEMORY_ALT_FILENAME, DEFAULT_MEMORY_FILENAME, DEFAULT_SOUL_FILENAME, DEFAULT_TOOLS_FILENAME, DEFAULT_USER_FILENAME, ensureAgentWorkspace, } from "../../agents/workspace.js";
|
|
5
|
+
import { movePathToTrash } from "../../browser/trash.js";
|
|
6
|
+
import { applyAgentConfig, findAgentEntryIndex, listAgentEntries, pruneAgentConfig, } from "../../commands/agents.config.js";
|
|
7
|
+
import { loadConfig, writeConfigFile } from "../../config/config.js";
|
|
8
|
+
import { resolveSessionTranscriptsDirForAgent } from "../../config/sessions/paths.js";
|
|
9
|
+
import { DEFAULT_AGENT_ID, normalizeAgentId } from "../../routing/session-key.js";
|
|
10
|
+
import { resolveUserPath } from "../../utils.js";
|
|
11
|
+
import { ErrorCodes, errorShape, formatValidationErrors, validateAgentsCreateParams, validateAgentsDeleteParams, validateAgentsFilesGetParams, validateAgentsFilesListParams, validateAgentsFilesSetParams, validateAgentsListParams, validateAgentsUpdateParams, } from "../protocol/index.js";
|
|
3
12
|
import { listAgentsForGateway } from "../session-utils.js";
|
|
13
|
+
const BOOTSTRAP_FILE_NAMES = [
|
|
14
|
+
DEFAULT_AGENTS_FILENAME,
|
|
15
|
+
DEFAULT_SOUL_FILENAME,
|
|
16
|
+
DEFAULT_TOOLS_FILENAME,
|
|
17
|
+
DEFAULT_IDENTITY_FILENAME,
|
|
18
|
+
DEFAULT_USER_FILENAME,
|
|
19
|
+
DEFAULT_HEARTBEAT_FILENAME,
|
|
20
|
+
DEFAULT_BOOTSTRAP_FILENAME,
|
|
21
|
+
];
|
|
22
|
+
const MEMORY_FILE_NAMES = [DEFAULT_MEMORY_FILENAME, DEFAULT_MEMORY_ALT_FILENAME];
|
|
23
|
+
const ALLOWED_FILE_NAMES = new Set([...BOOTSTRAP_FILE_NAMES, ...MEMORY_FILE_NAMES]);
|
|
24
|
+
async function statFile(filePath) {
|
|
25
|
+
try {
|
|
26
|
+
const stat = await fs.stat(filePath);
|
|
27
|
+
if (!stat.isFile()) {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
size: stat.size,
|
|
32
|
+
updatedAtMs: Math.floor(stat.mtimeMs),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async function listAgentFiles(workspaceDir) {
|
|
40
|
+
const files = [];
|
|
41
|
+
for (const name of BOOTSTRAP_FILE_NAMES) {
|
|
42
|
+
const filePath = path.join(workspaceDir, name);
|
|
43
|
+
const meta = await statFile(filePath);
|
|
44
|
+
if (meta) {
|
|
45
|
+
files.push({
|
|
46
|
+
name,
|
|
47
|
+
path: filePath,
|
|
48
|
+
missing: false,
|
|
49
|
+
size: meta.size,
|
|
50
|
+
updatedAtMs: meta.updatedAtMs,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
files.push({ name, path: filePath, missing: true });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const primaryMemoryPath = path.join(workspaceDir, DEFAULT_MEMORY_FILENAME);
|
|
58
|
+
const primaryMeta = await statFile(primaryMemoryPath);
|
|
59
|
+
if (primaryMeta) {
|
|
60
|
+
files.push({
|
|
61
|
+
name: DEFAULT_MEMORY_FILENAME,
|
|
62
|
+
path: primaryMemoryPath,
|
|
63
|
+
missing: false,
|
|
64
|
+
size: primaryMeta.size,
|
|
65
|
+
updatedAtMs: primaryMeta.updatedAtMs,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const altMemoryPath = path.join(workspaceDir, DEFAULT_MEMORY_ALT_FILENAME);
|
|
70
|
+
const altMeta = await statFile(altMemoryPath);
|
|
71
|
+
if (altMeta) {
|
|
72
|
+
files.push({
|
|
73
|
+
name: DEFAULT_MEMORY_ALT_FILENAME,
|
|
74
|
+
path: altMemoryPath,
|
|
75
|
+
missing: false,
|
|
76
|
+
size: altMeta.size,
|
|
77
|
+
updatedAtMs: altMeta.updatedAtMs,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
files.push({ name: DEFAULT_MEMORY_FILENAME, path: primaryMemoryPath, missing: true });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return files;
|
|
85
|
+
}
|
|
86
|
+
function resolveAgentIdOrError(agentIdRaw, cfg) {
|
|
87
|
+
const agentId = normalizeAgentId(agentIdRaw);
|
|
88
|
+
const allowed = new Set(listAgentIds(cfg));
|
|
89
|
+
if (!allowed.has(agentId)) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
return agentId;
|
|
93
|
+
}
|
|
94
|
+
function sanitizeIdentityLine(value) {
|
|
95
|
+
return value.replace(/\s+/g, " ").trim();
|
|
96
|
+
}
|
|
97
|
+
function resolveOptionalStringParam(value) {
|
|
98
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
99
|
+
}
|
|
100
|
+
async function moveToTrashBestEffort(pathname) {
|
|
101
|
+
if (!pathname) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
try {
|
|
105
|
+
await fs.access(pathname);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
try {
|
|
111
|
+
await movePathToTrash(pathname);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
// Best-effort: path may already be gone or trash unavailable.
|
|
115
|
+
}
|
|
116
|
+
}
|
|
4
117
|
export const agentsHandlers = {
|
|
5
118
|
"agents.list": ({ params, respond }) => {
|
|
6
119
|
if (!validateAgentsListParams(params)) {
|
|
@@ -11,4 +124,210 @@ export const agentsHandlers = {
|
|
|
11
124
|
const result = listAgentsForGateway(cfg);
|
|
12
125
|
respond(true, result, undefined);
|
|
13
126
|
},
|
|
127
|
+
"agents.create": async ({ params, respond }) => {
|
|
128
|
+
if (!validateAgentsCreateParams(params)) {
|
|
129
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid agents.create params: ${formatValidationErrors(validateAgentsCreateParams.errors)}`));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const cfg = loadConfig();
|
|
133
|
+
const rawName = String(params.name ?? "").trim();
|
|
134
|
+
const agentId = normalizeAgentId(rawName);
|
|
135
|
+
if (agentId === DEFAULT_AGENT_ID) {
|
|
136
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `"${DEFAULT_AGENT_ID}" is reserved`));
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
if (findAgentEntryIndex(listAgentEntries(cfg), agentId) >= 0) {
|
|
140
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `agent "${agentId}" already exists`));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const workspaceDir = resolveUserPath(String(params.workspace ?? "").trim());
|
|
144
|
+
// Resolve agentDir against the config we're about to persist (vs the pre-write config),
|
|
145
|
+
// so subsequent resolutions can't disagree about the agent's directory.
|
|
146
|
+
let nextConfig = applyAgentConfig(cfg, {
|
|
147
|
+
agentId,
|
|
148
|
+
name: rawName,
|
|
149
|
+
workspace: workspaceDir,
|
|
150
|
+
});
|
|
151
|
+
const agentDir = resolveAgentDir(nextConfig, agentId);
|
|
152
|
+
nextConfig = applyAgentConfig(nextConfig, { agentId, agentDir });
|
|
153
|
+
// Ensure workspace & transcripts exist BEFORE writing config so a failure
|
|
154
|
+
// here does not leave a broken config entry behind.
|
|
155
|
+
const skipBootstrap = Boolean(nextConfig.agents?.defaults?.skipBootstrap);
|
|
156
|
+
await ensureAgentWorkspace({ dir: workspaceDir, ensureBootstrapFiles: !skipBootstrap });
|
|
157
|
+
await fs.mkdir(resolveSessionTranscriptsDirForAgent(agentId), { recursive: true });
|
|
158
|
+
await writeConfigFile(nextConfig);
|
|
159
|
+
// Always write Name to IDENTITY.md; optionally include emoji/avatar.
|
|
160
|
+
const safeName = sanitizeIdentityLine(rawName);
|
|
161
|
+
const emoji = resolveOptionalStringParam(params.emoji);
|
|
162
|
+
const avatar = resolveOptionalStringParam(params.avatar);
|
|
163
|
+
const identityPath = path.join(workspaceDir, DEFAULT_IDENTITY_FILENAME);
|
|
164
|
+
const lines = [
|
|
165
|
+
"",
|
|
166
|
+
`- Name: ${safeName}`,
|
|
167
|
+
...(emoji ? [`- Emoji: ${sanitizeIdentityLine(emoji)}`] : []),
|
|
168
|
+
...(avatar ? [`- Avatar: ${sanitizeIdentityLine(avatar)}`] : []),
|
|
169
|
+
"",
|
|
170
|
+
];
|
|
171
|
+
await fs.appendFile(identityPath, lines.join("\n"), "utf-8");
|
|
172
|
+
respond(true, { ok: true, agentId, name: rawName, workspace: workspaceDir }, undefined);
|
|
173
|
+
},
|
|
174
|
+
"agents.update": async ({ params, respond }) => {
|
|
175
|
+
if (!validateAgentsUpdateParams(params)) {
|
|
176
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid agents.update params: ${formatValidationErrors(validateAgentsUpdateParams.errors)}`));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const cfg = loadConfig();
|
|
180
|
+
const agentId = normalizeAgentId(String(params.agentId ?? ""));
|
|
181
|
+
if (findAgentEntryIndex(listAgentEntries(cfg), agentId) < 0) {
|
|
182
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `agent "${agentId}" not found`));
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const workspaceDir = typeof params.workspace === "string" && params.workspace.trim()
|
|
186
|
+
? resolveUserPath(params.workspace.trim())
|
|
187
|
+
: undefined;
|
|
188
|
+
const model = resolveOptionalStringParam(params.model);
|
|
189
|
+
const avatar = resolveOptionalStringParam(params.avatar);
|
|
190
|
+
const nextConfig = applyAgentConfig(cfg, {
|
|
191
|
+
agentId,
|
|
192
|
+
...(typeof params.name === "string" && params.name.trim()
|
|
193
|
+
? { name: params.name.trim() }
|
|
194
|
+
: {}),
|
|
195
|
+
...(workspaceDir ? { workspace: workspaceDir } : {}),
|
|
196
|
+
...(model ? { model } : {}),
|
|
197
|
+
});
|
|
198
|
+
await writeConfigFile(nextConfig);
|
|
199
|
+
if (workspaceDir) {
|
|
200
|
+
const skipBootstrap = Boolean(nextConfig.agents?.defaults?.skipBootstrap);
|
|
201
|
+
await ensureAgentWorkspace({ dir: workspaceDir, ensureBootstrapFiles: !skipBootstrap });
|
|
202
|
+
}
|
|
203
|
+
if (avatar) {
|
|
204
|
+
const workspace = workspaceDir ?? resolveAgentWorkspaceDir(nextConfig, agentId);
|
|
205
|
+
await fs.mkdir(workspace, { recursive: true });
|
|
206
|
+
const identityPath = path.join(workspace, DEFAULT_IDENTITY_FILENAME);
|
|
207
|
+
await fs.appendFile(identityPath, `\n- Avatar: ${sanitizeIdentityLine(avatar)}\n`, "utf-8");
|
|
208
|
+
}
|
|
209
|
+
respond(true, { ok: true, agentId }, undefined);
|
|
210
|
+
},
|
|
211
|
+
"agents.delete": async ({ params, respond }) => {
|
|
212
|
+
if (!validateAgentsDeleteParams(params)) {
|
|
213
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid agents.delete params: ${formatValidationErrors(validateAgentsDeleteParams.errors)}`));
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const cfg = loadConfig();
|
|
217
|
+
const agentId = normalizeAgentId(String(params.agentId ?? ""));
|
|
218
|
+
if (agentId === DEFAULT_AGENT_ID) {
|
|
219
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `"${DEFAULT_AGENT_ID}" cannot be deleted`));
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (findAgentEntryIndex(listAgentEntries(cfg), agentId) < 0) {
|
|
223
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `agent "${agentId}" not found`));
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const deleteFiles = typeof params.deleteFiles === "boolean" ? params.deleteFiles : true;
|
|
227
|
+
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
|
|
228
|
+
const agentDir = resolveAgentDir(cfg, agentId);
|
|
229
|
+
const sessionsDir = resolveSessionTranscriptsDirForAgent(agentId);
|
|
230
|
+
const result = pruneAgentConfig(cfg, agentId);
|
|
231
|
+
await writeConfigFile(result.config);
|
|
232
|
+
if (deleteFiles) {
|
|
233
|
+
await Promise.all([
|
|
234
|
+
moveToTrashBestEffort(workspaceDir),
|
|
235
|
+
moveToTrashBestEffort(agentDir),
|
|
236
|
+
moveToTrashBestEffort(sessionsDir),
|
|
237
|
+
]);
|
|
238
|
+
}
|
|
239
|
+
respond(true, { ok: true, agentId, removedBindings: result.removedBindings }, undefined);
|
|
240
|
+
},
|
|
241
|
+
"agents.files.list": async ({ params, respond }) => {
|
|
242
|
+
if (!validateAgentsFilesListParams(params)) {
|
|
243
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid agents.files.list params: ${formatValidationErrors(validateAgentsFilesListParams.errors)}`));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const cfg = loadConfig();
|
|
247
|
+
const agentId = resolveAgentIdOrError(String(params.agentId ?? ""), cfg);
|
|
248
|
+
if (!agentId) {
|
|
249
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "unknown agent id"));
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
|
|
253
|
+
const files = await listAgentFiles(workspaceDir);
|
|
254
|
+
respond(true, { agentId, workspace: workspaceDir, files }, undefined);
|
|
255
|
+
},
|
|
256
|
+
"agents.files.get": async ({ params, respond }) => {
|
|
257
|
+
if (!validateAgentsFilesGetParams(params)) {
|
|
258
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid agents.files.get params: ${formatValidationErrors(validateAgentsFilesGetParams.errors)}`));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
const cfg = loadConfig();
|
|
262
|
+
const agentId = resolveAgentIdOrError(String(params.agentId ?? ""), cfg);
|
|
263
|
+
if (!agentId) {
|
|
264
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "unknown agent id"));
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const name = String(params.name ?? "").trim();
|
|
268
|
+
if (!ALLOWED_FILE_NAMES.has(name)) {
|
|
269
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `unsupported file "${name}"`));
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
|
|
273
|
+
const filePath = path.join(workspaceDir, name);
|
|
274
|
+
const meta = await statFile(filePath);
|
|
275
|
+
if (!meta) {
|
|
276
|
+
respond(true, {
|
|
277
|
+
agentId,
|
|
278
|
+
workspace: workspaceDir,
|
|
279
|
+
file: { name, path: filePath, missing: true },
|
|
280
|
+
}, undefined);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
284
|
+
respond(true, {
|
|
285
|
+
agentId,
|
|
286
|
+
workspace: workspaceDir,
|
|
287
|
+
file: {
|
|
288
|
+
name,
|
|
289
|
+
path: filePath,
|
|
290
|
+
missing: false,
|
|
291
|
+
size: meta.size,
|
|
292
|
+
updatedAtMs: meta.updatedAtMs,
|
|
293
|
+
content,
|
|
294
|
+
},
|
|
295
|
+
}, undefined);
|
|
296
|
+
},
|
|
297
|
+
"agents.files.set": async ({ params, respond }) => {
|
|
298
|
+
if (!validateAgentsFilesSetParams(params)) {
|
|
299
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `invalid agents.files.set params: ${formatValidationErrors(validateAgentsFilesSetParams.errors)}`));
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const cfg = loadConfig();
|
|
303
|
+
const agentId = resolveAgentIdOrError(String(params.agentId ?? ""), cfg);
|
|
304
|
+
if (!agentId) {
|
|
305
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, "unknown agent id"));
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const name = String(params.name ?? "").trim();
|
|
309
|
+
if (!ALLOWED_FILE_NAMES.has(name)) {
|
|
310
|
+
respond(false, undefined, errorShape(ErrorCodes.INVALID_REQUEST, `unsupported file "${name}"`));
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const workspaceDir = resolveAgentWorkspaceDir(cfg, agentId);
|
|
314
|
+
await fs.mkdir(workspaceDir, { recursive: true });
|
|
315
|
+
const filePath = path.join(workspaceDir, name);
|
|
316
|
+
const content = String(params.content ?? "");
|
|
317
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
318
|
+
const meta = await statFile(filePath);
|
|
319
|
+
respond(true, {
|
|
320
|
+
ok: true,
|
|
321
|
+
agentId,
|
|
322
|
+
workspace: workspaceDir,
|
|
323
|
+
file: {
|
|
324
|
+
name,
|
|
325
|
+
path: filePath,
|
|
326
|
+
missing: false,
|
|
327
|
+
size: meta?.size,
|
|
328
|
+
updatedAtMs: meta?.updatedAtMs,
|
|
329
|
+
content,
|
|
330
|
+
},
|
|
331
|
+
}, undefined);
|
|
332
|
+
},
|
|
14
333
|
};
|