piclaw 0.0.20 → 0.0.21
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/.output/nitro.json +1 -1
- package/.output/public/assets/defult-D5RLDUrI.js +1 -0
- package/.output/public/assets/{dist-D-Hc5HbQ.js → dist-BH_oa-kv.js} +1 -1
- package/.output/public/assets/index-7JvURuHy.js +204 -0
- package/.output/public/assets/index-K43slwjJ.css +1 -0
- package/.output/public/index.html +11 -2
- package/.output/server/_chunks/app.mjs +138 -104
- package/.output/server/_chunks/config.mjs +4 -0
- package/.output/server/_chunks/dummy.mjs +1 -1
- package/.output/server/_chunks/logger.mjs +1 -1
- package/.output/server/_chunks/notes.mjs +1 -3
- package/.output/server/_chunks/renderer-template.mjs +1 -1
- package/.output/server/_chunks/sandbox.mjs +217 -0
- package/.output/server/_chunks/server.mjs +411 -291
- package/.output/server/_chunks/terminal.mjs +47 -8
- package/.output/server/_chunks/virtual.mjs +192 -54
- package/.output/server/_id_.delete.mjs +5 -2
- package/.output/server/_id_2.delete.mjs +8 -0
- package/.output/server/_jid_.delete.mjs +0 -1
- package/.output/server/_jid_.patch.mjs +21 -3
- package/.output/server/_jid_2.delete.mjs +0 -1
- package/.output/server/_libs/@acemir/cssom+[...].mjs +2269 -1137
- package/.output/server/_libs/@google/genai.mjs +348 -284
- package/.output/server/_libs/@mariozechner/pi-agent-core+[...].mjs +381 -2073
- package/.output/server/_libs/@mariozechner/pi-coding-agent+[...].mjs +236 -136
- package/.output/server/_libs/_.mjs +3 -2
- package/.output/server/_libs/_10.mjs +2 -4
- package/.output/server/_libs/_11.mjs +2 -4
- package/.output/server/_libs/_12.mjs +2 -3
- package/.output/server/_libs/_13.mjs +2 -3
- package/.output/server/_libs/_14.mjs +2 -4
- package/.output/server/_libs/_15.mjs +2 -4
- package/.output/server/_libs/_16.mjs +2 -3
- package/.output/server/_libs/_17.mjs +2 -4
- package/.output/server/_libs/_18.mjs +2 -2
- package/.output/server/_libs/_19.mjs +2 -2
- package/.output/server/_libs/_2.mjs +3 -3
- package/.output/server/_libs/_20.mjs +2 -2
- package/.output/server/_libs/_21.mjs +2 -2
- package/.output/server/_libs/_22.mjs +2 -2
- package/.output/server/_libs/_23.mjs +2 -2
- package/.output/server/_libs/_24.mjs +2 -2
- package/.output/server/_libs/_25.mjs +2 -2
- package/.output/server/_libs/_26.mjs +2 -2
- package/.output/server/_libs/_27.mjs +2 -2
- package/.output/server/_libs/_28.mjs +2 -2
- package/.output/server/_libs/_29.mjs +2 -2
- package/.output/server/_libs/_3.mjs +3 -3
- package/.output/server/_libs/_30.mjs +2 -2
- package/.output/server/_libs/_31.mjs +2 -2
- package/.output/server/_libs/_32.mjs +2 -2
- package/.output/server/_libs/_33.mjs +2 -2
- package/.output/server/_libs/_34.mjs +2 -2
- package/.output/server/_libs/_35.mjs +2 -2
- package/.output/server/_libs/_36.mjs +2 -2
- package/.output/server/_libs/_37.mjs +2 -2
- package/.output/server/_libs/_38.mjs +2 -2
- package/.output/server/_libs/_39.mjs +2 -2
- package/.output/server/_libs/_4.mjs +4 -3
- package/.output/server/_libs/_40.mjs +2 -2
- package/.output/server/_libs/_41.mjs +2 -2
- package/.output/server/_libs/_42.mjs +2 -2
- package/.output/server/_libs/_43.mjs +2 -2
- package/.output/server/_libs/_44.mjs +2 -2
- package/.output/server/_libs/_45.mjs +2 -2
- package/.output/server/_libs/_46.mjs +2 -2
- package/.output/server/_libs/_47.mjs +2 -2
- package/.output/server/_libs/_48.mjs +2 -2
- package/.output/server/_libs/_49.mjs +2 -2
- package/.output/server/_libs/_5.mjs +2 -3
- package/.output/server/_libs/_50.mjs +2 -2
- package/.output/server/_libs/_51.mjs +2 -2
- package/.output/server/_libs/_52.mjs +2 -2
- package/.output/server/_libs/_53.mjs +2 -2
- package/.output/server/_libs/_54.mjs +2 -2
- package/.output/server/_libs/_55.mjs +2 -2
- package/.output/server/_libs/_56.mjs +2 -2
- package/.output/server/_libs/_57.mjs +2 -2
- package/.output/server/_libs/_58.mjs +2 -2
- package/.output/server/_libs/_59.mjs +2 -2
- package/.output/server/_libs/_6.mjs +2 -3
- package/.output/server/_libs/_60.mjs +2 -2
- package/.output/server/_libs/_61.mjs +2 -2
- package/.output/server/_libs/_62.mjs +2 -2
- package/.output/server/_libs/_63.mjs +2 -2
- package/.output/server/_libs/_64.mjs +2 -2
- package/.output/server/_libs/_65.mjs +2 -2
- package/.output/server/_libs/_66.mjs +2 -2
- package/.output/server/_libs/_67.mjs +2 -2
- package/.output/server/_libs/_68.mjs +2 -2
- package/.output/server/_libs/_69.mjs +2 -2
- package/.output/server/_libs/_7.mjs +2 -5
- package/.output/server/_libs/_70.mjs +2 -2
- package/.output/server/_libs/_71.mjs +2 -2
- package/.output/server/_libs/_72.mjs +2 -2
- package/.output/server/_libs/_73.mjs +2 -2
- package/.output/server/_libs/_74.mjs +2 -2
- package/.output/server/_libs/_75.mjs +2 -2
- package/.output/server/_libs/_76.mjs +2 -2
- package/.output/server/_libs/_77.mjs +2 -2
- package/.output/server/_libs/_78.mjs +2 -2
- package/.output/server/_libs/_79.mjs +2 -2
- package/.output/server/_libs/_8.mjs +2 -3
- package/.output/server/_libs/_80.mjs +2 -2
- package/.output/server/_libs/_81.mjs +2 -2
- package/.output/server/_libs/_82.mjs +2 -2
- package/.output/server/_libs/_83.mjs +2 -2
- package/.output/server/_libs/_84.mjs +2 -2
- package/.output/server/_libs/_85.mjs +2 -2
- package/.output/server/_libs/_86.mjs +2 -2
- package/.output/server/_libs/_87.mjs +2 -2
- package/.output/server/_libs/_88.mjs +2 -2
- package/.output/server/_libs/_89.mjs +2 -2
- package/.output/server/_libs/_9.mjs +2 -4
- package/.output/server/_libs/_90.mjs +5 -2
- package/.output/server/_libs/_91.mjs +3 -2
- package/.output/server/_libs/_92.mjs +2 -2
- package/.output/server/_libs/_93.mjs +2 -2
- package/.output/server/_libs/_94.mjs +2 -2
- package/.output/server/_libs/agent-base.mjs +1 -1
- package/.output/server/_libs/cheerio+[...].mjs +1 -1
- package/.output/server/_libs/data-uri-to-buffer.mjs +2 -67
- package/.output/server/_libs/data-urls+[...].mjs +1 -1
- package/.output/server/_libs/diff.mjs +1 -1
- package/.output/server/_libs/exodus__bytes.mjs +99 -81
- package/.output/server/_libs/fetch-blob+node-domexception.mjs +1 -1
- package/.output/server/_libs/h3+rou3+srvx.mjs +34 -4
- package/.output/server/_libs/html-encoding-sniffer.mjs +1 -1
- package/.output/server/_libs/https-proxy-agent.mjs +2 -2
- package/.output/server/_libs/jsdom.mjs +1 -1
- package/.output/server/_libs/just-bash+[...].mjs +4676 -3916
- package/.output/server/_libs/mariozechner__jiti.mjs +1 -1
- package/.output/server/_libs/mariozechner__pi-ai.mjs +1472 -0
- package/.output/server/_libs/md4x.mjs +1 -1
- package/.output/server/_libs/node-fetch.mjs +14 -14
- package/.output/server/_libs/node-liblzma.mjs +1 -1
- package/.output/server/_libs/silvia-odwyer__photon-node.mjs +1 -1
- package/.output/server/_routes/api/auth/status.mjs +25 -6
- package/.output/server/_routes/api/config2.mjs +2 -0
- package/.output/server/_routes/api/files/groups.mjs +0 -1
- package/.output/server/_routes/api/groups.mjs +4 -2
- package/.output/server/_routes/api/groups2.mjs +14 -5
- package/.output/server/_routes/api/health.mjs +0 -1
- package/.output/server/_routes/api/pi/apikey.mjs +0 -1
- package/.output/server/_routes/api/pi/apikey_providers.mjs +0 -1
- package/.output/server/_routes/api/pi/commands.mjs +1 -2
- package/.output/server/_routes/api/pi/login/events.mjs +0 -1
- package/.output/server/_routes/api/pi/login/respond.mjs +0 -1
- package/.output/server/_routes/api/pi/login.mjs +0 -1
- package/.output/server/_routes/api/pi/logout.mjs +0 -1
- package/.output/server/_routes/api/pi/models.mjs +0 -1
- package/.output/server/_routes/api/pi/status.mjs +0 -1
- package/.output/server/_routes/api/sandbox.mjs +26 -0
- package/.output/server/_routes/api/sandbox2.mjs +17 -0
- package/.output/server/_routes/api/send.mjs +12 -12
- package/.output/server/_routes/api/status.mjs +0 -1
- package/.output/server/_routes/api/stop.mjs +0 -1
- package/.output/server/_routes/api/tasks2.mjs +0 -1
- package/.output/server/_routes/api/telegram/setup.mjs +0 -1
- package/.output/server/_routes/api/telegram/status.mjs +0 -1
- package/.output/server/_routes/api/terminal2.mjs +2 -1
- package/.output/server/_routes/api/tunnel/setup.mjs +0 -1
- package/.output/server/_runtime.mjs +1 -2
- package/.output/server/index.mjs +1 -1
- package/.output/server/node_modules/amdefine/amdefine.js +301 -0
- package/.output/server/node_modules/amdefine/package.json +16 -0
- package/.output/server/node_modules/compressjs/lib/BWT.js +420 -0
- package/.output/server/node_modules/compressjs/lib/BWTC.js +234 -0
- package/.output/server/node_modules/compressjs/lib/BitStream.js +108 -0
- package/.output/server/node_modules/compressjs/lib/Bzip2.js +936 -0
- package/.output/server/node_modules/compressjs/lib/CRC32.js +105 -0
- package/.output/server/node_modules/compressjs/lib/Context1Model.js +56 -0
- package/.output/server/node_modules/compressjs/lib/DefSumModel.js +152 -0
- package/.output/server/node_modules/compressjs/lib/DeflateDistanceModel.js +55 -0
- package/.output/server/node_modules/compressjs/lib/Dmc.js +197 -0
- package/.output/server/node_modules/compressjs/lib/DummyRangeCoder.js +81 -0
- package/.output/server/node_modules/compressjs/lib/FenwickModel.js +194 -0
- package/.output/server/node_modules/compressjs/lib/Huffman.js +514 -0
- package/.output/server/node_modules/compressjs/lib/HuffmanAllocator.js +227 -0
- package/.output/server/node_modules/compressjs/lib/LogDistanceModel.js +46 -0
- package/.output/server/node_modules/compressjs/lib/Lzjb.js +300 -0
- package/.output/server/node_modules/compressjs/lib/LzjbR.js +241 -0
- package/.output/server/node_modules/compressjs/lib/Lzp3.js +273 -0
- package/.output/server/node_modules/compressjs/lib/MTFModel.js +208 -0
- package/.output/server/node_modules/compressjs/lib/NoModel.js +46 -0
- package/.output/server/node_modules/compressjs/lib/PPM.js +343 -0
- package/.output/server/node_modules/compressjs/lib/RangeCoder.js +238 -0
- package/.output/server/node_modules/compressjs/lib/Simple.js +111 -0
- package/.output/server/node_modules/compressjs/lib/Stream.js +53 -0
- package/.output/server/node_modules/compressjs/lib/Util.js +324 -0
- package/.output/server/node_modules/compressjs/lib/freeze.js +14 -0
- package/.output/server/node_modules/compressjs/main.js +29 -0
- package/.output/server/node_modules/compressjs/package.json +35 -0
- package/.output/server/package.json +2 -1
- package/README.md +10 -1
- package/lib/index.d.mts +1 -0
- package/lib/index.mjs +1 -0
- package/lib/piclaw.mjs +100 -0
- package/lib/utils.mjs +96 -0
- package/package.json +16 -11
- package/.output/public/assets/defult-DtwgaiMA.js +0 -1
- package/.output/public/assets/index-B5n0eraW.css +0 -1
- package/.output/public/assets/index-DUbn6fuj.js +0 -205
- package/.output/server/_libs/@aws-crypto/crc32+[...].mjs +0 -299
- package/.output/server/_libs/@aws-sdk/client-bedrock-runtime+[...].mjs +0 -17828
- package/.output/server/_libs/@aws-sdk/credential-provider-http+[...].mjs +0 -122
- package/.output/server/_libs/@aws-sdk/credential-provider-ini+[...].mjs +0 -417
- package/.output/server/_libs/@aws-sdk/credential-provider-process+[...].mjs +0 -54
- package/.output/server/_libs/@aws-sdk/credential-provider-sso+[...].mjs +0 -1151
- package/.output/server/_libs/@aws-sdk/credential-provider-web-identity+[...].mjs +0 -50
- package/.output/server/_libs/@smithy/credential-provider-imds+[...].mjs +0 -369
- package/.output/server/_libs/@tootallnate/quickjs-emscripten+[...].mjs +0 -3011
- package/.output/server/_libs/_100.mjs +0 -2
- package/.output/server/_libs/_101.mjs +0 -2
- package/.output/server/_libs/_102.mjs +0 -2
- package/.output/server/_libs/_103.mjs +0 -5
- package/.output/server/_libs/_104.mjs +0 -3
- package/.output/server/_libs/_105.mjs +0 -2
- package/.output/server/_libs/_106.mjs +0 -3
- package/.output/server/_libs/_107.mjs +0 -2
- package/.output/server/_libs/_108.mjs +0 -2
- package/.output/server/_libs/_95.mjs +0 -2
- package/.output/server/_libs/_96.mjs +0 -2
- package/.output/server/_libs/_97.mjs +0 -2
- package/.output/server/_libs/_98.mjs +0 -2
- package/.output/server/_libs/_99.mjs +0 -2
- package/.output/server/_libs/amdefine.mjs +0 -188
- package/.output/server/_libs/ast-types.mjs +0 -2270
- package/.output/server/_libs/aws-sdk__nested-clients.mjs +0 -3141
- package/.output/server/_libs/basic-ftp.mjs +0 -1906
- package/.output/server/_libs/compressjs.mjs +0 -50
- package/.output/server/_libs/degenerator+[...].mjs +0 -9964
- package/.output/server/_libs/get-uri.mjs +0 -413
- package/.output/server/_libs/http-proxy-agent.mjs +0 -123
- package/.output/server/_libs/ip-address.mjs +0 -1423
- package/.output/server/_libs/lru-cache.mjs +0 -732
- package/.output/server/_libs/netmask.mjs +0 -139
- package/.output/server/_libs/pac-proxy-agent+[...].mjs +0 -3104
- package/.output/server/_libs/proxy-agent+proxy-from-env.mjs +0 -204
- package/.output/server/_libs/smithy__core.mjs +0 -192
- package/.output/server/node_modules/tslib/modules/index.js +0 -70
- package/.output/server/node_modules/tslib/modules/package.json +0 -3
- package/.output/server/node_modules/tslib/package.json +0 -47
- package/.output/server/node_modules/tslib/tslib.js +0 -484
- package/bin/piclaw.mjs +0 -195
|
@@ -1,16 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { o as __toESM, r as __exportAll } from "../_runtime.mjs";
|
|
2
2
|
import { i as SESSIONS_DIR, n as GROUPS_DIR, r as MAIN_GROUP_FOLDER, s as config } from "./config.mjs";
|
|
3
3
|
import { t as createLogger } from "./logger.mjs";
|
|
4
4
|
import { C as setSession, D as updateTaskAfterRun, S as setRouterState, T as storeMessage, _ as removeRegisteredGroup, b as setConfig, c as getAllTasks, d as getMessagesSince, f as getNewMessages, g as logTaskRun, h as initDatabase, i as deleteConfig, l as getConfig, m as getTaskById, o as getAllRegisteredGroups, p as getRouterState, r as deleteChat, s as getAllSessions, t as clearMessages, u as getDueTasks, v as removeSession, w as storeChatMetadata, x as setRegisteredGroup, y as removeTasksByChat } from "./db.mjs";
|
|
5
|
-
import { a as
|
|
5
|
+
import { a as createWriteTool, c as createBashTool, d as stripAnsi$1, i as AuthStorage, l as SessionManager, n as DefaultResourceLoader, o as createReadTool, r as ModelRegistry, s as createEditTool, t as createAgentSession, u as SettingsManager } from "../_libs/@mariozechner/pi-coding-agent+[...].mjs";
|
|
6
6
|
import { n as parseAST, t as init } from "../_libs/md4x.mjs";
|
|
7
7
|
import { t as streamBus } from "./stream.mjs";
|
|
8
|
-
import {
|
|
8
|
+
import { h as Type } from "../_libs/@mariozechner/pi-agent-core+[...].mjs";
|
|
9
9
|
import { t as deviceBus } from "./device-bus.mjs";
|
|
10
10
|
import { n as setBrowserState, t as getBrowserState } from "./browser.mjs";
|
|
11
11
|
import { i as writeNote, n as listNotes, r as readNote, t as deleteNote } from "./notes.mjs";
|
|
12
12
|
import { n as getNtfyConfig, r as sendNtfyNotification } from "./ntfy.mjs";
|
|
13
13
|
import { t as terminalManager } from "./terminal.mjs";
|
|
14
|
+
import { t as sandboxManager } from "./sandbox.mjs";
|
|
14
15
|
import { a as saveAttachment, i as readAttachmentBase64, n as getMimeType, r as isImageMimeType } from "./uploads.mjs";
|
|
15
16
|
import { i as tunnelManager, n as getTunnelConfig, r as setTunnelConfig, t as clearTunnelConfig } from "./tunnel.mjs";
|
|
16
17
|
import fs from "fs";
|
|
@@ -20,7 +21,7 @@ import path$1 from "node:path";
|
|
|
20
21
|
import crypto from "node:crypto";
|
|
21
22
|
import fs$2 from "node:fs/promises";
|
|
22
23
|
import os from "node:os";
|
|
23
|
-
import
|
|
24
|
+
import "node:child_process";
|
|
24
25
|
function appendGroupEvent(folder, event) {
|
|
25
26
|
try {
|
|
26
27
|
const logsDir = path$1.join(GROUPS_DIR, folder, "logs");
|
|
@@ -32,6 +33,32 @@ function appendGroupEvent(folder, event) {
|
|
|
32
33
|
fs$1.appendFileSync(path$1.join(logsDir, "events.jsonl"), line, "utf-8");
|
|
33
34
|
} catch {}
|
|
34
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Extract readable text from a Pi SDK tool result.
|
|
38
|
+
* Handles `{ content: [{ type: "text", text }] }` objects, JSON strings, and plain strings.
|
|
39
|
+
* Strips ANSI/control chars and truncates.
|
|
40
|
+
*/
|
|
41
|
+
function extractToolResultText(value, maxLength = 200) {
|
|
42
|
+
let text = "";
|
|
43
|
+
if (typeof value === "string") try {
|
|
44
|
+
text = _textFromParsed(JSON.parse(value)) ?? value;
|
|
45
|
+
} catch {
|
|
46
|
+
text = value.match(/"text"\s*:\s*"((?:[^"\\]|\\.)*)(?:"|$)/)?.[1]?.replaceAll("\\n", "\n").replaceAll("\\t", " ").replaceAll("\\\"", "\"") ?? value;
|
|
47
|
+
}
|
|
48
|
+
else if (value && typeof value === "object") text = _textFromParsed(value) ?? JSON.stringify(value);
|
|
49
|
+
if (!text) return "";
|
|
50
|
+
text = stripAnsi$1(text).trim();
|
|
51
|
+
if (text.length > maxLength) return text.slice(0, maxLength) + "…";
|
|
52
|
+
return text;
|
|
53
|
+
}
|
|
54
|
+
function _textFromParsed(obj) {
|
|
55
|
+
if (!obj || typeof obj !== "object") return void 0;
|
|
56
|
+
const rec = obj;
|
|
57
|
+
if (Array.isArray(rec.content)) {
|
|
58
|
+
const first = rec.content[0];
|
|
59
|
+
if (first?.text && typeof first.text === "string") return first.text;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
35
62
|
var availableCommands;
|
|
36
63
|
async function getSystemInfo() {
|
|
37
64
|
const info = {
|
|
@@ -130,7 +157,8 @@ function sendPrompt(managed, group, input, onOutput) {
|
|
|
130
157
|
let thinkingText = "";
|
|
131
158
|
let resolved = false;
|
|
132
159
|
let flushedTextLength = 0;
|
|
133
|
-
|
|
160
|
+
/** Chronologically ordered raw items (thinking text + tool markers) flushed as single toggle */
|
|
161
|
+
const pendingItems = [];
|
|
134
162
|
let outputChain = Promise.resolve();
|
|
135
163
|
const timeout = setTimeout(() => {
|
|
136
164
|
if (resolved) return;
|
|
@@ -150,8 +178,7 @@ function sendPrompt(managed, group, input, onOutput) {
|
|
|
150
178
|
status: "error",
|
|
151
179
|
result: fullText || null,
|
|
152
180
|
newSessionId: session.sessionId,
|
|
153
|
-
error: "Agent timeout"
|
|
154
|
-
tools: toolsList.length ? toolsList : void 0
|
|
181
|
+
error: "Agent timeout"
|
|
155
182
|
});
|
|
156
183
|
}, config.get("agentTimeout"));
|
|
157
184
|
const finish = (status, error) => {
|
|
@@ -165,8 +192,7 @@ function sendPrompt(managed, group, input, onOutput) {
|
|
|
165
192
|
status,
|
|
166
193
|
result: fullText || null,
|
|
167
194
|
newSessionId: session.sessionId,
|
|
168
|
-
error
|
|
169
|
-
tools: toolsList.length ? toolsList : void 0
|
|
195
|
+
error
|
|
170
196
|
});
|
|
171
197
|
});
|
|
172
198
|
};
|
|
@@ -182,13 +208,19 @@ function sendPrompt(managed, group, input, onOutput) {
|
|
|
182
208
|
const unflushed = fullText.slice(flushedTextLength);
|
|
183
209
|
const jid = input.chatJid;
|
|
184
210
|
if (onOutput) {
|
|
185
|
-
const
|
|
211
|
+
const parts = [];
|
|
212
|
+
if (thinkingText) {
|
|
213
|
+
pendingItems.push(thinkingText);
|
|
214
|
+
thinkingText = "";
|
|
215
|
+
}
|
|
216
|
+
if (pendingItems.length) parts.push(flushPendingItems(pendingItems));
|
|
217
|
+
if (unflushed) parts.push(unflushed);
|
|
218
|
+
const partial = parts.length ? parts.join("\n\n") : null;
|
|
186
219
|
outputChain = outputChain.then(async () => {
|
|
187
220
|
await onOutput({
|
|
188
221
|
status: "success",
|
|
189
222
|
result: partial ? partial + "\n\n_(stopped)_" : "_(stopped)_",
|
|
190
|
-
newSessionId: session.sessionId
|
|
191
|
-
tools: toolsList.length ? toolsList : void 0
|
|
223
|
+
newSessionId: session.sessionId
|
|
192
224
|
});
|
|
193
225
|
if (partial) streamBus.emit({
|
|
194
226
|
type: "text_end",
|
|
@@ -226,20 +258,24 @@ function sendPrompt(managed, group, input, onOutput) {
|
|
|
226
258
|
}
|
|
227
259
|
if (ae.type === "text_end" && "content" in ae && onOutput) {
|
|
228
260
|
const text = ae.content;
|
|
229
|
-
const
|
|
230
|
-
thinkingText
|
|
261
|
+
const parts = [];
|
|
262
|
+
if (thinkingText) {
|
|
263
|
+
pendingItems.push(thinkingText);
|
|
264
|
+
thinkingText = "";
|
|
265
|
+
}
|
|
266
|
+
if (pendingItems.length) parts.push(flushPendingItems(pendingItems));
|
|
267
|
+
parts.push(text);
|
|
268
|
+
const stored = parts.join("\n\n");
|
|
231
269
|
appendGroupEvent(group.folder, {
|
|
232
270
|
type: "text_end",
|
|
233
271
|
length: stored.length,
|
|
234
272
|
preview: stored.slice(0, 100)
|
|
235
273
|
});
|
|
236
|
-
const snapshotTools = toolsList.length ? [...toolsList] : void 0;
|
|
237
274
|
outputChain = outputChain.then(async () => {
|
|
238
275
|
await onOutput({
|
|
239
276
|
status: "success",
|
|
240
277
|
result: stored,
|
|
241
|
-
newSessionId: session.sessionId
|
|
242
|
-
tools: snapshotTools
|
|
278
|
+
newSessionId: session.sessionId
|
|
243
279
|
});
|
|
244
280
|
flushedTextLength = fullText.length;
|
|
245
281
|
streamBus.emit({
|
|
@@ -257,11 +293,17 @@ function sendPrompt(managed, group, input, onOutput) {
|
|
|
257
293
|
delta: ae.delta
|
|
258
294
|
});
|
|
259
295
|
}
|
|
260
|
-
if (ae.type === "thinking_end" && "content" in ae)
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
296
|
+
if (ae.type === "thinking_end" && "content" in ae) {
|
|
297
|
+
if (thinkingText) {
|
|
298
|
+
pendingItems.push(thinkingText);
|
|
299
|
+
thinkingText = "";
|
|
300
|
+
}
|
|
301
|
+
streamBus.emit({
|
|
302
|
+
type: "thinking_end",
|
|
303
|
+
chatJid: jid,
|
|
304
|
+
content: ae.content
|
|
305
|
+
});
|
|
306
|
+
}
|
|
265
307
|
break;
|
|
266
308
|
}
|
|
267
309
|
case "agent_end":
|
|
@@ -308,7 +350,7 @@ function sendPrompt(managed, group, input, onOutput) {
|
|
|
308
350
|
isError: event.isError,
|
|
309
351
|
resultPreview: previewForLog(event.result)
|
|
310
352
|
};
|
|
311
|
-
if (event.isError) logger$6.
|
|
353
|
+
if (event.isError) logger$6.debug(payload, "Tool execution error");
|
|
312
354
|
else logger$6.debug(payload, "Tool execution finished");
|
|
313
355
|
appendGroupEvent(group.folder, {
|
|
314
356
|
type: "tool_end",
|
|
@@ -317,12 +359,8 @@ function sendPrompt(managed, group, input, onOutput) {
|
|
|
317
359
|
isError: event.isError,
|
|
318
360
|
resultPreview: previewForLog(event.result, 200)
|
|
319
361
|
});
|
|
320
|
-
const cleanPreview =
|
|
321
|
-
|
|
322
|
-
name: event.toolName,
|
|
323
|
-
isError: event.isError || void 0,
|
|
324
|
-
resultPreview: cleanPreview
|
|
325
|
-
});
|
|
362
|
+
const cleanPreview = extractToolResultText(event.result) || void 0;
|
|
363
|
+
pendingItems.push(toToolMarker(event.toolName, event.isError, cleanPreview));
|
|
326
364
|
streamBus.emit({
|
|
327
365
|
type: "tool_end",
|
|
328
366
|
chatJid: jid,
|
|
@@ -374,7 +412,7 @@ Execution rules:
|
|
|
374
412
|
- Use bash for operations (searching, building, testing, git-aware inspection).
|
|
375
413
|
- Use terminal for long running processes such as dev server or tmux.
|
|
376
414
|
- Do not use cat/sed/awk to read files use read tool instead.
|
|
377
|
-
-
|
|
415
|
+
- IMPORTANT: Always read a file before editing it. The edit tool requires an exact match of existing text — if you guess the content, it will fail. Read first, then copy the exact text to replace.
|
|
378
416
|
- Open browser tabs only when explicitly asked.
|
|
379
417
|
- Prefer edit for targeted changes; use write for new files or full rewrites.
|
|
380
418
|
- Use notify tool as a secondary channel to send important info to the user and for anything you can't send in chat. Do not repeat chat messages in notifications.
|
|
@@ -393,34 +431,15 @@ async function buildSystemPrompt(ctx) {
|
|
|
393
431
|
|
|
394
432
|
${baseSystemPrompt}
|
|
395
433
|
|
|
396
|
-
Current date and time: ${(/* @__PURE__ */ new Date()).toLocaleString()}
|
|
397
434
|
Current model name: ${ctx.modelName}
|
|
398
|
-
|
|
435
|
+
`;
|
|
399
436
|
const envInfo = await getSystemInfoText().catch((err) => {
|
|
400
|
-
logger$6.
|
|
437
|
+
logger$6.debug({ err }, "Failed to get system info");
|
|
401
438
|
return null;
|
|
402
439
|
});
|
|
403
440
|
if (envInfo) prompt += `\n\nEnvironment:\n${envInfo}`;
|
|
404
441
|
return prompt;
|
|
405
442
|
}
|
|
406
|
-
var RE_ANSI = /.\x08|\x1b\[[0-9;]*[A-Za-z]|\x1b[>?]?[0-9]*[A-Za-z]|[\x00-\x08\x0b\x0c\x0e-\x1f]/g;
|
|
407
|
-
/** Extract readable text from a tool result and strip ANSI/control chars */
|
|
408
|
-
function cleanToolPreview(value, maxLength = 200) {
|
|
409
|
-
let text = "";
|
|
410
|
-
if (typeof value === "string") text = value;
|
|
411
|
-
else if (value && typeof value === "object") {
|
|
412
|
-
const obj = value;
|
|
413
|
-
if (Array.isArray(obj.content)) {
|
|
414
|
-
const first = obj.content[0];
|
|
415
|
-
if (first?.text && typeof first.text === "string") text = first.text;
|
|
416
|
-
else text = JSON.stringify(value);
|
|
417
|
-
} else text = JSON.stringify(value);
|
|
418
|
-
}
|
|
419
|
-
if (!text) return "";
|
|
420
|
-
text = text.replace(RE_ANSI, "").trim();
|
|
421
|
-
if (text.length > maxLength) return text.slice(0, maxLength) + "…";
|
|
422
|
-
return text;
|
|
423
|
-
}
|
|
424
443
|
function previewForLog(value, maxLength = 1200) {
|
|
425
444
|
try {
|
|
426
445
|
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
@@ -431,6 +450,14 @@ function previewForLog(value, maxLength = 1200) {
|
|
|
431
450
|
return "[unserializable value]";
|
|
432
451
|
}
|
|
433
452
|
}
|
|
453
|
+
/** Flush all pending items (thinking text + tool markers) into a single collapsible block */
|
|
454
|
+
function flushPendingItems(items) {
|
|
455
|
+
return `<details><summary>thinking</summary>\n\n${items.splice(0).join("\n\n")}\n\n</details>`;
|
|
456
|
+
}
|
|
457
|
+
/** Build an inline tool marker tag */
|
|
458
|
+
function toToolMarker(name, isError, preview) {
|
|
459
|
+
return `<tool name="${name}" status="${isError ? "error" : "ok"}"${preview ? ` preview="${preview.replaceAll("\"", """)}"` : ""}/>`;
|
|
460
|
+
}
|
|
434
461
|
const browserTool = {
|
|
435
462
|
name: "browser",
|
|
436
463
|
label: "Browser",
|
|
@@ -631,14 +658,14 @@ const notesTool = {
|
|
|
631
658
|
"Actions: list (show notes, optionally filtered by category),",
|
|
632
659
|
"read (get note content), write (create/overwrite a note), delete (remove a note).",
|
|
633
660
|
"Notes are referenced as \"category/id\" (e.g. \"ideas/project-plan\").",
|
|
634
|
-
"Virtual ~agents notes
|
|
661
|
+
"Virtual ~agents notes map to per-session AGENTS.md files."
|
|
635
662
|
].join(" "),
|
|
636
663
|
promptSnippet: "notes: Read, write, list, and delete markdown notes organized by category",
|
|
637
664
|
promptGuidelines: [
|
|
638
665
|
"Use notes to persist structured information across sessions — plans, references, logs, etc.",
|
|
639
666
|
"Note refs use \"category/id\" format, e.g. \"tasks/backlog\" or \"docs/api-spec\".",
|
|
640
667
|
"List notes first to see what exists before reading or writing.",
|
|
641
|
-
"Virtual ~agents/ notes map to per-session AGENTS.md files
|
|
668
|
+
"Virtual ~agents/ notes map to per-session AGENTS.md files."
|
|
642
669
|
],
|
|
643
670
|
parameters: Type.Object({
|
|
644
671
|
action: Type.Union([
|
|
@@ -679,6 +706,7 @@ function handleWrite$1(p) {
|
|
|
679
706
|
if (p.content == null) return err$2("content is required for write");
|
|
680
707
|
try {
|
|
681
708
|
writeNote(p.ref, p.content);
|
|
709
|
+
deviceBus.emitDeviceEvent("notes");
|
|
682
710
|
return ok$2(`Note "${p.ref}" saved.`, { ref: p.ref });
|
|
683
711
|
} catch (e) {
|
|
684
712
|
return err$2(`Failed to write note: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -687,6 +715,7 @@ function handleWrite$1(p) {
|
|
|
687
715
|
function handleDelete(p) {
|
|
688
716
|
if (!p.ref) return err$2("ref is required for delete");
|
|
689
717
|
if (!deleteNote(p.ref)) return err$2(`Note "${p.ref}" not found or cannot be deleted`);
|
|
718
|
+
deviceBus.emitDeviceEvent("notes");
|
|
690
719
|
return ok$2(`Note "${p.ref}" deleted.`, { ref: p.ref });
|
|
691
720
|
}
|
|
692
721
|
function ok$2(text, details) {
|
|
@@ -818,7 +847,7 @@ const terminalTool = {
|
|
|
818
847
|
input: Type.Optional(Type.String({ description: "Command or input text (exec appends newline automatically)" })),
|
|
819
848
|
cwd: Type.Optional(Type.String({ description: "Working directory for new session (create)" })),
|
|
820
849
|
label: Type.Optional(Type.String({ description: "Session label (create)" })),
|
|
821
|
-
|
|
850
|
+
sandbox: Type.Optional(Type.Boolean({ description: "Use sandboxed virtual TTY (create, default: false)" })),
|
|
822
851
|
cols: Type.Optional(Type.Number({ description: "Terminal columns (create/resize, default: 120)" })),
|
|
823
852
|
rows: Type.Optional(Type.Number({ description: "Terminal rows (create/resize, default: 40)" })),
|
|
824
853
|
timeout: Type.Optional(Type.Number({ description: "Max ms to wait for output (exec only, default: 10000, max: 30000)" }))
|
|
@@ -850,7 +879,7 @@ async function handleCreate(p) {
|
|
|
850
879
|
rows: p.rows ?? 40,
|
|
851
880
|
cwd: p.cwd,
|
|
852
881
|
label: p.label,
|
|
853
|
-
|
|
882
|
+
sandbox: p.sandbox
|
|
854
883
|
});
|
|
855
884
|
return ok$1(`Terminal session created: ${session.id} [${session.label}] ${session.cols}x${session.rows}`, {
|
|
856
885
|
id: session.id,
|
|
@@ -961,6 +990,61 @@ function err$1(text) {
|
|
|
961
990
|
};
|
|
962
991
|
}
|
|
963
992
|
/**
|
|
993
|
+
* Sandboxed coding tools — real pi SDK tools backed by an in-memory filesystem.
|
|
994
|
+
* Uses the centralized sandbox manager so the filesystem is shared with virtual PTY.
|
|
995
|
+
*/
|
|
996
|
+
function createSandboxedCodingTools(opts) {
|
|
997
|
+
const sb = sandboxManager.get(opts.sandboxId, { ...opts.realCwd && { realCwd: opts.realCwd } });
|
|
998
|
+
const { fs } = sb;
|
|
999
|
+
const read = createReadTool(sb.cwd, { operations: {
|
|
1000
|
+
readFile: async (p) => {
|
|
1001
|
+
const content = await fs.readFile(p);
|
|
1002
|
+
return Buffer.from(content);
|
|
1003
|
+
},
|
|
1004
|
+
access: async (p) => {
|
|
1005
|
+
if (!await fs.exists(p)) throw new Error(`ENOENT: no such file or directory, access '${p}'`);
|
|
1006
|
+
}
|
|
1007
|
+
} });
|
|
1008
|
+
const write = createWriteTool(sb.cwd, { operations: {
|
|
1009
|
+
writeFile: async (p, content) => {
|
|
1010
|
+
await fs.writeFile(p, content);
|
|
1011
|
+
await sb.writeBack();
|
|
1012
|
+
},
|
|
1013
|
+
mkdir: (dir) => {
|
|
1014
|
+
return fs.mkdir(dir, { recursive: true });
|
|
1015
|
+
}
|
|
1016
|
+
} });
|
|
1017
|
+
const edit = createEditTool(sb.cwd, { operations: {
|
|
1018
|
+
readFile: async (p) => {
|
|
1019
|
+
const content = await fs.readFile(p);
|
|
1020
|
+
return Buffer.from(content);
|
|
1021
|
+
},
|
|
1022
|
+
writeFile: async (p, content) => {
|
|
1023
|
+
await fs.writeFile(p, content);
|
|
1024
|
+
await sb.writeBack();
|
|
1025
|
+
},
|
|
1026
|
+
access: async (p) => {
|
|
1027
|
+
if (!await fs.exists(p)) throw new Error(`ENOENT: no such file or directory, access '${p}'`);
|
|
1028
|
+
}
|
|
1029
|
+
} });
|
|
1030
|
+
return [
|
|
1031
|
+
read,
|
|
1032
|
+
createBashTool(sb.cwd, { operations: { exec: async (command, execCwd, options) => {
|
|
1033
|
+
try {
|
|
1034
|
+
const result = await sb.exec(command, { cwd: execCwd || void 0 });
|
|
1035
|
+
if (result.stdout) options.onData(Buffer.from(result.stdout));
|
|
1036
|
+
if (result.stderr) options.onData(Buffer.from(result.stderr));
|
|
1037
|
+
return { exitCode: result.exitCode };
|
|
1038
|
+
} catch (err) {
|
|
1039
|
+
options.onData(Buffer.from(err.message + "\n"));
|
|
1040
|
+
return { exitCode: 1 };
|
|
1041
|
+
}
|
|
1042
|
+
} } }),
|
|
1043
|
+
edit,
|
|
1044
|
+
write
|
|
1045
|
+
];
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
964
1048
|
* MIT License
|
|
965
1049
|
* Copyright (c) 2024 Mario Zechner
|
|
966
1050
|
* Based on https://github.com/badlogic/pi-skills/tree/main/brave-search
|
|
@@ -1069,8 +1153,8 @@ async function fetchPageContent(url, signal) {
|
|
|
1069
1153
|
}
|
|
1070
1154
|
async function extractReadableText(html, url) {
|
|
1071
1155
|
try {
|
|
1072
|
-
const { Readability } = await import("../_libs/
|
|
1073
|
-
const { JSDOM } = await import("../_libs/
|
|
1156
|
+
const { Readability } = await import("../_libs/_.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
|
|
1157
|
+
const { JSDOM } = await import("../_libs/_4.mjs").then((m) => /* @__PURE__ */ __toESM(m.default, 1));
|
|
1074
1158
|
const article = new Readability(new JSDOM(html, { url }).window.document).parse();
|
|
1075
1159
|
if (article?.content) return (await htmlToMarkdown(article.content)).slice(0, 5e3);
|
|
1076
1160
|
const doc = new JSDOM(html, { url }).window.document;
|
|
@@ -1079,13 +1163,13 @@ async function extractReadableText(html, url) {
|
|
|
1079
1163
|
if (text.trim().length > 100) return text.trim().slice(0, 5e3);
|
|
1080
1164
|
return "(Could not extract content)";
|
|
1081
1165
|
} catch {
|
|
1082
|
-
return stripHtmlTags(html).slice(0, 5e3) || "(Could not extract content)";
|
|
1166
|
+
return stripHtmlTags$1(html).slice(0, 5e3) || "(Could not extract content)";
|
|
1083
1167
|
}
|
|
1084
1168
|
}
|
|
1085
1169
|
async function htmlToMarkdown(html) {
|
|
1086
1170
|
try {
|
|
1087
|
-
const TurndownService = (await import("../_libs/
|
|
1088
|
-
const { gfm } = await import("../_libs/
|
|
1171
|
+
const TurndownService = (await import("../_libs/_29.mjs")).default;
|
|
1172
|
+
const { gfm } = await import("../_libs/_93.mjs");
|
|
1089
1173
|
const turndown = new TurndownService({
|
|
1090
1174
|
headingStyle: "atx",
|
|
1091
1175
|
codeBlockStyle: "fenced"
|
|
@@ -1097,10 +1181,10 @@ async function htmlToMarkdown(html) {
|
|
|
1097
1181
|
});
|
|
1098
1182
|
return turndown.turndown(html).replace(/\[\\?\[\s*\\?\]\]\([^)]*\)/g, "").replace(/ +/g, " ").replace(/\s+,/g, ",").replace(/\s+\./g, ".").replace(/\n{3,}/g, "\n\n").trim();
|
|
1099
1183
|
} catch {
|
|
1100
|
-
return stripHtmlTags(html);
|
|
1184
|
+
return stripHtmlTags$1(html);
|
|
1101
1185
|
}
|
|
1102
1186
|
}
|
|
1103
|
-
function stripHtmlTags(html) {
|
|
1187
|
+
function stripHtmlTags$1(html) {
|
|
1104
1188
|
return html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "").replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
|
1105
1189
|
}
|
|
1106
1190
|
function ok(text, details) {
|
|
@@ -1242,11 +1326,15 @@ function resolveDefaultModel() {
|
|
|
1242
1326
|
}
|
|
1243
1327
|
async function createManagedSession(group, input, sessionDir) {
|
|
1244
1328
|
const groupDir = path.join(GROUPS_DIR, group.folder);
|
|
1329
|
+
const sandboxEnforced = config.get("sandbox") === "enforced";
|
|
1330
|
+
const sandbox = sandboxEnforced || !!group.sandbox;
|
|
1245
1331
|
logger$5.info({
|
|
1246
1332
|
group: group.name,
|
|
1247
1333
|
folder: group.folder,
|
|
1248
1334
|
isMain: input.isMain,
|
|
1249
|
-
hasSession: !!input.sessionId
|
|
1335
|
+
hasSession: !!input.sessionId,
|
|
1336
|
+
sandbox,
|
|
1337
|
+
...sandboxEnforced && { sandboxSource: "enforced" }
|
|
1250
1338
|
}, "Creating pi SDK session");
|
|
1251
1339
|
const model = resolveGroupModel(group);
|
|
1252
1340
|
const settingsManager = SettingsManager.inMemory({
|
|
@@ -1265,18 +1353,31 @@ async function createManagedSession(group, input, sessionDir) {
|
|
|
1265
1353
|
const resourceLoader = new DefaultResourceLoader({
|
|
1266
1354
|
cwd: groupDir,
|
|
1267
1355
|
settingsManager,
|
|
1268
|
-
systemPromptOverride: () => systemPrompt
|
|
1356
|
+
systemPromptOverride: () => systemPrompt,
|
|
1357
|
+
...sandbox && {
|
|
1358
|
+
agentDir: groupDir,
|
|
1359
|
+
noExtensions: true,
|
|
1360
|
+
noSkills: true,
|
|
1361
|
+
noPromptTemplates: true,
|
|
1362
|
+
noThemes: true,
|
|
1363
|
+
agentsFilesOverride: (_base) => ({ agentsFiles: [] })
|
|
1364
|
+
}
|
|
1269
1365
|
});
|
|
1270
1366
|
await resourceLoader.reload();
|
|
1271
1367
|
const sessionManager = input.sessionId ? SessionManager.continueRecent(groupDir, sessionDir) : SessionManager.create(groupDir, sessionDir);
|
|
1368
|
+
const thinkingLevel = group.thinkingLevel || "off";
|
|
1369
|
+
if (sandbox) logger$5.info({ group: group.name }, "Using sandboxed coding tools (in-memory filesystem)");
|
|
1370
|
+
const sandboxedTools = sandbox ? Object.fromEntries(createSandboxedCodingTools({
|
|
1371
|
+
sandboxId: `~${group.folder}`,
|
|
1372
|
+
realCwd: groupDir
|
|
1373
|
+
}).map((t) => [t.name, t])) : void 0;
|
|
1272
1374
|
const { session, modelFallbackMessage } = await createAgentSession({
|
|
1273
1375
|
cwd: groupDir,
|
|
1274
1376
|
model,
|
|
1275
|
-
thinkingLevel
|
|
1377
|
+
thinkingLevel,
|
|
1276
1378
|
authStorage,
|
|
1277
1379
|
modelRegistry,
|
|
1278
|
-
|
|
1279
|
-
customTools: [
|
|
1380
|
+
customTools: sandbox ? [notifyTool, webSearchTool] : [
|
|
1280
1381
|
browserTool,
|
|
1281
1382
|
createMessageTool(group.folder),
|
|
1282
1383
|
notesTool,
|
|
@@ -1288,6 +1389,10 @@ async function createManagedSession(group, input, sessionDir) {
|
|
|
1288
1389
|
sessionManager,
|
|
1289
1390
|
settingsManager
|
|
1290
1391
|
});
|
|
1392
|
+
if (sandboxedTools) {
|
|
1393
|
+
session._baseToolsOverride = sandboxedTools;
|
|
1394
|
+
await session.reload();
|
|
1395
|
+
}
|
|
1291
1396
|
if (modelFallbackMessage) logger$5.warn({
|
|
1292
1397
|
group: group.name,
|
|
1293
1398
|
message: modelFallbackMessage
|
|
@@ -1461,23 +1566,33 @@ var logger$4 = createLogger("pi");
|
|
|
1461
1566
|
async function run(group, input, onOutput) {
|
|
1462
1567
|
return sendPrompt(await getOrCreateSession(group, input), group, input, onOutput);
|
|
1463
1568
|
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Deliver a message to an active streaming session.
|
|
1571
|
+
* If the message ends with `!`, uses `steer` (interrupts agent after current tool).
|
|
1572
|
+
* Otherwise uses `followUp` (delivered after agent finishes current work).
|
|
1573
|
+
* Returns the delivery method, or `false` if no active streaming session.
|
|
1574
|
+
*/
|
|
1464
1575
|
function sendMessage(groupJid, message) {
|
|
1465
1576
|
const managed = sessions.get(groupJid);
|
|
1466
|
-
if (!managed) return false;
|
|
1577
|
+
if (!managed || !managed.session.isStreaming) return false;
|
|
1467
1578
|
const { session } = managed;
|
|
1468
|
-
if (
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1579
|
+
if (message.includes("!")) {
|
|
1580
|
+
session.steer(message).catch((err) => {
|
|
1581
|
+
logger$4.error({
|
|
1582
|
+
group: managed.groupFolder,
|
|
1583
|
+
err
|
|
1584
|
+
}, "Failed to steer message");
|
|
1585
|
+
});
|
|
1586
|
+
return "steer";
|
|
1587
|
+
} else {
|
|
1588
|
+
session.followUp(message).catch((err) => {
|
|
1589
|
+
logger$4.error({
|
|
1590
|
+
group: managed.groupFolder,
|
|
1591
|
+
err
|
|
1592
|
+
}, "Failed to queue follow-up");
|
|
1593
|
+
});
|
|
1594
|
+
return "followup";
|
|
1595
|
+
}
|
|
1481
1596
|
}
|
|
1482
1597
|
async function kill(groupJid) {
|
|
1483
1598
|
const managed = sessions.get(groupJid);
|
|
@@ -1548,8 +1663,7 @@ var commands = {
|
|
|
1548
1663
|
stop: handleStop,
|
|
1549
1664
|
model: handleModel,
|
|
1550
1665
|
thinking: handleThinking,
|
|
1551
|
-
compact: handleCompact
|
|
1552
|
-
brief: handleBrief
|
|
1666
|
+
compact: handleCompact
|
|
1553
1667
|
};
|
|
1554
1668
|
var VALID_THINKING_LEVELS = [
|
|
1555
1669
|
"off",
|
|
@@ -1567,7 +1681,7 @@ const commandDescriptions = [
|
|
|
1567
1681
|
},
|
|
1568
1682
|
{
|
|
1569
1683
|
command: "info",
|
|
1570
|
-
description: "Show session info"
|
|
1684
|
+
description: "Show session info and workspace overview"
|
|
1571
1685
|
},
|
|
1572
1686
|
{
|
|
1573
1687
|
command: "new",
|
|
@@ -1591,10 +1705,6 @@ const commandDescriptions = [
|
|
|
1591
1705
|
{
|
|
1592
1706
|
command: "compact",
|
|
1593
1707
|
description: "Compact current session context"
|
|
1594
|
-
},
|
|
1595
|
-
{
|
|
1596
|
-
command: "brief",
|
|
1597
|
-
description: "Show workspace snapshot: groups, sessions, tasks"
|
|
1598
1708
|
}
|
|
1599
1709
|
];
|
|
1600
1710
|
/** Commands that bypass the queue and execute immediately (even during streaming). */
|
|
@@ -1639,31 +1749,6 @@ async function tryCommand(ctx, text) {
|
|
|
1639
1749
|
await ctx.server.sendBotMessage(ctx.chatJid, `Unknown command \`/${name}\`. Use \`/help\` for available commands.`);
|
|
1640
1750
|
return true;
|
|
1641
1751
|
}
|
|
1642
|
-
/**
|
|
1643
|
-
* Try to handle a message as a `! <bash>` command. Returns true if handled.
|
|
1644
|
-
* Runs the command directly in the group's working directory, bypassing the agent.
|
|
1645
|
-
*/
|
|
1646
|
-
function tryBashCommand(ctx, text) {
|
|
1647
|
-
if (!text.startsWith("!")) return false;
|
|
1648
|
-
const cmd = text.slice(1).trim();
|
|
1649
|
-
if (!cmd) return false;
|
|
1650
|
-
const cwd = path$1.join(GROUPS_DIR, ctx.group.folder);
|
|
1651
|
-
ctx.server.sendBotMessage(ctx.chatJid, `\`$ ${cmd}\``).catch(() => {});
|
|
1652
|
-
exec(cmd, {
|
|
1653
|
-
cwd,
|
|
1654
|
-
timeout: 5e3
|
|
1655
|
-
}, (err, stdout, stderr) => {
|
|
1656
|
-
const parts = [];
|
|
1657
|
-
if (stdout) parts.push(stdout.trimEnd());
|
|
1658
|
-
if (stderr) parts.push(stderr.trimEnd());
|
|
1659
|
-
if (err && !stderr) parts.push(String(err.message || err));
|
|
1660
|
-
const timedOut = err && "killed" in err && err.killed;
|
|
1661
|
-
const output = parts.join("\n").replace(stripAnsiRe, "") || "(no output)";
|
|
1662
|
-
const suffix = timedOut ? "\n(timed out)" : "";
|
|
1663
|
-
ctx.server.sendBotMessage(ctx.chatJid, `\`\`\`\n${output}${suffix}\n\`\`\``).catch(() => {});
|
|
1664
|
-
});
|
|
1665
|
-
return true;
|
|
1666
|
-
}
|
|
1667
1752
|
async function handleHelp(ctx) {
|
|
1668
1753
|
const lines = ["**Available commands:**", ...commandDescriptions.map((c) => `- \`/${c.command}\` — ${c.description}`)];
|
|
1669
1754
|
await ctx.server.sendBotMessage(ctx.chatJid, lines.join("\n"));
|
|
@@ -1689,6 +1774,28 @@ async function handleInfo(ctx) {
|
|
|
1689
1774
|
lines.push(`**Context:** ${ctx$.tokens.toLocaleString()} / ${ctx$.contextWindow.toLocaleString()} tokens (${pct}%)`);
|
|
1690
1775
|
}
|
|
1691
1776
|
} else lines.push("", "*No active session*");
|
|
1777
|
+
const allGroups = ctx.server.getRegisteredGroups();
|
|
1778
|
+
const allTasks = await getAllTasks();
|
|
1779
|
+
lines.push("", "---", "", "**All Groups:**");
|
|
1780
|
+
for (const [jid, g] of Object.entries(allGroups)) {
|
|
1781
|
+
const gResolved = resolveGroupModel(g);
|
|
1782
|
+
const gModelLabel = gResolved ? `${gResolved.provider}/${gResolved.id}` : "none";
|
|
1783
|
+
const gManaged = sessions.get(jid);
|
|
1784
|
+
const sessionPart = gManaged ? `active, idle ${formatDuration(Date.now() - gManaged.lastActivity)}` : "no session";
|
|
1785
|
+
const soulPart = g.soul ? " 🧬" : "";
|
|
1786
|
+
lines.push(`- **${g.name}** (\`${g.folder}\`) — \`${gModelLabel}\` — ${sessionPart}${soulPart}`);
|
|
1787
|
+
}
|
|
1788
|
+
const cutoff = (/* @__PURE__ */ new Date(Date.now() - 1440 * 60 * 1e3)).toISOString();
|
|
1789
|
+
const recentTasks = allTasks.filter((t) => t.status === "active" || t.last_run && t.last_run > cutoff);
|
|
1790
|
+
if (recentTasks.length > 0) {
|
|
1791
|
+
lines.push("", `**Tasks** (active or run in last 24h): ${recentTasks.length}`);
|
|
1792
|
+
for (const t of recentTasks.slice(0, 10)) {
|
|
1793
|
+
const groupName = Object.values(allGroups).find((g) => g.folder === t.group_folder)?.name || t.group_folder;
|
|
1794
|
+
lines.push(`- [${t.status}] \`${t.schedule_type}:${t.schedule_value}\` in **${groupName}** — ${t.prompt.slice(0, 60)}`);
|
|
1795
|
+
}
|
|
1796
|
+
if (recentTasks.length > 10) lines.push(` … and ${recentTasks.length - 10} more`);
|
|
1797
|
+
}
|
|
1798
|
+
lines.push("", `*${Object.keys(allGroups).length} groups, ${allTasks.filter((t) => t.status === "active").length} active tasks*`);
|
|
1692
1799
|
await ctx.server.sendBotMessage(ctx.chatJid, lines.join("\n"));
|
|
1693
1800
|
}
|
|
1694
1801
|
function formatDuration(ms) {
|
|
@@ -1803,32 +1910,6 @@ async function handleCompact(ctx, arg) {
|
|
|
1803
1910
|
if (after?.tokens != null) lines.push(`**Est. after:** ${after.tokens.toLocaleString()}`);
|
|
1804
1911
|
await ctx.server.sendBotMessage(ctx.chatJid, lines.join("\n"));
|
|
1805
1912
|
}
|
|
1806
|
-
async function handleBrief(ctx) {
|
|
1807
|
-
const allGroups = ctx.server.getRegisteredGroups();
|
|
1808
|
-
const allTasks = await getAllTasks();
|
|
1809
|
-
const lines = ["**Workspace Brief**", ""];
|
|
1810
|
-
lines.push("**Groups:**");
|
|
1811
|
-
for (const [jid, g] of Object.entries(allGroups)) {
|
|
1812
|
-
const resolved = resolveGroupModel(g);
|
|
1813
|
-
const modelLabel = resolved ? `${resolved.provider}/${resolved.id}` : "none";
|
|
1814
|
-
const managed = sessions.get(jid);
|
|
1815
|
-
const sessionPart = managed ? `active, idle ${formatDuration(Date.now() - managed.lastActivity)}` : "no session";
|
|
1816
|
-
const soulPart = g.soul ? " 🧬" : "";
|
|
1817
|
-
lines.push(`- **${g.name}** (\`${g.folder}\`) — \`${modelLabel}\` — ${sessionPart}${soulPart}`);
|
|
1818
|
-
}
|
|
1819
|
-
lines.push("");
|
|
1820
|
-
const cutoff = (/* @__PURE__ */ new Date(Date.now() - 1440 * 60 * 1e3)).toISOString();
|
|
1821
|
-
const recentTasks = allTasks.filter((t) => t.status === "active" || t.last_run && t.last_run > cutoff);
|
|
1822
|
-
lines.push(`**Tasks** (active or run in last 24h): ${recentTasks.length}`);
|
|
1823
|
-
for (const t of recentTasks.slice(0, 10)) {
|
|
1824
|
-
const groupName = Object.values(allGroups).find((g) => g.folder === t.group_folder)?.name || t.group_folder;
|
|
1825
|
-
lines.push(`- [${t.status}] \`${t.schedule_type}:${t.schedule_value}\` in **${groupName}** — ${t.prompt.slice(0, 60)}`);
|
|
1826
|
-
}
|
|
1827
|
-
if (recentTasks.length > 10) lines.push(` … and ${recentTasks.length - 10} more`);
|
|
1828
|
-
lines.push("");
|
|
1829
|
-
lines.push(`*${Object.keys(allGroups).length} groups, ${allTasks.filter((t) => t.status === "active").length} active tasks*`);
|
|
1830
|
-
await ctx.server.sendBotMessage(ctx.chatJid, lines.join("\n"));
|
|
1831
|
-
}
|
|
1832
1913
|
function findModel(query) {
|
|
1833
1914
|
const available = getFilteredModels();
|
|
1834
1915
|
if (available.length === 0) return void 0;
|
|
@@ -1847,16 +1928,16 @@ function findModel(query) {
|
|
|
1847
1928
|
matches.sort((a, b) => a.id.length - b.id.length || b.id.localeCompare(a.id, void 0, { numeric: true }));
|
|
1848
1929
|
return matches[0];
|
|
1849
1930
|
}
|
|
1850
|
-
|
|
1931
|
+
new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*[A-Za-z]`, "g");
|
|
1851
1932
|
await init();
|
|
1852
1933
|
var logger$3 = createLogger("telegram");
|
|
1853
1934
|
var JID_PREFIX = "tg:";
|
|
1854
1935
|
var POLL_INTERVAL = 1e3;
|
|
1855
1936
|
var POLL_TIMEOUT = 30;
|
|
1856
1937
|
var API_BASE = "https://api.telegram.org/bot";
|
|
1857
|
-
var STREAM_THROTTLE_MIN =
|
|
1858
|
-
var STREAM_THROTTLE_MAX =
|
|
1859
|
-
var STREAM_THROTTLE_STEP =
|
|
1938
|
+
var STREAM_THROTTLE_MIN = 1e3;
|
|
1939
|
+
var STREAM_THROTTLE_MAX = 1e4;
|
|
1940
|
+
var STREAM_THROTTLE_STEP = 1e3;
|
|
1860
1941
|
var STREAM_THROTTLE_JITTER = 120;
|
|
1861
1942
|
var STREAM_INITIAL_DELAY = 100;
|
|
1862
1943
|
var TG_MAX_TEXT = 4096;
|
|
@@ -1894,10 +1975,9 @@ var TelegramChannel = class {
|
|
|
1894
1975
|
async sendMessage(jid, text) {
|
|
1895
1976
|
const chatId = jid.slice(3);
|
|
1896
1977
|
const state = this.streamState.get(jid);
|
|
1897
|
-
const
|
|
1898
|
-
const fullText = toolSummary ? `${toolSummary}\n\n${text}` : text;
|
|
1899
|
-
const { text: formatted, parse_mode } = formatForTelegram(fullText);
|
|
1978
|
+
const { text: formatted, parse_mode } = formatForTelegram(text);
|
|
1900
1979
|
const parseMode = { parse_mode };
|
|
1980
|
+
const plainText = stripThinkingTags(text);
|
|
1901
1981
|
if (state) {
|
|
1902
1982
|
this.streamState.delete(jid);
|
|
1903
1983
|
if (state.editTimer) clearTimeout(state.editTimer);
|
|
@@ -1912,19 +1992,19 @@ var TelegramChannel = class {
|
|
|
1912
1992
|
return;
|
|
1913
1993
|
} catch (editErr) {
|
|
1914
1994
|
if (isTgParseError(editErr)) try {
|
|
1915
|
-
logger$3.
|
|
1995
|
+
logger$3.debug({
|
|
1916
1996
|
err: editErr,
|
|
1917
1997
|
formattedText: formatted.slice(0, 500),
|
|
1918
|
-
originalText:
|
|
1919
|
-
}, "
|
|
1998
|
+
originalText: text.slice(0, 500)
|
|
1999
|
+
}, "HTML edit failed, retrying as plain text");
|
|
1920
2000
|
await this.api("editMessageText", {
|
|
1921
2001
|
chat_id: chatId,
|
|
1922
2002
|
message_id: state.messageId,
|
|
1923
|
-
text:
|
|
2003
|
+
text: plainText.slice(0, TG_MAX_TEXT)
|
|
1924
2004
|
});
|
|
1925
2005
|
return;
|
|
1926
2006
|
} catch (plainErr) {
|
|
1927
|
-
logger$3.
|
|
2007
|
+
logger$3.debug({ err: plainErr }, "Plain-text edit also failed, sending new message");
|
|
1928
2008
|
}
|
|
1929
2009
|
}
|
|
1930
2010
|
}
|
|
@@ -1936,14 +2016,14 @@ var TelegramChannel = class {
|
|
|
1936
2016
|
});
|
|
1937
2017
|
} catch (err) {
|
|
1938
2018
|
if (isTgParseError(err)) {
|
|
1939
|
-
logger$3.
|
|
2019
|
+
logger$3.debug({
|
|
1940
2020
|
err,
|
|
1941
2021
|
formattedText: formatted.slice(0, 500),
|
|
1942
|
-
originalText:
|
|
1943
|
-
}, "
|
|
2022
|
+
originalText: text.slice(0, 500)
|
|
2023
|
+
}, "HTML send failed, retrying as plain text");
|
|
1944
2024
|
await this.api("sendMessage", {
|
|
1945
2025
|
chat_id: chatId,
|
|
1946
|
-
text:
|
|
2026
|
+
text: plainText
|
|
1947
2027
|
});
|
|
1948
2028
|
} else throw err;
|
|
1949
2029
|
}
|
|
@@ -1980,32 +2060,50 @@ var TelegramChannel = class {
|
|
|
1980
2060
|
switch (event.type) {
|
|
1981
2061
|
case "thinking_delta": {
|
|
1982
2062
|
const state = this.getOrCreateStreamState(jid);
|
|
1983
|
-
|
|
2063
|
+
const last = state.details[state.details.length - 1];
|
|
2064
|
+
if (last && last.type === "thinking") last.text += event.delta || "";
|
|
2065
|
+
else state.details.push({
|
|
2066
|
+
type: "thinking",
|
|
2067
|
+
text: event.delta || ""
|
|
2068
|
+
});
|
|
1984
2069
|
this.scheduleStreamEdit(jid, state);
|
|
1985
2070
|
break;
|
|
1986
2071
|
}
|
|
1987
2072
|
case "text_delta": {
|
|
1988
2073
|
const state = this.getOrCreateStreamState(jid);
|
|
1989
|
-
|
|
2074
|
+
const last = state.details[state.details.length - 1];
|
|
2075
|
+
if (last && last.type === "text") last.text += event.delta || "";
|
|
2076
|
+
else state.details.push({
|
|
2077
|
+
type: "text",
|
|
2078
|
+
text: event.delta || ""
|
|
2079
|
+
});
|
|
1990
2080
|
this.scheduleStreamEdit(jid, state);
|
|
1991
2081
|
break;
|
|
1992
2082
|
}
|
|
1993
2083
|
case "tool_start": {
|
|
1994
2084
|
const state = this.getOrCreateStreamState(jid);
|
|
1995
|
-
|
|
2085
|
+
const tool = {
|
|
1996
2086
|
name: event.toolName || "tool",
|
|
1997
2087
|
done: false
|
|
2088
|
+
};
|
|
2089
|
+
state.tools.push(tool);
|
|
2090
|
+
state.details.push({
|
|
2091
|
+
type: "tool",
|
|
2092
|
+
tool
|
|
1998
2093
|
});
|
|
1999
2094
|
this.scheduleStreamEdit(jid, state);
|
|
2000
2095
|
break;
|
|
2001
2096
|
}
|
|
2002
2097
|
case "tool_end": {
|
|
2003
2098
|
const state = this.getOrCreateStreamState(jid);
|
|
2004
|
-
for (let i = state.tools.length - 1; i >= 0; i--)
|
|
2005
|
-
state.tools[i]
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2099
|
+
for (let i = state.tools.length - 1; i >= 0; i--) {
|
|
2100
|
+
const t = state.tools[i];
|
|
2101
|
+
if (t.name === event.toolName && !t.done) {
|
|
2102
|
+
t.done = true;
|
|
2103
|
+
t.isError = event.isError;
|
|
2104
|
+
t.resultPreview = event.resultPreview;
|
|
2105
|
+
break;
|
|
2106
|
+
}
|
|
2009
2107
|
}
|
|
2010
2108
|
this.scheduleStreamEdit(jid, state);
|
|
2011
2109
|
break;
|
|
@@ -2041,8 +2139,7 @@ var TelegramChannel = class {
|
|
|
2041
2139
|
if (!state) {
|
|
2042
2140
|
state = {
|
|
2043
2141
|
chatId: jid.slice(3),
|
|
2044
|
-
|
|
2045
|
-
thinkingText: "",
|
|
2142
|
+
details: [],
|
|
2046
2143
|
tools: [],
|
|
2047
2144
|
messageId: null,
|
|
2048
2145
|
sendPromise: null,
|
|
@@ -2065,29 +2162,55 @@ var TelegramChannel = class {
|
|
|
2065
2162
|
}, delay);
|
|
2066
2163
|
}
|
|
2067
2164
|
async flushStreamEdit(jid, state) {
|
|
2165
|
+
if (!this.streamState.has(jid)) return;
|
|
2068
2166
|
if (state.sendPromise) {
|
|
2069
2167
|
await state.sendPromise.catch(() => {});
|
|
2070
2168
|
state.sendPromise = null;
|
|
2071
2169
|
}
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
const
|
|
2170
|
+
if (!this.streamState.has(jid)) return;
|
|
2171
|
+
const htmlParts = [];
|
|
2172
|
+
let toolBuf = [];
|
|
2173
|
+
const flushTools = () => {
|
|
2174
|
+
if (!toolBuf.length) return;
|
|
2175
|
+
htmlParts.push(formatToolLines(toolBuf));
|
|
2176
|
+
toolBuf = [];
|
|
2177
|
+
};
|
|
2178
|
+
for (const item of state.details) if (item.type === "tool") toolBuf.push(item.tool);
|
|
2179
|
+
else if (item.type === "thinking") {
|
|
2180
|
+
flushTools();
|
|
2181
|
+
const trimmed = item.text.trim().replace(/\*\*/g, "");
|
|
2182
|
+
if (trimmed) htmlParts.push(`💭 <b>${escHtml(trimmed)}</b>`);
|
|
2183
|
+
} else {
|
|
2184
|
+
flushTools();
|
|
2185
|
+
const clean = stripInternalTags(item.text).trim();
|
|
2186
|
+
if (clean) {
|
|
2187
|
+
const truncated = clean.length > TG_MAX_TEXT ? clean.slice(0, TG_MAX_TEXT - 1) + "…" : clean;
|
|
2188
|
+
htmlParts.push(formatForTelegram(truncated).text);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
flushTools();
|
|
2192
|
+
if (!htmlParts.length) return;
|
|
2193
|
+
htmlParts.push("⏳");
|
|
2194
|
+
const formatted = htmlParts.join("\n\n");
|
|
2195
|
+
const parse_mode = "HTML";
|
|
2196
|
+
const snapshotLen = detailsTextLen(state.details);
|
|
2197
|
+
const streamApiOpts = { maxRetryWait: 5 };
|
|
2076
2198
|
const sendOrEdit = async (text, parseMode) => {
|
|
2077
|
-
|
|
2199
|
+
if (!this.streamState.has(jid)) return;
|
|
2200
|
+
const body = {
|
|
2078
2201
|
chat_id: state.chatId,
|
|
2079
2202
|
text
|
|
2080
2203
|
};
|
|
2081
|
-
if (parseMode)
|
|
2204
|
+
if (parseMode) body.parse_mode = parseMode;
|
|
2082
2205
|
if (state.messageId === null) {
|
|
2083
|
-
state.sendPromise = this.api("sendMessage",
|
|
2206
|
+
state.sendPromise = this.api("sendMessage", body, streamApiOpts).then((msg) => {
|
|
2084
2207
|
state.messageId = msg.message_id;
|
|
2085
2208
|
state.sendPromise = null;
|
|
2086
2209
|
});
|
|
2087
2210
|
await state.sendPromise;
|
|
2088
2211
|
} else {
|
|
2089
|
-
|
|
2090
|
-
await this.api("editMessageText",
|
|
2212
|
+
body.message_id = state.messageId;
|
|
2213
|
+
await this.api("editMessageText", body, streamApiOpts);
|
|
2091
2214
|
}
|
|
2092
2215
|
};
|
|
2093
2216
|
try {
|
|
@@ -2096,16 +2219,15 @@ var TelegramChannel = class {
|
|
|
2096
2219
|
state.throttleMs = Math.max(STREAM_THROTTLE_MIN, state.throttleMs - 50);
|
|
2097
2220
|
} catch (err) {
|
|
2098
2221
|
if (isTgParseError(err)) {
|
|
2099
|
-
logger$3.
|
|
2222
|
+
logger$3.debug({
|
|
2100
2223
|
err,
|
|
2101
|
-
formattedText: formatted.slice(0, 500)
|
|
2102
|
-
|
|
2103
|
-
}, "Stream MarkdownV2 failed, retrying as plain text");
|
|
2224
|
+
formattedText: formatted.slice(0, 500)
|
|
2225
|
+
}, "Stream HTML failed, retrying as plain text");
|
|
2104
2226
|
try {
|
|
2105
|
-
await sendOrEdit(
|
|
2227
|
+
await sendOrEdit(stripHtmlTags(formatted).slice(0, TG_MAX_TEXT));
|
|
2106
2228
|
state.lastEdit = Date.now();
|
|
2107
2229
|
} catch (retryErr) {
|
|
2108
|
-
logger$3.
|
|
2230
|
+
logger$3.debug({ err: retryErr }, "Stream plain-text fallback also failed");
|
|
2109
2231
|
}
|
|
2110
2232
|
} else if (isTgMessageNotModified(err)) {} else {
|
|
2111
2233
|
const retryAfterMs = getRetryAfterMs(err);
|
|
@@ -2116,7 +2238,7 @@ var TelegramChannel = class {
|
|
|
2116
2238
|
}, "Stream edit failed");
|
|
2117
2239
|
}
|
|
2118
2240
|
}
|
|
2119
|
-
if ((state.
|
|
2241
|
+
if (detailsTextLen(state.details) > snapshotLen && this.streamState.has(jid)) this.scheduleStreamEdit(jid, state);
|
|
2120
2242
|
}
|
|
2121
2243
|
startPolling() {
|
|
2122
2244
|
this.stopPolling();
|
|
@@ -2138,17 +2260,17 @@ var TelegramChannel = class {
|
|
|
2138
2260
|
const res = await fetch(`${API_BASE}${this.config.botToken}/getUpdates?${params}`, { signal });
|
|
2139
2261
|
if (!res.ok) {
|
|
2140
2262
|
if (res.status === 409) {
|
|
2141
|
-
logger$3.
|
|
2263
|
+
logger$3.debug("Telegram 409 conflict: another polling instance is active, retrying in 30s");
|
|
2142
2264
|
await sleep(3e4, signal);
|
|
2143
2265
|
continue;
|
|
2144
2266
|
}
|
|
2145
|
-
logger$3.
|
|
2267
|
+
logger$3.debug({ status: res.status }, "Telegram getUpdates HTTP error");
|
|
2146
2268
|
await sleep(POLL_INTERVAL, signal);
|
|
2147
2269
|
continue;
|
|
2148
2270
|
}
|
|
2149
2271
|
const data = await res.json();
|
|
2150
2272
|
if (!data.ok) {
|
|
2151
|
-
logger$3.
|
|
2273
|
+
logger$3.debug({ description: data.description }, "Telegram getUpdates API error");
|
|
2152
2274
|
await sleep(POLL_INTERVAL, signal);
|
|
2153
2275
|
continue;
|
|
2154
2276
|
}
|
|
@@ -2159,7 +2281,7 @@ var TelegramChannel = class {
|
|
|
2159
2281
|
}
|
|
2160
2282
|
} catch (err) {
|
|
2161
2283
|
if (signal.aborted) break;
|
|
2162
|
-
logger$3.
|
|
2284
|
+
logger$3.debug({ err }, "Telegram polling error");
|
|
2163
2285
|
await sleep(POLL_INTERVAL, signal);
|
|
2164
2286
|
}
|
|
2165
2287
|
};
|
|
@@ -2169,14 +2291,35 @@ var TelegramChannel = class {
|
|
|
2169
2291
|
this.pollAbort?.abort();
|
|
2170
2292
|
this.pollAbort = null;
|
|
2171
2293
|
}
|
|
2172
|
-
async api(method, body) {
|
|
2294
|
+
async api(method, body, opts, _retries = 0) {
|
|
2173
2295
|
const res = await fetch(`${API_BASE}${this.config.botToken}/${method}`, {
|
|
2174
2296
|
method: "POST",
|
|
2175
2297
|
headers: { "content-type": "application/json" },
|
|
2176
2298
|
body: body ? JSON.stringify(body) : void 0
|
|
2177
2299
|
});
|
|
2178
2300
|
const data = await res.json();
|
|
2179
|
-
if (!data.ok)
|
|
2301
|
+
if (!data.ok) {
|
|
2302
|
+
if (res.status === 429 && _retries < 2) {
|
|
2303
|
+
const retryAfter = Number(data.parameters?.retry_after || 5);
|
|
2304
|
+
const maxWait = opts?.maxRetryWait ?? 30;
|
|
2305
|
+
if (retryAfter > maxWait) {
|
|
2306
|
+
logger$3.debug({
|
|
2307
|
+
method,
|
|
2308
|
+
retryAfter,
|
|
2309
|
+
maxWait
|
|
2310
|
+
}, "Telegram 429 retry_after exceeds max wait, skipping retry");
|
|
2311
|
+
throw new TelegramApiError(method, data.description || "unknown error", res.status, data);
|
|
2312
|
+
}
|
|
2313
|
+
logger$3.debug({
|
|
2314
|
+
method,
|
|
2315
|
+
retryAfter,
|
|
2316
|
+
attempt: _retries + 1
|
|
2317
|
+
}, "Telegram 429 rate limit, waiting");
|
|
2318
|
+
await sleep(retryAfter * 1e3);
|
|
2319
|
+
return this.api(method, body, opts, _retries + 1);
|
|
2320
|
+
}
|
|
2321
|
+
throw new TelegramApiError(method, data.description || "unknown error", res.status, data);
|
|
2322
|
+
}
|
|
2180
2323
|
return data.result;
|
|
2181
2324
|
}
|
|
2182
2325
|
handleUpdate(update) {
|
|
@@ -2206,7 +2349,7 @@ var TelegramChannel = class {
|
|
|
2206
2349
|
this.opts.onMessage(jid, msg);
|
|
2207
2350
|
this.opts.onChatMetadata(jid, now, senderName, "telegram", isGroup);
|
|
2208
2351
|
}).catch((err) => {
|
|
2209
|
-
logger$3.
|
|
2352
|
+
logger$3.debug({ err }, "Failed to download Telegram media, sending text-only");
|
|
2210
2353
|
this.opts.onMessage(jid, msg);
|
|
2211
2354
|
this.opts.onChatMetadata(jid, now, senderName, "telegram", isGroup);
|
|
2212
2355
|
});
|
|
@@ -2256,7 +2399,7 @@ var TelegramChannel = class {
|
|
|
2256
2399
|
name
|
|
2257
2400
|
});
|
|
2258
2401
|
} catch (err) {
|
|
2259
|
-
logger$3.
|
|
2402
|
+
logger$3.debug({
|
|
2260
2403
|
err,
|
|
2261
2404
|
fileId
|
|
2262
2405
|
}, "Failed to download Telegram file");
|
|
@@ -2272,39 +2415,28 @@ var TelegramApiError = class extends Error {
|
|
|
2272
2415
|
this.response = response;
|
|
2273
2416
|
}
|
|
2274
2417
|
};
|
|
2275
|
-
/** Format message text as Telegram
|
|
2418
|
+
/** Format message text as Telegram HTML — parse markdown with md4x, render AST to HTML tags */
|
|
2276
2419
|
function formatForTelegram(text) {
|
|
2277
2420
|
let remaining = text.replace(/<\/?internal>/g, (t) => t.replace("internal", "thinking"));
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
let m;
|
|
2282
|
-
while ((m = thinkingRe.exec(remaining)) !== null) {
|
|
2283
|
-
if (m.index > lastIdx) parts.push(mdToTgV2(remaining.slice(lastIdx, m.index)));
|
|
2284
|
-
const trimmed = (m[1] || "").trim();
|
|
2285
|
-
if (trimmed) {
|
|
2286
|
-
const quoted = escTgV2(trimmed).split("\n").map((l) => `>${l}`).join("\n");
|
|
2287
|
-
parts.push(quoted);
|
|
2288
|
-
}
|
|
2289
|
-
lastIdx = m.index + m[0].length;
|
|
2290
|
-
}
|
|
2291
|
-
if (lastIdx < remaining.length) parts.push(mdToTgV2(remaining.slice(lastIdx)));
|
|
2421
|
+
remaining = remaining.replace(/<tool\s+name="[^"]*"\s+status="[^"]*"(?:\s+preview="[^"]*")?\s*\/>/g, "");
|
|
2422
|
+
remaining = remaining.replace(/<details><summary>thinking<\/summary>[\s\S]*?<\/details>/g, "");
|
|
2423
|
+
remaining = remaining.replace(/<thinking>[\s\S]*?<\/thinking>/g, "");
|
|
2292
2424
|
return {
|
|
2293
|
-
text:
|
|
2294
|
-
parse_mode: "
|
|
2425
|
+
text: [mdToTgHtml(remaining)].join("").replace(/\n{3,}/g, "\n\n").trim(),
|
|
2426
|
+
parse_mode: "HTML"
|
|
2295
2427
|
};
|
|
2296
2428
|
}
|
|
2297
|
-
/** Parse markdown with md4x and render AST nodes to Telegram
|
|
2298
|
-
function
|
|
2429
|
+
/** Parse markdown with md4x and render AST nodes to Telegram HTML */
|
|
2430
|
+
function mdToTgHtml(text) {
|
|
2299
2431
|
return parseAST(text).nodes.map((node) => renderNode(node)).join("\n\n");
|
|
2300
2432
|
}
|
|
2301
2433
|
/** Collect raw text from children recursively (for code content that must not be escaped) */
|
|
2302
2434
|
function collectText(children) {
|
|
2303
2435
|
return children.map((c) => typeof c === "string" ? c : collectText(c.slice(2))).join("");
|
|
2304
2436
|
}
|
|
2305
|
-
/** Render a single AST node to Telegram
|
|
2437
|
+
/** Render a single AST node to Telegram HTML */
|
|
2306
2438
|
function renderNode(node) {
|
|
2307
|
-
if (typeof node === "string") return
|
|
2439
|
+
if (typeof node === "string") return escHtml(node);
|
|
2308
2440
|
const [tag, attrs, ...children] = node;
|
|
2309
2441
|
const inner = () => children.map((c) => renderNode(c)).join("");
|
|
2310
2442
|
switch (tag) {
|
|
@@ -2314,52 +2446,41 @@ function renderNode(node) {
|
|
|
2314
2446
|
case "h3":
|
|
2315
2447
|
case "h4":
|
|
2316
2448
|
case "h5":
|
|
2317
|
-
case "h6": return
|
|
2318
|
-
case "strong": return
|
|
2319
|
-
case "em": return
|
|
2320
|
-
case "u": return
|
|
2321
|
-
case "del": return
|
|
2322
|
-
case "code": {
|
|
2323
|
-
let codeText = collectText(children);
|
|
2324
|
-
if (codeText.endsWith("\\")) codeText += " ";
|
|
2325
|
-
return `\`${codeText}\``;
|
|
2326
|
-
}
|
|
2449
|
+
case "h6": return `<b>${inner()}</b>`;
|
|
2450
|
+
case "strong": return `<b>${inner()}</b>`;
|
|
2451
|
+
case "em": return `<i>${inner()}</i>`;
|
|
2452
|
+
case "u": return `<u>${inner()}</u>`;
|
|
2453
|
+
case "del": return `<s>${inner()}</s>`;
|
|
2454
|
+
case "code": return `<code>${escHtml(collectText(children))}</code>`;
|
|
2327
2455
|
case "pre": {
|
|
2328
2456
|
const codeNode = children.find((c) => typeof c !== "string" && c[0] === "code");
|
|
2329
2457
|
const lang = String(attrs.language || "");
|
|
2330
2458
|
const raw = codeNode ? collectText(codeNode.slice(2)) : collectText(children);
|
|
2331
2459
|
if (!raw.trim() && !lang) return "";
|
|
2332
|
-
return
|
|
2333
|
-
}
|
|
2334
|
-
case "a": {
|
|
2335
|
-
const href = String(attrs.href || "");
|
|
2336
|
-
return `[${inner()}](${escTgUrl(href)})`;
|
|
2460
|
+
return `<pre><code${lang ? ` class="language-${escHtml(lang)}"` : ""}>${escHtml(raw.replace(/\n$/, ""))}</code></pre>`;
|
|
2337
2461
|
}
|
|
2338
|
-
case "
|
|
2339
|
-
case "
|
|
2462
|
+
case "a": return `<a href="${escHtml(String(attrs.href || ""))}">${inner()}</a>`;
|
|
2463
|
+
case "img": return escHtml(String(attrs.alt || "image"));
|
|
2464
|
+
case "blockquote": return `<blockquote>${inner()}</blockquote>`;
|
|
2340
2465
|
case "ul":
|
|
2341
2466
|
case "ol": return children.map((c, i) => {
|
|
2342
|
-
if (typeof c === "string") return
|
|
2343
|
-
return (tag === "ol" ? `${
|
|
2467
|
+
if (typeof c === "string") return escHtml(c);
|
|
2468
|
+
return (tag === "ol" ? `${i + 1}. ` : "• ") + renderNode(c);
|
|
2344
2469
|
}).filter((l) => l.trim()).join("\n");
|
|
2345
2470
|
case "li": return inner();
|
|
2346
|
-
case "hr": return
|
|
2471
|
+
case "hr": return "---";
|
|
2347
2472
|
case "br": return "\n";
|
|
2348
2473
|
default: return inner();
|
|
2349
2474
|
}
|
|
2350
2475
|
}
|
|
2351
|
-
/** Escape
|
|
2352
|
-
function
|
|
2353
|
-
return s.replace(
|
|
2476
|
+
/** Escape HTML entities for Telegram HTML mode */
|
|
2477
|
+
function escHtml(s) {
|
|
2478
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
2354
2479
|
}
|
|
2355
|
-
/** Check if a Telegram API error is
|
|
2480
|
+
/** Check if a Telegram API error is an HTML parse failure */
|
|
2356
2481
|
function isTgParseError(err) {
|
|
2357
2482
|
return err instanceof TelegramApiError && err.status === 400 && typeof err.response?.description === "string" && err.response.description.includes("can't parse entities");
|
|
2358
2483
|
}
|
|
2359
|
-
/** Escape only ) and \ inside MarkdownV2 link URLs */
|
|
2360
|
-
function escTgUrl(s) {
|
|
2361
|
-
return s.replace(/([)\\])/g, "\\$1");
|
|
2362
|
-
}
|
|
2363
2484
|
/** Check if a Telegram API error is "message is not modified" (identical content edit) */
|
|
2364
2485
|
function isTgMessageNotModified(err) {
|
|
2365
2486
|
return err instanceof TelegramApiError && err.status === 400 && typeof err.response?.description === "string" && err.response.description.includes("message is not modified");
|
|
@@ -2373,45 +2494,36 @@ function getRetryAfterMs(err) {
|
|
|
2373
2494
|
/** Verbose tool lines with result previews — used during streaming */
|
|
2374
2495
|
function formatToolLines(tools) {
|
|
2375
2496
|
return tools.map((t) => {
|
|
2376
|
-
const icon = t.done ? t.isError ? "
|
|
2497
|
+
const icon = t.done ? t.isError ? "❌" : "✅" : "🔧";
|
|
2377
2498
|
const preview = t.done && t.resultPreview ? ` ${tgToolPreview(t.resultPreview)}` : "";
|
|
2378
|
-
return `${icon} ${t.name}${preview}`;
|
|
2499
|
+
return `${icon} ${escHtml(t.name)}${preview}`;
|
|
2379
2500
|
}).join("\n");
|
|
2380
2501
|
}
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
const
|
|
2384
|
-
|
|
2385
|
-
while (i < tools.length) {
|
|
2386
|
-
const t = tools[i];
|
|
2387
|
-
if (t.resultPreview) {
|
|
2388
|
-
const icon = t.isError ? "✗" : "✓";
|
|
2389
|
-
lines.push(`${icon} ${t.name} ${tgToolPreview(t.resultPreview)}`);
|
|
2390
|
-
i++;
|
|
2391
|
-
continue;
|
|
2392
|
-
}
|
|
2393
|
-
let count = 1;
|
|
2394
|
-
let hasError = !!t.isError;
|
|
2395
|
-
while (i + count < tools.length && tools[i + count].name === t.name && !tools[i + count].resultPreview) {
|
|
2396
|
-
if (tools[i + count].isError) hasError = true;
|
|
2397
|
-
count++;
|
|
2398
|
-
}
|
|
2399
|
-
const icon = hasError ? "✗" : "✓";
|
|
2400
|
-
const label = count > 1 ? `${t.name} ×${count}` : t.name;
|
|
2401
|
-
lines.push(`${icon} ${label}`);
|
|
2402
|
-
i += count;
|
|
2403
|
-
}
|
|
2404
|
-
return lines.join("\n");
|
|
2502
|
+
function detailsTextLen(details) {
|
|
2503
|
+
let len = 0;
|
|
2504
|
+
for (const d of details) len += d.type === "tool" ? 1 : d.text.length;
|
|
2505
|
+
return len;
|
|
2405
2506
|
}
|
|
2406
2507
|
function tgToolPreview(raw) {
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2508
|
+
const firstLine = extractToolResultText(raw, 200).split("\n")[0] || "";
|
|
2509
|
+
return escHtml(firstLine.length > 80 ? firstLine.slice(0, 79) + "…" : firstLine);
|
|
2510
|
+
}
|
|
2511
|
+
/** Strip <internal>/<thinking> tags from streamed text (handles partial/unclosed tags mid-stream) */
|
|
2512
|
+
function stripInternalTags(text) {
|
|
2513
|
+
let s = text.replace(/<(internal|thinking)>[\s\S]*?<\/\1>/g, "");
|
|
2514
|
+
s = s.replace(/<(internal|thinking)>[\s\S]*$/, "");
|
|
2515
|
+
s = s.replace(/<(?:internal|thinking|intern|inter|inte|int|in|i|thinkin|thinki|think|thin|thi|th|t)?$/i, "");
|
|
2516
|
+
return s;
|
|
2517
|
+
}
|
|
2518
|
+
/** Strip <internal>/<thinking>/tool tags for plain-text fallback — keeps inner content */
|
|
2519
|
+
function stripHtmlTags(html) {
|
|
2520
|
+
return html.replace(/<[^>]*>/g, "");
|
|
2521
|
+
}
|
|
2522
|
+
function stripThinkingTags(text) {
|
|
2523
|
+
let s = text.replace(/<\/?(internal|thinking)>/g, "");
|
|
2524
|
+
s = s.replace(/<details><summary>thinking<\/summary>([\s\S]*?)<\/details>/g, "$1");
|
|
2525
|
+
s = s.replace(/<tool\s+name="([^"]+)"\s+status="[^"]*"(?:\s+preview="[^"]*")?\s*\/>/g, "[$1]");
|
|
2526
|
+
return s;
|
|
2415
2527
|
}
|
|
2416
2528
|
function randomJitter(maxAbs) {
|
|
2417
2529
|
return Math.round((Math.random() * 2 - 1) * maxAbs);
|
|
@@ -2486,16 +2598,18 @@ var GroupQueue = class {
|
|
|
2486
2598
|
this.drain();
|
|
2487
2599
|
}
|
|
2488
2600
|
drain() {
|
|
2601
|
+
const deferred = [];
|
|
2489
2602
|
while (this.active.size < config.get("maxConcurrentAgents") && this.queue.length > 0) {
|
|
2490
2603
|
const entry = this.queue.shift();
|
|
2491
2604
|
if (!entry) break;
|
|
2492
2605
|
if (this.active.has(entry.chatJid)) {
|
|
2493
|
-
|
|
2494
|
-
|
|
2606
|
+
deferred.push(entry);
|
|
2607
|
+
continue;
|
|
2495
2608
|
}
|
|
2496
2609
|
this.active.add(entry.chatJid);
|
|
2497
2610
|
this.processEntry(entry);
|
|
2498
2611
|
}
|
|
2612
|
+
this.queue.push(...deferred);
|
|
2499
2613
|
}
|
|
2500
2614
|
async processEntry(entry) {
|
|
2501
2615
|
try {
|
|
@@ -2554,7 +2668,9 @@ function formatOutbound(rawText) {
|
|
|
2554
2668
|
function needsProcessing(group, messages) {
|
|
2555
2669
|
if (group.folder === "main") return true;
|
|
2556
2670
|
if (group.requiresTrigger === false) return true;
|
|
2557
|
-
|
|
2671
|
+
const trigger = group.trigger || `@${config.get("assistantName")}`;
|
|
2672
|
+
const pattern = new RegExp(`^${trigger.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
|
|
2673
|
+
return messages.some((m) => pattern.test(m.content.trim()));
|
|
2558
2674
|
}
|
|
2559
2675
|
/**
|
|
2560
2676
|
* Task scheduler — runs scheduled tasks (cron/interval/once) using the pi agent.
|
|
@@ -2831,8 +2947,18 @@ var PiClawServer = class {
|
|
|
2831
2947
|
return new TelegramChannel({
|
|
2832
2948
|
onMessage: async (jid, msg) => {
|
|
2833
2949
|
await storeChatMetadata(jid, msg.timestamp, msg.sender_name, "telegram");
|
|
2950
|
+
const group = this.registeredGroups[jid];
|
|
2951
|
+
if (group) {
|
|
2952
|
+
const trimmed = msg.content.trim();
|
|
2953
|
+
if (tryPriorityCommand({
|
|
2954
|
+
chatJid: jid,
|
|
2955
|
+
group,
|
|
2956
|
+
server: this
|
|
2957
|
+
}, trimmed)) return;
|
|
2958
|
+
}
|
|
2834
2959
|
await storeMessage(msg);
|
|
2835
|
-
if (this.
|
|
2960
|
+
if (group) if (this.pi.sendMessage(jid, formatMessages([msg]))) await this.advanceCursor(jid, msg.timestamp);
|
|
2961
|
+
else this.queue.enqueueMessageCheck(jid);
|
|
2836
2962
|
else await this.notifyUnregistered(jid);
|
|
2837
2963
|
},
|
|
2838
2964
|
onChatMetadata: async (jid, ts, name, channel, isGroup) => {
|
|
@@ -2916,18 +3042,22 @@ var PiClawServer = class {
|
|
|
2916
3042
|
await setRouterState("last_timestamp", this.lastTimestamp);
|
|
2917
3043
|
await setRouterState("last_agent_timestamp", JSON.stringify(this.lastAgentTimestamp));
|
|
2918
3044
|
}
|
|
3045
|
+
/** Advance the agent cursor for a chat — marks messages up to `timestamp` as processed. */
|
|
3046
|
+
async advanceCursor(chatJid, timestamp) {
|
|
3047
|
+
this.lastAgentTimestamp[chatJid] = timestamp;
|
|
3048
|
+
await this.saveState();
|
|
3049
|
+
}
|
|
2919
3050
|
async handleCommands(chatJid, messages) {
|
|
2920
3051
|
const group = this.registeredGroups[chatJid];
|
|
2921
3052
|
if (!group) return messages;
|
|
2922
3053
|
const remaining = [];
|
|
2923
3054
|
for (const msg of messages) {
|
|
2924
3055
|
const text = msg.content.trim();
|
|
2925
|
-
|
|
3056
|
+
if (!await tryCommand({
|
|
2926
3057
|
chatJid,
|
|
2927
3058
|
group,
|
|
2928
3059
|
server: this
|
|
2929
|
-
};
|
|
2930
|
-
if (!(tryBashCommand(ctx, text) || await tryCommand(ctx, text))) remaining.push(msg);
|
|
3060
|
+
}, text)) remaining.push(msg);
|
|
2931
3061
|
}
|
|
2932
3062
|
return remaining;
|
|
2933
3063
|
}
|
|
@@ -2989,8 +3119,7 @@ var PiClawServer = class {
|
|
|
2989
3119
|
if (result.result) {
|
|
2990
3120
|
const text = formatOutbound(result.result);
|
|
2991
3121
|
if (text) {
|
|
2992
|
-
|
|
2993
|
-
await this.sendBotMessage(chatJid, text, meta);
|
|
3122
|
+
await this.sendBotMessage(chatJid, text);
|
|
2994
3123
|
outputSentToUser = true;
|
|
2995
3124
|
}
|
|
2996
3125
|
}
|
|
@@ -3040,15 +3169,6 @@ var PiClawServer = class {
|
|
|
3040
3169
|
for (const [chatJid, groupMessages] of byGroup) {
|
|
3041
3170
|
const group = this.registeredGroups[chatJid];
|
|
3042
3171
|
if (!group) continue;
|
|
3043
|
-
for (const msg of groupMessages) {
|
|
3044
|
-
const ctx = {
|
|
3045
|
-
chatJid,
|
|
3046
|
-
group,
|
|
3047
|
-
server: this
|
|
3048
|
-
};
|
|
3049
|
-
tryBashCommand(ctx, msg.content.trim());
|
|
3050
|
-
tryPriorityCommand(ctx, msg.content.trim());
|
|
3051
|
-
}
|
|
3052
3172
|
if (!needsProcessing(group, groupMessages)) continue;
|
|
3053
3173
|
this.queue.enqueueMessageCheck(chatJid);
|
|
3054
3174
|
}
|
|
@@ -3088,4 +3208,4 @@ const startTime = globalThis.__piclaw_start_time__ || performance.now();
|
|
|
3088
3208
|
const server = new PiClawServer();
|
|
3089
3209
|
await server.start();
|
|
3090
3210
|
var server_default = () => {};
|
|
3091
|
-
export {
|
|
3211
|
+
export { commandDescriptions as a, authStorage as c, getSystemInfo as d, formatMessages as i, getFilteredModels as l, server_default as n, tryPriorityCommand as o, startTime as r, getPiSdkStatus as s, server as t, modelRegistry as u };
|