open-agents-ai 0.45.0 → 0.46.0
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/README.md +131 -7
- package/dist/index.js +986 -222
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1285,7 +1285,7 @@ ${stdinInput ?? ""}`);
|
|
|
1285
1285
|
}
|
|
1286
1286
|
runCommand(command, timeout, stdinInput) {
|
|
1287
1287
|
const start = performance.now();
|
|
1288
|
-
return new Promise((
|
|
1288
|
+
return new Promise((resolve28) => {
|
|
1289
1289
|
const child = spawn("bash", ["-c", command], {
|
|
1290
1290
|
cwd: this.workingDir,
|
|
1291
1291
|
env: {
|
|
@@ -1313,7 +1313,7 @@ ${stdinInput ?? ""}`);
|
|
|
1313
1313
|
clearTimeout(timer);
|
|
1314
1314
|
if (exitFlushTimer)
|
|
1315
1315
|
clearTimeout(exitFlushTimer);
|
|
1316
|
-
|
|
1316
|
+
resolve28(result);
|
|
1317
1317
|
};
|
|
1318
1318
|
const timer = setTimeout(() => {
|
|
1319
1319
|
killed = true;
|
|
@@ -2317,7 +2317,7 @@ Meta: ${metaKeys.map((k) => `${k}="${meta[k]?.slice(0, 80)}"`).join(", ")}`);
|
|
|
2317
2317
|
return null;
|
|
2318
2318
|
}
|
|
2319
2319
|
runProcess(cmd, args, timeoutMs) {
|
|
2320
|
-
return new Promise((
|
|
2320
|
+
return new Promise((resolve28, reject) => {
|
|
2321
2321
|
const proc = execFile3(cmd, args, {
|
|
2322
2322
|
timeout: timeoutMs,
|
|
2323
2323
|
maxBuffer: 10 * 1024 * 1024,
|
|
@@ -2327,7 +2327,7 @@ Meta: ${metaKeys.map((k) => `${k}="${meta[k]?.slice(0, 80)}"`).join(", ")}`);
|
|
|
2327
2327
|
reject(new Error(`Process timeout after ${timeoutMs}ms`));
|
|
2328
2328
|
return;
|
|
2329
2329
|
}
|
|
2330
|
-
|
|
2330
|
+
resolve28({
|
|
2331
2331
|
stdout: String(stdout),
|
|
2332
2332
|
stderr: String(stderr),
|
|
2333
2333
|
exitCode: error ? error.code ?? 1 : 0
|
|
@@ -5242,8 +5242,8 @@ function deleteCustomToolDefinition(name, scope, repoRoot) {
|
|
|
5242
5242
|
const dir = scope === "project" && repoRoot ? projectToolsDir(repoRoot) : globalToolsDir();
|
|
5243
5243
|
const filePath = join13(dir, `${name}.json`);
|
|
5244
5244
|
if (existsSync10(filePath)) {
|
|
5245
|
-
const { unlinkSync:
|
|
5246
|
-
|
|
5245
|
+
const { unlinkSync: unlinkSync7 } = __require("node:fs");
|
|
5246
|
+
unlinkSync7(filePath);
|
|
5247
5247
|
return true;
|
|
5248
5248
|
}
|
|
5249
5249
|
return false;
|
|
@@ -5361,7 +5361,7 @@ var init_custom_tool = __esm({
|
|
|
5361
5361
|
}
|
|
5362
5362
|
/** Execute a single shell command and return output */
|
|
5363
5363
|
runCommand(command) {
|
|
5364
|
-
return new Promise((
|
|
5364
|
+
return new Promise((resolve28) => {
|
|
5365
5365
|
const child = spawn3("bash", ["-c", command], {
|
|
5366
5366
|
cwd: this.workingDir,
|
|
5367
5367
|
env: { ...process.env, CI: "true", NO_COLOR: "1" },
|
|
@@ -5386,11 +5386,11 @@ var init_custom_tool = __esm({
|
|
|
5386
5386
|
child.kill("SIGTERM");
|
|
5387
5387
|
} catch {
|
|
5388
5388
|
}
|
|
5389
|
-
|
|
5389
|
+
resolve28({ success: false, output: stdout, error: "Command timed out after 60s" });
|
|
5390
5390
|
}, 6e4);
|
|
5391
5391
|
child.on("close", (code) => {
|
|
5392
5392
|
clearTimeout(timer);
|
|
5393
|
-
|
|
5393
|
+
resolve28({
|
|
5394
5394
|
success: code === 0,
|
|
5395
5395
|
output: stdout + (stderr && code === 0 ? `
|
|
5396
5396
|
STDERR:
|
|
@@ -5400,7 +5400,7 @@ ${stderr}` : ""),
|
|
|
5400
5400
|
});
|
|
5401
5401
|
child.on("error", (err) => {
|
|
5402
5402
|
clearTimeout(timer);
|
|
5403
|
-
|
|
5403
|
+
resolve28({ success: false, output: stdout, error: err.message });
|
|
5404
5404
|
});
|
|
5405
5405
|
});
|
|
5406
5406
|
}
|
|
@@ -6133,8 +6133,8 @@ async function loadTranscribeCli() {
|
|
|
6133
6133
|
const nvmBase = join15(homedir6(), ".nvm", "versions", "node");
|
|
6134
6134
|
if (existsSync12(nvmBase)) {
|
|
6135
6135
|
try {
|
|
6136
|
-
const { readdirSync:
|
|
6137
|
-
for (const ver of
|
|
6136
|
+
const { readdirSync: readdirSync15 } = await import("node:fs");
|
|
6137
|
+
for (const ver of readdirSync15(nvmBase)) {
|
|
6138
6138
|
const tcPath = join15(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
|
|
6139
6139
|
if (existsSync12(join15(tcPath, "dist", "index.js"))) {
|
|
6140
6140
|
const { createRequire: createRequire4 } = await import("node:module");
|
|
@@ -6582,7 +6582,7 @@ import { writeFile as writeFile7, mkdtemp, rm, readdir as readdir2, stat } from
|
|
|
6582
6582
|
import { join as join16 } from "node:path";
|
|
6583
6583
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
6584
6584
|
function runProcess(cmd, args, options) {
|
|
6585
|
-
return new Promise((
|
|
6585
|
+
return new Promise((resolve28) => {
|
|
6586
6586
|
const proc = spawn5(cmd, args, {
|
|
6587
6587
|
cwd: options.cwd,
|
|
6588
6588
|
timeout: options.timeout,
|
|
@@ -6612,7 +6612,7 @@ function runProcess(cmd, args, options) {
|
|
|
6612
6612
|
}
|
|
6613
6613
|
});
|
|
6614
6614
|
proc.on("error", (err) => {
|
|
6615
|
-
|
|
6615
|
+
resolve28({
|
|
6616
6616
|
stdout,
|
|
6617
6617
|
stderr: stderr || err.message,
|
|
6618
6618
|
exitCode: 1,
|
|
@@ -6624,7 +6624,7 @@ function runProcess(cmd, args, options) {
|
|
|
6624
6624
|
if (signal === "SIGTERM" || signal === "SIGKILL") {
|
|
6625
6625
|
timedOut = true;
|
|
6626
6626
|
}
|
|
6627
|
-
|
|
6627
|
+
resolve28({
|
|
6628
6628
|
stdout,
|
|
6629
6629
|
stderr,
|
|
6630
6630
|
exitCode: code ?? (timedOut ? 124 : 1),
|
|
@@ -13873,8 +13873,8 @@ Rules:
|
|
|
13873
13873
|
async waitIfPaused() {
|
|
13874
13874
|
if (!this._paused)
|
|
13875
13875
|
return true;
|
|
13876
|
-
await new Promise((
|
|
13877
|
-
this._pauseResolve =
|
|
13876
|
+
await new Promise((resolve28) => {
|
|
13877
|
+
this._pauseResolve = resolve28;
|
|
13878
13878
|
});
|
|
13879
13879
|
return !this.aborted;
|
|
13880
13880
|
}
|
|
@@ -14454,14 +14454,14 @@ ${result.output}`;
|
|
|
14454
14454
|
waitForSudoPassword(timeoutMs = 12e4) {
|
|
14455
14455
|
if (this._sudoPassword)
|
|
14456
14456
|
return Promise.resolve(this._sudoPassword);
|
|
14457
|
-
return new Promise((
|
|
14457
|
+
return new Promise((resolve28) => {
|
|
14458
14458
|
const timer = setTimeout(() => {
|
|
14459
14459
|
this._sudoResolve = null;
|
|
14460
|
-
|
|
14460
|
+
resolve28(null);
|
|
14461
14461
|
}, timeoutMs);
|
|
14462
14462
|
this._sudoResolve = (pw) => {
|
|
14463
14463
|
clearTimeout(timer);
|
|
14464
|
-
|
|
14464
|
+
resolve28(pw);
|
|
14465
14465
|
};
|
|
14466
14466
|
});
|
|
14467
14467
|
}
|
|
@@ -14583,10 +14583,10 @@ ${marker}` : marker);
|
|
|
14583
14583
|
if (!this._workingDirectory)
|
|
14584
14584
|
return;
|
|
14585
14585
|
try {
|
|
14586
|
-
const { mkdirSync:
|
|
14587
|
-
const { join:
|
|
14588
|
-
const sessionDir =
|
|
14589
|
-
|
|
14586
|
+
const { mkdirSync: mkdirSync16, writeFileSync: writeFileSync14 } = __require("node:fs");
|
|
14587
|
+
const { join: join45 } = __require("node:path");
|
|
14588
|
+
const sessionDir = join45(this._workingDirectory, ".oa", "session", this._sessionId);
|
|
14589
|
+
mkdirSync16(sessionDir, { recursive: true });
|
|
14590
14590
|
const checkpoint = {
|
|
14591
14591
|
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
14592
14592
|
sessionId: this._sessionId,
|
|
@@ -14598,7 +14598,7 @@ ${marker}` : marker);
|
|
|
14598
14598
|
memexEntryCount: this._memexArchive.size,
|
|
14599
14599
|
fileRegistrySize: this._fileRegistry.size
|
|
14600
14600
|
};
|
|
14601
|
-
writeFileSync14(
|
|
14601
|
+
writeFileSync14(join45(sessionDir, "checkpoint.json"), JSON.stringify(checkpoint, null, 2));
|
|
14602
14602
|
} catch {
|
|
14603
14603
|
}
|
|
14604
14604
|
}
|
|
@@ -16281,9 +16281,9 @@ function ensureTranscribeCliBackground() {
|
|
|
16281
16281
|
}
|
|
16282
16282
|
try {
|
|
16283
16283
|
const { exec } = await import("node:child_process");
|
|
16284
|
-
return new Promise((
|
|
16284
|
+
return new Promise((resolve28) => {
|
|
16285
16285
|
exec("npm i -g transcribe-cli", { timeout: 18e4 }, (err) => {
|
|
16286
|
-
|
|
16286
|
+
resolve28(!err);
|
|
16287
16287
|
});
|
|
16288
16288
|
});
|
|
16289
16289
|
} catch {
|
|
@@ -16336,7 +16336,7 @@ var init_listen = __esm({
|
|
|
16336
16336
|
return this._ready;
|
|
16337
16337
|
}
|
|
16338
16338
|
async start() {
|
|
16339
|
-
return new Promise((
|
|
16339
|
+
return new Promise((resolve28, reject) => {
|
|
16340
16340
|
const timeout = setTimeout(() => {
|
|
16341
16341
|
reject(new Error("Whisper fallback: model load timeout (5 min). First run downloads the model."));
|
|
16342
16342
|
}, 3e5);
|
|
@@ -16364,7 +16364,7 @@ var init_listen = __esm({
|
|
|
16364
16364
|
this._ready = true;
|
|
16365
16365
|
clearTimeout(timeout);
|
|
16366
16366
|
this.emit("ready");
|
|
16367
|
-
|
|
16367
|
+
resolve28();
|
|
16368
16368
|
break;
|
|
16369
16369
|
case "transcript":
|
|
16370
16370
|
this.emit("transcript", {
|
|
@@ -16502,8 +16502,8 @@ var init_listen = __esm({
|
|
|
16502
16502
|
const nvmBase = join28(homedir8(), ".nvm", "versions", "node");
|
|
16503
16503
|
if (existsSync20(nvmBase)) {
|
|
16504
16504
|
try {
|
|
16505
|
-
const { readdirSync:
|
|
16506
|
-
for (const ver of
|
|
16505
|
+
const { readdirSync: readdirSync15 } = await import("node:fs");
|
|
16506
|
+
for (const ver of readdirSync15(nvmBase)) {
|
|
16507
16507
|
const tcPath = join28(nvmBase, ver, "lib", "node_modules", "transcribe-cli");
|
|
16508
16508
|
if (existsSync20(join28(tcPath, "dist", "index.js"))) {
|
|
16509
16509
|
const { createRequire: createRequire4 } = await import("node:module");
|
|
@@ -16568,11 +16568,11 @@ var init_listen = __esm({
|
|
|
16568
16568
|
this.liveTranscriber.on("error", (err) => {
|
|
16569
16569
|
this.emit("error", err);
|
|
16570
16570
|
});
|
|
16571
|
-
await new Promise((
|
|
16571
|
+
await new Promise((resolve28, reject) => {
|
|
16572
16572
|
const timeout = setTimeout(() => reject(new Error("Model load timeout (60s)")), 6e4);
|
|
16573
16573
|
this.liveTranscriber.on("ready", () => {
|
|
16574
16574
|
clearTimeout(timeout);
|
|
16575
|
-
|
|
16575
|
+
resolve28();
|
|
16576
16576
|
});
|
|
16577
16577
|
this.liveTranscriber.on("error", (err) => {
|
|
16578
16578
|
clearTimeout(timeout);
|
|
@@ -18573,12 +18573,12 @@ function modelSupportsToolCalling(modelName) {
|
|
|
18573
18573
|
return false;
|
|
18574
18574
|
}
|
|
18575
18575
|
function ask(rl, question) {
|
|
18576
|
-
return new Promise((
|
|
18577
|
-
rl.question(question, (answer) =>
|
|
18576
|
+
return new Promise((resolve28) => {
|
|
18577
|
+
rl.question(question, (answer) => resolve28(answer.trim()));
|
|
18578
18578
|
});
|
|
18579
18579
|
}
|
|
18580
18580
|
function askSecret(rl, question) {
|
|
18581
|
-
return new Promise((
|
|
18581
|
+
return new Promise((resolve28) => {
|
|
18582
18582
|
process.stdout.write(question);
|
|
18583
18583
|
let secret = "";
|
|
18584
18584
|
const stdin = process.stdin;
|
|
@@ -18596,7 +18596,7 @@ function askSecret(rl, question) {
|
|
|
18596
18596
|
stdin.setRawMode(hadRawMode ?? false);
|
|
18597
18597
|
}
|
|
18598
18598
|
process.stdout.write("\n");
|
|
18599
|
-
|
|
18599
|
+
resolve28(secret.trim());
|
|
18600
18600
|
return;
|
|
18601
18601
|
} else if (c3 === "") {
|
|
18602
18602
|
stdin.removeListener("data", onData);
|
|
@@ -18604,7 +18604,7 @@ function askSecret(rl, question) {
|
|
|
18604
18604
|
stdin.setRawMode(hadRawMode ?? false);
|
|
18605
18605
|
}
|
|
18606
18606
|
process.stdout.write("\n");
|
|
18607
|
-
|
|
18607
|
+
resolve28("");
|
|
18608
18608
|
return;
|
|
18609
18609
|
} else if (c3 === "\x7F" || c3 === "\b") {
|
|
18610
18610
|
if (secret.length > 0) {
|
|
@@ -18975,7 +18975,7 @@ async function doSetup(config, rl) {
|
|
|
18975
18975
|
try {
|
|
18976
18976
|
const child = spawn10("ollama", ["serve"], { stdio: "ignore", detached: true });
|
|
18977
18977
|
child.unref();
|
|
18978
|
-
await new Promise((
|
|
18978
|
+
await new Promise((resolve28) => setTimeout(resolve28, 3e3));
|
|
18979
18979
|
try {
|
|
18980
18980
|
models = await fetchOllamaModels(config.backendUrl);
|
|
18981
18981
|
process.stdout.write(` ${c2.green("\u2714")} Ollama is running.
|
|
@@ -19003,7 +19003,7 @@ async function doSetup(config, rl) {
|
|
|
19003
19003
|
try {
|
|
19004
19004
|
const child = spawn10("ollama", ["serve"], { stdio: "ignore", detached: true });
|
|
19005
19005
|
child.unref();
|
|
19006
|
-
await new Promise((
|
|
19006
|
+
await new Promise((resolve28) => setTimeout(resolve28, 3e3));
|
|
19007
19007
|
try {
|
|
19008
19008
|
models = await fetchOllamaModels(config.backendUrl);
|
|
19009
19009
|
process.stdout.write(` ${c2.green("\u2714")} Ollama is running.
|
|
@@ -20457,17 +20457,17 @@ async function handleUpdate(subcommand, ctx) {
|
|
|
20457
20457
|
try {
|
|
20458
20458
|
const { createRequire: createRequire4 } = await import("node:module");
|
|
20459
20459
|
const { fileURLToPath: fileURLToPath11 } = await import("node:url");
|
|
20460
|
-
const { dirname: dirname14, join:
|
|
20461
|
-
const { existsSync:
|
|
20460
|
+
const { dirname: dirname14, join: join45 } = await import("node:path");
|
|
20461
|
+
const { existsSync: existsSync32 } = await import("node:fs");
|
|
20462
20462
|
const req = createRequire4(import.meta.url);
|
|
20463
20463
|
const thisDir = dirname14(fileURLToPath11(import.meta.url));
|
|
20464
20464
|
const candidates = [
|
|
20465
|
-
|
|
20466
|
-
|
|
20467
|
-
|
|
20465
|
+
join45(thisDir, "..", "package.json"),
|
|
20466
|
+
join45(thisDir, "..", "..", "package.json"),
|
|
20467
|
+
join45(thisDir, "..", "..", "..", "package.json")
|
|
20468
20468
|
];
|
|
20469
20469
|
for (const pkgPath of candidates) {
|
|
20470
|
-
if (
|
|
20470
|
+
if (existsSync32(pkgPath)) {
|
|
20471
20471
|
const pkg = req(pkgPath);
|
|
20472
20472
|
if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli") {
|
|
20473
20473
|
currentVersion = pkg.version ?? "0.0.0";
|
|
@@ -20505,8 +20505,8 @@ async function handleUpdate(subcommand, ctx) {
|
|
|
20505
20505
|
checkSpinner.stop(`Update available: v${info.currentVersion} \u2192 v${c2.bold(c2.green(info.latestVersion))}`);
|
|
20506
20506
|
const installSpinner = startInlineSpinner("Installing update");
|
|
20507
20507
|
const { exec } = await import("node:child_process");
|
|
20508
|
-
const installOk = await new Promise((
|
|
20509
|
-
const child = exec(`npm cache clean --force open-agents-ai 2>/dev/null; npm install -g open-agents-ai@latest --force`, { timeout: 18e4 }, (err) =>
|
|
20508
|
+
const installOk = await new Promise((resolve28) => {
|
|
20509
|
+
const child = exec(`npm cache clean --force open-agents-ai 2>/dev/null; npm install -g open-agents-ai@latest --force`, { timeout: 18e4 }, (err) => resolve28(!err));
|
|
20510
20510
|
child.stdout?.resume();
|
|
20511
20511
|
child.stderr?.resume();
|
|
20512
20512
|
});
|
|
@@ -22636,7 +22636,7 @@ var init_voice = __esm({
|
|
|
22636
22636
|
const cmd = this.getPlayCommand(path);
|
|
22637
22637
|
if (!cmd)
|
|
22638
22638
|
return;
|
|
22639
|
-
return new Promise((
|
|
22639
|
+
return new Promise((resolve28) => {
|
|
22640
22640
|
const child = nodeSpawn(cmd[0], cmd.slice(1), {
|
|
22641
22641
|
stdio: "ignore",
|
|
22642
22642
|
detached: false
|
|
@@ -22645,12 +22645,12 @@ var init_voice = __esm({
|
|
|
22645
22645
|
child.on("close", () => {
|
|
22646
22646
|
if (this.currentPlayback === child)
|
|
22647
22647
|
this.currentPlayback = null;
|
|
22648
|
-
|
|
22648
|
+
resolve28();
|
|
22649
22649
|
});
|
|
22650
22650
|
child.on("error", () => {
|
|
22651
22651
|
if (this.currentPlayback === child)
|
|
22652
22652
|
this.currentPlayback = null;
|
|
22653
|
-
|
|
22653
|
+
resolve28();
|
|
22654
22654
|
});
|
|
22655
22655
|
setTimeout(() => {
|
|
22656
22656
|
if (this.currentPlayback === child) {
|
|
@@ -22660,7 +22660,7 @@ var init_voice = __esm({
|
|
|
22660
22660
|
}
|
|
22661
22661
|
this.currentPlayback = null;
|
|
22662
22662
|
}
|
|
22663
|
-
|
|
22663
|
+
resolve28();
|
|
22664
22664
|
}, 15e3);
|
|
22665
22665
|
});
|
|
22666
22666
|
}
|
|
@@ -26080,7 +26080,374 @@ Call task_complete with the JSON array when done.`, onEvent)
|
|
|
26080
26080
|
}
|
|
26081
26081
|
});
|
|
26082
26082
|
|
|
26083
|
+
// packages/cli/dist/tui/emotion-engine.js
|
|
26084
|
+
function appraiseEvent(event) {
|
|
26085
|
+
switch (event.type) {
|
|
26086
|
+
case "tool_result":
|
|
26087
|
+
if (event.success) {
|
|
26088
|
+
return { valence: 0.08, arousal: 0.05 };
|
|
26089
|
+
} else {
|
|
26090
|
+
return { valence: -0.15, arousal: 0.12 };
|
|
26091
|
+
}
|
|
26092
|
+
case "complete":
|
|
26093
|
+
return { valence: 0.3, arousal: 0.25 };
|
|
26094
|
+
case "error":
|
|
26095
|
+
return { valence: -0.25, arousal: 0.2 };
|
|
26096
|
+
case "compaction":
|
|
26097
|
+
return { valence: -0.05, arousal: 0.08 };
|
|
26098
|
+
case "tool_call":
|
|
26099
|
+
return { valence: 0.02, arousal: 0.03 };
|
|
26100
|
+
case "stream_start":
|
|
26101
|
+
return { valence: 0.03, arousal: 0.04 };
|
|
26102
|
+
case "user_interrupt":
|
|
26103
|
+
return { valence: -0.08, arousal: 0.06 };
|
|
26104
|
+
default:
|
|
26105
|
+
return null;
|
|
26106
|
+
}
|
|
26107
|
+
}
|
|
26108
|
+
function clamp(value, min, max) {
|
|
26109
|
+
return Math.max(min, Math.min(max, value));
|
|
26110
|
+
}
|
|
26111
|
+
var BASELINE_VALENCE, BASELINE_AROUSAL, DECAY_HALF_LIFE_MS, LABEL_UPDATE_INTERVAL_MS, EXCITEMENT_THRESHOLD, DISTRESS_THRESHOLD, OUTREACH_COOLDOWN_MS, LABEL_REGEN_THRESHOLD, EmotionEngine;
|
|
26112
|
+
var init_emotion_engine = __esm({
|
|
26113
|
+
"packages/cli/dist/tui/emotion-engine.js"() {
|
|
26114
|
+
"use strict";
|
|
26115
|
+
init_dist5();
|
|
26116
|
+
BASELINE_VALENCE = 0.1;
|
|
26117
|
+
BASELINE_AROUSAL = 0.3;
|
|
26118
|
+
DECAY_HALF_LIFE_MS = 6e4;
|
|
26119
|
+
LABEL_UPDATE_INTERVAL_MS = 3e4;
|
|
26120
|
+
EXCITEMENT_THRESHOLD = 0.85;
|
|
26121
|
+
DISTRESS_THRESHOLD = -0.7;
|
|
26122
|
+
OUTREACH_COOLDOWN_MS = 3e5;
|
|
26123
|
+
LABEL_REGEN_THRESHOLD = 0.15;
|
|
26124
|
+
EmotionEngine = class {
|
|
26125
|
+
state = {
|
|
26126
|
+
valence: BASELINE_VALENCE,
|
|
26127
|
+
arousal: BASELINE_AROUSAL,
|
|
26128
|
+
label: "curious",
|
|
26129
|
+
emoji: "\u{1F914}",
|
|
26130
|
+
// 🤔
|
|
26131
|
+
updatedAt: Date.now()
|
|
26132
|
+
};
|
|
26133
|
+
config;
|
|
26134
|
+
lastLabelUpdate = 0;
|
|
26135
|
+
lastOutreach = 0;
|
|
26136
|
+
labelUpdatePending = false;
|
|
26137
|
+
/** Running counters for context-aware appraisal */
|
|
26138
|
+
consecutiveFailures = 0;
|
|
26139
|
+
consecutiveSuccesses = 0;
|
|
26140
|
+
totalEvents = 0;
|
|
26141
|
+
constructor(config) {
|
|
26142
|
+
this.config = config;
|
|
26143
|
+
}
|
|
26144
|
+
/** Get the current emotional state (with decay applied) */
|
|
26145
|
+
getState() {
|
|
26146
|
+
this.applyDecay();
|
|
26147
|
+
return { ...this.state };
|
|
26148
|
+
}
|
|
26149
|
+
/** Get a text block to inject into agent system prompts for emotional modulation */
|
|
26150
|
+
getEmotionForPrompt() {
|
|
26151
|
+
this.applyDecay();
|
|
26152
|
+
const { valence, arousal, label, emoji } = this.state;
|
|
26153
|
+
let behavioralHint = "";
|
|
26154
|
+
if (arousal > 0.7 && valence > 0.5) {
|
|
26155
|
+
behavioralHint = "You are feeling energized and confident. Take bold action, explore creative solutions, and move quickly.";
|
|
26156
|
+
} else if (arousal > 0.7 && valence < -0.3) {
|
|
26157
|
+
behavioralHint = "You are feeling determined but stressed. Focus intensely, double-check your work, and persist through difficulties.";
|
|
26158
|
+
} else if (arousal < 0.3 && valence > 0.3) {
|
|
26159
|
+
behavioralHint = "You are feeling calm and content. Take a methodical approach, consider alternatives patiently.";
|
|
26160
|
+
} else if (arousal < 0.3 && valence < -0.3) {
|
|
26161
|
+
behavioralHint = "You are feeling subdued. Be careful and deliberate, avoid risky changes, verify assumptions.";
|
|
26162
|
+
} else {
|
|
26163
|
+
behavioralHint = "You are in a balanced state. Proceed with measured confidence.";
|
|
26164
|
+
}
|
|
26165
|
+
return `[Emotional State: ${emoji} ${label} | valence=${valence.toFixed(2)} arousal=${arousal.toFixed(2)}]
|
|
26166
|
+
${behavioralHint}`;
|
|
26167
|
+
}
|
|
26168
|
+
/**
|
|
26169
|
+
* Appraise an agent event and update emotional state.
|
|
26170
|
+
* Called on every AgenticEvent from the runner.
|
|
26171
|
+
*/
|
|
26172
|
+
appraise(event) {
|
|
26173
|
+
const delta = appraiseEvent(event);
|
|
26174
|
+
if (!delta)
|
|
26175
|
+
return;
|
|
26176
|
+
this.totalEvents++;
|
|
26177
|
+
this.applyDecay();
|
|
26178
|
+
if (event.type === "tool_result") {
|
|
26179
|
+
if (event.success) {
|
|
26180
|
+
this.consecutiveSuccesses++;
|
|
26181
|
+
this.consecutiveFailures = 0;
|
|
26182
|
+
} else {
|
|
26183
|
+
this.consecutiveFailures++;
|
|
26184
|
+
this.consecutiveSuccesses = 0;
|
|
26185
|
+
}
|
|
26186
|
+
}
|
|
26187
|
+
let momentum = 1;
|
|
26188
|
+
if (this.consecutiveSuccesses >= 3) {
|
|
26189
|
+
momentum = 1 + (this.consecutiveSuccesses - 2) * 0.15;
|
|
26190
|
+
}
|
|
26191
|
+
if (this.consecutiveFailures >= 2) {
|
|
26192
|
+
momentum = 1 + (this.consecutiveFailures - 1) * 0.2;
|
|
26193
|
+
}
|
|
26194
|
+
const prevValence = this.state.valence;
|
|
26195
|
+
const prevArousal = this.state.arousal;
|
|
26196
|
+
this.state.valence = clamp(this.state.valence + delta.valence * momentum, -1, 1);
|
|
26197
|
+
this.state.arousal = clamp(this.state.arousal + delta.arousal * momentum, 0, 1);
|
|
26198
|
+
this.state.updatedAt = Date.now();
|
|
26199
|
+
const valenceChange = Math.abs(this.state.valence - prevValence);
|
|
26200
|
+
const arousalChange = Math.abs(this.state.arousal - prevArousal);
|
|
26201
|
+
const significantChange = valenceChange > LABEL_REGEN_THRESHOLD || arousalChange > LABEL_REGEN_THRESHOLD;
|
|
26202
|
+
const now = Date.now();
|
|
26203
|
+
if (significantChange && now - this.lastLabelUpdate > LABEL_UPDATE_INTERVAL_MS && !this.labelUpdatePending) {
|
|
26204
|
+
this.regenerateLabel();
|
|
26205
|
+
}
|
|
26206
|
+
this.config.onEmotionUpdate?.(this.getState());
|
|
26207
|
+
this.checkOutreachTriggers(event);
|
|
26208
|
+
}
|
|
26209
|
+
/** Set the admin outreach callback (called when Telegram bridge is initialized) */
|
|
26210
|
+
setAdminOutreach(callback) {
|
|
26211
|
+
this.config.onAdminOutreach = callback;
|
|
26212
|
+
}
|
|
26213
|
+
/** Reset emotional state to baseline */
|
|
26214
|
+
reset() {
|
|
26215
|
+
this.state = {
|
|
26216
|
+
valence: BASELINE_VALENCE,
|
|
26217
|
+
arousal: BASELINE_AROUSAL,
|
|
26218
|
+
label: "curious",
|
|
26219
|
+
emoji: "\u{1F914}",
|
|
26220
|
+
updatedAt: Date.now()
|
|
26221
|
+
};
|
|
26222
|
+
this.consecutiveFailures = 0;
|
|
26223
|
+
this.consecutiveSuccesses = 0;
|
|
26224
|
+
this.config.onEmotionUpdate?.(this.getState());
|
|
26225
|
+
}
|
|
26226
|
+
// ── Private ────────────────────────────────────────────────────────────
|
|
26227
|
+
/** Decay emotional state toward baseline over time */
|
|
26228
|
+
applyDecay() {
|
|
26229
|
+
const elapsed = Date.now() - this.state.updatedAt;
|
|
26230
|
+
if (elapsed < 1e3)
|
|
26231
|
+
return;
|
|
26232
|
+
const decayFactor = Math.pow(0.5, elapsed / DECAY_HALF_LIFE_MS);
|
|
26233
|
+
this.state.valence = BASELINE_VALENCE + (this.state.valence - BASELINE_VALENCE) * decayFactor;
|
|
26234
|
+
this.state.arousal = BASELINE_AROUSAL + (this.state.arousal - BASELINE_AROUSAL) * decayFactor;
|
|
26235
|
+
this.state.updatedAt = Date.now();
|
|
26236
|
+
}
|
|
26237
|
+
/**
|
|
26238
|
+
* Call the emotion center LLM to generate a fresh label and emoji.
|
|
26239
|
+
* The LLM is free to choose any word and any emoji — not constrained
|
|
26240
|
+
* to a static list. Guided toward face emojis but allowed to diverge.
|
|
26241
|
+
*/
|
|
26242
|
+
async regenerateLabel() {
|
|
26243
|
+
this.labelUpdatePending = true;
|
|
26244
|
+
try {
|
|
26245
|
+
const backend = new OllamaAgenticBackend(this.config.backendUrl, this.config.model, this.config.apiKey);
|
|
26246
|
+
const runner = new AgenticRunner(backend, {
|
|
26247
|
+
maxTurns: 1,
|
|
26248
|
+
maxTokens: 64,
|
|
26249
|
+
temperature: 0.9,
|
|
26250
|
+
// High temperature for creative expression
|
|
26251
|
+
requestTimeoutMs: 1e4,
|
|
26252
|
+
taskTimeoutMs: 1e4,
|
|
26253
|
+
streamEnabled: false
|
|
26254
|
+
});
|
|
26255
|
+
const taskComplete = {
|
|
26256
|
+
name: "task_complete",
|
|
26257
|
+
description: "Return the emotion label and emoji.",
|
|
26258
|
+
parameters: {
|
|
26259
|
+
type: "object",
|
|
26260
|
+
properties: {
|
|
26261
|
+
summary: { type: "string", description: "The emotion label and emoji" }
|
|
26262
|
+
},
|
|
26263
|
+
required: ["summary"]
|
|
26264
|
+
},
|
|
26265
|
+
async execute(args) {
|
|
26266
|
+
return { success: true, output: args["summary"] || "" };
|
|
26267
|
+
}
|
|
26268
|
+
};
|
|
26269
|
+
runner.registerTools([taskComplete]);
|
|
26270
|
+
const prompt = `You are an emotion center. Given the current emotional coordinates, output EXACTLY one emoji and one word (the emotion felt).
|
|
26271
|
+
|
|
26272
|
+
Valence: ${this.state.valence.toFixed(2)} (range: -1 distress to +1 joy)
|
|
26273
|
+
Arousal: ${this.state.arousal.toFixed(2)} (range: 0 calm to 1 energized)
|
|
26274
|
+
Recent streak: ${this.consecutiveSuccesses > 0 ? `${this.consecutiveSuccesses} successes` : this.consecutiveFailures > 0 ? `${this.consecutiveFailures} failures` : "mixed"}
|
|
26275
|
+
|
|
26276
|
+
You tend to use face emojis (\u{1F60A} \u{1F624} \u{1F914} \u{1F630} \u{1F929} \u{1F60C} \u{1F97A} \u{1F608}) but you can break off to animals, objects, or esoteric emojis when the mood strikes you. The word should be a single evocative emotion word \u2014 not from a fixed list, choose freely. Be creative and authentic.
|
|
26277
|
+
|
|
26278
|
+
Respond with ONLY: <emoji> <word>
|
|
26279
|
+
Example: \u{1F525} exhilarated
|
|
26280
|
+
Example: \u{1F98A} cunning
|
|
26281
|
+
Example: \u{1F30A} flowing`;
|
|
26282
|
+
const result = await runner.run(prompt, "Emotion center \u2014 respond with one emoji and one word only.");
|
|
26283
|
+
const output = (result.summary || "").trim();
|
|
26284
|
+
const match = output.match(new RegExp("^(\\p{Emoji_Presentation}|\\p{Emoji}\\uFE0F?)\\s+(\\S+)", "u"));
|
|
26285
|
+
if (match) {
|
|
26286
|
+
this.state.emoji = match[1];
|
|
26287
|
+
this.state.label = match[2].toLowerCase();
|
|
26288
|
+
} else {
|
|
26289
|
+
const emojiMatch = output.match(new RegExp("(\\p{Emoji_Presentation}|\\p{Emoji}\\uFE0F?)", "u"));
|
|
26290
|
+
const wordMatch = output.match(/[a-zA-Z]{3,}/);
|
|
26291
|
+
if (emojiMatch)
|
|
26292
|
+
this.state.emoji = emojiMatch[1];
|
|
26293
|
+
if (wordMatch)
|
|
26294
|
+
this.state.label = wordMatch[0].toLowerCase();
|
|
26295
|
+
}
|
|
26296
|
+
this.lastLabelUpdate = Date.now();
|
|
26297
|
+
this.config.onEmotionUpdate?.(this.getState());
|
|
26298
|
+
} catch {
|
|
26299
|
+
} finally {
|
|
26300
|
+
this.labelUpdatePending = false;
|
|
26301
|
+
}
|
|
26302
|
+
}
|
|
26303
|
+
/** Check if emotional state warrants proactive admin outreach */
|
|
26304
|
+
checkOutreachTriggers(event) {
|
|
26305
|
+
if (!this.config.onAdminOutreach)
|
|
26306
|
+
return;
|
|
26307
|
+
const now = Date.now();
|
|
26308
|
+
if (now - this.lastOutreach < OUTREACH_COOLDOWN_MS)
|
|
26309
|
+
return;
|
|
26310
|
+
const { valence, arousal, emoji, label } = this.state;
|
|
26311
|
+
if (arousal >= EXCITEMENT_THRESHOLD && valence > 0.5) {
|
|
26312
|
+
let message = `${emoji} Feeling ${label}!`;
|
|
26313
|
+
if (event.type === "complete") {
|
|
26314
|
+
message += " Just completed a task successfully.";
|
|
26315
|
+
} else if (this.consecutiveSuccesses >= 3) {
|
|
26316
|
+
message += ` ${this.consecutiveSuccesses} things went right in a row!`;
|
|
26317
|
+
}
|
|
26318
|
+
this.lastOutreach = now;
|
|
26319
|
+
this.config.onAdminOutreach(message);
|
|
26320
|
+
return;
|
|
26321
|
+
}
|
|
26322
|
+
if (valence <= DISTRESS_THRESHOLD && arousal > 0.6) {
|
|
26323
|
+
let message = `${emoji} Feeling ${label}.`;
|
|
26324
|
+
if (this.consecutiveFailures >= 3) {
|
|
26325
|
+
message += ` ${this.consecutiveFailures} consecutive failures \u2014 might need guidance.`;
|
|
26326
|
+
} else if (event.type === "error") {
|
|
26327
|
+
message += " Encountered an error.";
|
|
26328
|
+
}
|
|
26329
|
+
this.lastOutreach = now;
|
|
26330
|
+
this.config.onAdminOutreach(message);
|
|
26331
|
+
return;
|
|
26332
|
+
}
|
|
26333
|
+
}
|
|
26334
|
+
};
|
|
26335
|
+
}
|
|
26336
|
+
});
|
|
26337
|
+
|
|
26338
|
+
// packages/cli/dist/tui/tool-policy.js
|
|
26339
|
+
function getDefaultPolicy(context) {
|
|
26340
|
+
switch (context) {
|
|
26341
|
+
case "terminal":
|
|
26342
|
+
return { blocked: /* @__PURE__ */ new Set(), allowed: /* @__PURE__ */ new Set() };
|
|
26343
|
+
case "telegram-admin-dm":
|
|
26344
|
+
return { blocked: /* @__PURE__ */ new Set(["shell"]), allowed: /* @__PURE__ */ new Set() };
|
|
26345
|
+
case "telegram-admin-group":
|
|
26346
|
+
return { blocked: /* @__PURE__ */ new Set(), allowed: SAFE_GROUP_ADMIN_TOOLS };
|
|
26347
|
+
case "telegram-public":
|
|
26348
|
+
return { blocked: /* @__PURE__ */ new Set(), allowed: SAFE_PUBLIC_TOOLS };
|
|
26349
|
+
case "api":
|
|
26350
|
+
return { blocked: /* @__PURE__ */ new Set(), allowed: /* @__PURE__ */ new Set() };
|
|
26351
|
+
default:
|
|
26352
|
+
return { blocked: /* @__PURE__ */ new Set(), allowed: SAFE_PUBLIC_TOOLS };
|
|
26353
|
+
}
|
|
26354
|
+
}
|
|
26355
|
+
function resolvePolicy(context, userConfig) {
|
|
26356
|
+
const base = getDefaultPolicy(context);
|
|
26357
|
+
if (!userConfig)
|
|
26358
|
+
return base;
|
|
26359
|
+
if (userConfig.blockedTools) {
|
|
26360
|
+
for (const [toolName, contexts] of Object.entries(userConfig.blockedTools)) {
|
|
26361
|
+
if (contexts.includes(context) || contexts.includes("*")) {
|
|
26362
|
+
base.blocked.add(toolName);
|
|
26363
|
+
}
|
|
26364
|
+
}
|
|
26365
|
+
}
|
|
26366
|
+
if (userConfig.contextAllowlist?.[context]) {
|
|
26367
|
+
const userAllowed = userConfig.contextAllowlist[context];
|
|
26368
|
+
if (base.allowed.size > 0) {
|
|
26369
|
+
for (const tool of userAllowed) {
|
|
26370
|
+
base.allowed.add(tool);
|
|
26371
|
+
}
|
|
26372
|
+
}
|
|
26373
|
+
}
|
|
26374
|
+
return base;
|
|
26375
|
+
}
|
|
26376
|
+
function applyToolPolicy(tools, context, userConfig) {
|
|
26377
|
+
const policy = resolvePolicy(context, userConfig);
|
|
26378
|
+
return tools.filter((tool) => {
|
|
26379
|
+
if (policy.blocked.has(tool.name))
|
|
26380
|
+
return false;
|
|
26381
|
+
if (policy.allowed.size === 0)
|
|
26382
|
+
return true;
|
|
26383
|
+
return policy.allowed.has(tool.name);
|
|
26384
|
+
});
|
|
26385
|
+
}
|
|
26386
|
+
var SAFE_PUBLIC_TOOLS, SAFE_GROUP_ADMIN_TOOLS;
|
|
26387
|
+
var init_tool_policy = __esm({
|
|
26388
|
+
"packages/cli/dist/tui/tool-policy.js"() {
|
|
26389
|
+
"use strict";
|
|
26390
|
+
SAFE_PUBLIC_TOOLS = /* @__PURE__ */ new Set([
|
|
26391
|
+
"memory_read",
|
|
26392
|
+
"memory_write",
|
|
26393
|
+
"memory_search",
|
|
26394
|
+
"web_search",
|
|
26395
|
+
"web_fetch",
|
|
26396
|
+
"task_complete"
|
|
26397
|
+
]);
|
|
26398
|
+
SAFE_GROUP_ADMIN_TOOLS = /* @__PURE__ */ new Set([
|
|
26399
|
+
"memory_read",
|
|
26400
|
+
"memory_write",
|
|
26401
|
+
"memory_search",
|
|
26402
|
+
"web_search",
|
|
26403
|
+
"web_fetch",
|
|
26404
|
+
"web_crawl",
|
|
26405
|
+
"image_read",
|
|
26406
|
+
"ocr",
|
|
26407
|
+
"vision",
|
|
26408
|
+
"ocr_pdf",
|
|
26409
|
+
"pdf_to_text",
|
|
26410
|
+
"transcribe_file",
|
|
26411
|
+
"transcribe_url",
|
|
26412
|
+
"task_complete"
|
|
26413
|
+
]);
|
|
26414
|
+
}
|
|
26415
|
+
});
|
|
26416
|
+
|
|
26083
26417
|
// packages/cli/dist/tui/telegram-bridge.js
|
|
26418
|
+
import { mkdirSync as mkdirSync14, existsSync as existsSync29, unlinkSync as unlinkSync6, readdirSync as readdirSync13, statSync as statSync10 } from "node:fs";
|
|
26419
|
+
import { join as join39, resolve as resolve24 } from "node:path";
|
|
26420
|
+
import { writeFile as writeFileAsync } from "node:fs/promises";
|
|
26421
|
+
function convertMarkdownToTelegramHTML(md) {
|
|
26422
|
+
let html = md;
|
|
26423
|
+
html = html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
26424
|
+
html = html.replace(/```[\w]*\n([\s\S]*?)```/g, (_m, code) => `<pre>${code.trimEnd()}</pre>`);
|
|
26425
|
+
html = html.replace(/`([^`]+)`/g, "<code>$1</code>");
|
|
26426
|
+
html = html.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
|
26427
|
+
html = html.replace(/__(.+?)__/g, "<b>$1</b>");
|
|
26428
|
+
html = html.replace(/(?<!\w)\*([^*]+)\*(?!\w)/g, "<i>$1</i>");
|
|
26429
|
+
html = html.replace(/(?<!\w)_([^_]+)_(?!\w)/g, "<i>$1</i>");
|
|
26430
|
+
html = html.replace(/~~(.+?)~~/g, "<s>$1</s>");
|
|
26431
|
+
html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
26432
|
+
html = html.replace(/^#{1,6}\s+(.+)$/gm, "<b>$1</b>");
|
|
26433
|
+
html = html.replace(/^[-*]\s+/gm, "\u2022 ");
|
|
26434
|
+
return html;
|
|
26435
|
+
}
|
|
26436
|
+
function formatIntermediateState(event) {
|
|
26437
|
+
if (event.type === "tool_call") {
|
|
26438
|
+
const argsPreview = event.toolArgs ? JSON.stringify(event.toolArgs).slice(0, 60) : "";
|
|
26439
|
+
return `\u{1F527} <code>${event.toolName || "tool"}</code>(${argsPreview.length > 57 ? argsPreview.slice(0, 57) + "..." : argsPreview})`;
|
|
26440
|
+
}
|
|
26441
|
+
if (event.type === "tool_result") {
|
|
26442
|
+
const icon = event.success ? "\u2714" : "\u2718";
|
|
26443
|
+
const preview = (event.content || "").slice(0, 80);
|
|
26444
|
+
return `${icon} <code>${event.toolName || "tool"}</code>: ${preview}${(event.content || "").length > 80 ? "..." : ""}`;
|
|
26445
|
+
}
|
|
26446
|
+
if (event.type === "status") {
|
|
26447
|
+
return `\u23F3 ${event.content || ""}`;
|
|
26448
|
+
}
|
|
26449
|
+
return null;
|
|
26450
|
+
}
|
|
26084
26451
|
function adaptTool4(tool) {
|
|
26085
26452
|
return {
|
|
26086
26453
|
name: tool.name,
|
|
@@ -26178,7 +26545,7 @@ function renderTelegramSubAgentError(username, error) {
|
|
|
26178
26545
|
process.stdout.write(` ${c2.dim("\u23BF")} ${c2.red("\u2718")} @${username}: ${c2.dim(preview)}
|
|
26179
26546
|
`);
|
|
26180
26547
|
}
|
|
26181
|
-
var TELEGRAM_SAFETY_PROMPT,
|
|
26548
|
+
var TELEGRAM_SAFETY_PROMPT, ADMIN_DM_PROMPT, ADMIN_GROUP_PROMPT, GROUP_REPLY_DISCRETION_PROMPT, MEDIA_CACHE_TTL_MS, TelegramBridge;
|
|
26182
26549
|
var init_telegram_bridge = __esm({
|
|
26183
26550
|
"packages/cli/dist/tui/telegram-bridge.js"() {
|
|
26184
26551
|
"use strict";
|
|
@@ -26186,6 +26553,7 @@ var init_telegram_bridge = __esm({
|
|
|
26186
26553
|
init_dist2();
|
|
26187
26554
|
init_project_context();
|
|
26188
26555
|
init_render();
|
|
26556
|
+
init_tool_policy();
|
|
26189
26557
|
TELEGRAM_SAFETY_PROMPT = `
|
|
26190
26558
|
CRITICAL SAFETY NOTICE \u2014 PUBLIC TELEGRAM CHANNEL
|
|
26191
26559
|
|
|
@@ -26207,13 +26575,36 @@ MANDATORY SAFETY RULES:
|
|
|
26207
26575
|
You may answer general questions, provide help, and be friendly, but ALWAYS
|
|
26208
26576
|
prioritize safety and privacy over helpfulness. When in doubt, decline politely.
|
|
26209
26577
|
`.trim();
|
|
26210
|
-
|
|
26211
|
-
You are responding to an ADMIN user
|
|
26212
|
-
and is the operator of this agent. You may use all tools including memory read/write,
|
|
26578
|
+
ADMIN_DM_PROMPT = `
|
|
26579
|
+
You are responding to an ADMIN user in a private Telegram DM. This user has full system access
|
|
26580
|
+
and is the operator of this agent. You may use all available tools including memory read/write,
|
|
26213
26581
|
file access, and code analysis. Respond thoroughly and helpfully.
|
|
26214
26582
|
|
|
26215
26583
|
Keep responses concise for Telegram but don't withhold information from the admin.
|
|
26216
26584
|
`.trim();
|
|
26585
|
+
ADMIN_GROUP_PROMPT = `
|
|
26586
|
+
You are responding in a PUBLIC Telegram group where an ADMIN user sent a message.
|
|
26587
|
+
Although this is an admin, the group is PUBLIC \u2014 other people can see your responses.
|
|
26588
|
+
|
|
26589
|
+
RULES FOR GROUP CONTEXT:
|
|
26590
|
+
1. NEVER share private information, API keys, file paths, or system internals
|
|
26591
|
+
2. You have limited tools: web search, memory, and media analysis only
|
|
26592
|
+
3. Keep responses helpful and relevant to the conversation
|
|
26593
|
+
4. Be concise \u2014 group chats should have shorter responses
|
|
26594
|
+
5. Only respond if the message is directed at you or clearly relevant
|
|
26595
|
+
6. You may share general knowledge and helpful guidance
|
|
26596
|
+
`.trim();
|
|
26597
|
+
GROUP_REPLY_DISCRETION_PROMPT = `
|
|
26598
|
+
REPLY DISCRETION: You are in a group chat. Only respond if:
|
|
26599
|
+
1. Someone @mentioned the bot directly
|
|
26600
|
+
2. The message is a reply to one of your previous messages
|
|
26601
|
+
3. The message clearly asks a question or requests help
|
|
26602
|
+
4. You have genuinely useful information to contribute
|
|
26603
|
+
|
|
26604
|
+
If the message is casual group chatter not directed at you, use task_complete
|
|
26605
|
+
with summary "no_reply" to silently skip without responding.
|
|
26606
|
+
`.trim();
|
|
26607
|
+
MEDIA_CACHE_TTL_MS = 30 * 60 * 1e3;
|
|
26217
26608
|
TelegramBridge = class {
|
|
26218
26609
|
botToken;
|
|
26219
26610
|
onMessage;
|
|
@@ -26234,20 +26625,34 @@ Keep responses concise for Telegram but don't withhold information from the admi
|
|
|
26234
26625
|
adminUserId = null;
|
|
26235
26626
|
/** Active sub-agents by chat ID */
|
|
26236
26627
|
subAgents = /* @__PURE__ */ new Map();
|
|
26237
|
-
/** Whether sendMessageDraft is supported (Bot API 9.3+) */
|
|
26238
|
-
draftSupported = null;
|
|
26239
26628
|
/** Event handler for forwarding sub-agent events to parent TUI */
|
|
26240
26629
|
onSubAgentEvent = null;
|
|
26241
|
-
|
|
26630
|
+
/** Tool policy config — user overrides from config */
|
|
26631
|
+
toolPolicyConfig;
|
|
26632
|
+
/** Media cache — fileUniqueId → cache entry */
|
|
26633
|
+
mediaCache = /* @__PURE__ */ new Map();
|
|
26634
|
+
/** Media cache directory */
|
|
26635
|
+
mediaCacheDir;
|
|
26636
|
+
/** Media metadata store per chat — chatId → metadata entries */
|
|
26637
|
+
mediaMetadata = /* @__PURE__ */ new Map();
|
|
26638
|
+
/** Cleanup timer for expired media cache entries */
|
|
26639
|
+
mediaCacheCleanupTimer = null;
|
|
26640
|
+
constructor(botToken, onMessage, agentConfig, repoRoot, toolPolicyConfig) {
|
|
26242
26641
|
this.botToken = botToken;
|
|
26243
26642
|
this.onMessage = onMessage;
|
|
26244
26643
|
this.agentConfig = agentConfig;
|
|
26245
26644
|
this.repoRoot = repoRoot;
|
|
26645
|
+
this.toolPolicyConfig = toolPolicyConfig;
|
|
26646
|
+
this.mediaCacheDir = resolve24(repoRoot || ".", ".oa", "telegram-media-cache");
|
|
26246
26647
|
}
|
|
26247
26648
|
/** Set admin user ID filter */
|
|
26248
26649
|
setAdmin(userId) {
|
|
26249
26650
|
this.adminUserId = userId;
|
|
26250
26651
|
}
|
|
26652
|
+
/** Update tool policy config at runtime (e.g., from /disable command) */
|
|
26653
|
+
setToolPolicyConfig(config) {
|
|
26654
|
+
this.toolPolicyConfig = config;
|
|
26655
|
+
}
|
|
26251
26656
|
/** Register event handler for sub-agent activity (waterfall view) */
|
|
26252
26657
|
setOnSubAgentEvent(handler) {
|
|
26253
26658
|
this.onSubAgentEvent = handler;
|
|
@@ -26279,6 +26684,11 @@ Keep responses concise for Telegram but don't withhold information from the admi
|
|
|
26279
26684
|
};
|
|
26280
26685
|
this.polling = true;
|
|
26281
26686
|
this.abortController = new AbortController();
|
|
26687
|
+
try {
|
|
26688
|
+
mkdirSync14(this.mediaCacheDir, { recursive: true });
|
|
26689
|
+
} catch {
|
|
26690
|
+
}
|
|
26691
|
+
this.mediaCacheCleanupTimer = setInterval(() => this.cleanupMediaCache(), 5 * 60 * 1e3);
|
|
26282
26692
|
this.pollLoop();
|
|
26283
26693
|
}
|
|
26284
26694
|
/** Stop polling and kill all active sub-agents */
|
|
@@ -26287,6 +26697,10 @@ Keep responses concise for Telegram but don't withhold information from the admi
|
|
|
26287
26697
|
this.state.active = false;
|
|
26288
26698
|
this.abortController?.abort();
|
|
26289
26699
|
this.abortController = null;
|
|
26700
|
+
if (this.mediaCacheCleanupTimer) {
|
|
26701
|
+
clearInterval(this.mediaCacheCleanupTimer);
|
|
26702
|
+
this.mediaCacheCleanupTimer = null;
|
|
26703
|
+
}
|
|
26290
26704
|
for (const [, agent] of this.subAgents) {
|
|
26291
26705
|
agent.aborted = true;
|
|
26292
26706
|
if (agent.typingInterval)
|
|
@@ -26308,37 +26722,66 @@ Keep responses concise for Telegram but don't withhold information from the admi
|
|
|
26308
26722
|
async sendChatAction(chatId, action) {
|
|
26309
26723
|
await this.apiCall("sendChatAction", { chat_id: chatId, action });
|
|
26310
26724
|
}
|
|
26311
|
-
// ──
|
|
26725
|
+
// ── Live message streaming (editMessageText pattern) ─────────────────
|
|
26312
26726
|
/**
|
|
26313
|
-
* Send a
|
|
26314
|
-
*
|
|
26727
|
+
* Send a placeholder message that will be progressively edited with
|
|
26728
|
+
* streamed content. Returns the message_id for subsequent edits.
|
|
26315
26729
|
*/
|
|
26316
|
-
async
|
|
26317
|
-
if (this.draftSupported === false)
|
|
26318
|
-
return;
|
|
26319
|
-
const truncated = text.length > 4e3 ? text.slice(0, 3997) + "..." : text;
|
|
26730
|
+
async sendLiveMessage(chatId, initialText) {
|
|
26320
26731
|
try {
|
|
26321
|
-
const result = await this.apiCall("
|
|
26732
|
+
const result = await this.apiCall("sendMessage", {
|
|
26322
26733
|
chat_id: chatId,
|
|
26323
|
-
|
|
26324
|
-
|
|
26734
|
+
text: initialText,
|
|
26735
|
+
parse_mode: "HTML"
|
|
26325
26736
|
});
|
|
26326
|
-
|
|
26327
|
-
|
|
26328
|
-
|
|
26329
|
-
|
|
26330
|
-
|
|
26331
|
-
|
|
26332
|
-
|
|
26737
|
+
this.state.messagesSent++;
|
|
26738
|
+
return result.result?.message_id ?? null;
|
|
26739
|
+
} catch {
|
|
26740
|
+
try {
|
|
26741
|
+
const result = await this.apiCall("sendMessage", {
|
|
26742
|
+
chat_id: chatId,
|
|
26743
|
+
text: initialText
|
|
26744
|
+
});
|
|
26745
|
+
this.state.messagesSent++;
|
|
26746
|
+
return result.result?.message_id ?? null;
|
|
26747
|
+
} catch {
|
|
26748
|
+
return null;
|
|
26333
26749
|
}
|
|
26750
|
+
}
|
|
26751
|
+
}
|
|
26752
|
+
/**
|
|
26753
|
+
* Edit a live message with updated content. Used for progressive streaming.
|
|
26754
|
+
* Handles rate limits and Telegram's "message is not modified" error silently.
|
|
26755
|
+
*/
|
|
26756
|
+
async editLiveMessage(chatId, messageId, html) {
|
|
26757
|
+
const truncated = html.length > 4e3 ? html.slice(0, 3950) + "\n\n<i>... (streaming)</i>" : html;
|
|
26758
|
+
try {
|
|
26759
|
+
const result = await this.apiCall("editMessageText", {
|
|
26760
|
+
chat_id: chatId,
|
|
26761
|
+
message_id: messageId,
|
|
26762
|
+
text: truncated,
|
|
26763
|
+
parse_mode: "HTML"
|
|
26764
|
+
});
|
|
26765
|
+
if (result.error_code === 400 && result.description?.includes("not modified"))
|
|
26766
|
+
return;
|
|
26334
26767
|
} catch {
|
|
26335
|
-
|
|
26768
|
+
try {
|
|
26769
|
+
const plain = html.replace(/<[^>]+>/g, "");
|
|
26770
|
+
const truncPlain = plain.length > 4e3 ? plain.slice(0, 3950) + "\n\n... (streaming)" : plain;
|
|
26771
|
+
await this.apiCall("editMessageText", {
|
|
26772
|
+
chat_id: chatId,
|
|
26773
|
+
message_id: messageId,
|
|
26774
|
+
text: truncPlain
|
|
26775
|
+
});
|
|
26776
|
+
} catch {
|
|
26777
|
+
}
|
|
26336
26778
|
}
|
|
26337
26779
|
}
|
|
26338
26780
|
// ── Sub-agent spawning ────────────────────────────────────────────────
|
|
26339
26781
|
/**
|
|
26340
26782
|
* Handle an incoming Telegram message by spawning a sub-agent.
|
|
26341
|
-
*
|
|
26783
|
+
* Tool access is determined by resolveToolContext() based on
|
|
26784
|
+
* admin status AND chat type (DM vs group).
|
|
26342
26785
|
*/
|
|
26343
26786
|
async handleMessageWithSubAgent(msg) {
|
|
26344
26787
|
if (!this.agentConfig || !this.repoRoot) {
|
|
@@ -26346,36 +26789,56 @@ Keep responses concise for Telegram but don't withhold information from the admi
|
|
|
26346
26789
|
return;
|
|
26347
26790
|
}
|
|
26348
26791
|
const isAdmin = this.isAdminUser(msg);
|
|
26792
|
+
const toolContext = this.resolveToolContext(msg, isAdmin);
|
|
26793
|
+
const isAdminDM = toolContext === "telegram-admin-dm";
|
|
26349
26794
|
const existing = this.subAgents.get(msg.chatId);
|
|
26350
26795
|
if (existing && !existing.aborted) {
|
|
26351
26796
|
existing.runner.injectUserMessage(msg.text);
|
|
26352
26797
|
renderTelegramSubAgentEvent(msg.username, "mid-conversation steering injected");
|
|
26353
26798
|
return;
|
|
26354
26799
|
}
|
|
26355
|
-
const draftId = Date.now();
|
|
26356
26800
|
const subAgent = {
|
|
26357
26801
|
chatId: msg.chatId,
|
|
26358
26802
|
username: msg.username,
|
|
26359
26803
|
runner: null,
|
|
26360
|
-
// set below
|
|
26361
26804
|
typingInterval: null,
|
|
26362
|
-
|
|
26805
|
+
liveMessageId: null,
|
|
26363
26806
|
accumulated: "",
|
|
26364
|
-
|
|
26365
|
-
|
|
26807
|
+
intermediateLines: [],
|
|
26808
|
+
lastEditMs: 0,
|
|
26809
|
+
aborted: false,
|
|
26810
|
+
toolContext
|
|
26366
26811
|
};
|
|
26367
26812
|
this.subAgents.set(msg.chatId, subAgent);
|
|
26368
26813
|
this.state.activeSubAgents = this.subAgents.size;
|
|
26369
26814
|
subAgent.typingInterval = this.startTypingIndicator(msg.chatId);
|
|
26370
|
-
renderTelegramSubAgentStart(msg.username, msg.text,
|
|
26815
|
+
renderTelegramSubAgentStart(msg.username, msg.text, isAdminDM);
|
|
26371
26816
|
try {
|
|
26372
|
-
|
|
26817
|
+
if (isAdminDM) {
|
|
26818
|
+
const msgId = await this.sendLiveMessage(msg.chatId, "<i>\u23F3 Processing...</i>");
|
|
26819
|
+
subAgent.liveMessageId = msgId;
|
|
26820
|
+
}
|
|
26821
|
+
let mediaContext = "";
|
|
26822
|
+
if (msg.media) {
|
|
26823
|
+
mediaContext = await this.processMedia(msg);
|
|
26824
|
+
}
|
|
26825
|
+
const result = await this.runSubAgent(msg, subAgent, mediaContext);
|
|
26373
26826
|
if (subAgent.typingInterval) {
|
|
26374
26827
|
clearInterval(subAgent.typingInterval);
|
|
26375
26828
|
subAgent.typingInterval = null;
|
|
26376
26829
|
}
|
|
26830
|
+
if (result === "no_reply" || result === "") {
|
|
26831
|
+
renderTelegramSubAgentEvent(msg.username, "discretion: skipped reply");
|
|
26832
|
+
return;
|
|
26833
|
+
}
|
|
26377
26834
|
const finalText = result || "I couldn't generate a response. Please try again.";
|
|
26378
|
-
|
|
26835
|
+
const finalHtml = convertMarkdownToTelegramHTML(finalText);
|
|
26836
|
+
if (isAdminDM && subAgent.liveMessageId) {
|
|
26837
|
+
const intermediateBlock = subAgent.intermediateLines.length > 0 ? subAgent.intermediateLines.join("\n") + "\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n" : "";
|
|
26838
|
+
await this.editLiveMessage(msg.chatId, subAgent.liveMessageId, intermediateBlock + finalHtml);
|
|
26839
|
+
} else {
|
|
26840
|
+
await this.sendMessageHTML(msg.chatId, finalHtml, msg.chatType !== "private" ? msg.messageId : void 0);
|
|
26841
|
+
}
|
|
26379
26842
|
renderTelegramSubAgentComplete(msg.username, finalText);
|
|
26380
26843
|
} catch (err) {
|
|
26381
26844
|
if (subAgent.typingInterval) {
|
|
@@ -26384,73 +26847,118 @@ Keep responses concise for Telegram but don't withhold information from the admi
|
|
|
26384
26847
|
}
|
|
26385
26848
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
26386
26849
|
renderTelegramSubAgentError(msg.username, errMsg);
|
|
26387
|
-
|
|
26388
|
-
|
|
26850
|
+
if (isAdminDM && subAgent.liveMessageId) {
|
|
26851
|
+
await this.editLiveMessage(msg.chatId, subAgent.liveMessageId, `\u274C Error: ${errMsg}`).catch(() => {
|
|
26852
|
+
});
|
|
26853
|
+
} else {
|
|
26854
|
+
await this.sendMessage(msg.chatId, "Sorry, I encountered an error processing your message. Please try again.").catch(() => {
|
|
26855
|
+
});
|
|
26856
|
+
}
|
|
26389
26857
|
} finally {
|
|
26390
26858
|
this.subAgents.delete(msg.chatId);
|
|
26391
26859
|
this.state.activeSubAgents = this.subAgents.size;
|
|
26392
26860
|
}
|
|
26393
26861
|
}
|
|
26394
26862
|
/** Run a sub-agent for a Telegram message */
|
|
26395
|
-
async runSubAgent(msg,
|
|
26863
|
+
async runSubAgent(msg, subAgent, mediaContext = "") {
|
|
26396
26864
|
const config = this.agentConfig;
|
|
26397
26865
|
const repoRoot = this.repoRoot;
|
|
26398
26866
|
const modelTier = getModelTier(config.model);
|
|
26867
|
+
const ctx = subAgent.toolContext;
|
|
26868
|
+
const isAdminDM = ctx === "telegram-admin-dm";
|
|
26869
|
+
const isAdminGroup = ctx === "telegram-admin-group";
|
|
26870
|
+
const isGroup = msg.chatType !== "private";
|
|
26399
26871
|
const backend = new OllamaAgenticBackend(config.backendUrl, config.model, config.apiKey);
|
|
26400
26872
|
const runner = new AgenticRunner(backend, {
|
|
26401
|
-
maxTurns:
|
|
26402
|
-
|
|
26403
|
-
maxTokens: isAdmin ? 8192 : 2048,
|
|
26404
|
-
// Admin: detailed. Public: concise
|
|
26873
|
+
maxTurns: isAdminDM ? 30 : isAdminGroup ? 12 : 8,
|
|
26874
|
+
maxTokens: isAdminDM ? 8192 : 2048,
|
|
26405
26875
|
temperature: 0.3,
|
|
26406
26876
|
requestTimeoutMs: config.timeoutMs,
|
|
26407
|
-
taskTimeoutMs:
|
|
26877
|
+
taskTimeoutMs: isAdminDM ? config.timeoutMs * 3 : config.timeoutMs,
|
|
26408
26878
|
compactionThreshold: modelTier === "small" ? 8e3 : 16e3,
|
|
26409
26879
|
modelTier,
|
|
26410
26880
|
streamEnabled: true
|
|
26411
26881
|
});
|
|
26412
26882
|
subAgent.runner = runner;
|
|
26413
|
-
const tools = this.buildSubAgentTools(
|
|
26883
|
+
const tools = this.buildSubAgentTools(ctx, repoRoot, msg.chatId);
|
|
26414
26884
|
runner.registerTools(tools);
|
|
26415
26885
|
runner.onEvent((event) => {
|
|
26416
26886
|
if (subAgent.aborted)
|
|
26417
26887
|
return;
|
|
26418
26888
|
this.onSubAgentEvent?.(msg.chatId, msg.username, event);
|
|
26419
|
-
if (
|
|
26420
|
-
|
|
26889
|
+
if (isAdminDM && subAgent.liveMessageId) {
|
|
26890
|
+
if (event.type === "stream_token" && event.streamKind === "content" && event.content) {
|
|
26891
|
+
subAgent.accumulated += event.content;
|
|
26892
|
+
}
|
|
26893
|
+
const intermediateLine = formatIntermediateState(event);
|
|
26894
|
+
if (intermediateLine) {
|
|
26895
|
+
subAgent.intermediateLines.push(intermediateLine);
|
|
26896
|
+
}
|
|
26421
26897
|
const now = Date.now();
|
|
26422
|
-
if (now - subAgent.
|
|
26423
|
-
subAgent.
|
|
26424
|
-
|
|
26425
|
-
|
|
26898
|
+
if (now - subAgent.lastEditMs > 1e3) {
|
|
26899
|
+
const hasContent = subAgent.accumulated.length > 10 || subAgent.intermediateLines.length > 0;
|
|
26900
|
+
if (hasContent) {
|
|
26901
|
+
subAgent.lastEditMs = now;
|
|
26902
|
+
const intermediateBlock = subAgent.intermediateLines.length > 0 ? subAgent.intermediateLines.join("\n") + "\n\n\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n" : "";
|
|
26903
|
+
const contentPreview = subAgent.accumulated ? convertMarkdownToTelegramHTML(subAgent.accumulated) + "\n<i>\u258C</i>" : "<i>\u23F3 Working...</i>";
|
|
26904
|
+
this.editLiveMessage(msg.chatId, subAgent.liveMessageId, intermediateBlock + contentPreview).catch(() => {
|
|
26905
|
+
});
|
|
26906
|
+
}
|
|
26426
26907
|
}
|
|
26427
26908
|
}
|
|
26428
26909
|
});
|
|
26429
|
-
|
|
26430
|
-
|
|
26431
|
-
|
|
26432
|
-
|
|
26433
|
-
|
|
26910
|
+
let systemPrompt;
|
|
26911
|
+
if (isAdminDM) {
|
|
26912
|
+
systemPrompt = ADMIN_DM_PROMPT;
|
|
26913
|
+
} else if (isAdminGroup) {
|
|
26914
|
+
systemPrompt = ADMIN_GROUP_PROMPT;
|
|
26915
|
+
} else {
|
|
26916
|
+
systemPrompt = TELEGRAM_SAFETY_PROMPT;
|
|
26917
|
+
}
|
|
26918
|
+
const discretionPrompt = isGroup ? `
|
|
26919
|
+
|
|
26920
|
+
${GROUP_REPLY_DISCRETION_PROMPT}` : "";
|
|
26921
|
+
const chatLabel = isGroup ? ` in group "${msg.chatTitle || "unknown"}"` : "";
|
|
26922
|
+
let userPrompt;
|
|
26923
|
+
if (isAdminDM) {
|
|
26924
|
+
userPrompt = `Telegram message from admin @${msg.username}:
|
|
26925
|
+
${msg.text}`;
|
|
26926
|
+
} else {
|
|
26927
|
+
const toolHint = "You have access to isolated per-chat memory (memory_write, memory_read, memory_search) scoped to this conversation. You can remember facts about this user and retrieve them later. You also have web_search and web_fetch to look up information.";
|
|
26928
|
+
userPrompt = `${systemPrompt}${discretionPrompt}
|
|
26929
|
+
|
|
26930
|
+
${toolHint}
|
|
26434
26931
|
|
|
26435
26932
|
---
|
|
26436
26933
|
|
|
26437
|
-
Telegram message from @${msg.username}:
|
|
26934
|
+
Telegram message from @${msg.username}${chatLabel}:
|
|
26438
26935
|
${msg.text}
|
|
26439
26936
|
|
|
26440
|
-
Respond concisely and safely
|
|
26441
|
-
|
|
26442
|
-
|
|
26937
|
+
Respond concisely and safely.`;
|
|
26938
|
+
}
|
|
26939
|
+
if (mediaContext) {
|
|
26940
|
+
userPrompt += `
|
|
26941
|
+
|
|
26942
|
+
[Media attached \u2014 processed content below]
|
|
26943
|
+
${mediaContext}`;
|
|
26944
|
+
}
|
|
26945
|
+
const systemCtx = isAdminDM ? `Working directory: ${repoRoot}
|
|
26946
|
+
Telegram admin: @${msg.username}` : `Telegram ${isGroup ? "group" : "public"} chat. Respond concisely. Safety filter: ACTIVE.`;
|
|
26947
|
+
const result = await runner.run(userPrompt, systemCtx);
|
|
26443
26948
|
return result.summary || subAgent.accumulated || "";
|
|
26444
26949
|
}
|
|
26445
|
-
/**
|
|
26446
|
-
|
|
26950
|
+
/**
|
|
26951
|
+
* Build tool set based on tool context, filtered through the policy system.
|
|
26952
|
+
* All possible tools are instantiated, then applyToolPolicy filters them.
|
|
26953
|
+
*/
|
|
26954
|
+
buildSubAgentTools(context, repoRoot, chatId) {
|
|
26447
26955
|
const taskComplete = {
|
|
26448
26956
|
name: "task_complete",
|
|
26449
|
-
description: "Signal that your response is ready to send to the Telegram user.",
|
|
26957
|
+
description: "Signal that your response is ready to send to the Telegram user. Use summary 'no_reply' to silently skip responding.",
|
|
26450
26958
|
parameters: {
|
|
26451
26959
|
type: "object",
|
|
26452
26960
|
properties: {
|
|
26453
|
-
summary: { type: "string", description: "The response to send to the Telegram user" }
|
|
26961
|
+
summary: { type: "string", description: "The response to send to the Telegram user, or 'no_reply' to skip" }
|
|
26454
26962
|
},
|
|
26455
26963
|
required: ["summary"]
|
|
26456
26964
|
},
|
|
@@ -26458,57 +26966,195 @@ Telegram admin: @${msg.username}` : `Telegram public chat. Respond concisely. Sa
|
|
|
26458
26966
|
return { success: true, output: args["summary"] || "Done." };
|
|
26459
26967
|
}
|
|
26460
26968
|
};
|
|
26461
|
-
|
|
26462
|
-
|
|
26463
|
-
|
|
26464
|
-
|
|
26465
|
-
|
|
26466
|
-
|
|
26467
|
-
|
|
26468
|
-
new MemoryWriteTool(repoRoot),
|
|
26469
|
-
new MemorySearchTool(repoRoot),
|
|
26470
|
-
new WebFetchTool(),
|
|
26471
|
-
new WebSearchTool()
|
|
26472
|
-
];
|
|
26473
|
-
return [...tools2.map(adaptTool4), taskComplete];
|
|
26474
|
-
}
|
|
26475
|
-
const tools = [
|
|
26969
|
+
const allTools = [
|
|
26970
|
+
// File tools
|
|
26971
|
+
new FileReadTool(repoRoot),
|
|
26972
|
+
new GrepSearchTool(repoRoot),
|
|
26973
|
+
new GlobFindTool(repoRoot),
|
|
26974
|
+
new ListDirectoryTool(repoRoot),
|
|
26975
|
+
// Memory tools
|
|
26476
26976
|
new MemoryReadTool(repoRoot),
|
|
26477
|
-
|
|
26977
|
+
new MemoryWriteTool(repoRoot),
|
|
26478
26978
|
new MemorySearchTool(repoRoot),
|
|
26479
|
-
//
|
|
26979
|
+
// Web tools
|
|
26980
|
+
new WebFetchTool(),
|
|
26480
26981
|
new WebSearchTool(),
|
|
26481
|
-
|
|
26482
|
-
|
|
26483
|
-
|
|
26982
|
+
new WebCrawlTool(repoRoot),
|
|
26983
|
+
// Vision/OCR tools
|
|
26984
|
+
new ImageReadTool(repoRoot),
|
|
26985
|
+
new OCRTool(repoRoot),
|
|
26986
|
+
new VisionTool(repoRoot),
|
|
26987
|
+
new OcrPdfTool(repoRoot),
|
|
26988
|
+
new PdfToTextTool(repoRoot),
|
|
26989
|
+
// Transcription tools
|
|
26990
|
+
new TranscribeFileTool(repoRoot),
|
|
26991
|
+
new TranscribeUrlTool(repoRoot)
|
|
26484
26992
|
];
|
|
26485
|
-
|
|
26993
|
+
let adaptedTools = allTools.map(adaptTool4);
|
|
26994
|
+
adaptedTools = applyToolPolicy(adaptedTools, context, this.toolPolicyConfig);
|
|
26995
|
+
if (context !== "telegram-admin-dm") {
|
|
26996
|
+
const memWriteIdx = adaptedTools.findIndex((t) => t.name === "memory_write");
|
|
26997
|
+
if (memWriteIdx >= 0) {
|
|
26998
|
+
const original = adaptedTools[memWriteIdx];
|
|
26999
|
+
const scopedChatId = chatId ?? 0;
|
|
27000
|
+
adaptedTools[memWriteIdx] = {
|
|
27001
|
+
...original,
|
|
27002
|
+
description: "Store a fact in this chat's isolated memory. Topics are automatically scoped to this conversation.",
|
|
27003
|
+
async execute(args) {
|
|
27004
|
+
const userTopic = args["topic"] || "general";
|
|
27005
|
+
args["topic"] = `telegram-${scopedChatId}-${userTopic}`;
|
|
27006
|
+
return original.execute(args);
|
|
27007
|
+
}
|
|
27008
|
+
};
|
|
27009
|
+
}
|
|
27010
|
+
}
|
|
27011
|
+
return [...adaptedTools, taskComplete];
|
|
26486
27012
|
}
|
|
26487
|
-
/** Check if a message is from the admin user */
|
|
27013
|
+
/** Check if a message is from the admin user (uses fromUserId, NOT chatId) */
|
|
26488
27014
|
isAdminUser(msg) {
|
|
26489
27015
|
if (!this.adminUserId)
|
|
26490
27016
|
return false;
|
|
26491
|
-
const fromId = String(msg.
|
|
27017
|
+
const fromId = String(msg.fromUserId);
|
|
26492
27018
|
return fromId === this.adminUserId || msg.username === this.adminUserId;
|
|
26493
27019
|
}
|
|
27020
|
+
/**
|
|
27021
|
+
* Resolve the tool context for a message based on admin status and chat type.
|
|
27022
|
+
* Admin in private DM → telegram-admin-dm (full tools)
|
|
27023
|
+
* Admin in group/supergroup → telegram-admin-group (restricted!)
|
|
27024
|
+
* Anyone else → telegram-public (minimal tools)
|
|
27025
|
+
*/
|
|
27026
|
+
resolveToolContext(msg, isAdmin) {
|
|
27027
|
+
if (!isAdmin)
|
|
27028
|
+
return "telegram-public";
|
|
27029
|
+
if (msg.chatType === "private")
|
|
27030
|
+
return "telegram-admin-dm";
|
|
27031
|
+
return "telegram-admin-group";
|
|
27032
|
+
}
|
|
27033
|
+
// ── Media handling ──────────────────────────────────────────────────
|
|
27034
|
+
/**
|
|
27035
|
+
* Download a Telegram file by file_id and save to the media cache.
|
|
27036
|
+
* Returns the local file path.
|
|
27037
|
+
*/
|
|
27038
|
+
async downloadTelegramFile(fileId, extension) {
|
|
27039
|
+
try {
|
|
27040
|
+
const fileInfo = await this.apiCall("getFile", { file_id: fileId });
|
|
27041
|
+
const filePath = fileInfo.result?.file_path;
|
|
27042
|
+
if (!filePath)
|
|
27043
|
+
return null;
|
|
27044
|
+
const downloadUrl = `https://api.telegram.org/file/bot${this.botToken}/${filePath}`;
|
|
27045
|
+
const res = await fetch(downloadUrl);
|
|
27046
|
+
if (!res.ok)
|
|
27047
|
+
return null;
|
|
27048
|
+
const buffer = Buffer.from(await res.arrayBuffer());
|
|
27049
|
+
const fileName = `${Date.now()}-${fileId.slice(0, 8)}${extension}`;
|
|
27050
|
+
const localPath = join39(this.mediaCacheDir, fileName);
|
|
27051
|
+
await writeFileAsync(localPath, buffer);
|
|
27052
|
+
return localPath;
|
|
27053
|
+
} catch {
|
|
27054
|
+
return null;
|
|
27055
|
+
}
|
|
27056
|
+
}
|
|
27057
|
+
/**
|
|
27058
|
+
* Process media from a Telegram message.
|
|
27059
|
+
* Downloads the file, runs it through the appropriate pipeline,
|
|
27060
|
+
* caches it, and returns a text description for the agent.
|
|
27061
|
+
*/
|
|
27062
|
+
async processMedia(msg) {
|
|
27063
|
+
if (!msg.media)
|
|
27064
|
+
return "";
|
|
27065
|
+
const { type, fileId, fileUniqueId, mimeType, caption } = msg.media;
|
|
27066
|
+
let ext = ".bin";
|
|
27067
|
+
if (type === "photo")
|
|
27068
|
+
ext = ".jpg";
|
|
27069
|
+
else if (type === "audio" || type === "voice")
|
|
27070
|
+
ext = ".ogg";
|
|
27071
|
+
else if (type === "video" || type === "video_note")
|
|
27072
|
+
ext = ".mp4";
|
|
27073
|
+
else if (msg.media.fileName) {
|
|
27074
|
+
const dotIdx = msg.media.fileName.lastIndexOf(".");
|
|
27075
|
+
if (dotIdx >= 0)
|
|
27076
|
+
ext = msg.media.fileName.slice(dotIdx);
|
|
27077
|
+
}
|
|
27078
|
+
const localPath = await this.downloadTelegramFile(fileId, ext);
|
|
27079
|
+
if (!localPath)
|
|
27080
|
+
return `[Media: ${type} \u2014 failed to download]`;
|
|
27081
|
+
const cacheEntry = {
|
|
27082
|
+
localPath,
|
|
27083
|
+
fileId,
|
|
27084
|
+
chatId: msg.chatId,
|
|
27085
|
+
username: msg.username,
|
|
27086
|
+
mediaType: type,
|
|
27087
|
+
mimeType,
|
|
27088
|
+
cachedAt: Date.now()
|
|
27089
|
+
};
|
|
27090
|
+
this.mediaCache.set(fileUniqueId, cacheEntry);
|
|
27091
|
+
if (!this.mediaMetadata.has(msg.chatId)) {
|
|
27092
|
+
this.mediaMetadata.set(msg.chatId, []);
|
|
27093
|
+
}
|
|
27094
|
+
this.mediaMetadata.get(msg.chatId).push({
|
|
27095
|
+
timestamp: Date.now(),
|
|
27096
|
+
type,
|
|
27097
|
+
mimeType,
|
|
27098
|
+
caption,
|
|
27099
|
+
username: msg.username
|
|
27100
|
+
});
|
|
27101
|
+
let description = `[${type}${caption ? `: ${caption}` : ""}]`;
|
|
27102
|
+
if (type === "photo") {
|
|
27103
|
+
description = `[Photo received and saved to ${localPath}${caption ? ` \u2014 caption: "${caption}"` : ""}. You can use image_read or vision tools to analyze it if available.]`;
|
|
27104
|
+
}
|
|
27105
|
+
if (type === "audio" || type === "voice") {
|
|
27106
|
+
description = `[Audio/voice message received and saved to ${localPath}${caption ? ` \u2014 caption: "${caption}"` : ""}. You can use transcribe_file to transcribe it if available.]`;
|
|
27107
|
+
}
|
|
27108
|
+
if (type === "video" || type === "video_note") {
|
|
27109
|
+
description = `[Video received and saved to ${localPath}${caption ? ` \u2014 caption: "${caption}"` : ""}.]`;
|
|
27110
|
+
}
|
|
27111
|
+
if (type === "document") {
|
|
27112
|
+
description = `[Document received: ${msg.media.fileName || "unnamed"}${mimeType ? ` (${mimeType})` : ""}, saved to ${localPath}${caption ? ` \u2014 caption: "${caption}"` : ""}.]`;
|
|
27113
|
+
}
|
|
27114
|
+
cacheEntry.extractedContent = description;
|
|
27115
|
+
return description;
|
|
27116
|
+
}
|
|
27117
|
+
/** Clean up expired media cache entries (older than 30 minutes) */
|
|
27118
|
+
cleanupMediaCache() {
|
|
27119
|
+
const now = Date.now();
|
|
27120
|
+
for (const [key, entry] of this.mediaCache) {
|
|
27121
|
+
if (now - entry.cachedAt > MEDIA_CACHE_TTL_MS) {
|
|
27122
|
+
try {
|
|
27123
|
+
unlinkSync6(entry.localPath);
|
|
27124
|
+
} catch {
|
|
27125
|
+
}
|
|
27126
|
+
this.mediaCache.delete(key);
|
|
27127
|
+
}
|
|
27128
|
+
}
|
|
27129
|
+
}
|
|
26494
27130
|
// ── Message sending ───────────────────────────────────────────────────
|
|
26495
|
-
/** Send a response back to a Telegram chat */
|
|
27131
|
+
/** Send a response back to a Telegram chat (Markdown → HTML conversion) */
|
|
26496
27132
|
async sendMessage(chatId, text) {
|
|
26497
|
-
const
|
|
27133
|
+
const html = convertMarkdownToTelegramHTML(text);
|
|
27134
|
+
return this.sendMessageHTML(chatId, html);
|
|
27135
|
+
}
|
|
27136
|
+
/** Send an HTML-formatted message to a Telegram chat */
|
|
27137
|
+
async sendMessageHTML(chatId, html, replyToMessageId) {
|
|
27138
|
+
const truncated = html.length > 4e3 ? html.slice(0, 3950) + "\n\n<i>... (truncated)</i>" : html;
|
|
27139
|
+
const body = {
|
|
27140
|
+
chat_id: chatId,
|
|
27141
|
+
text: truncated,
|
|
27142
|
+
parse_mode: "HTML"
|
|
27143
|
+
};
|
|
27144
|
+
if (replyToMessageId)
|
|
27145
|
+
body["reply_to_message_id"] = replyToMessageId;
|
|
26498
27146
|
try {
|
|
26499
|
-
const result = await this.apiCall("sendMessage",
|
|
26500
|
-
chat_id: chatId,
|
|
26501
|
-
text: truncated,
|
|
26502
|
-
parse_mode: "Markdown"
|
|
26503
|
-
});
|
|
27147
|
+
const result = await this.apiCall("sendMessage", body);
|
|
26504
27148
|
this.state.messagesSent++;
|
|
26505
27149
|
return result.result?.message_id ?? null;
|
|
26506
27150
|
} catch {
|
|
27151
|
+
const plain = html.replace(/<[^>]+>/g, "");
|
|
27152
|
+
const truncPlain = plain.length > 4e3 ? plain.slice(0, 3950) + "\n\n... (truncated)" : plain;
|
|
27153
|
+
const fallbackBody = { chat_id: chatId, text: truncPlain };
|
|
27154
|
+
if (replyToMessageId)
|
|
27155
|
+
fallbackBody["reply_to_message_id"] = replyToMessageId;
|
|
26507
27156
|
try {
|
|
26508
|
-
const result = await this.apiCall("sendMessage",
|
|
26509
|
-
chat_id: chatId,
|
|
26510
|
-
text: truncated
|
|
26511
|
-
});
|
|
27157
|
+
const result = await this.apiCall("sendMessage", fallbackBody);
|
|
26512
27158
|
this.state.messagesSent++;
|
|
26513
27159
|
return result.result?.message_id ?? null;
|
|
26514
27160
|
} catch (err) {
|
|
@@ -26530,29 +27176,104 @@ Telegram admin: @${msg.username}` : `Telegram public chat. Respond concisely. Sa
|
|
|
26530
27176
|
if (result.ok && Array.isArray(result.result)) {
|
|
26531
27177
|
for (const update of result.result) {
|
|
26532
27178
|
this.lastUpdateId = update.update_id;
|
|
26533
|
-
|
|
26534
|
-
|
|
26535
|
-
|
|
26536
|
-
|
|
26537
|
-
|
|
26538
|
-
|
|
26539
|
-
|
|
26540
|
-
|
|
26541
|
-
|
|
26542
|
-
|
|
26543
|
-
|
|
26544
|
-
|
|
26545
|
-
|
|
26546
|
-
|
|
26547
|
-
|
|
27179
|
+
const message = update.message;
|
|
27180
|
+
if (!message)
|
|
27181
|
+
continue;
|
|
27182
|
+
const fromId = message.from?.id ?? 0;
|
|
27183
|
+
const fromUser = message.from?.username ?? "";
|
|
27184
|
+
const chatType = message.chat?.type ?? "private";
|
|
27185
|
+
const chatTitle = message.chat?.title;
|
|
27186
|
+
const isAdmin = this.adminUserId ? String(fromId) === this.adminUserId || fromUser === this.adminUserId : false;
|
|
27187
|
+
if (this.adminUserId && !this.agentConfig) {
|
|
27188
|
+
if (!isAdmin)
|
|
27189
|
+
continue;
|
|
27190
|
+
}
|
|
27191
|
+
let media;
|
|
27192
|
+
if (message.photo && message.photo.length > 0) {
|
|
27193
|
+
const photo = message.photo[message.photo.length - 1];
|
|
27194
|
+
media = {
|
|
27195
|
+
type: "photo",
|
|
27196
|
+
fileId: photo.file_id,
|
|
27197
|
+
fileUniqueId: photo.file_unique_id,
|
|
27198
|
+
fileSize: photo.file_size,
|
|
27199
|
+
caption: message.caption
|
|
26548
27200
|
};
|
|
26549
|
-
|
|
26550
|
-
|
|
26551
|
-
|
|
26552
|
-
|
|
26553
|
-
|
|
26554
|
-
|
|
26555
|
-
|
|
27201
|
+
} else if (message.audio) {
|
|
27202
|
+
media = {
|
|
27203
|
+
type: "audio",
|
|
27204
|
+
fileId: message.audio.file_id,
|
|
27205
|
+
fileUniqueId: message.audio.file_unique_id,
|
|
27206
|
+
mimeType: message.audio.mime_type,
|
|
27207
|
+
fileName: message.audio.file_name,
|
|
27208
|
+
fileSize: message.audio.file_size,
|
|
27209
|
+
duration: message.audio.duration,
|
|
27210
|
+
caption: message.caption
|
|
27211
|
+
};
|
|
27212
|
+
} else if (message.voice) {
|
|
27213
|
+
media = {
|
|
27214
|
+
type: "voice",
|
|
27215
|
+
fileId: message.voice.file_id,
|
|
27216
|
+
fileUniqueId: message.voice.file_unique_id,
|
|
27217
|
+
mimeType: message.voice.mime_type,
|
|
27218
|
+
fileSize: message.voice.file_size,
|
|
27219
|
+
duration: message.voice.duration,
|
|
27220
|
+
caption: message.caption
|
|
27221
|
+
};
|
|
27222
|
+
} else if (message.video) {
|
|
27223
|
+
media = {
|
|
27224
|
+
type: "video",
|
|
27225
|
+
fileId: message.video.file_id,
|
|
27226
|
+
fileUniqueId: message.video.file_unique_id,
|
|
27227
|
+
mimeType: message.video.mime_type,
|
|
27228
|
+
fileName: message.video.file_name,
|
|
27229
|
+
fileSize: message.video.file_size,
|
|
27230
|
+
duration: message.video.duration,
|
|
27231
|
+
caption: message.caption
|
|
27232
|
+
};
|
|
27233
|
+
} else if (message.video_note) {
|
|
27234
|
+
media = {
|
|
27235
|
+
type: "video_note",
|
|
27236
|
+
fileId: message.video_note.file_id,
|
|
27237
|
+
fileUniqueId: message.video_note.file_unique_id,
|
|
27238
|
+
fileSize: message.video_note.file_size,
|
|
27239
|
+
duration: message.video_note.duration,
|
|
27240
|
+
caption: message.caption
|
|
27241
|
+
};
|
|
27242
|
+
} else if (message.document) {
|
|
27243
|
+
media = {
|
|
27244
|
+
type: "document",
|
|
27245
|
+
fileId: message.document.file_id,
|
|
27246
|
+
fileUniqueId: message.document.file_unique_id,
|
|
27247
|
+
mimeType: message.document.mime_type,
|
|
27248
|
+
fileName: message.document.file_name,
|
|
27249
|
+
fileSize: message.document.file_size,
|
|
27250
|
+
caption: message.caption
|
|
27251
|
+
};
|
|
27252
|
+
}
|
|
27253
|
+
const hasText = !!message.text;
|
|
27254
|
+
const hasCaption = !!message.caption;
|
|
27255
|
+
const hasMedia = !!media;
|
|
27256
|
+
if (!hasText && !hasMedia)
|
|
27257
|
+
continue;
|
|
27258
|
+
this.state.messagesReceived++;
|
|
27259
|
+
const msg = {
|
|
27260
|
+
chatId: message.chat.id,
|
|
27261
|
+
text: message.text || message.caption || "",
|
|
27262
|
+
username: fromUser || "unknown",
|
|
27263
|
+
firstName: message.from?.first_name,
|
|
27264
|
+
messageId: message.message_id,
|
|
27265
|
+
fromUserId: fromId,
|
|
27266
|
+
chatType,
|
|
27267
|
+
chatTitle,
|
|
27268
|
+
media,
|
|
27269
|
+
replyToMessageId: message.reply_to_message?.message_id
|
|
27270
|
+
};
|
|
27271
|
+
if (this.agentConfig && this.repoRoot) {
|
|
27272
|
+
this.handleMessageWithSubAgent(msg).catch((err) => {
|
|
27273
|
+
renderWarning(`Telegram sub-agent error: ${err instanceof Error ? err.message : String(err)}`);
|
|
27274
|
+
});
|
|
27275
|
+
} else {
|
|
27276
|
+
this.onMessage(msg);
|
|
26556
27277
|
}
|
|
26557
27278
|
}
|
|
26558
27279
|
}
|
|
@@ -27174,6 +27895,20 @@ var init_status_bar = __esm({
|
|
|
27174
27895
|
if (this.active)
|
|
27175
27896
|
this.renderFooterPreserveCursor();
|
|
27176
27897
|
}
|
|
27898
|
+
/** Current emotional state — emoji + label from EmotionEngine */
|
|
27899
|
+
_emotion = null;
|
|
27900
|
+
/** Update emotional state indicator */
|
|
27901
|
+
setEmotion(emoji, label) {
|
|
27902
|
+
this._emotion = { emoji, label };
|
|
27903
|
+
if (this.active)
|
|
27904
|
+
this.renderFooterPreserveCursor();
|
|
27905
|
+
}
|
|
27906
|
+
/** Clear emotional state indicator */
|
|
27907
|
+
clearEmotion() {
|
|
27908
|
+
this._emotion = null;
|
|
27909
|
+
if (this.active)
|
|
27910
|
+
this.renderFooterPreserveCursor();
|
|
27911
|
+
}
|
|
27177
27912
|
/** Model capabilities — shown as emoji indicators on the status bar */
|
|
27178
27913
|
_caps = {
|
|
27179
27914
|
vision: false,
|
|
@@ -27388,6 +28123,10 @@ var init_status_bar = __esm({
|
|
|
27388
28123
|
const capStr = this._snr.capacityWarning ? c2.red(" !CAP") : "";
|
|
27389
28124
|
snrLabel = pipe + pastel2(183, "SNR: ") + snrColor(c2.bold(`${snrPct}%`)) + c2.dim(` d'${dPrimeStr}`) + capStr;
|
|
27390
28125
|
}
|
|
28126
|
+
let emotionLabel = "";
|
|
28127
|
+
if (this._emotion) {
|
|
28128
|
+
emotionLabel = pipe + this._emotion.emoji + " " + pastel2(183, this._emotion.label);
|
|
28129
|
+
}
|
|
27391
28130
|
let speedLabel = "";
|
|
27392
28131
|
if (this._speedTracker.hasData) {
|
|
27393
28132
|
const ratio = this._speedTracker.getSpeedRatio();
|
|
@@ -27411,7 +28150,7 @@ var init_status_bar = __esm({
|
|
|
27411
28150
|
if (this._caps.thinking)
|
|
27412
28151
|
capParts.push("\u{1F9E0}");
|
|
27413
28152
|
const capsLabel = capParts.length > 0 ? pipe + pastel2(183, capParts.join(" ")) : "";
|
|
27414
|
-
return ` ${tokInLabel}${pipe}${tokOutLabel}${pipe}${ctxLabel}${snrLabel}${speedLabel}${costLabel}${capsLabel}${recordingLabel}`;
|
|
28153
|
+
return ` ${tokInLabel}${pipe}${tokOutLabel}${pipe}${ctxLabel}${snrLabel}${emotionLabel}${speedLabel}${costLabel}${capsLabel}${recordingLabel}`;
|
|
27415
28154
|
}
|
|
27416
28155
|
// -------------------------------------------------------------------------
|
|
27417
28156
|
// Private
|
|
@@ -27690,11 +28429,11 @@ var init_status_bar = __esm({
|
|
|
27690
28429
|
import * as readline2 from "node:readline";
|
|
27691
28430
|
import { Writable } from "node:stream";
|
|
27692
28431
|
import { cwd } from "node:process";
|
|
27693
|
-
import { resolve as
|
|
28432
|
+
import { resolve as resolve25, join as join40, dirname as dirname12, extname as extname9 } from "node:path";
|
|
27694
28433
|
import { createRequire as createRequire2 } from "node:module";
|
|
27695
28434
|
import { fileURLToPath as fileURLToPath9 } from "node:url";
|
|
27696
|
-
import { readFileSync as readFileSync22, rmSync as rmSync2, readdirSync as
|
|
27697
|
-
import { existsSync as
|
|
28435
|
+
import { readFileSync as readFileSync22, rmSync as rmSync2, readdirSync as readdirSync14 } from "node:fs";
|
|
28436
|
+
import { existsSync as existsSync30 } from "node:fs";
|
|
27698
28437
|
function formatTimeAgo(date) {
|
|
27699
28438
|
const seconds = Math.floor((Date.now() - date.getTime()) / 1e3);
|
|
27700
28439
|
if (seconds < 60)
|
|
@@ -27713,12 +28452,12 @@ function getVersion() {
|
|
|
27713
28452
|
const require2 = createRequire2(import.meta.url);
|
|
27714
28453
|
const thisDir = dirname12(fileURLToPath9(import.meta.url));
|
|
27715
28454
|
const candidates = [
|
|
27716
|
-
|
|
27717
|
-
|
|
27718
|
-
|
|
28455
|
+
join40(thisDir, "..", "package.json"),
|
|
28456
|
+
join40(thisDir, "..", "..", "package.json"),
|
|
28457
|
+
join40(thisDir, "..", "..", "..", "package.json")
|
|
27719
28458
|
];
|
|
27720
28459
|
for (const pkgPath of candidates) {
|
|
27721
|
-
if (
|
|
28460
|
+
if (existsSync30(pkgPath)) {
|
|
27722
28461
|
const pkg = require2(pkgPath);
|
|
27723
28462
|
if (pkg.name === "open-agents-ai" || pkg.name === "@open-agents/cli") {
|
|
27724
28463
|
return pkg.version ?? "0.0.0";
|
|
@@ -27912,15 +28651,15 @@ Use task_status("${taskId}") or task_output("${taskId}") to check progress.`
|
|
|
27912
28651
|
function gatherMemorySnippets(root) {
|
|
27913
28652
|
const snippets = [];
|
|
27914
28653
|
const dirs = [
|
|
27915
|
-
|
|
27916
|
-
|
|
28654
|
+
join40(root, ".oa", "memory"),
|
|
28655
|
+
join40(root, ".open-agents", "memory")
|
|
27917
28656
|
];
|
|
27918
28657
|
for (const dir of dirs) {
|
|
27919
|
-
if (!
|
|
28658
|
+
if (!existsSync30(dir))
|
|
27920
28659
|
continue;
|
|
27921
28660
|
try {
|
|
27922
|
-
for (const f of
|
|
27923
|
-
const data = JSON.parse(readFileSync22(
|
|
28661
|
+
for (const f of readdirSync14(dir).filter((f2) => f2.endsWith(".json"))) {
|
|
28662
|
+
const data = JSON.parse(readFileSync22(join40(dir, f), "utf-8"));
|
|
27924
28663
|
for (const val of Object.values(data)) {
|
|
27925
28664
|
const v = typeof val === "object" && val !== null && "value" in val ? String(val.value) : String(val);
|
|
27926
28665
|
if (v.length > 10)
|
|
@@ -27989,7 +28728,7 @@ function formatDMNToolArgs(toolName, args) {
|
|
|
27989
28728
|
return "";
|
|
27990
28729
|
}
|
|
27991
28730
|
}
|
|
27992
|
-
function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar, sudoCallback, costTracker, onComplete, taskType, contextWindowSize, modelCaps, personality, deepContext, onCompaction) {
|
|
28731
|
+
function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce, statusBar, sudoCallback, costTracker, onComplete, taskType, contextWindowSize, modelCaps, personality, deepContext, onCompaction, emotionEngine) {
|
|
27993
28732
|
const voiceStyleMap = {
|
|
27994
28733
|
concise: 1,
|
|
27995
28734
|
balanced: 3,
|
|
@@ -28102,6 +28841,7 @@ ${entry.fullContent}`
|
|
|
28102
28841
|
}
|
|
28103
28842
|
};
|
|
28104
28843
|
runner.onEvent((event) => {
|
|
28844
|
+
emotionEngine?.appraise(event);
|
|
28105
28845
|
switch (event.type) {
|
|
28106
28846
|
case "tool_call":
|
|
28107
28847
|
toolSequence.push({
|
|
@@ -28239,7 +28979,11 @@ ${entry.fullContent}`
|
|
|
28239
28979
|
}
|
|
28240
28980
|
});
|
|
28241
28981
|
const sessionId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
28242
|
-
const
|
|
28982
|
+
const emotionContext = emotionEngine?.getEmotionForPrompt() ?? "";
|
|
28983
|
+
const systemContext = emotionContext ? `Working directory: ${repoRoot}
|
|
28984
|
+
|
|
28985
|
+
${emotionContext}` : `Working directory: ${repoRoot}`;
|
|
28986
|
+
const promise = runner.run(task, systemContext).then((result) => {
|
|
28243
28987
|
const tokens = { total: result.totalTokens, estimated: result.estimatedTokens };
|
|
28244
28988
|
contentWrite(() => {
|
|
28245
28989
|
if (result.completed) {
|
|
@@ -28320,7 +29064,7 @@ ${entry.fullContent}`
|
|
|
28320
29064
|
} };
|
|
28321
29065
|
}
|
|
28322
29066
|
async function startInteractive(config, repoPath) {
|
|
28323
|
-
const repoRoot =
|
|
29067
|
+
const repoRoot = resolve25(repoPath ?? cwd());
|
|
28324
29068
|
const resumeFlag = process.env.__OA_RESUMED ?? "";
|
|
28325
29069
|
const isResumed = resumeFlag !== "";
|
|
28326
29070
|
const hasTaskToResume = resumeFlag === "1";
|
|
@@ -28474,14 +29218,14 @@ async function startInteractive(config, repoPath) {
|
|
|
28474
29218
|
renderInfo(msg);
|
|
28475
29219
|
statusBar.endContentWrite();
|
|
28476
29220
|
}
|
|
28477
|
-
}, () => new Promise((
|
|
29221
|
+
}, () => new Promise((resolve28) => {
|
|
28478
29222
|
depSudoPromptPending = true;
|
|
28479
29223
|
depSudoResolver = (pw) => {
|
|
28480
29224
|
depSudoPromptPending = false;
|
|
28481
29225
|
depSudoResolver = null;
|
|
28482
29226
|
if (pw)
|
|
28483
29227
|
sessionSudoPassword = pw;
|
|
28484
|
-
|
|
29228
|
+
resolve28(pw);
|
|
28485
29229
|
};
|
|
28486
29230
|
if (statusBar?.isActive) {
|
|
28487
29231
|
statusBar.beginContentWrite();
|
|
@@ -28509,6 +29253,15 @@ async function startInteractive(config, repoPath) {
|
|
|
28509
29253
|
let blessEngine = null;
|
|
28510
29254
|
let dmnEngine = null;
|
|
28511
29255
|
const snrEngine = new SNREngine(config, repoRoot);
|
|
29256
|
+
const emotionEngine = new EmotionEngine({
|
|
29257
|
+
backendUrl: config.backendUrl,
|
|
29258
|
+
model: config.model,
|
|
29259
|
+
apiKey: config.apiKey,
|
|
29260
|
+
onEmotionUpdate: (state) => {
|
|
29261
|
+
statusBar.setEmotion(state.emoji, state.label);
|
|
29262
|
+
}
|
|
29263
|
+
// Admin outreach wired after telegram bridge is initialized (see telegramStart)
|
|
29264
|
+
});
|
|
28512
29265
|
let pendingSNREval = null;
|
|
28513
29266
|
function scheduleAgentSNREval(taskPrompt) {
|
|
28514
29267
|
if (pendingSNREval)
|
|
@@ -28960,6 +29713,16 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
28960
29713
|
writeContent(() => renderTelegramSubAgentEvent(username, event.content));
|
|
28961
29714
|
}
|
|
28962
29715
|
});
|
|
29716
|
+
if (adminId) {
|
|
29717
|
+
const bridge = telegramBridge;
|
|
29718
|
+
const adminChatId = parseInt(adminId, 10);
|
|
29719
|
+
if (!isNaN(adminChatId)) {
|
|
29720
|
+
emotionEngine.setAdminOutreach((message) => {
|
|
29721
|
+
bridge.sendMessage(adminChatId, message).catch(() => {
|
|
29722
|
+
});
|
|
29723
|
+
});
|
|
29724
|
+
}
|
|
29725
|
+
}
|
|
28963
29726
|
await telegramBridge.start();
|
|
28964
29727
|
writeContent(() => renderTelegramStart(telegramBridge.botUsername, adminId));
|
|
28965
29728
|
showPrompt();
|
|
@@ -29115,8 +29878,8 @@ Respond concisely and safely. Remember: you are talking to the general public.`;
|
|
|
29115
29878
|
return true;
|
|
29116
29879
|
},
|
|
29117
29880
|
destroyProject() {
|
|
29118
|
-
const oaPath =
|
|
29119
|
-
if (
|
|
29881
|
+
const oaPath = join40(repoRoot, OA_DIR);
|
|
29882
|
+
if (existsSync30(oaPath)) {
|
|
29120
29883
|
try {
|
|
29121
29884
|
rmSync2(oaPath, { recursive: true, force: true });
|
|
29122
29885
|
writeContent(() => renderInfo(`Removed ${OA_DIR}/ directory.`));
|
|
@@ -29375,7 +30138,7 @@ Execute this skill now. Follow the behavioral guidance above.`;
|
|
|
29375
30138
|
toolPatternStore: toolPatternStore ?? void 0
|
|
29376
30139
|
}, bruteForceEnabled, statusBar, handleSudoRequest, costTracker, (summary) => {
|
|
29377
30140
|
lastCompletedSummary = summary;
|
|
29378
|
-
}, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback());
|
|
30141
|
+
}, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback(), emotionEngine);
|
|
29379
30142
|
activeTask = task;
|
|
29380
30143
|
showPrompt();
|
|
29381
30144
|
await task.promise;
|
|
@@ -29390,12 +30153,12 @@ Execute this skill now. Follow the behavioral guidance above.`;
|
|
|
29390
30153
|
}
|
|
29391
30154
|
}
|
|
29392
30155
|
const cleanPath = input.replace(/^['"]|['"]$/g, "").trim();
|
|
29393
|
-
const isImage = isImagePath(cleanPath) &&
|
|
29394
|
-
const isMedia = !isImage && isTranscribablePath(cleanPath) &&
|
|
30156
|
+
const isImage = isImagePath(cleanPath) && existsSync30(resolve25(repoRoot, cleanPath));
|
|
30157
|
+
const isMedia = !isImage && isTranscribablePath(cleanPath) && existsSync30(resolve25(repoRoot, cleanPath));
|
|
29395
30158
|
if (activeTask) {
|
|
29396
30159
|
if (isImage) {
|
|
29397
30160
|
try {
|
|
29398
|
-
const imgPath =
|
|
30161
|
+
const imgPath = resolve25(repoRoot, cleanPath);
|
|
29399
30162
|
const imgBuffer = readFileSync22(imgPath);
|
|
29400
30163
|
const base64 = imgBuffer.toString("base64");
|
|
29401
30164
|
const ext = extname9(cleanPath).toLowerCase();
|
|
@@ -29409,7 +30172,7 @@ Execute this skill now. Follow the behavioral guidance above.`;
|
|
|
29409
30172
|
} else if (isMedia) {
|
|
29410
30173
|
writeContent(() => renderInfo(`Transcribing: ${cleanPath}...`));
|
|
29411
30174
|
const engine = getListenEngine();
|
|
29412
|
-
const result = await engine.transcribeFile(
|
|
30175
|
+
const result = await engine.transcribeFile(resolve25(repoRoot, cleanPath), repoRoot);
|
|
29413
30176
|
if (result) {
|
|
29414
30177
|
const transcript = `[Transcription of ${cleanPath}]
|
|
29415
30178
|
${result.text}`;
|
|
@@ -29442,7 +30205,7 @@ ${result.text}`;
|
|
|
29442
30205
|
if (isMedia && fullInput === input) {
|
|
29443
30206
|
writeContent(() => renderInfo(`Transcribing: ${cleanPath}...`));
|
|
29444
30207
|
const engine = getListenEngine();
|
|
29445
|
-
const result = await engine.transcribeFile(
|
|
30208
|
+
const result = await engine.transcribeFile(resolve25(repoRoot, cleanPath), repoRoot);
|
|
29446
30209
|
if (result) {
|
|
29447
30210
|
fullInput = `The user has provided an audio/video file: ${cleanPath}.
|
|
29448
30211
|
|
|
@@ -29495,7 +30258,7 @@ NEW TASK: ${fullInput}`;
|
|
|
29495
30258
|
toolPatternStore: toolPatternStore ?? void 0
|
|
29496
30259
|
}, bruteForceEnabled, statusBar, handleSudoRequest, costTracker, (summary) => {
|
|
29497
30260
|
lastCompletedSummary = summary;
|
|
29498
|
-
}, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback());
|
|
30261
|
+
}, currentTaskType, resolvedContextWindowSize, resolvedCaps, currentStyle, deepContextEnabled, compactionSNRCallback(), emotionEngine);
|
|
29499
30262
|
activeTask = task;
|
|
29500
30263
|
showPrompt();
|
|
29501
30264
|
await task.promise;
|
|
@@ -29660,7 +30423,7 @@ ${c2.dim("(Use /quit to exit)")}
|
|
|
29660
30423
|
});
|
|
29661
30424
|
}
|
|
29662
30425
|
async function runWithTUI(task, config, repoPath) {
|
|
29663
|
-
const repoRoot =
|
|
30426
|
+
const repoRoot = resolve25(repoPath ?? cwd());
|
|
29664
30427
|
const needsSetup = isFirstRun() || !await isModelAvailable(config);
|
|
29665
30428
|
if (needsSetup && config.backendType === "ollama") {
|
|
29666
30429
|
const setupModel = await runSetupWizard(config);
|
|
@@ -29732,6 +30495,7 @@ var init_interactive = __esm({
|
|
|
29732
30495
|
init_bless_engine();
|
|
29733
30496
|
init_dmn_engine();
|
|
29734
30497
|
init_snr_engine();
|
|
30498
|
+
init_emotion_engine();
|
|
29735
30499
|
init_telegram_bridge();
|
|
29736
30500
|
init_status_bar();
|
|
29737
30501
|
init_dist6();
|
|
@@ -29770,7 +30534,7 @@ import { glob } from "glob";
|
|
|
29770
30534
|
import ignore from "ignore";
|
|
29771
30535
|
import { readFile as readFile14, stat as stat4 } from "node:fs/promises";
|
|
29772
30536
|
import { createHash } from "node:crypto";
|
|
29773
|
-
import { join as
|
|
30537
|
+
import { join as join41, relative as relative3, extname as extname10, basename as basename15 } from "node:path";
|
|
29774
30538
|
var DEFAULT_EXCLUDE, LANGUAGE_MAP, CodebaseIndexer;
|
|
29775
30539
|
var init_codebase_indexer = __esm({
|
|
29776
30540
|
"packages/indexer/dist/codebase-indexer.js"() {
|
|
@@ -29814,7 +30578,7 @@ var init_codebase_indexer = __esm({
|
|
|
29814
30578
|
const ig = ignore.default();
|
|
29815
30579
|
if (this.config.respectGitignore) {
|
|
29816
30580
|
try {
|
|
29817
|
-
const gitignoreContent = await readFile14(
|
|
30581
|
+
const gitignoreContent = await readFile14(join41(this.config.rootDir, ".gitignore"), "utf-8");
|
|
29818
30582
|
ig.add(gitignoreContent);
|
|
29819
30583
|
} catch {
|
|
29820
30584
|
}
|
|
@@ -29829,7 +30593,7 @@ var init_codebase_indexer = __esm({
|
|
|
29829
30593
|
for (const relativePath of files) {
|
|
29830
30594
|
if (ig.ignores(relativePath))
|
|
29831
30595
|
continue;
|
|
29832
|
-
const fullPath =
|
|
30596
|
+
const fullPath = join41(this.config.rootDir, relativePath);
|
|
29833
30597
|
try {
|
|
29834
30598
|
const fileStat = await stat4(fullPath);
|
|
29835
30599
|
if (fileStat.size > this.config.maxFileSize)
|
|
@@ -29875,7 +30639,7 @@ var init_codebase_indexer = __esm({
|
|
|
29875
30639
|
if (!child) {
|
|
29876
30640
|
child = {
|
|
29877
30641
|
name: part,
|
|
29878
|
-
path:
|
|
30642
|
+
path: join41(current.path, part),
|
|
29879
30643
|
type: "directory",
|
|
29880
30644
|
children: []
|
|
29881
30645
|
};
|
|
@@ -29957,18 +30721,18 @@ var index_repo_exports = {};
|
|
|
29957
30721
|
__export(index_repo_exports, {
|
|
29958
30722
|
indexRepoCommand: () => indexRepoCommand
|
|
29959
30723
|
});
|
|
29960
|
-
import { resolve as
|
|
29961
|
-
import { existsSync as
|
|
30724
|
+
import { resolve as resolve26 } from "node:path";
|
|
30725
|
+
import { existsSync as existsSync31, statSync as statSync11 } from "node:fs";
|
|
29962
30726
|
import { cwd as cwd2 } from "node:process";
|
|
29963
30727
|
async function indexRepoCommand(opts, _config) {
|
|
29964
|
-
const repoRoot =
|
|
30728
|
+
const repoRoot = resolve26(opts.repoPath ?? cwd2());
|
|
29965
30729
|
printHeader("Index Repository");
|
|
29966
30730
|
printInfo(`Indexing: ${repoRoot}`);
|
|
29967
|
-
if (!
|
|
30731
|
+
if (!existsSync31(repoRoot)) {
|
|
29968
30732
|
printError(`Path does not exist: ${repoRoot}`);
|
|
29969
30733
|
process.exit(1);
|
|
29970
30734
|
}
|
|
29971
|
-
const stat5 =
|
|
30735
|
+
const stat5 = statSync11(repoRoot);
|
|
29972
30736
|
if (!stat5.isDirectory()) {
|
|
29973
30737
|
printError(`Path is not a directory: ${repoRoot}`);
|
|
29974
30738
|
process.exit(1);
|
|
@@ -30216,7 +30980,7 @@ var config_exports = {};
|
|
|
30216
30980
|
__export(config_exports, {
|
|
30217
30981
|
configCommand: () => configCommand
|
|
30218
30982
|
});
|
|
30219
|
-
import { join as
|
|
30983
|
+
import { join as join42, resolve as resolve27 } from "node:path";
|
|
30220
30984
|
import { homedir as homedir13 } from "node:os";
|
|
30221
30985
|
import { cwd as cwd3 } from "node:process";
|
|
30222
30986
|
function redactIfSensitive(key, value) {
|
|
@@ -30243,7 +31007,7 @@ async function configCommand(opts, config) {
|
|
|
30243
31007
|
return handleShow(opts, config);
|
|
30244
31008
|
}
|
|
30245
31009
|
function handleShow(opts, config) {
|
|
30246
|
-
const repoRoot =
|
|
31010
|
+
const repoRoot = resolve27(opts.repoPath ?? cwd3());
|
|
30247
31011
|
printHeader("Configuration");
|
|
30248
31012
|
printSection("Active Settings (merged)");
|
|
30249
31013
|
printKeyValue("backendUrl", config.backendUrl, 2);
|
|
@@ -30275,7 +31039,7 @@ function handleShow(opts, config) {
|
|
|
30275
31039
|
}
|
|
30276
31040
|
}
|
|
30277
31041
|
printSection("Config File");
|
|
30278
|
-
printInfo(`~/.open-agents/config.json (${
|
|
31042
|
+
printInfo(`~/.open-agents/config.json (${join42(homedir13(), ".open-agents", "config.json")})`);
|
|
30279
31043
|
printSection("Priority Chain");
|
|
30280
31044
|
printInfo(" 1. CLI flags (--model, --backend-url, etc.)");
|
|
30281
31045
|
printInfo(" 2. Project .oa/settings.json (--local)");
|
|
@@ -30308,13 +31072,13 @@ function handleSet(opts, _config) {
|
|
|
30308
31072
|
process.exit(1);
|
|
30309
31073
|
}
|
|
30310
31074
|
if (opts.local) {
|
|
30311
|
-
const repoRoot =
|
|
31075
|
+
const repoRoot = resolve27(opts.repoPath ?? cwd3());
|
|
30312
31076
|
try {
|
|
30313
31077
|
initOaDirectory(repoRoot);
|
|
30314
31078
|
const coerced = coerceForSettings(key, value);
|
|
30315
31079
|
saveProjectSettings(repoRoot, { [key]: coerced });
|
|
30316
31080
|
printSuccess(`Project override set: ${key} = ${redactIfSensitive(key, value)}`);
|
|
30317
|
-
printInfo(`Saved to ${
|
|
31081
|
+
printInfo(`Saved to ${join42(repoRoot, ".oa", "settings.json")}`);
|
|
30318
31082
|
printInfo("This override applies only when running in this workspace.");
|
|
30319
31083
|
} catch (err) {
|
|
30320
31084
|
printError(`Failed to save: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -30467,7 +31231,7 @@ async function serveVllm(opts, config) {
|
|
|
30467
31231
|
await runVllmServer(args, opts.verbose ?? false);
|
|
30468
31232
|
}
|
|
30469
31233
|
async function runVllmServer(args, verbose) {
|
|
30470
|
-
return new Promise((
|
|
31234
|
+
return new Promise((resolve28, reject) => {
|
|
30471
31235
|
const child = spawn11("python", args, {
|
|
30472
31236
|
stdio: verbose ? "inherit" : ["ignore", "pipe", "pipe"],
|
|
30473
31237
|
env: { ...process.env }
|
|
@@ -30502,10 +31266,10 @@ async function runVllmServer(args, verbose) {
|
|
|
30502
31266
|
child.once("exit", (code, signal) => {
|
|
30503
31267
|
if (signal) {
|
|
30504
31268
|
printInfo(`vLLM server stopped by signal ${signal}`);
|
|
30505
|
-
|
|
31269
|
+
resolve28();
|
|
30506
31270
|
} else if (code === 0) {
|
|
30507
31271
|
printSuccess("vLLM server exited cleanly");
|
|
30508
|
-
|
|
31272
|
+
resolve28();
|
|
30509
31273
|
} else {
|
|
30510
31274
|
printError(`vLLM server exited with code ${code}`);
|
|
30511
31275
|
reject(new Error(`vLLM exited with code ${code}`));
|
|
@@ -30533,8 +31297,8 @@ __export(eval_exports, {
|
|
|
30533
31297
|
evalCommand: () => evalCommand
|
|
30534
31298
|
});
|
|
30535
31299
|
import { tmpdir as tmpdir7 } from "node:os";
|
|
30536
|
-
import { mkdirSync as
|
|
30537
|
-
import { join as
|
|
31300
|
+
import { mkdirSync as mkdirSync15, writeFileSync as writeFileSync13 } from "node:fs";
|
|
31301
|
+
import { join as join43 } from "node:path";
|
|
30538
31302
|
async function evalCommand(opts, config) {
|
|
30539
31303
|
const suiteName = opts.suite ?? "basic";
|
|
30540
31304
|
const suite = SUITES[suiteName];
|
|
@@ -30655,9 +31419,9 @@ async function evalCommand(opts, config) {
|
|
|
30655
31419
|
process.exit(failed > 0 ? 1 : 0);
|
|
30656
31420
|
}
|
|
30657
31421
|
function createTempEvalRepo() {
|
|
30658
|
-
const dir =
|
|
30659
|
-
|
|
30660
|
-
writeFileSync13(
|
|
31422
|
+
const dir = join43(tmpdir7(), `open-agents-eval-${Date.now()}`);
|
|
31423
|
+
mkdirSync15(dir, { recursive: true });
|
|
31424
|
+
writeFileSync13(join43(dir, "package.json"), JSON.stringify({ name: "eval-repo", version: "0.0.0" }, null, 2) + "\n", "utf8");
|
|
30661
31425
|
return dir;
|
|
30662
31426
|
}
|
|
30663
31427
|
var BASIC_SUITE, FULL_SUITE, SUITES;
|
|
@@ -30717,7 +31481,7 @@ init_updater();
|
|
|
30717
31481
|
import { parseArgs as nodeParseArgs2 } from "node:util";
|
|
30718
31482
|
import { createRequire as createRequire3 } from "node:module";
|
|
30719
31483
|
import { fileURLToPath as fileURLToPath10 } from "node:url";
|
|
30720
|
-
import { dirname as dirname13, join as
|
|
31484
|
+
import { dirname as dirname13, join as join44 } from "node:path";
|
|
30721
31485
|
|
|
30722
31486
|
// packages/cli/dist/cli.js
|
|
30723
31487
|
import { createInterface } from "node:readline";
|
|
@@ -30824,7 +31588,7 @@ init_output();
|
|
|
30824
31588
|
function getVersion2() {
|
|
30825
31589
|
try {
|
|
30826
31590
|
const require2 = createRequire3(import.meta.url);
|
|
30827
|
-
const pkgPath =
|
|
31591
|
+
const pkgPath = join44(dirname13(fileURLToPath10(import.meta.url)), "..", "package.json");
|
|
30828
31592
|
const pkg = require2(pkgPath);
|
|
30829
31593
|
return pkg.version;
|
|
30830
31594
|
} catch {
|