open-agents-ai 0.187.224 → 0.187.226
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/dist/index.js +959 -490
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -287697,6 +287697,13 @@ var init_braille_spinner = __esm({
|
|
|
287697
287697
|
});
|
|
287698
287698
|
|
|
287699
287699
|
// packages/cli/src/tui/system-metrics.ts
|
|
287700
|
+
var system_metrics_exports = {};
|
|
287701
|
+
__export(system_metrics_exports, {
|
|
287702
|
+
SystemMetricsCollector: () => SystemMetricsCollector,
|
|
287703
|
+
formatRate: () => formatRate,
|
|
287704
|
+
getInstantSnapshot: () => getInstantSnapshot,
|
|
287705
|
+
instantaneousCpuPct: () => instantaneousCpuPct
|
|
287706
|
+
});
|
|
287700
287707
|
import { loadavg as loadavg2, cpus as cpus3, totalmem as totalmem4, freemem as freemem3, platform as platform2 } from "node:os";
|
|
287701
287708
|
import { exec as exec3 } from "node:child_process";
|
|
287702
287709
|
import { readFile as readFile22 } from "node:fs/promises";
|
|
@@ -287824,14 +287831,39 @@ function getInstantSnapshot() {
|
|
|
287824
287831
|
network: { rxBytesPerSec: 0, txBytesPerSec: 0 }
|
|
287825
287832
|
};
|
|
287826
287833
|
}
|
|
287834
|
+
function readCpuTimes() {
|
|
287835
|
+
let idle = 0;
|
|
287836
|
+
let total = 0;
|
|
287837
|
+
for (const cpu of cpus3()) {
|
|
287838
|
+
const t2 = cpu.times;
|
|
287839
|
+
idle += t2.idle;
|
|
287840
|
+
total += t2.user + t2.nice + t2.sys + t2.idle + t2.irq;
|
|
287841
|
+
}
|
|
287842
|
+
return { idle, total };
|
|
287843
|
+
}
|
|
287844
|
+
function instantaneousCpuPct() {
|
|
287845
|
+
const cur = readCpuTimes();
|
|
287846
|
+
const prev = _cpuPrevSnapshot;
|
|
287847
|
+
_cpuPrevSnapshot = cur;
|
|
287848
|
+
if (!prev) return -1;
|
|
287849
|
+
const idleDelta = cur.idle - prev.idle;
|
|
287850
|
+
const totalDelta = cur.total - prev.total;
|
|
287851
|
+
if (totalDelta <= 0) return 0;
|
|
287852
|
+
const usage = 1 - idleDelta / totalDelta;
|
|
287853
|
+
return Math.max(0, Math.min(100, Math.round(usage * 100)));
|
|
287854
|
+
}
|
|
287827
287855
|
function collectCpuRam() {
|
|
287828
|
-
const [l1] = loadavg2();
|
|
287829
287856
|
const cores = cpus3().length;
|
|
287830
287857
|
const cpuModel = cpus3()[0]?.model ?? "";
|
|
287831
287858
|
const totalMem = totalmem4();
|
|
287832
287859
|
const usedMem = totalMem - freemem3();
|
|
287860
|
+
let cpuUtil = instantaneousCpuPct();
|
|
287861
|
+
if (cpuUtil < 0) {
|
|
287862
|
+
const [l1] = loadavg2();
|
|
287863
|
+
cpuUtil = Math.max(0, Math.min(100, Math.round(l1 / cores * 100)));
|
|
287864
|
+
}
|
|
287833
287865
|
return {
|
|
287834
|
-
cpuUtil
|
|
287866
|
+
cpuUtil,
|
|
287835
287867
|
cpuCores: cores,
|
|
287836
287868
|
cpuModel,
|
|
287837
287869
|
memUtil: Math.round(usedMem / totalMem * 100),
|
|
@@ -287863,12 +287895,13 @@ async function collectLocalMetrics() {
|
|
|
287863
287895
|
network
|
|
287864
287896
|
};
|
|
287865
287897
|
}
|
|
287866
|
-
var _lastNetSnapshot, _nvidiaSmiAvailable, SystemMetricsCollector;
|
|
287898
|
+
var _lastNetSnapshot, _nvidiaSmiAvailable, _cpuPrevSnapshot, SystemMetricsCollector;
|
|
287867
287899
|
var init_system_metrics = __esm({
|
|
287868
287900
|
"packages/cli/src/tui/system-metrics.ts"() {
|
|
287869
287901
|
"use strict";
|
|
287870
287902
|
_lastNetSnapshot = null;
|
|
287871
287903
|
_nvidiaSmiAvailable = null;
|
|
287904
|
+
_cpuPrevSnapshot = null;
|
|
287872
287905
|
SystemMetricsCollector = class {
|
|
287873
287906
|
_timer = null;
|
|
287874
287907
|
_source = "local";
|
|
@@ -313404,6 +313437,439 @@ var init_task_manager_singleton = __esm({
|
|
|
313404
313437
|
}
|
|
313405
313438
|
});
|
|
313406
313439
|
|
|
313440
|
+
// packages/cli/src/api/chat-session.ts
|
|
313441
|
+
var chat_session_exports = {};
|
|
313442
|
+
__export(chat_session_exports, {
|
|
313443
|
+
addAssistantMessage: () => addAssistantMessage,
|
|
313444
|
+
addCheckinMessage: () => addCheckinMessage,
|
|
313445
|
+
addToolCallMessage: () => addToolCallMessage,
|
|
313446
|
+
addToolResultMessage: () => addToolResultMessage,
|
|
313447
|
+
addTriageResponseMessage: () => addTriageResponseMessage,
|
|
313448
|
+
addUserMessage: () => addUserMessage,
|
|
313449
|
+
appendCheckin: () => appendCheckin,
|
|
313450
|
+
appendInFlightOutput: () => appendInFlightOutput,
|
|
313451
|
+
compactSession: () => compactSession,
|
|
313452
|
+
deleteSession: () => deleteSession2,
|
|
313453
|
+
drainCheckins: () => drainCheckins,
|
|
313454
|
+
finishInFlightChat: () => finishInFlightChat,
|
|
313455
|
+
getInFlightChat: () => getInFlightChat,
|
|
313456
|
+
getSession: () => getSession,
|
|
313457
|
+
listSessions: () => listSessions2,
|
|
313458
|
+
loadPersistedSessions: () => loadPersistedSessions,
|
|
313459
|
+
lookupSession: () => lookupSession,
|
|
313460
|
+
startInFlightChat: () => startInFlightChat,
|
|
313461
|
+
trackSessionTokens: () => trackSessionTokens
|
|
313462
|
+
});
|
|
313463
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
313464
|
+
import {
|
|
313465
|
+
existsSync as existsSync74,
|
|
313466
|
+
readFileSync as readFileSync58,
|
|
313467
|
+
readdirSync as readdirSync24,
|
|
313468
|
+
writeFileSync as writeFileSync39,
|
|
313469
|
+
renameSync as renameSync4,
|
|
313470
|
+
mkdirSync as mkdirSync44,
|
|
313471
|
+
unlinkSync as unlinkSync20
|
|
313472
|
+
} from "node:fs";
|
|
313473
|
+
import { join as join91 } from "node:path";
|
|
313474
|
+
import { homedir as homedir31 } from "node:os";
|
|
313475
|
+
function sessionsDir() {
|
|
313476
|
+
return join91(homedir31(), ".open-agents", "chat-sessions");
|
|
313477
|
+
}
|
|
313478
|
+
function sessionPath(id) {
|
|
313479
|
+
const safe = id.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
313480
|
+
return join91(sessionsDir(), `${safe}.json`);
|
|
313481
|
+
}
|
|
313482
|
+
function inFlightPath(id) {
|
|
313483
|
+
const safe = id.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
313484
|
+
return join91(sessionsDir(), `${safe}.inflight.json`);
|
|
313485
|
+
}
|
|
313486
|
+
function persistSession(s2) {
|
|
313487
|
+
try {
|
|
313488
|
+
mkdirSync44(sessionsDir(), { recursive: true });
|
|
313489
|
+
const final2 = sessionPath(s2.id);
|
|
313490
|
+
const tmp = `${final2}.tmp.${process.pid}.${Date.now()}`;
|
|
313491
|
+
writeFileSync39(tmp, JSON.stringify(s2, null, 2), "utf-8");
|
|
313492
|
+
renameSync4(tmp, final2);
|
|
313493
|
+
} catch {
|
|
313494
|
+
}
|
|
313495
|
+
}
|
|
313496
|
+
function persistInFlight(j) {
|
|
313497
|
+
try {
|
|
313498
|
+
mkdirSync44(sessionsDir(), { recursive: true });
|
|
313499
|
+
const final2 = inFlightPath(j.sessionId);
|
|
313500
|
+
const tmp = `${final2}.tmp.${process.pid}.${Date.now()}`;
|
|
313501
|
+
writeFileSync39(tmp, JSON.stringify(j, null, 2), "utf-8");
|
|
313502
|
+
renameSync4(tmp, final2);
|
|
313503
|
+
} catch {
|
|
313504
|
+
}
|
|
313505
|
+
}
|
|
313506
|
+
function deleteInFlightFile(id) {
|
|
313507
|
+
try {
|
|
313508
|
+
const p2 = inFlightPath(id);
|
|
313509
|
+
if (existsSync74(p2)) unlinkSync20(p2);
|
|
313510
|
+
} catch {
|
|
313511
|
+
}
|
|
313512
|
+
}
|
|
313513
|
+
function loadPersistedSessions() {
|
|
313514
|
+
const report = { restored: 0, staleInFlight: 0 };
|
|
313515
|
+
try {
|
|
313516
|
+
const dir = sessionsDir();
|
|
313517
|
+
if (!existsSync74(dir)) return report;
|
|
313518
|
+
const cutoff = Date.now() - SESSION_TTL_MS;
|
|
313519
|
+
for (const f2 of readdirSync24(dir)) {
|
|
313520
|
+
if (!f2.endsWith(".json") || f2.includes(".tmp.")) continue;
|
|
313521
|
+
const fp = join91(dir, f2);
|
|
313522
|
+
try {
|
|
313523
|
+
const parsed = JSON.parse(readFileSync58(fp, "utf-8"));
|
|
313524
|
+
if (f2.endsWith(".inflight.json")) {
|
|
313525
|
+
if (parsed && parsed.pid && parsed.status === "running") {
|
|
313526
|
+
try {
|
|
313527
|
+
process.kill(parsed.pid, 0);
|
|
313528
|
+
} catch {
|
|
313529
|
+
parsed.status = "failed";
|
|
313530
|
+
parsed.error = "Daemon restart while subprocess was running";
|
|
313531
|
+
parsed.completedAt = Date.now();
|
|
313532
|
+
try {
|
|
313533
|
+
writeFileSync39(fp, JSON.stringify(parsed, null, 2), "utf-8");
|
|
313534
|
+
} catch {
|
|
313535
|
+
}
|
|
313536
|
+
report.staleInFlight++;
|
|
313537
|
+
}
|
|
313538
|
+
}
|
|
313539
|
+
continue;
|
|
313540
|
+
}
|
|
313541
|
+
if (parsed && typeof parsed === "object" && parsed.id) {
|
|
313542
|
+
if ((parsed.lastActivity ?? 0) >= cutoff) {
|
|
313543
|
+
sessions.set(parsed.id, parsed);
|
|
313544
|
+
report.restored++;
|
|
313545
|
+
}
|
|
313546
|
+
}
|
|
313547
|
+
} catch {
|
|
313548
|
+
}
|
|
313549
|
+
}
|
|
313550
|
+
} catch {
|
|
313551
|
+
}
|
|
313552
|
+
return report;
|
|
313553
|
+
}
|
|
313554
|
+
function buildSystemPrompt(cwd4) {
|
|
313555
|
+
const parts = [];
|
|
313556
|
+
parts.push(
|
|
313557
|
+
"You are Open Agent (OA), an AI coding assistant running locally via Ollama. You have access to the user's workspace and can discuss code, files, and projects. Be helpful, concise, and technically precise. When asked about files or code, describe what you know from the conversation context."
|
|
313558
|
+
);
|
|
313559
|
+
parts.push(`\\nEnvironment: ${process.platform}, Node ${process.version}, CWD: ${cwd4}`);
|
|
313560
|
+
const diaryPath = join91(cwd4, ".oa", "context", "session-diary.md");
|
|
313561
|
+
if (existsSync74(diaryPath)) {
|
|
313562
|
+
try {
|
|
313563
|
+
const diary = readFileSync58(diaryPath, "utf-8").slice(0, 1e3);
|
|
313564
|
+
parts.push(`\\nPrevious session history:\\n${diary}`);
|
|
313565
|
+
} catch {
|
|
313566
|
+
}
|
|
313567
|
+
}
|
|
313568
|
+
const memDir = join91(cwd4, ".oa", "memory");
|
|
313569
|
+
if (existsSync74(memDir)) {
|
|
313570
|
+
try {
|
|
313571
|
+
const files = readdirSync24(memDir).filter((f2) => f2.endsWith(".json")).slice(0, 5);
|
|
313572
|
+
if (files.length > 0) {
|
|
313573
|
+
parts.push("\\nPersistent memory topics: " + files.map((f2) => f2.replace(".json", "")).join(", "));
|
|
313574
|
+
for (const f2 of files.slice(0, 3)) {
|
|
313575
|
+
try {
|
|
313576
|
+
const data = JSON.parse(readFileSync58(join91(memDir, f2), "utf-8"));
|
|
313577
|
+
const entries = Object.entries(data).slice(0, 3);
|
|
313578
|
+
if (entries.length > 0) {
|
|
313579
|
+
parts.push(`\\nMemory [${f2.replace(".json", "")}]: ` + entries.map(([k, v]) => `${k}: ${String(v.value ?? v).slice(0, 100)}`).join("; "));
|
|
313580
|
+
}
|
|
313581
|
+
} catch {
|
|
313582
|
+
}
|
|
313583
|
+
}
|
|
313584
|
+
}
|
|
313585
|
+
} catch {
|
|
313586
|
+
}
|
|
313587
|
+
}
|
|
313588
|
+
for (const name10 of ["AGENTS.md", "OA.md", ".open-agents.md"]) {
|
|
313589
|
+
const p2 = join91(cwd4, name10);
|
|
313590
|
+
if (existsSync74(p2)) {
|
|
313591
|
+
try {
|
|
313592
|
+
const content = readFileSync58(p2, "utf-8").slice(0, 500);
|
|
313593
|
+
parts.push(`\\nProject instructions (${name10}):\\n${content}`);
|
|
313594
|
+
} catch {
|
|
313595
|
+
}
|
|
313596
|
+
}
|
|
313597
|
+
}
|
|
313598
|
+
return parts.join("");
|
|
313599
|
+
}
|
|
313600
|
+
function getSession(sessionId, model, cwd4) {
|
|
313601
|
+
if (sessionId && sessions.has(sessionId)) {
|
|
313602
|
+
const s2 = sessions.get(sessionId);
|
|
313603
|
+
s2.lastActivity = Date.now();
|
|
313604
|
+
return s2;
|
|
313605
|
+
}
|
|
313606
|
+
if (sessionId) {
|
|
313607
|
+
try {
|
|
313608
|
+
const fp = sessionPath(sessionId);
|
|
313609
|
+
if (existsSync74(fp)) {
|
|
313610
|
+
const parsed = JSON.parse(readFileSync58(fp, "utf-8"));
|
|
313611
|
+
if (parsed && parsed.id === sessionId) {
|
|
313612
|
+
parsed.lastActivity = Date.now();
|
|
313613
|
+
sessions.set(sessionId, parsed);
|
|
313614
|
+
return parsed;
|
|
313615
|
+
}
|
|
313616
|
+
}
|
|
313617
|
+
} catch {
|
|
313618
|
+
}
|
|
313619
|
+
}
|
|
313620
|
+
const id = sessionId || randomUUID10();
|
|
313621
|
+
const systemPrompt = buildSystemPrompt(cwd4);
|
|
313622
|
+
const session = {
|
|
313623
|
+
id,
|
|
313624
|
+
messages: [{ role: "system", content: systemPrompt }],
|
|
313625
|
+
model,
|
|
313626
|
+
createdAt: Date.now(),
|
|
313627
|
+
lastActivity: Date.now(),
|
|
313628
|
+
tokensIn: 0,
|
|
313629
|
+
tokensOut: 0
|
|
313630
|
+
};
|
|
313631
|
+
sessions.set(id, session);
|
|
313632
|
+
persistSession(session);
|
|
313633
|
+
return session;
|
|
313634
|
+
}
|
|
313635
|
+
function addUserMessage(session, content) {
|
|
313636
|
+
session.messages.push({ role: "user", content });
|
|
313637
|
+
session.lastActivity = Date.now();
|
|
313638
|
+
persistSession(session);
|
|
313639
|
+
return session.messages.filter((m2) => INFERENCE_ROLES.includes(m2.role));
|
|
313640
|
+
}
|
|
313641
|
+
function addAssistantMessage(session, content) {
|
|
313642
|
+
session.messages.push({ role: "assistant", content });
|
|
313643
|
+
session.lastActivity = Date.now();
|
|
313644
|
+
persistSession(session);
|
|
313645
|
+
}
|
|
313646
|
+
function addToolCallMessage(session, tool, args) {
|
|
313647
|
+
let cappedArgs = args;
|
|
313648
|
+
try {
|
|
313649
|
+
const json = JSON.stringify(args);
|
|
313650
|
+
if (json && json.length > 4e3) {
|
|
313651
|
+
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
313652
|
+
const pruned = {};
|
|
313653
|
+
for (const [k, v] of Object.entries(args)) {
|
|
313654
|
+
if (typeof v === "string" && v.length > 1e3) {
|
|
313655
|
+
pruned[k] = v.slice(0, 800) + `… [+${v.length - 800} chars truncated for storage]`;
|
|
313656
|
+
} else {
|
|
313657
|
+
pruned[k] = v;
|
|
313658
|
+
}
|
|
313659
|
+
}
|
|
313660
|
+
cappedArgs = pruned;
|
|
313661
|
+
} else {
|
|
313662
|
+
cappedArgs = { _truncated: true, _bytes: json.length };
|
|
313663
|
+
}
|
|
313664
|
+
}
|
|
313665
|
+
} catch {
|
|
313666
|
+
}
|
|
313667
|
+
session.messages.push({
|
|
313668
|
+
role: "tool_call",
|
|
313669
|
+
content: "",
|
|
313670
|
+
tool,
|
|
313671
|
+
args: cappedArgs,
|
|
313672
|
+
ts: Date.now()
|
|
313673
|
+
});
|
|
313674
|
+
session.lastActivity = Date.now();
|
|
313675
|
+
persistSession(session);
|
|
313676
|
+
}
|
|
313677
|
+
function addToolResultMessage(session, tool, output, success) {
|
|
313678
|
+
const capped = output && output.length > 2048 ? output.slice(0, 2e3) + `… [+${output.length - 2e3} chars truncated for storage]` : output;
|
|
313679
|
+
session.messages.push({
|
|
313680
|
+
role: "tool_result",
|
|
313681
|
+
content: "",
|
|
313682
|
+
tool,
|
|
313683
|
+
output: capped,
|
|
313684
|
+
success,
|
|
313685
|
+
ts: Date.now()
|
|
313686
|
+
});
|
|
313687
|
+
session.lastActivity = Date.now();
|
|
313688
|
+
persistSession(session);
|
|
313689
|
+
}
|
|
313690
|
+
function addCheckinMessage(session, content) {
|
|
313691
|
+
session.messages.push({
|
|
313692
|
+
role: "user_checkin",
|
|
313693
|
+
content,
|
|
313694
|
+
ts: Date.now()
|
|
313695
|
+
});
|
|
313696
|
+
session.lastActivity = Date.now();
|
|
313697
|
+
persistSession(session);
|
|
313698
|
+
}
|
|
313699
|
+
function addTriageResponseMessage(session, acknowledgment, steering) {
|
|
313700
|
+
session.messages.push({
|
|
313701
|
+
role: "triage_response",
|
|
313702
|
+
content: acknowledgment,
|
|
313703
|
+
acknowledgment,
|
|
313704
|
+
steering,
|
|
313705
|
+
ts: Date.now()
|
|
313706
|
+
});
|
|
313707
|
+
session.lastActivity = Date.now();
|
|
313708
|
+
persistSession(session);
|
|
313709
|
+
}
|
|
313710
|
+
function checkinPath(sessionId) {
|
|
313711
|
+
const safe = sessionId.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
313712
|
+
return join91(sessionsDir(), `${safe}.checkins.jsonl`);
|
|
313713
|
+
}
|
|
313714
|
+
function appendCheckin(sessionId, steering) {
|
|
313715
|
+
try {
|
|
313716
|
+
mkdirSync44(sessionsDir(), { recursive: true });
|
|
313717
|
+
const fp = checkinPath(sessionId);
|
|
313718
|
+
const entry = JSON.stringify({ ts: Date.now(), steering }) + "\n";
|
|
313719
|
+
const { appendFileSync: appendFileSync7 } = __require("node:fs");
|
|
313720
|
+
appendFileSync7(fp, entry, "utf-8");
|
|
313721
|
+
} catch {
|
|
313722
|
+
}
|
|
313723
|
+
}
|
|
313724
|
+
function drainCheckins(sessionId) {
|
|
313725
|
+
const fp = checkinPath(sessionId);
|
|
313726
|
+
if (!existsSync74(fp)) return [];
|
|
313727
|
+
try {
|
|
313728
|
+
const raw = readFileSync58(fp, "utf-8");
|
|
313729
|
+
try {
|
|
313730
|
+
unlinkSync20(fp);
|
|
313731
|
+
} catch {
|
|
313732
|
+
}
|
|
313733
|
+
if (!raw.trim()) return [];
|
|
313734
|
+
const out = [];
|
|
313735
|
+
for (const line of raw.split("\n")) {
|
|
313736
|
+
if (!line.trim()) continue;
|
|
313737
|
+
try {
|
|
313738
|
+
const parsed = JSON.parse(line);
|
|
313739
|
+
if (typeof parsed.steering === "string" && parsed.steering) {
|
|
313740
|
+
out.push(parsed.steering);
|
|
313741
|
+
}
|
|
313742
|
+
} catch {
|
|
313743
|
+
}
|
|
313744
|
+
}
|
|
313745
|
+
return out;
|
|
313746
|
+
} catch {
|
|
313747
|
+
return [];
|
|
313748
|
+
}
|
|
313749
|
+
}
|
|
313750
|
+
function trackSessionTokens(session, tokensIn, tokensOut) {
|
|
313751
|
+
session.tokensIn += tokensIn;
|
|
313752
|
+
session.tokensOut += tokensOut;
|
|
313753
|
+
}
|
|
313754
|
+
function listSessions2() {
|
|
313755
|
+
return Array.from(sessions.values()).map((s2) => ({
|
|
313756
|
+
id: s2.id,
|
|
313757
|
+
model: s2.model,
|
|
313758
|
+
messages: s2.messages.length - 1,
|
|
313759
|
+
// exclude system prompt
|
|
313760
|
+
tokensIn: s2.tokensIn,
|
|
313761
|
+
tokensOut: s2.tokensOut,
|
|
313762
|
+
lastActivity: new Date(s2.lastActivity).toISOString()
|
|
313763
|
+
}));
|
|
313764
|
+
}
|
|
313765
|
+
function deleteSession2(id) {
|
|
313766
|
+
try {
|
|
313767
|
+
const p2 = sessionPath(id);
|
|
313768
|
+
if (existsSync74(p2)) unlinkSync20(p2);
|
|
313769
|
+
} catch {
|
|
313770
|
+
}
|
|
313771
|
+
deleteInFlightFile(id);
|
|
313772
|
+
inFlight.delete(id);
|
|
313773
|
+
return sessions.delete(id);
|
|
313774
|
+
}
|
|
313775
|
+
function lookupSession(id) {
|
|
313776
|
+
const cached = sessions.get(id);
|
|
313777
|
+
if (cached) return cached;
|
|
313778
|
+
try {
|
|
313779
|
+
const fp = sessionPath(id);
|
|
313780
|
+
if (existsSync74(fp)) {
|
|
313781
|
+
const parsed = JSON.parse(readFileSync58(fp, "utf-8"));
|
|
313782
|
+
if (parsed && parsed.id === id) {
|
|
313783
|
+
sessions.set(id, parsed);
|
|
313784
|
+
return parsed;
|
|
313785
|
+
}
|
|
313786
|
+
}
|
|
313787
|
+
} catch {
|
|
313788
|
+
}
|
|
313789
|
+
return null;
|
|
313790
|
+
}
|
|
313791
|
+
function startInFlightChat(opts) {
|
|
313792
|
+
const job = {
|
|
313793
|
+
sessionId: opts.sessionId,
|
|
313794
|
+
runId: opts.runId,
|
|
313795
|
+
pid: opts.pid,
|
|
313796
|
+
startedAt: Date.now(),
|
|
313797
|
+
partialOutput: "",
|
|
313798
|
+
status: "running",
|
|
313799
|
+
tailBytes: 0
|
|
313800
|
+
};
|
|
313801
|
+
inFlight.set(opts.sessionId, job);
|
|
313802
|
+
persistInFlight(job);
|
|
313803
|
+
return job;
|
|
313804
|
+
}
|
|
313805
|
+
function appendInFlightOutput(sessionId, chunk) {
|
|
313806
|
+
const job = inFlight.get(sessionId);
|
|
313807
|
+
if (!job) return;
|
|
313808
|
+
job.partialOutput += chunk;
|
|
313809
|
+
job.tailBytes += chunk.length;
|
|
313810
|
+
if (job.partialOutput.length > PARTIAL_TAIL_BUDGET) {
|
|
313811
|
+
job.partialOutput = job.partialOutput.slice(-PARTIAL_TAIL_BUDGET);
|
|
313812
|
+
}
|
|
313813
|
+
persistInFlight(job);
|
|
313814
|
+
}
|
|
313815
|
+
function finishInFlightChat(sessionId, status, finalContent, error) {
|
|
313816
|
+
const job = inFlight.get(sessionId);
|
|
313817
|
+
if (!job) return;
|
|
313818
|
+
job.status = status;
|
|
313819
|
+
job.completedAt = Date.now();
|
|
313820
|
+
if (finalContent !== void 0) job.finalContent = finalContent;
|
|
313821
|
+
if (error !== void 0) job.error = error;
|
|
313822
|
+
persistInFlight(job);
|
|
313823
|
+
setTimeout(() => {
|
|
313824
|
+
inFlight.delete(sessionId);
|
|
313825
|
+
deleteInFlightFile(sessionId);
|
|
313826
|
+
}, 3e4).unref?.();
|
|
313827
|
+
}
|
|
313828
|
+
function getInFlightChat(sessionId) {
|
|
313829
|
+
const cached = inFlight.get(sessionId);
|
|
313830
|
+
if (cached) return cached;
|
|
313831
|
+
try {
|
|
313832
|
+
const p2 = inFlightPath(sessionId);
|
|
313833
|
+
if (existsSync74(p2)) {
|
|
313834
|
+
const parsed = JSON.parse(readFileSync58(p2, "utf-8"));
|
|
313835
|
+
if (parsed && parsed.sessionId === sessionId) {
|
|
313836
|
+
return parsed;
|
|
313837
|
+
}
|
|
313838
|
+
}
|
|
313839
|
+
} catch {
|
|
313840
|
+
}
|
|
313841
|
+
return null;
|
|
313842
|
+
}
|
|
313843
|
+
function compactSession(session, maxMessages = 40) {
|
|
313844
|
+
if (session.messages.length <= maxMessages) return;
|
|
313845
|
+
const system = session.messages[0];
|
|
313846
|
+
const recent = session.messages.slice(-20);
|
|
313847
|
+
const middle = session.messages.slice(1, -20);
|
|
313848
|
+
const summary = `[Earlier conversation: ${middle.length} messages discussed ` + middle.filter((m2) => m2.role === "user").map((m2) => m2.content.slice(0, 50)).slice(0, 5).join(", ") + "...]";
|
|
313849
|
+
session.messages = [
|
|
313850
|
+
system,
|
|
313851
|
+
{ role: "system", content: summary },
|
|
313852
|
+
...recent
|
|
313853
|
+
];
|
|
313854
|
+
}
|
|
313855
|
+
var sessions, inFlight, SESSION_TTL_MS, INFERENCE_ROLES, PARTIAL_TAIL_BUDGET;
|
|
313856
|
+
var init_chat_session = __esm({
|
|
313857
|
+
"packages/cli/src/api/chat-session.ts"() {
|
|
313858
|
+
"use strict";
|
|
313859
|
+
sessions = /* @__PURE__ */ new Map();
|
|
313860
|
+
inFlight = /* @__PURE__ */ new Map();
|
|
313861
|
+
SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
313862
|
+
setInterval(() => {
|
|
313863
|
+
const now = Date.now();
|
|
313864
|
+
for (const [id, s2] of sessions) {
|
|
313865
|
+
if (now - s2.lastActivity > SESSION_TTL_MS) sessions.delete(id);
|
|
313866
|
+
}
|
|
313867
|
+
}, 5 * 60 * 1e3);
|
|
313868
|
+
INFERENCE_ROLES = ["system", "user", "assistant"];
|
|
313869
|
+
PARTIAL_TAIL_BUDGET = 8 * 1024;
|
|
313870
|
+
}
|
|
313871
|
+
});
|
|
313872
|
+
|
|
313407
313873
|
// packages/cli/src/tui/mouse-filter.ts
|
|
313408
313874
|
var mouse_filter_exports = {};
|
|
313409
313875
|
__export(mouse_filter_exports, {
|
|
@@ -313922,13 +314388,13 @@ __export(audit_log_exports, {
|
|
|
313922
314388
|
recordAudit: () => recordAudit,
|
|
313923
314389
|
sanitizeBody: () => sanitizeBody
|
|
313924
314390
|
});
|
|
313925
|
-
import { mkdirSync as
|
|
313926
|
-
import { join as
|
|
314391
|
+
import { mkdirSync as mkdirSync45, appendFileSync as appendFileSync5, readFileSync as readFileSync59, existsSync as existsSync75 } from "node:fs";
|
|
314392
|
+
import { join as join92 } from "node:path";
|
|
313927
314393
|
function initAuditLog(oaDir) {
|
|
313928
|
-
auditDir =
|
|
313929
|
-
auditFile =
|
|
314394
|
+
auditDir = join92(oaDir, "audit");
|
|
314395
|
+
auditFile = join92(auditDir, "audit.jsonl");
|
|
313930
314396
|
try {
|
|
313931
|
-
|
|
314397
|
+
mkdirSync45(auditDir, { recursive: true });
|
|
313932
314398
|
initialized = true;
|
|
313933
314399
|
} catch {
|
|
313934
314400
|
}
|
|
@@ -313957,9 +314423,9 @@ function sanitizeBody(body, maxLen = 200) {
|
|
|
313957
314423
|
return safe.length > maxLen ? safe.slice(0, maxLen) + "..." : safe;
|
|
313958
314424
|
}
|
|
313959
314425
|
function queryAudit(opts) {
|
|
313960
|
-
if (!initialized || !
|
|
314426
|
+
if (!initialized || !existsSync75(auditFile)) return [];
|
|
313961
314427
|
try {
|
|
313962
|
-
const raw =
|
|
314428
|
+
const raw = readFileSync59(auditFile, "utf-8");
|
|
313963
314429
|
const lines = raw.split("\n").filter(Boolean);
|
|
313964
314430
|
let records = lines.map((l2) => {
|
|
313965
314431
|
try {
|
|
@@ -313996,8 +314462,8 @@ var init_audit_log = __esm({
|
|
|
313996
314462
|
|
|
313997
314463
|
// packages/cli/src/api/disk-task-output.ts
|
|
313998
314464
|
import { open } from "node:fs/promises";
|
|
313999
|
-
import { existsSync as
|
|
314000
|
-
import { dirname as
|
|
314465
|
+
import { existsSync as existsSync76, mkdirSync as mkdirSync46, statSync as statSync21 } from "node:fs";
|
|
314466
|
+
import { dirname as dirname26 } from "node:path";
|
|
314001
314467
|
import * as fsConstants from "node:constants";
|
|
314002
314468
|
var O_NOFOLLOW2, O_APPEND2, O_CREAT2, O_WRONLY2, OPEN_FLAGS_WRITE, OPEN_MODE, DiskTaskOutput;
|
|
314003
314469
|
var init_disk_task_output = __esm({
|
|
@@ -314016,7 +314482,7 @@ var init_disk_task_output = __esm({
|
|
|
314016
314482
|
fileSize = 0;
|
|
314017
314483
|
constructor(outputPath) {
|
|
314018
314484
|
this.path = outputPath;
|
|
314019
|
-
|
|
314485
|
+
mkdirSync46(dirname26(outputPath), { recursive: true });
|
|
314020
314486
|
}
|
|
314021
314487
|
/** Queue content for async append. Non-blocking. */
|
|
314022
314488
|
append(chunk) {
|
|
@@ -314092,7 +314558,7 @@ var init_disk_task_output = __esm({
|
|
|
314092
314558
|
async readFrom(offset, limit = 65536) {
|
|
314093
314559
|
let handle2 = null;
|
|
314094
314560
|
try {
|
|
314095
|
-
if (!
|
|
314561
|
+
if (!existsSync76(this.path)) {
|
|
314096
314562
|
return { content: "", nextOffset: offset, eof: true, size: 0 };
|
|
314097
314563
|
}
|
|
314098
314564
|
const st = statSync21(this.path);
|
|
@@ -314294,19 +314760,19 @@ __export(aiwg_exports, {
|
|
|
314294
314760
|
resolveAiwgRoot: () => resolveAiwgRoot,
|
|
314295
314761
|
tryRouteAiwg: () => tryRouteAiwg
|
|
314296
314762
|
});
|
|
314297
|
-
import { existsSync as
|
|
314298
|
-
import { join as
|
|
314299
|
-
import { homedir as
|
|
314763
|
+
import { existsSync as existsSync77, readFileSync as readFileSync60, readdirSync as readdirSync25, statSync as statSync22 } from "node:fs";
|
|
314764
|
+
import { join as join93 } from "node:path";
|
|
314765
|
+
import { homedir as homedir32 } from "node:os";
|
|
314300
314766
|
import { execSync as execSync53 } from "node:child_process";
|
|
314301
314767
|
function resolveAiwgRoot() {
|
|
314302
314768
|
if (_cachedAiwgRoot !== void 0) return _cachedAiwgRoot;
|
|
314303
314769
|
const envRoot = process.env["OA_AIWG_ROOT"];
|
|
314304
|
-
if (envRoot &&
|
|
314770
|
+
if (envRoot && existsSync77(join93(envRoot, "package.json"))) {
|
|
314305
314771
|
_cachedAiwgRoot = envRoot;
|
|
314306
314772
|
return envRoot;
|
|
314307
314773
|
}
|
|
314308
|
-
const shareDir =
|
|
314309
|
-
if (
|
|
314774
|
+
const shareDir = join93(homedir32(), ".local", "share", "ai-writing-guide");
|
|
314775
|
+
if (existsSync77(join93(shareDir, "agentic"))) {
|
|
314310
314776
|
_cachedAiwgRoot = shareDir;
|
|
314311
314777
|
return shareDir;
|
|
314312
314778
|
}
|
|
@@ -314316,8 +314782,8 @@ function resolveAiwgRoot() {
|
|
|
314316
314782
|
timeout: 5e3,
|
|
314317
314783
|
stdio: ["pipe", "pipe", "pipe"]
|
|
314318
314784
|
}).trim();
|
|
314319
|
-
const candidate =
|
|
314320
|
-
if (
|
|
314785
|
+
const candidate = join93(globalRoot, "aiwg");
|
|
314786
|
+
if (existsSync77(join93(candidate, "package.json"))) {
|
|
314321
314787
|
_cachedAiwgRoot = candidate;
|
|
314322
314788
|
return candidate;
|
|
314323
314789
|
}
|
|
@@ -314328,22 +314794,22 @@ function resolveAiwgRoot() {
|
|
|
314328
314794
|
"/usr/lib/node_modules/aiwg",
|
|
314329
314795
|
"/opt/homebrew/lib/node_modules/aiwg"
|
|
314330
314796
|
]) {
|
|
314331
|
-
if (
|
|
314797
|
+
if (existsSync77(join93(p2, "package.json"))) {
|
|
314332
314798
|
_cachedAiwgRoot = p2;
|
|
314333
314799
|
return p2;
|
|
314334
314800
|
}
|
|
314335
314801
|
}
|
|
314336
314802
|
const versionDirs = [
|
|
314337
|
-
|
|
314338
|
-
|
|
314803
|
+
join93(homedir32(), ".nvm", "versions", "node"),
|
|
314804
|
+
join93(homedir32(), ".local", "share", "fnm", "node-versions")
|
|
314339
314805
|
];
|
|
314340
314806
|
for (const vdir of versionDirs) {
|
|
314341
|
-
if (!
|
|
314807
|
+
if (!existsSync77(vdir)) continue;
|
|
314342
314808
|
try {
|
|
314343
|
-
for (const ver of
|
|
314809
|
+
for (const ver of readdirSync25(vdir)) {
|
|
314344
314810
|
for (const prefix of ["lib/node_modules/aiwg", "installation/lib/node_modules/aiwg"]) {
|
|
314345
|
-
const cand =
|
|
314346
|
-
if (
|
|
314811
|
+
const cand = join93(vdir, ver, prefix);
|
|
314812
|
+
if (existsSync77(join93(cand, "package.json"))) {
|
|
314347
314813
|
_cachedAiwgRoot = cand;
|
|
314348
314814
|
return cand;
|
|
314349
314815
|
}
|
|
@@ -314361,11 +314827,11 @@ function resolveAiwgRoot() {
|
|
|
314361
314827
|
if (whichAiwg) {
|
|
314362
314828
|
let cur = whichAiwg;
|
|
314363
314829
|
for (let i2 = 0; i2 < 8; i2++) {
|
|
314364
|
-
cur =
|
|
314365
|
-
const pj =
|
|
314366
|
-
if (
|
|
314830
|
+
cur = join93(cur, "..");
|
|
314831
|
+
const pj = join93(cur, "package.json");
|
|
314832
|
+
if (existsSync77(pj)) {
|
|
314367
314833
|
try {
|
|
314368
|
-
const pkg = JSON.parse(
|
|
314834
|
+
const pkg = JSON.parse(readFileSync60(pj, "utf-8"));
|
|
314369
314835
|
if (pkg.name === "aiwg") {
|
|
314370
314836
|
_cachedAiwgRoot = cur;
|
|
314371
314837
|
return cur;
|
|
@@ -314387,14 +314853,14 @@ function listAiwgFrameworks() {
|
|
|
314387
314853
|
_cachedFrameworks = [];
|
|
314388
314854
|
return _cachedFrameworks;
|
|
314389
314855
|
}
|
|
314390
|
-
const frameworksDir =
|
|
314391
|
-
if (!
|
|
314856
|
+
const frameworksDir = join93(root, "agentic", "code", "frameworks");
|
|
314857
|
+
if (!existsSync77(frameworksDir)) {
|
|
314392
314858
|
_cachedFrameworks = [];
|
|
314393
314859
|
return _cachedFrameworks;
|
|
314394
314860
|
}
|
|
314395
314861
|
const out = [];
|
|
314396
|
-
for (const name10 of
|
|
314397
|
-
const p2 =
|
|
314862
|
+
for (const name10 of readdirSync25(frameworksDir)) {
|
|
314863
|
+
const p2 = join93(frameworksDir, name10);
|
|
314398
314864
|
try {
|
|
314399
314865
|
const st = statSync22(p2);
|
|
314400
314866
|
if (!st.isDirectory()) continue;
|
|
@@ -314420,9 +314886,9 @@ function aggregateDir(dir, depth = 0) {
|
|
|
314420
314886
|
const out = { files: 0, bytes: 0, mdChars: 0, skills: 0, agents: 0, commands: 0 };
|
|
314421
314887
|
if (depth > 8) return out;
|
|
314422
314888
|
try {
|
|
314423
|
-
for (const e2 of
|
|
314889
|
+
for (const e2 of readdirSync25(dir, { withFileTypes: true })) {
|
|
314424
314890
|
if (e2.name.startsWith(".") || e2.name === "node_modules") continue;
|
|
314425
|
-
const p2 =
|
|
314891
|
+
const p2 = join93(dir, e2.name);
|
|
314426
314892
|
if (e2.isDirectory()) {
|
|
314427
314893
|
const sub = aggregateDir(p2, depth + 1);
|
|
314428
314894
|
out.files += sub.files;
|
|
@@ -314452,10 +314918,10 @@ function aggregateDir(dir, depth = 0) {
|
|
|
314452
314918
|
}
|
|
314453
314919
|
function readFirstLineDescription(dir) {
|
|
314454
314920
|
for (const candidate of ["README.md", "SKILL.md", "INDEX.md"]) {
|
|
314455
|
-
const p2 =
|
|
314456
|
-
if (!
|
|
314921
|
+
const p2 = join93(dir, candidate);
|
|
314922
|
+
if (!existsSync77(p2)) continue;
|
|
314457
314923
|
try {
|
|
314458
|
-
const txt =
|
|
314924
|
+
const txt = readFileSync60(p2, "utf-8");
|
|
314459
314925
|
const descMatch = txt.match(/^description:\s*(.+)$/m);
|
|
314460
314926
|
if (descMatch) return descMatch[1].trim().slice(0, 200);
|
|
314461
314927
|
for (const line of txt.split("\n")) {
|
|
@@ -314477,12 +314943,12 @@ function listAiwgItems() {
|
|
|
314477
314943
|
}
|
|
314478
314944
|
const out = [];
|
|
314479
314945
|
const walkRoots = [
|
|
314480
|
-
|
|
314481
|
-
|
|
314482
|
-
|
|
314946
|
+
join93(root, "agentic", "code", "frameworks"),
|
|
314947
|
+
join93(root, "agentic", "code", "addons"),
|
|
314948
|
+
join93(root, "plugins")
|
|
314483
314949
|
];
|
|
314484
314950
|
for (const wr of walkRoots) {
|
|
314485
|
-
if (!
|
|
314951
|
+
if (!existsSync77(wr)) continue;
|
|
314486
314952
|
walkForItems(wr, out, 0);
|
|
314487
314953
|
}
|
|
314488
314954
|
_cachedItems = out;
|
|
@@ -314491,9 +314957,9 @@ function listAiwgItems() {
|
|
|
314491
314957
|
function walkForItems(dir, out, depth) {
|
|
314492
314958
|
if (depth > 10) return;
|
|
314493
314959
|
try {
|
|
314494
|
-
for (const e2 of
|
|
314960
|
+
for (const e2 of readdirSync25(dir, { withFileTypes: true })) {
|
|
314495
314961
|
if (e2.name.startsWith(".") || e2.name === "node_modules") continue;
|
|
314496
|
-
const p2 =
|
|
314962
|
+
const p2 = join93(dir, e2.name);
|
|
314497
314963
|
if (e2.isDirectory()) {
|
|
314498
314964
|
walkForItems(p2, out, depth + 1);
|
|
314499
314965
|
} else if (e2.isFile() && e2.name.endsWith(".md")) {
|
|
@@ -314506,7 +314972,7 @@ function walkForItems(dir, out, depth) {
|
|
|
314506
314972
|
}
|
|
314507
314973
|
function parseItem(p2) {
|
|
314508
314974
|
try {
|
|
314509
|
-
const raw =
|
|
314975
|
+
const raw = readFileSync60(p2, "utf-8");
|
|
314510
314976
|
const header = raw.slice(0, 3e3);
|
|
314511
314977
|
const nameMatch = header.match(/^name:\s*(.+)$/m);
|
|
314512
314978
|
const descMatch = header.match(/^description:\s*(.+)$/m);
|
|
@@ -314544,8 +315010,8 @@ function deriveSource(p2) {
|
|
|
314544
315010
|
}
|
|
314545
315011
|
function loadAiwgItemContent(path5, maxBytes = 2e4) {
|
|
314546
315012
|
try {
|
|
314547
|
-
if (!
|
|
314548
|
-
const raw =
|
|
315013
|
+
if (!existsSync77(path5)) return null;
|
|
315014
|
+
const raw = readFileSync60(path5, "utf-8");
|
|
314549
315015
|
return raw.length > maxBytes ? raw.slice(0, maxBytes) + "\n\n...(truncated for context budget)" : raw;
|
|
314550
315016
|
} catch {
|
|
314551
315017
|
return null;
|
|
@@ -314558,14 +315024,14 @@ function listAiwgAddons() {
|
|
|
314558
315024
|
_cachedAddons = [];
|
|
314559
315025
|
return _cachedAddons;
|
|
314560
315026
|
}
|
|
314561
|
-
const addonsDir =
|
|
314562
|
-
if (!
|
|
315027
|
+
const addonsDir = join93(root, "agentic", "code", "addons");
|
|
315028
|
+
if (!existsSync77(addonsDir)) {
|
|
314563
315029
|
_cachedAddons = [];
|
|
314564
315030
|
return _cachedAddons;
|
|
314565
315031
|
}
|
|
314566
315032
|
const out = [];
|
|
314567
|
-
for (const name10 of
|
|
314568
|
-
const p2 =
|
|
315033
|
+
for (const name10 of readdirSync25(addonsDir)) {
|
|
315034
|
+
const p2 = join93(addonsDir, name10);
|
|
314569
315035
|
try {
|
|
314570
315036
|
const st = statSync22(p2);
|
|
314571
315037
|
if (!st.isDirectory()) continue;
|
|
@@ -315047,9 +315513,9 @@ var init_aiwg = __esm({
|
|
|
315047
315513
|
});
|
|
315048
315514
|
|
|
315049
315515
|
// packages/cli/src/api/routes-v1.ts
|
|
315050
|
-
import { existsSync as
|
|
315051
|
-
import { join as
|
|
315052
|
-
import { homedir as
|
|
315516
|
+
import { existsSync as existsSync78, readFileSync as readFileSync61, readdirSync as readdirSync26, statSync as statSync23 } from "node:fs";
|
|
315517
|
+
import { join as join94, resolve as pathResolve } from "node:path";
|
|
315518
|
+
import { homedir as homedir33 } from "node:os";
|
|
315053
315519
|
async function tryRouteV1(ctx3) {
|
|
315054
315520
|
const { pathname, method } = ctx3;
|
|
315055
315521
|
if (pathname === "/v1/skills" && method === "GET") {
|
|
@@ -315254,11 +315720,11 @@ async function handleGetSkill(ctx3, name10) {
|
|
|
315254
315720
|
async function fallbackDiscoverSkills() {
|
|
315255
315721
|
return (_root) => {
|
|
315256
315722
|
const roots = [
|
|
315257
|
-
|
|
315723
|
+
join94(homedir33(), ".local", "share", "ai-writing-guide")
|
|
315258
315724
|
];
|
|
315259
315725
|
const out = [];
|
|
315260
315726
|
for (const root of roots) {
|
|
315261
|
-
if (!
|
|
315727
|
+
if (!existsSync78(root)) continue;
|
|
315262
315728
|
walkForSkills(root, out, 0);
|
|
315263
315729
|
}
|
|
315264
315730
|
return out;
|
|
@@ -315267,14 +315733,14 @@ async function fallbackDiscoverSkills() {
|
|
|
315267
315733
|
function walkForSkills(dir, out, depth) {
|
|
315268
315734
|
if (depth > 6) return;
|
|
315269
315735
|
try {
|
|
315270
|
-
for (const e2 of
|
|
315736
|
+
for (const e2 of readdirSync26(dir, { withFileTypes: true })) {
|
|
315271
315737
|
if (e2.name.startsWith(".") || e2.name === "node_modules") continue;
|
|
315272
|
-
const p2 =
|
|
315738
|
+
const p2 = join94(dir, e2.name);
|
|
315273
315739
|
if (e2.isDirectory()) {
|
|
315274
315740
|
walkForSkills(p2, out, depth + 1);
|
|
315275
315741
|
} else if (e2.isFile() && e2.name === "SKILL.md") {
|
|
315276
315742
|
try {
|
|
315277
|
-
const content =
|
|
315743
|
+
const content = readFileSync61(p2, "utf-8").slice(0, 2e3);
|
|
315278
315744
|
const nameMatch = content.match(/^name:\s*(.+)$/m);
|
|
315279
315745
|
const descMatch = content.match(/^description:\s*(.+)$/m);
|
|
315280
315746
|
out.push({
|
|
@@ -315458,7 +315924,7 @@ async function getMemoryStores() {
|
|
|
315458
315924
|
if (memoryInitTried) return null;
|
|
315459
315925
|
memoryInitTried = true;
|
|
315460
315926
|
try {
|
|
315461
|
-
const dbPath =
|
|
315927
|
+
const dbPath = join94(homedir33(), ".open-agents", "memory.db");
|
|
315462
315928
|
const sharedDb = initDb(dbPath);
|
|
315463
315929
|
memoryStoresCache = {
|
|
315464
315930
|
episode: new EpisodeStore(dbPath),
|
|
@@ -315716,7 +316182,7 @@ async function handleFilesRead(ctx3) {
|
|
|
315716
316182
|
}));
|
|
315717
316183
|
return true;
|
|
315718
316184
|
}
|
|
315719
|
-
if (!
|
|
316185
|
+
if (!existsSync78(resolved)) {
|
|
315720
316186
|
sendProblem(res, problemDetails({
|
|
315721
316187
|
type: P.notFound,
|
|
315722
316188
|
status: 404,
|
|
@@ -315748,7 +316214,7 @@ async function handleFilesRead(ctx3) {
|
|
|
315748
316214
|
}));
|
|
315749
316215
|
return true;
|
|
315750
316216
|
}
|
|
315751
|
-
const content =
|
|
316217
|
+
const content = readFileSync61(resolved, "utf-8");
|
|
315752
316218
|
const offset = typeof body.offset === "number" && body.offset >= 0 ? body.offset : 0;
|
|
315753
316219
|
const limit = typeof body.limit === "number" && body.limit > 0 ? body.limit : content.length;
|
|
315754
316220
|
const slice2 = content.slice(offset, offset + limit);
|
|
@@ -315979,14 +316445,14 @@ async function handleNexusStatus(ctx3) {
|
|
|
315979
316445
|
const { res, requestId } = ctx3;
|
|
315980
316446
|
try {
|
|
315981
316447
|
const statePaths = [
|
|
315982
|
-
|
|
315983
|
-
|
|
316448
|
+
join94(process.cwd(), ".oa", "nexus-peer-state.json"),
|
|
316449
|
+
join94(homedir33(), ".open-agents", "nexus-peer-cache.json")
|
|
315984
316450
|
];
|
|
315985
316451
|
const states = [];
|
|
315986
316452
|
for (const p2 of statePaths) {
|
|
315987
|
-
if (!
|
|
316453
|
+
if (!existsSync78(p2)) continue;
|
|
315988
316454
|
try {
|
|
315989
|
-
const raw =
|
|
316455
|
+
const raw = readFileSync61(p2, "utf-8");
|
|
315990
316456
|
states.push({ source: p2, data: JSON.parse(raw) });
|
|
315991
316457
|
} catch (e2) {
|
|
315992
316458
|
states.push({ source: p2, error: String(e2) });
|
|
@@ -316013,8 +316479,8 @@ async function handleNexusStatus(ctx3) {
|
|
|
316013
316479
|
}
|
|
316014
316480
|
function loadAgentName() {
|
|
316015
316481
|
try {
|
|
316016
|
-
const p2 =
|
|
316017
|
-
if (
|
|
316482
|
+
const p2 = join94(homedir33(), ".open-agents", "agent-name");
|
|
316483
|
+
if (existsSync78(p2)) return readFileSync61(p2, "utf-8").trim();
|
|
316018
316484
|
} catch {
|
|
316019
316485
|
}
|
|
316020
316486
|
return null;
|
|
@@ -316023,14 +316489,14 @@ async function handleSponsors(ctx3) {
|
|
|
316023
316489
|
const { req: req2, res, url, requestId } = ctx3;
|
|
316024
316490
|
try {
|
|
316025
316491
|
const candidates = [
|
|
316026
|
-
|
|
316027
|
-
|
|
316492
|
+
join94(homedir33(), ".open-agents", "sponsor-cache.json"),
|
|
316493
|
+
join94(homedir33(), ".open-agents", "sponsors.json")
|
|
316028
316494
|
];
|
|
316029
316495
|
let sponsors = [];
|
|
316030
316496
|
for (const p2 of candidates) {
|
|
316031
|
-
if (!
|
|
316497
|
+
if (!existsSync78(p2)) continue;
|
|
316032
316498
|
try {
|
|
316033
|
-
const raw = JSON.parse(
|
|
316499
|
+
const raw = JSON.parse(readFileSync61(p2, "utf-8"));
|
|
316034
316500
|
if (Array.isArray(raw)) {
|
|
316035
316501
|
sponsors = raw;
|
|
316036
316502
|
break;
|
|
@@ -316099,8 +316565,8 @@ async function handleEvaluate(ctx3) {
|
|
|
316099
316565
|
}));
|
|
316100
316566
|
return true;
|
|
316101
316567
|
}
|
|
316102
|
-
const jobPath =
|
|
316103
|
-
if (!
|
|
316568
|
+
const jobPath = join94(process.cwd(), ".oa", "jobs", `${runId}.json`);
|
|
316569
|
+
if (!existsSync78(jobPath)) {
|
|
316104
316570
|
sendProblem(res, problemDetails({
|
|
316105
316571
|
type: P.notFound,
|
|
316106
316572
|
status: 404,
|
|
@@ -316110,7 +316576,7 @@ async function handleEvaluate(ctx3) {
|
|
|
316110
316576
|
}));
|
|
316111
316577
|
return true;
|
|
316112
316578
|
}
|
|
316113
|
-
const job = JSON.parse(
|
|
316579
|
+
const job = JSON.parse(readFileSync61(jobPath, "utf-8"));
|
|
316114
316580
|
sendJson(res, 200, {
|
|
316115
316581
|
run_id: runId,
|
|
316116
316582
|
task: job.task,
|
|
@@ -316248,17 +316714,17 @@ async function handleListAgentTypes(ctx3) {
|
|
|
316248
316714
|
}
|
|
316249
316715
|
async function handleListEngines(ctx3) {
|
|
316250
316716
|
const { res } = ctx3;
|
|
316251
|
-
const home =
|
|
316717
|
+
const home = homedir33();
|
|
316252
316718
|
sendJson(res, 200, {
|
|
316253
316719
|
engines: [
|
|
316254
|
-
{ name: "dream", state_file:
|
|
316255
|
-
{ name: "bless", state_file:
|
|
316256
|
-
{ name: "call", state_file:
|
|
316257
|
-
{ name: "listen", state_file:
|
|
316258
|
-
{ name: "telegram", state_file:
|
|
316259
|
-
{ name: "expose", state_file:
|
|
316260
|
-
{ name: "nexus", state_file:
|
|
316261
|
-
{ name: "ipfs", state_file:
|
|
316720
|
+
{ name: "dream", state_file: join94(process.cwd(), ".oa", "dreams"), controllable_via: "SSE + slash commands" },
|
|
316721
|
+
{ name: "bless", state_file: join94(process.cwd(), ".oa", "bless-state.json"), controllable_via: "slash commands" },
|
|
316722
|
+
{ name: "call", state_file: join94(process.cwd(), ".oa", "call-state.json"), controllable_via: "slash commands" },
|
|
316723
|
+
{ name: "listen", state_file: join94(process.cwd(), ".oa", "listen-state.json"), controllable_via: "slash commands" },
|
|
316724
|
+
{ name: "telegram", state_file: join94(home, ".open-agents", "telegram-state.json"), controllable_via: "slash commands" },
|
|
316725
|
+
{ name: "expose", state_file: join94(process.cwd(), ".oa", "expose-state.json"), controllable_via: "/expose commands" },
|
|
316726
|
+
{ name: "nexus", state_file: join94(home, ".open-agents", "nexus-peer-cache.json"), controllable_via: "/nexus commands" },
|
|
316727
|
+
{ name: "ipfs", state_file: join94(process.cwd(), ".oa", "ipfs"), controllable_via: "slash commands" }
|
|
316262
316728
|
],
|
|
316263
316729
|
note: "Engine instrumentation lives in the running TUI process. Full status + control requires the daemon↔TUI bridge (PT-07). See parity audit WO-PARITY-04."
|
|
316264
316730
|
});
|
|
@@ -316341,12 +316807,12 @@ async function tryAimsRoute(ctx3) {
|
|
|
316341
316807
|
return false;
|
|
316342
316808
|
}
|
|
316343
316809
|
function aimsDir() {
|
|
316344
|
-
return
|
|
316810
|
+
return join94(homedir33(), ".open-agents", "aims");
|
|
316345
316811
|
}
|
|
316346
316812
|
function readAimsFile(name10, fallback) {
|
|
316347
316813
|
try {
|
|
316348
|
-
const p2 =
|
|
316349
|
-
if (
|
|
316814
|
+
const p2 = join94(aimsDir(), name10);
|
|
316815
|
+
if (existsSync78(p2)) return JSON.parse(readFileSync61(p2, "utf-8"));
|
|
316350
316816
|
} catch {
|
|
316351
316817
|
}
|
|
316352
316818
|
return fallback;
|
|
@@ -316355,7 +316821,7 @@ function writeAimsFile(name10, data) {
|
|
|
316355
316821
|
const dir = aimsDir();
|
|
316356
316822
|
const { mkdirSync: mkdirSync54, writeFileSync: wf, renameSync: rn } = __require("node:fs");
|
|
316357
316823
|
mkdirSync54(dir, { recursive: true });
|
|
316358
|
-
const finalPath =
|
|
316824
|
+
const finalPath = join94(dir, name10);
|
|
316359
316825
|
const tmpPath = `${finalPath}.tmp.${process.pid}.${Date.now()}`;
|
|
316360
316826
|
try {
|
|
316361
316827
|
wf(tmpPath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
|
|
@@ -316685,12 +317151,12 @@ async function handleAimsSuppliers(ctx3) {
|
|
|
316685
317151
|
}
|
|
316686
317152
|
];
|
|
316687
317153
|
const sponsorPaths = [
|
|
316688
|
-
|
|
317154
|
+
join94(homedir33(), ".open-agents", "sponsor-cache.json")
|
|
316689
317155
|
];
|
|
316690
317156
|
for (const p2 of sponsorPaths) {
|
|
316691
|
-
if (!
|
|
317157
|
+
if (!existsSync78(p2)) continue;
|
|
316692
317158
|
try {
|
|
316693
|
-
const raw = JSON.parse(
|
|
317159
|
+
const raw = JSON.parse(readFileSync61(p2, "utf-8"));
|
|
316694
317160
|
const list = Array.isArray(raw) ? raw : raw?.sponsors ?? [];
|
|
316695
317161
|
for (const s2 of list) {
|
|
316696
317162
|
suppliers.push({
|
|
@@ -317536,6 +318002,11 @@ body {
|
|
|
317536
318002
|
<!-- Popover for process details (absolute positioned above the process row) -->
|
|
317537
318003
|
<div id="proc-popover"></div>
|
|
317538
318004
|
|
|
318005
|
+
<!-- WO-CHAT-AUTOSCROLL — tiny tag that appears far-right just above
|
|
318006
|
+
the tasks-row when the user has scrolled up from live. Click
|
|
318007
|
+
jumps to bottom and re-enables auto-scroll. -->
|
|
318008
|
+
<div id="scroll-bottom-tag" style="display:none;position:absolute;right:14px;top:-22px;background:#17171a;border:1px solid #b2920a;color:#b2920a;padding:2px 8px;border-radius:3px;font-size:0.6rem;cursor:pointer;z-index:5;line-height:1.4" onclick="scrollChatToBottom()">scroll to bottom ↓</div>
|
|
318009
|
+
|
|
317539
318010
|
<!-- WO-TASK-06: Compact aggregated pill label above the dots row.
|
|
317540
318011
|
Hidden when no processes are active; click to toggle dots row visibility. -->
|
|
317541
318012
|
<div id="processes-pill" style="display:none;padding:4px 16px;background:#17171a;border-bottom:1px solid #2a2a30;color:#666;font-size:0.62rem;cursor:pointer" onclick="toggleProcessesRow()">
|
|
@@ -317560,6 +318031,12 @@ body {
|
|
|
317560
318031
|
<textarea id="input-area" placeholder="Type a message..." rows="1"></textarea>
|
|
317561
318032
|
<button id="send-btn" onclick="sendMessage()">send</button>
|
|
317562
318033
|
<button id="stop-btn" onclick="stopChat()" style="display:none;background:#2a2a30;border:1px solid #ff4444;color:#ff4444;padding:10px 16px;border-radius:3px;font-family:inherit;font-size:0.75rem;cursor:pointer;flex-shrink:0">stop</button>
|
|
318034
|
+
<!-- WO-CHAT-CHECKIN — teal accent button that takes the place of stop
|
|
318035
|
+
when the user starts typing during an active run. Click sends a
|
|
318036
|
+
side-channel check-in to the triage sub-agent (route /v1/chat/check-in)
|
|
318037
|
+
which expands the input into a steering instruction the main agent
|
|
318038
|
+
picks up at its next turn. -->
|
|
318039
|
+
<button id="checkin-btn" onclick="sendCheckin()" style="display:none;background:#0a2a2e;border:1px solid #2db4b4;color:#2db4b4;padding:10px 16px;border-radius:3px;font-family:inherit;font-size:0.75rem;cursor:pointer;flex-shrink:0">check in</button>
|
|
317563
318040
|
</div>
|
|
317564
318041
|
</div>
|
|
317565
318042
|
|
|
@@ -317595,13 +318072,72 @@ let chatAbortController = null; // for stop button
|
|
|
317595
318072
|
// state OR the user navigates away.
|
|
317596
318073
|
let chatInFlightPoller = null;
|
|
317597
318074
|
|
|
317598
|
-
//
|
|
318075
|
+
// WO-CHAT-AUTOSCROLL — track whether the user is parked at the bottom.
|
|
318076
|
+
// When they scroll up by more than ~SCROLL_TOLERANCE px, we stop auto-
|
|
318077
|
+
// scrolling on new content and show the "scroll to bottom" tag. When
|
|
318078
|
+
// they scroll back to the bottom (or click the tag), auto-scroll
|
|
318079
|
+
// resumes. autoScrollPinned defaults to true so first-load works.
|
|
318080
|
+
let autoScrollPinned = true;
|
|
318081
|
+
const SCROLL_TOLERANCE = 80;
|
|
318082
|
+
function isAtBottom() {
|
|
318083
|
+
if (!conv) return true;
|
|
318084
|
+
return (conv.scrollHeight - conv.scrollTop - conv.clientHeight) < SCROLL_TOLERANCE;
|
|
318085
|
+
}
|
|
318086
|
+
function maybeAutoScroll() {
|
|
318087
|
+
if (autoScrollPinned && conv) conv.scrollTop = conv.scrollHeight;
|
|
318088
|
+
}
|
|
318089
|
+
function scrollChatToBottom() {
|
|
318090
|
+
if (!conv) return;
|
|
318091
|
+
conv.scrollTop = conv.scrollHeight;
|
|
318092
|
+
autoScrollPinned = true;
|
|
318093
|
+
const tag = document.getElementById('scroll-bottom-tag');
|
|
318094
|
+
if (tag) tag.style.display = 'none';
|
|
318095
|
+
}
|
|
318096
|
+
window.scrollChatToBottom = scrollChatToBottom;
|
|
318097
|
+
if (conv) {
|
|
318098
|
+
conv.addEventListener('scroll', () => {
|
|
318099
|
+
const atBottom = isAtBottom();
|
|
318100
|
+
if (atBottom) {
|
|
318101
|
+
autoScrollPinned = true;
|
|
318102
|
+
const tag = document.getElementById('scroll-bottom-tag');
|
|
318103
|
+
if (tag) tag.style.display = 'none';
|
|
318104
|
+
} else {
|
|
318105
|
+
autoScrollPinned = false;
|
|
318106
|
+
const tag = document.getElementById('scroll-bottom-tag');
|
|
318107
|
+
if (tag) tag.style.display = 'block';
|
|
318108
|
+
}
|
|
318109
|
+
}, { passive: true });
|
|
318110
|
+
}
|
|
318111
|
+
|
|
318112
|
+
// Auto-resize textarea + WO-CHAT-CHECKIN typing detection
|
|
317599
318113
|
input.addEventListener('input', () => {
|
|
317600
318114
|
input.style.height = 'auto';
|
|
317601
318115
|
input.style.height = Math.min(input.scrollHeight, 120) + 'px';
|
|
318116
|
+
// While a run is streaming, swap stop button → check-in button as
|
|
318117
|
+
// soon as the user types anything. When the input is cleared, swap back.
|
|
318118
|
+
if (streaming) {
|
|
318119
|
+
const checkinBtn = document.getElementById('checkin-btn');
|
|
318120
|
+
const stopBtn = document.getElementById('stop-btn');
|
|
318121
|
+
if (input.value.trim().length > 0) {
|
|
318122
|
+
if (checkinBtn) checkinBtn.style.display = 'inline-block';
|
|
318123
|
+
if (stopBtn) stopBtn.style.display = 'none';
|
|
318124
|
+
} else {
|
|
318125
|
+
if (checkinBtn) checkinBtn.style.display = 'none';
|
|
318126
|
+
if (stopBtn) stopBtn.style.display = 'inline-block';
|
|
318127
|
+
}
|
|
318128
|
+
}
|
|
317602
318129
|
});
|
|
317603
318130
|
input.addEventListener('keydown', (e) => {
|
|
317604
|
-
if (e.key === 'Enter' && !e.shiftKey) {
|
|
318131
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
318132
|
+
e.preventDefault();
|
|
318133
|
+
// While a run is streaming, Enter dispatches a check-in instead of
|
|
318134
|
+
// a new top-level send (which would block on streaming === true).
|
|
318135
|
+
if (streaming && input.value.trim().length > 0) {
|
|
318136
|
+
sendCheckin();
|
|
318137
|
+
} else {
|
|
318138
|
+
sendMessage();
|
|
318139
|
+
}
|
|
318140
|
+
}
|
|
317605
318141
|
});
|
|
317606
318142
|
|
|
317607
318143
|
function headers() {
|
|
@@ -317705,7 +318241,7 @@ function addMessage(role, content) {
|
|
|
317705
318241
|
div.appendChild(actions);
|
|
317706
318242
|
}
|
|
317707
318243
|
conv.appendChild(div);
|
|
317708
|
-
|
|
318244
|
+
maybeAutoScroll();
|
|
317709
318245
|
return div;
|
|
317710
318246
|
}
|
|
317711
318247
|
|
|
@@ -317954,6 +318490,127 @@ function renderToolResultEvent(parent, chunkLike) {
|
|
|
317954
318490
|
return resultEl;
|
|
317955
318491
|
}
|
|
317956
318492
|
|
|
318493
|
+
// WO-CHAT-CHECKIN — render a teal-accent user check-in entry. The
|
|
318494
|
+
// content is the raw message the user typed during an active run,
|
|
318495
|
+
// before the triage sub-agent expanded it. Sits inline with the
|
|
318496
|
+
// active assistant turn's tool dropdowns.
|
|
318497
|
+
function renderCheckinEvent(parent, content) {
|
|
318498
|
+
const el = document.createElement('div');
|
|
318499
|
+
el.style.cssText = 'background:#0a2a2e;border-left:3px solid #2db4b4;color:#7fdada;padding:6px 10px 6px 14px;margin:3px 0;font-size:0.7rem;font-family:inherit';
|
|
318500
|
+
const label = document.createElement('div');
|
|
318501
|
+
label.style.cssText = 'color:#2db4b4;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:2px';
|
|
318502
|
+
label.textContent = '\\u25B8 user check-in';
|
|
318503
|
+
el.appendChild(label);
|
|
318504
|
+
const body = document.createElement('div');
|
|
318505
|
+
body.style.cssText = 'color:#b0d4d4;white-space:pre-wrap;word-break:break-word';
|
|
318506
|
+
body.textContent = String(content || '');
|
|
318507
|
+
el.appendChild(body);
|
|
318508
|
+
parent.appendChild(el);
|
|
318509
|
+
return el;
|
|
318510
|
+
}
|
|
318511
|
+
|
|
318512
|
+
// WO-CHAT-CHECKIN — render the triage sub-agent's structured response
|
|
318513
|
+
// (acknowledgment + steering instruction). Uses the same teal accent
|
|
318514
|
+
// so the user can visually trace each side-channel exchange in the
|
|
318515
|
+
// stack of dropdowns.
|
|
318516
|
+
function renderTriageResponseEvent(parent, ack, steering) {
|
|
318517
|
+
const el = document.createElement('div');
|
|
318518
|
+
el.style.cssText = 'background:#0a2628;border-left:3px solid #2db4b4;color:#7fdada;padding:6px 10px 6px 14px;margin:3px 0;font-size:0.7rem';
|
|
318519
|
+
const label = document.createElement('div');
|
|
318520
|
+
label.style.cssText = 'color:#2db4b4;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:2px';
|
|
318521
|
+
label.textContent = '\\u25B8 triage \\u2192 main agent';
|
|
318522
|
+
el.appendChild(label);
|
|
318523
|
+
if (ack) {
|
|
318524
|
+
const ackEl = document.createElement('div');
|
|
318525
|
+
ackEl.style.cssText = 'color:#9fe4e4;font-style:italic;margin-bottom:3px';
|
|
318526
|
+
ackEl.textContent = ack;
|
|
318527
|
+
el.appendChild(ackEl);
|
|
318528
|
+
}
|
|
318529
|
+
if (steering) {
|
|
318530
|
+
const steerWrap = document.createElement('div');
|
|
318531
|
+
steerWrap.style.cssText = 'color:#7fdada;font-size:0.65rem';
|
|
318532
|
+
appendExpandableContent(steerWrap, steering, { truncateAt: 200, baseStyle: 'color:#7fdada;' });
|
|
318533
|
+
el.appendChild(steerWrap);
|
|
318534
|
+
}
|
|
318535
|
+
parent.appendChild(el);
|
|
318536
|
+
return el;
|
|
318537
|
+
}
|
|
318538
|
+
|
|
318539
|
+
// WO-CHAT-CHECKIN — POST a side-channel check-in to /v1/chat/check-in
|
|
318540
|
+
// while a chat run is streaming. Renders the raw input + the triage
|
|
318541
|
+
// response as teal entries inline with the live assistant turn, and
|
|
318542
|
+
// the triage's steering string is forwarded into the agent's
|
|
318543
|
+
// pendingUserMessages queue via the daemon-side mailbox file.
|
|
318544
|
+
async function sendCheckin() {
|
|
318545
|
+
const text = input.value.trim();
|
|
318546
|
+
if (!text || !streaming || !chatSessionId) return;
|
|
318547
|
+
input.value = '';
|
|
318548
|
+
input.style.height = 'auto';
|
|
318549
|
+
// Swap back to stop button after submission
|
|
318550
|
+
const checkinBtn = document.getElementById('checkin-btn');
|
|
318551
|
+
const stopBtn = document.getElementById('stop-btn');
|
|
318552
|
+
if (checkinBtn) checkinBtn.style.display = 'none';
|
|
318553
|
+
if (stopBtn) stopBtn.style.display = 'inline-block';
|
|
318554
|
+
// Find the active assistant bubble — the last assistant message
|
|
318555
|
+
// currently in the conversation. We attach the teal entries inline
|
|
318556
|
+
// with its tool dropdowns so the side-channel exchange interleaves
|
|
318557
|
+
// with the main agent's actions.
|
|
318558
|
+
const conv = document.getElementById('conversation');
|
|
318559
|
+
let toolsContainer = null;
|
|
318560
|
+
if (conv) {
|
|
318561
|
+
const lastAssistant = conv.querySelector('.msg.assistant:last-of-type');
|
|
318562
|
+
if (lastAssistant) {
|
|
318563
|
+
// Reuse the existing tools container if any, else create one
|
|
318564
|
+
toolsContainer = lastAssistant.querySelector('.tools-container');
|
|
318565
|
+
if (!toolsContainer) {
|
|
318566
|
+
toolsContainer = document.createElement('div');
|
|
318567
|
+
toolsContainer.className = 'tools-container';
|
|
318568
|
+
toolsContainer.style.cssText = 'margin:4px 0;font-size:0.7rem';
|
|
318569
|
+
lastAssistant.appendChild(toolsContainer);
|
|
318570
|
+
}
|
|
318571
|
+
} else {
|
|
318572
|
+
// No active assistant bubble — create a fresh one
|
|
318573
|
+
const md = addMessage('assistant', '');
|
|
318574
|
+
toolsContainer = document.createElement('div');
|
|
318575
|
+
toolsContainer.className = 'tools-container';
|
|
318576
|
+
toolsContainer.style.cssText = 'margin:4px 0;font-size:0.7rem';
|
|
318577
|
+
md.appendChild(toolsContainer);
|
|
318578
|
+
}
|
|
318579
|
+
}
|
|
318580
|
+
if (toolsContainer) renderCheckinEvent(toolsContainer, text);
|
|
318581
|
+
conv && maybeAutoScroll();
|
|
318582
|
+
// Fire the request — non-blocking from the user's perspective
|
|
318583
|
+
try {
|
|
318584
|
+
const r = await fetch('/v1/chat/check-in', {
|
|
318585
|
+
method: 'POST',
|
|
318586
|
+
headers: headers(),
|
|
318587
|
+
body: JSON.stringify({ session_id: chatSessionId, message: text }),
|
|
318588
|
+
});
|
|
318589
|
+
if (!r.ok) {
|
|
318590
|
+
if (toolsContainer) {
|
|
318591
|
+
const errEl = document.createElement('div');
|
|
318592
|
+
errEl.style.cssText = 'color:#b25f5f;font-size:0.6rem;padding:2px 14px';
|
|
318593
|
+
errEl.textContent = 'Check-in failed: HTTP ' + r.status;
|
|
318594
|
+
toolsContainer.appendChild(errEl);
|
|
318595
|
+
}
|
|
318596
|
+
return;
|
|
318597
|
+
}
|
|
318598
|
+
const data = await r.json();
|
|
318599
|
+
if (toolsContainer) {
|
|
318600
|
+
renderTriageResponseEvent(toolsContainer, data.acknowledgment || '', data.steering || '');
|
|
318601
|
+
conv && maybeAutoScroll();
|
|
318602
|
+
}
|
|
318603
|
+
} catch (err) {
|
|
318604
|
+
if (toolsContainer) {
|
|
318605
|
+
const errEl = document.createElement('div');
|
|
318606
|
+
errEl.style.cssText = 'color:#b25f5f;font-size:0.6rem;padding:2px 14px';
|
|
318607
|
+
errEl.textContent = 'Check-in network error: ' + (err && err.message || String(err));
|
|
318608
|
+
toolsContainer.appendChild(errEl);
|
|
318609
|
+
}
|
|
318610
|
+
}
|
|
318611
|
+
}
|
|
318612
|
+
window.sendCheckin = sendCheckin;
|
|
318613
|
+
|
|
317957
318614
|
async function sendMessage() {
|
|
317958
318615
|
const text = input.value.trim();
|
|
317959
318616
|
if (!text || streaming) return;
|
|
@@ -317977,8 +318634,11 @@ async function sendMessage() {
|
|
|
317977
318634
|
let chatTools = []; // tool calls collected during streaming
|
|
317978
318635
|
let metaInfo = null; // completion metadata
|
|
317979
318636
|
|
|
317980
|
-
// Tool calls container — shown live during streaming
|
|
318637
|
+
// Tool calls container — shown live during streaming. Class name lets
|
|
318638
|
+
// the WO-CHAT-CHECKIN sendCheckin() helper find it and attach teal
|
|
318639
|
+
// entries inline with the active assistant's tool dropdowns.
|
|
317981
318640
|
const toolsContainer = document.createElement('div');
|
|
318641
|
+
toolsContainer.className = 'tools-container';
|
|
317982
318642
|
toolsContainer.style.cssText = 'margin:4px 0;font-size:0.7rem';
|
|
317983
318643
|
msgDiv.appendChild(toolsContainer);
|
|
317984
318644
|
|
|
@@ -318066,7 +318726,7 @@ async function sendMessage() {
|
|
|
318066
318726
|
if (fullContent && !fullContent.endsWith('\\n')) fullContent += '\\n\\n';
|
|
318067
318727
|
fullContent += summaryText;
|
|
318068
318728
|
contentDiv.innerHTML = renderMarkdown(fullContent);
|
|
318069
|
-
|
|
318729
|
+
maybeAutoScroll();
|
|
318070
318730
|
}
|
|
318071
318731
|
// fall through so the dropdown still renders for inspection
|
|
318072
318732
|
}
|
|
@@ -318178,7 +318838,7 @@ async function sendMessage() {
|
|
|
318178
318838
|
details.appendChild(argsDiv);
|
|
318179
318839
|
}
|
|
318180
318840
|
toolsContainer.appendChild(details);
|
|
318181
|
-
|
|
318841
|
+
maybeAutoScroll();
|
|
318182
318842
|
continue;
|
|
318183
318843
|
}
|
|
318184
318844
|
|
|
@@ -318204,7 +318864,7 @@ async function sendMessage() {
|
|
|
318204
318864
|
if (delta) {
|
|
318205
318865
|
fullContent += delta;
|
|
318206
318866
|
contentDiv.innerHTML = renderMarkdown(fullContent);
|
|
318207
|
-
|
|
318867
|
+
maybeAutoScroll();
|
|
318208
318868
|
}
|
|
318209
318869
|
} catch {}
|
|
318210
318870
|
}
|
|
@@ -318260,14 +318920,17 @@ async function sendMessage() {
|
|
|
318260
318920
|
actions.appendChild(copyBtn);
|
|
318261
318921
|
msgDiv.appendChild(actions);
|
|
318262
318922
|
} catch (err) {
|
|
318263
|
-
|
|
318923
|
+
// Match the red left-border styling used by failed tool_result
|
|
318924
|
+
// and the stop-button. Sits inside the assistant bubble so it
|
|
318925
|
+
// visually parents to the same turn the user initiated.
|
|
318926
|
+
msgDiv.innerHTML = '<div style="background:#2a1e1e;border-left:3px solid #ff4444;color:#ff4444;padding:6px 10px 6px 14px;margin:3px 0;font-family:inherit"><div style="color:#ff4444;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:2px">\\u25B8 error</div><div style="color:#ff7777;white-space:pre-wrap;word-break:break-word">' + escHtml(err.message) + '</div></div>';
|
|
318264
318927
|
}
|
|
318265
318928
|
|
|
318266
318929
|
streaming = false;
|
|
318267
318930
|
chatAbortController = null;
|
|
318268
318931
|
document.getElementById('send-btn').style.display = 'inline-block';
|
|
318269
318932
|
document.getElementById('stop-btn').style.display = 'none';
|
|
318270
|
-
|
|
318933
|
+
maybeAutoScroll();
|
|
318271
318934
|
}
|
|
318272
318935
|
|
|
318273
318936
|
function toggleSystemPrompt() {
|
|
@@ -318566,7 +319229,7 @@ async function submitAgentTask() {
|
|
|
318566
319229
|
}
|
|
318567
319230
|
}
|
|
318568
319231
|
} catch (err) {
|
|
318569
|
-
eventsDiv.innerHTML += '<div style="color:#ff4444">
|
|
319232
|
+
eventsDiv.innerHTML += '<div style="background:#2a1e1e;border-left:3px solid #ff4444;color:#ff7777;padding:6px 10px 6px 14px;margin:3px 0"><div style="color:#ff4444;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:2px">\\u25B8 error</div>' + escHtml(err.message) + '</div>';
|
|
318570
319233
|
}
|
|
318571
319234
|
document.getElementById('agent-submit').style.display = 'inline-block';
|
|
318572
319235
|
document.getElementById('agent-abort').style.display = 'none';
|
|
@@ -319777,13 +320440,39 @@ function hideProcPopover() {
|
|
|
319777
320440
|
procPopover.classList.remove('visible');
|
|
319778
320441
|
}
|
|
319779
320442
|
|
|
320443
|
+
// Render a transient error toast in the conversation with the same
|
|
320444
|
+
// red-left-border styling used by failed tool_result entries. Replaces
|
|
320445
|
+
// alert() popups so users can see "Task not found" inline with the
|
|
320446
|
+
// rest of the agent's activity instead of in a modal.
|
|
320447
|
+
function renderInlineError(message) {
|
|
320448
|
+
const conv = document.getElementById('conversation');
|
|
320449
|
+
if (!conv) {
|
|
320450
|
+
// Fallback if conversation doesn't exist (config tab, etc.)
|
|
320451
|
+
console.error(message);
|
|
320452
|
+
return;
|
|
320453
|
+
}
|
|
320454
|
+
const wrap = document.createElement('div');
|
|
320455
|
+
wrap.className = 'msg assistant';
|
|
320456
|
+
wrap.innerHTML = '<div style="background:#2a1e1e;border-left:3px solid #ff4444;color:#ff7777;padding:6px 10px 6px 14px;margin:3px 0;font-family:inherit"><div style="color:#ff4444;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:2px">\\u25B8 error</div><div style="color:#ff7777;white-space:pre-wrap;word-break:break-word">' + escHtml(message) + '</div></div>';
|
|
320457
|
+
conv.appendChild(wrap);
|
|
320458
|
+
maybeAutoScroll();
|
|
320459
|
+
}
|
|
320460
|
+
|
|
319780
320461
|
async function killRun(id) {
|
|
319781
320462
|
try {
|
|
319782
320463
|
const r = await fetch('/v1/runs/' + encodeURIComponent(id), { method: 'DELETE', headers: headers() });
|
|
319783
320464
|
const text = await r.text().catch(() => '');
|
|
319784
|
-
|
|
320465
|
+
if (!r.ok) {
|
|
320466
|
+
// Try to extract a clean error message from the response body
|
|
320467
|
+
let msg = 'Kill ' + id + ' (HTTP ' + r.status + ')';
|
|
320468
|
+
try {
|
|
320469
|
+
const j = JSON.parse(text);
|
|
320470
|
+
if (j.error) msg = String(j.error);
|
|
320471
|
+
} catch { if (text) msg = text.slice(0, 200); }
|
|
320472
|
+
renderInlineError(msg);
|
|
320473
|
+
}
|
|
319785
320474
|
} catch (e) {
|
|
319786
|
-
|
|
320475
|
+
renderInlineError('Kill failed: ' + (e && e.message || String(e)));
|
|
319787
320476
|
}
|
|
319788
320477
|
hideProcPopover();
|
|
319789
320478
|
}
|
|
@@ -319791,10 +320480,21 @@ async function killRun(id) {
|
|
|
319791
320480
|
async function viewRun(id) {
|
|
319792
320481
|
try {
|
|
319793
320482
|
const r = await fetch('/v1/runs/' + encodeURIComponent(id), { headers: headers() });
|
|
320483
|
+
if (!r.ok) {
|
|
320484
|
+
const text = await r.text().catch(() => '');
|
|
320485
|
+
let msg = 'View ' + id + ' (HTTP ' + r.status + ')';
|
|
320486
|
+
try { const j = JSON.parse(text); if (j.error) msg = String(j.error); }
|
|
320487
|
+
catch { if (text) msg = text.slice(0, 200); }
|
|
320488
|
+
renderInlineError(msg);
|
|
320489
|
+
return;
|
|
320490
|
+
}
|
|
319794
320491
|
const j = await r.json();
|
|
320492
|
+
// Detail rendering still uses alert because the JSON dump is huge
|
|
320493
|
+
// and overflowing the conversation isn't useful — but errors fall
|
|
320494
|
+
// through to the inline path.
|
|
319795
320495
|
alert(JSON.stringify(j, null, 2).slice(0, 1500));
|
|
319796
320496
|
} catch (e) {
|
|
319797
|
-
|
|
320497
|
+
renderInlineError('Fetch failed: ' + (e && e.message || String(e)));
|
|
319798
320498
|
}
|
|
319799
320499
|
}
|
|
319800
320500
|
|
|
@@ -319962,37 +320662,36 @@ async function restoreChatSession() {
|
|
|
319962
320662
|
// assistant bubble's tools container so consecutive tool events
|
|
319963
320663
|
// attach to the same bubble (the live SSE handler does the same).
|
|
319964
320664
|
let currentAssistantTools = null;
|
|
320665
|
+
const ensureTools = () => {
|
|
320666
|
+
if (!currentAssistantTools) {
|
|
320667
|
+
const md = addMessage('assistant', '');
|
|
320668
|
+
currentAssistantTools = document.createElement('div');
|
|
320669
|
+
currentAssistantTools.className = 'tools-container';
|
|
320670
|
+
currentAssistantTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
|
|
320671
|
+
md.appendChild(currentAssistantTools);
|
|
320672
|
+
}
|
|
320673
|
+
return currentAssistantTools;
|
|
320674
|
+
};
|
|
319965
320675
|
for (const m of allMessages) {
|
|
319966
320676
|
if (m.role === 'user') {
|
|
319967
320677
|
addMessage('user', m.content);
|
|
319968
320678
|
currentAssistantTools = null;
|
|
319969
320679
|
} else if (m.role === 'assistant') {
|
|
319970
320680
|
const msgDiv = addMessage('assistant', m.content);
|
|
319971
|
-
// Stage a tools container for any tool events that come AFTER
|
|
319972
|
-
// this assistant turn (small models sometimes emit text first
|
|
319973
|
-
// then tool calls — we attach those tools to the most recent
|
|
319974
|
-
// assistant bubble for visual coherence).
|
|
319975
320681
|
currentAssistantTools = document.createElement('div');
|
|
320682
|
+
currentAssistantTools.className = 'tools-container';
|
|
319976
320683
|
currentAssistantTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
|
|
319977
320684
|
msgDiv.appendChild(currentAssistantTools);
|
|
319978
320685
|
} else if (m.role === 'tool_call') {
|
|
319979
|
-
|
|
319980
|
-
// create one with empty content so the dropdown has a parent.
|
|
319981
|
-
if (!currentAssistantTools) {
|
|
319982
|
-
const msgDiv = addMessage('assistant', '');
|
|
319983
|
-
currentAssistantTools = document.createElement('div');
|
|
319984
|
-
currentAssistantTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
|
|
319985
|
-
msgDiv.appendChild(currentAssistantTools);
|
|
319986
|
-
}
|
|
319987
|
-
renderToolCallEvent(currentAssistantTools, { tool: m.tool, args: m.args });
|
|
320686
|
+
renderToolCallEvent(ensureTools(), { tool: m.tool, args: m.args });
|
|
319988
320687
|
} else if (m.role === 'tool_result') {
|
|
319989
|
-
|
|
319990
|
-
|
|
319991
|
-
|
|
319992
|
-
|
|
319993
|
-
|
|
319994
|
-
|
|
319995
|
-
|
|
320688
|
+
renderToolResultEvent(ensureTools(), { output: m.output, success: m.success });
|
|
320689
|
+
} else if (m.role === 'user_checkin') {
|
|
320690
|
+
// WO-CHAT-CHECKIN — replay the teal user check-in entry
|
|
320691
|
+
renderCheckinEvent(ensureTools(), m.content);
|
|
320692
|
+
} else if (m.role === 'triage_response') {
|
|
320693
|
+
// WO-CHAT-CHECKIN — replay the triage sub-agent's structured response
|
|
320694
|
+
renderTriageResponseEvent(ensureTools(), m.acknowledgment || m.content || '', m.steering || '');
|
|
319996
320695
|
}
|
|
319997
320696
|
}
|
|
319998
320697
|
}
|
|
@@ -320625,352 +321324,6 @@ var init_auth_oidc = __esm({
|
|
|
320625
321324
|
}
|
|
320626
321325
|
});
|
|
320627
321326
|
|
|
320628
|
-
// packages/cli/src/api/chat-session.ts
|
|
320629
|
-
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
320630
|
-
import {
|
|
320631
|
-
existsSync as existsSync78,
|
|
320632
|
-
readFileSync as readFileSync61,
|
|
320633
|
-
readdirSync as readdirSync26,
|
|
320634
|
-
writeFileSync as writeFileSync39,
|
|
320635
|
-
renameSync as renameSync4,
|
|
320636
|
-
mkdirSync as mkdirSync46,
|
|
320637
|
-
unlinkSync as unlinkSync20
|
|
320638
|
-
} from "node:fs";
|
|
320639
|
-
import { join as join94 } from "node:path";
|
|
320640
|
-
import { homedir as homedir33 } from "node:os";
|
|
320641
|
-
function sessionsDir() {
|
|
320642
|
-
return join94(homedir33(), ".open-agents", "chat-sessions");
|
|
320643
|
-
}
|
|
320644
|
-
function sessionPath(id) {
|
|
320645
|
-
const safe = id.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
320646
|
-
return join94(sessionsDir(), `${safe}.json`);
|
|
320647
|
-
}
|
|
320648
|
-
function inFlightPath(id) {
|
|
320649
|
-
const safe = id.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
320650
|
-
return join94(sessionsDir(), `${safe}.inflight.json`);
|
|
320651
|
-
}
|
|
320652
|
-
function persistSession(s2) {
|
|
320653
|
-
try {
|
|
320654
|
-
mkdirSync46(sessionsDir(), { recursive: true });
|
|
320655
|
-
const final2 = sessionPath(s2.id);
|
|
320656
|
-
const tmp = `${final2}.tmp.${process.pid}.${Date.now()}`;
|
|
320657
|
-
writeFileSync39(tmp, JSON.stringify(s2, null, 2), "utf-8");
|
|
320658
|
-
renameSync4(tmp, final2);
|
|
320659
|
-
} catch {
|
|
320660
|
-
}
|
|
320661
|
-
}
|
|
320662
|
-
function persistInFlight(j) {
|
|
320663
|
-
try {
|
|
320664
|
-
mkdirSync46(sessionsDir(), { recursive: true });
|
|
320665
|
-
const final2 = inFlightPath(j.sessionId);
|
|
320666
|
-
const tmp = `${final2}.tmp.${process.pid}.${Date.now()}`;
|
|
320667
|
-
writeFileSync39(tmp, JSON.stringify(j, null, 2), "utf-8");
|
|
320668
|
-
renameSync4(tmp, final2);
|
|
320669
|
-
} catch {
|
|
320670
|
-
}
|
|
320671
|
-
}
|
|
320672
|
-
function deleteInFlightFile(id) {
|
|
320673
|
-
try {
|
|
320674
|
-
const p2 = inFlightPath(id);
|
|
320675
|
-
if (existsSync78(p2)) unlinkSync20(p2);
|
|
320676
|
-
} catch {
|
|
320677
|
-
}
|
|
320678
|
-
}
|
|
320679
|
-
function loadPersistedSessions() {
|
|
320680
|
-
const report = { restored: 0, staleInFlight: 0 };
|
|
320681
|
-
try {
|
|
320682
|
-
const dir = sessionsDir();
|
|
320683
|
-
if (!existsSync78(dir)) return report;
|
|
320684
|
-
const cutoff = Date.now() - SESSION_TTL_MS;
|
|
320685
|
-
for (const f2 of readdirSync26(dir)) {
|
|
320686
|
-
if (!f2.endsWith(".json") || f2.includes(".tmp.")) continue;
|
|
320687
|
-
const fp = join94(dir, f2);
|
|
320688
|
-
try {
|
|
320689
|
-
const parsed = JSON.parse(readFileSync61(fp, "utf-8"));
|
|
320690
|
-
if (f2.endsWith(".inflight.json")) {
|
|
320691
|
-
if (parsed && parsed.pid && parsed.status === "running") {
|
|
320692
|
-
try {
|
|
320693
|
-
process.kill(parsed.pid, 0);
|
|
320694
|
-
} catch {
|
|
320695
|
-
parsed.status = "failed";
|
|
320696
|
-
parsed.error = "Daemon restart while subprocess was running";
|
|
320697
|
-
parsed.completedAt = Date.now();
|
|
320698
|
-
try {
|
|
320699
|
-
writeFileSync39(fp, JSON.stringify(parsed, null, 2), "utf-8");
|
|
320700
|
-
} catch {
|
|
320701
|
-
}
|
|
320702
|
-
report.staleInFlight++;
|
|
320703
|
-
}
|
|
320704
|
-
}
|
|
320705
|
-
continue;
|
|
320706
|
-
}
|
|
320707
|
-
if (parsed && typeof parsed === "object" && parsed.id) {
|
|
320708
|
-
if ((parsed.lastActivity ?? 0) >= cutoff) {
|
|
320709
|
-
sessions.set(parsed.id, parsed);
|
|
320710
|
-
report.restored++;
|
|
320711
|
-
}
|
|
320712
|
-
}
|
|
320713
|
-
} catch {
|
|
320714
|
-
}
|
|
320715
|
-
}
|
|
320716
|
-
} catch {
|
|
320717
|
-
}
|
|
320718
|
-
return report;
|
|
320719
|
-
}
|
|
320720
|
-
function buildSystemPrompt(cwd4) {
|
|
320721
|
-
const parts = [];
|
|
320722
|
-
parts.push(
|
|
320723
|
-
"You are Open Agent (OA), an AI coding assistant running locally via Ollama. You have access to the user's workspace and can discuss code, files, and projects. Be helpful, concise, and technically precise. When asked about files or code, describe what you know from the conversation context."
|
|
320724
|
-
);
|
|
320725
|
-
parts.push(`\\nEnvironment: ${process.platform}, Node ${process.version}, CWD: ${cwd4}`);
|
|
320726
|
-
const diaryPath = join94(cwd4, ".oa", "context", "session-diary.md");
|
|
320727
|
-
if (existsSync78(diaryPath)) {
|
|
320728
|
-
try {
|
|
320729
|
-
const diary = readFileSync61(diaryPath, "utf-8").slice(0, 1e3);
|
|
320730
|
-
parts.push(`\\nPrevious session history:\\n${diary}`);
|
|
320731
|
-
} catch {
|
|
320732
|
-
}
|
|
320733
|
-
}
|
|
320734
|
-
const memDir = join94(cwd4, ".oa", "memory");
|
|
320735
|
-
if (existsSync78(memDir)) {
|
|
320736
|
-
try {
|
|
320737
|
-
const files = readdirSync26(memDir).filter((f2) => f2.endsWith(".json")).slice(0, 5);
|
|
320738
|
-
if (files.length > 0) {
|
|
320739
|
-
parts.push("\\nPersistent memory topics: " + files.map((f2) => f2.replace(".json", "")).join(", "));
|
|
320740
|
-
for (const f2 of files.slice(0, 3)) {
|
|
320741
|
-
try {
|
|
320742
|
-
const data = JSON.parse(readFileSync61(join94(memDir, f2), "utf-8"));
|
|
320743
|
-
const entries = Object.entries(data).slice(0, 3);
|
|
320744
|
-
if (entries.length > 0) {
|
|
320745
|
-
parts.push(`\\nMemory [${f2.replace(".json", "")}]: ` + entries.map(([k, v]) => `${k}: ${String(v.value ?? v).slice(0, 100)}`).join("; "));
|
|
320746
|
-
}
|
|
320747
|
-
} catch {
|
|
320748
|
-
}
|
|
320749
|
-
}
|
|
320750
|
-
}
|
|
320751
|
-
} catch {
|
|
320752
|
-
}
|
|
320753
|
-
}
|
|
320754
|
-
for (const name10 of ["AGENTS.md", "OA.md", ".open-agents.md"]) {
|
|
320755
|
-
const p2 = join94(cwd4, name10);
|
|
320756
|
-
if (existsSync78(p2)) {
|
|
320757
|
-
try {
|
|
320758
|
-
const content = readFileSync61(p2, "utf-8").slice(0, 500);
|
|
320759
|
-
parts.push(`\\nProject instructions (${name10}):\\n${content}`);
|
|
320760
|
-
} catch {
|
|
320761
|
-
}
|
|
320762
|
-
}
|
|
320763
|
-
}
|
|
320764
|
-
return parts.join("");
|
|
320765
|
-
}
|
|
320766
|
-
function getSession(sessionId, model, cwd4) {
|
|
320767
|
-
if (sessionId && sessions.has(sessionId)) {
|
|
320768
|
-
const s2 = sessions.get(sessionId);
|
|
320769
|
-
s2.lastActivity = Date.now();
|
|
320770
|
-
return s2;
|
|
320771
|
-
}
|
|
320772
|
-
if (sessionId) {
|
|
320773
|
-
try {
|
|
320774
|
-
const fp = sessionPath(sessionId);
|
|
320775
|
-
if (existsSync78(fp)) {
|
|
320776
|
-
const parsed = JSON.parse(readFileSync61(fp, "utf-8"));
|
|
320777
|
-
if (parsed && parsed.id === sessionId) {
|
|
320778
|
-
parsed.lastActivity = Date.now();
|
|
320779
|
-
sessions.set(sessionId, parsed);
|
|
320780
|
-
return parsed;
|
|
320781
|
-
}
|
|
320782
|
-
}
|
|
320783
|
-
} catch {
|
|
320784
|
-
}
|
|
320785
|
-
}
|
|
320786
|
-
const id = sessionId || randomUUID10();
|
|
320787
|
-
const systemPrompt = buildSystemPrompt(cwd4);
|
|
320788
|
-
const session = {
|
|
320789
|
-
id,
|
|
320790
|
-
messages: [{ role: "system", content: systemPrompt }],
|
|
320791
|
-
model,
|
|
320792
|
-
createdAt: Date.now(),
|
|
320793
|
-
lastActivity: Date.now(),
|
|
320794
|
-
tokensIn: 0,
|
|
320795
|
-
tokensOut: 0
|
|
320796
|
-
};
|
|
320797
|
-
sessions.set(id, session);
|
|
320798
|
-
persistSession(session);
|
|
320799
|
-
return session;
|
|
320800
|
-
}
|
|
320801
|
-
function addUserMessage(session, content) {
|
|
320802
|
-
session.messages.push({ role: "user", content });
|
|
320803
|
-
session.lastActivity = Date.now();
|
|
320804
|
-
persistSession(session);
|
|
320805
|
-
return session.messages.filter((m2) => m2.role === "system" || m2.role === "user" || m2.role === "assistant");
|
|
320806
|
-
}
|
|
320807
|
-
function addAssistantMessage(session, content) {
|
|
320808
|
-
session.messages.push({ role: "assistant", content });
|
|
320809
|
-
session.lastActivity = Date.now();
|
|
320810
|
-
persistSession(session);
|
|
320811
|
-
}
|
|
320812
|
-
function addToolCallMessage(session, tool, args) {
|
|
320813
|
-
let cappedArgs = args;
|
|
320814
|
-
try {
|
|
320815
|
-
const json = JSON.stringify(args);
|
|
320816
|
-
if (json && json.length > 4e3) {
|
|
320817
|
-
if (args && typeof args === "object" && !Array.isArray(args)) {
|
|
320818
|
-
const pruned = {};
|
|
320819
|
-
for (const [k, v] of Object.entries(args)) {
|
|
320820
|
-
if (typeof v === "string" && v.length > 1e3) {
|
|
320821
|
-
pruned[k] = v.slice(0, 800) + `… [+${v.length - 800} chars truncated for storage]`;
|
|
320822
|
-
} else {
|
|
320823
|
-
pruned[k] = v;
|
|
320824
|
-
}
|
|
320825
|
-
}
|
|
320826
|
-
cappedArgs = pruned;
|
|
320827
|
-
} else {
|
|
320828
|
-
cappedArgs = { _truncated: true, _bytes: json.length };
|
|
320829
|
-
}
|
|
320830
|
-
}
|
|
320831
|
-
} catch {
|
|
320832
|
-
}
|
|
320833
|
-
session.messages.push({
|
|
320834
|
-
role: "tool_call",
|
|
320835
|
-
content: "",
|
|
320836
|
-
tool,
|
|
320837
|
-
args: cappedArgs,
|
|
320838
|
-
ts: Date.now()
|
|
320839
|
-
});
|
|
320840
|
-
session.lastActivity = Date.now();
|
|
320841
|
-
persistSession(session);
|
|
320842
|
-
}
|
|
320843
|
-
function addToolResultMessage(session, tool, output, success) {
|
|
320844
|
-
const capped = output && output.length > 2048 ? output.slice(0, 2e3) + `… [+${output.length - 2e3} chars truncated for storage]` : output;
|
|
320845
|
-
session.messages.push({
|
|
320846
|
-
role: "tool_result",
|
|
320847
|
-
content: "",
|
|
320848
|
-
tool,
|
|
320849
|
-
output: capped,
|
|
320850
|
-
success,
|
|
320851
|
-
ts: Date.now()
|
|
320852
|
-
});
|
|
320853
|
-
session.lastActivity = Date.now();
|
|
320854
|
-
persistSession(session);
|
|
320855
|
-
}
|
|
320856
|
-
function listSessions2() {
|
|
320857
|
-
return Array.from(sessions.values()).map((s2) => ({
|
|
320858
|
-
id: s2.id,
|
|
320859
|
-
model: s2.model,
|
|
320860
|
-
messages: s2.messages.length - 1,
|
|
320861
|
-
// exclude system prompt
|
|
320862
|
-
tokensIn: s2.tokensIn,
|
|
320863
|
-
tokensOut: s2.tokensOut,
|
|
320864
|
-
lastActivity: new Date(s2.lastActivity).toISOString()
|
|
320865
|
-
}));
|
|
320866
|
-
}
|
|
320867
|
-
function deleteSession2(id) {
|
|
320868
|
-
try {
|
|
320869
|
-
const p2 = sessionPath(id);
|
|
320870
|
-
if (existsSync78(p2)) unlinkSync20(p2);
|
|
320871
|
-
} catch {
|
|
320872
|
-
}
|
|
320873
|
-
deleteInFlightFile(id);
|
|
320874
|
-
inFlight.delete(id);
|
|
320875
|
-
return sessions.delete(id);
|
|
320876
|
-
}
|
|
320877
|
-
function lookupSession(id) {
|
|
320878
|
-
const cached = sessions.get(id);
|
|
320879
|
-
if (cached) return cached;
|
|
320880
|
-
try {
|
|
320881
|
-
const fp = sessionPath(id);
|
|
320882
|
-
if (existsSync78(fp)) {
|
|
320883
|
-
const parsed = JSON.parse(readFileSync61(fp, "utf-8"));
|
|
320884
|
-
if (parsed && parsed.id === id) {
|
|
320885
|
-
sessions.set(id, parsed);
|
|
320886
|
-
return parsed;
|
|
320887
|
-
}
|
|
320888
|
-
}
|
|
320889
|
-
} catch {
|
|
320890
|
-
}
|
|
320891
|
-
return null;
|
|
320892
|
-
}
|
|
320893
|
-
function startInFlightChat(opts) {
|
|
320894
|
-
const job = {
|
|
320895
|
-
sessionId: opts.sessionId,
|
|
320896
|
-
runId: opts.runId,
|
|
320897
|
-
pid: opts.pid,
|
|
320898
|
-
startedAt: Date.now(),
|
|
320899
|
-
partialOutput: "",
|
|
320900
|
-
status: "running",
|
|
320901
|
-
tailBytes: 0
|
|
320902
|
-
};
|
|
320903
|
-
inFlight.set(opts.sessionId, job);
|
|
320904
|
-
persistInFlight(job);
|
|
320905
|
-
return job;
|
|
320906
|
-
}
|
|
320907
|
-
function appendInFlightOutput(sessionId, chunk) {
|
|
320908
|
-
const job = inFlight.get(sessionId);
|
|
320909
|
-
if (!job) return;
|
|
320910
|
-
job.partialOutput += chunk;
|
|
320911
|
-
job.tailBytes += chunk.length;
|
|
320912
|
-
if (job.partialOutput.length > PARTIAL_TAIL_BUDGET) {
|
|
320913
|
-
job.partialOutput = job.partialOutput.slice(-PARTIAL_TAIL_BUDGET);
|
|
320914
|
-
}
|
|
320915
|
-
persistInFlight(job);
|
|
320916
|
-
}
|
|
320917
|
-
function finishInFlightChat(sessionId, status, finalContent, error) {
|
|
320918
|
-
const job = inFlight.get(sessionId);
|
|
320919
|
-
if (!job) return;
|
|
320920
|
-
job.status = status;
|
|
320921
|
-
job.completedAt = Date.now();
|
|
320922
|
-
if (finalContent !== void 0) job.finalContent = finalContent;
|
|
320923
|
-
if (error !== void 0) job.error = error;
|
|
320924
|
-
persistInFlight(job);
|
|
320925
|
-
setTimeout(() => {
|
|
320926
|
-
inFlight.delete(sessionId);
|
|
320927
|
-
deleteInFlightFile(sessionId);
|
|
320928
|
-
}, 3e4).unref?.();
|
|
320929
|
-
}
|
|
320930
|
-
function getInFlightChat(sessionId) {
|
|
320931
|
-
const cached = inFlight.get(sessionId);
|
|
320932
|
-
if (cached) return cached;
|
|
320933
|
-
try {
|
|
320934
|
-
const p2 = inFlightPath(sessionId);
|
|
320935
|
-
if (existsSync78(p2)) {
|
|
320936
|
-
const parsed = JSON.parse(readFileSync61(p2, "utf-8"));
|
|
320937
|
-
if (parsed && parsed.sessionId === sessionId) {
|
|
320938
|
-
return parsed;
|
|
320939
|
-
}
|
|
320940
|
-
}
|
|
320941
|
-
} catch {
|
|
320942
|
-
}
|
|
320943
|
-
return null;
|
|
320944
|
-
}
|
|
320945
|
-
function compactSession(session, maxMessages = 40) {
|
|
320946
|
-
if (session.messages.length <= maxMessages) return;
|
|
320947
|
-
const system = session.messages[0];
|
|
320948
|
-
const recent = session.messages.slice(-20);
|
|
320949
|
-
const middle = session.messages.slice(1, -20);
|
|
320950
|
-
const summary = `[Earlier conversation: ${middle.length} messages discussed ` + middle.filter((m2) => m2.role === "user").map((m2) => m2.content.slice(0, 50)).slice(0, 5).join(", ") + "...]";
|
|
320951
|
-
session.messages = [
|
|
320952
|
-
system,
|
|
320953
|
-
{ role: "system", content: summary },
|
|
320954
|
-
...recent
|
|
320955
|
-
];
|
|
320956
|
-
}
|
|
320957
|
-
var sessions, inFlight, SESSION_TTL_MS, PARTIAL_TAIL_BUDGET;
|
|
320958
|
-
var init_chat_session = __esm({
|
|
320959
|
-
"packages/cli/src/api/chat-session.ts"() {
|
|
320960
|
-
"use strict";
|
|
320961
|
-
sessions = /* @__PURE__ */ new Map();
|
|
320962
|
-
inFlight = /* @__PURE__ */ new Map();
|
|
320963
|
-
SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
320964
|
-
setInterval(() => {
|
|
320965
|
-
const now = Date.now();
|
|
320966
|
-
for (const [id, s2] of sessions) {
|
|
320967
|
-
if (now - s2.lastActivity > SESSION_TTL_MS) sessions.delete(id);
|
|
320968
|
-
}
|
|
320969
|
-
}, 5 * 60 * 1e3);
|
|
320970
|
-
PARTIAL_TAIL_BUDGET = 8 * 1024;
|
|
320971
|
-
}
|
|
320972
|
-
});
|
|
320973
|
-
|
|
320974
321327
|
// packages/cli/src/api/usage-tracker.ts
|
|
320975
321328
|
import { mkdirSync as mkdirSync47, readFileSync as readFileSync62, writeFileSync as writeFileSync40, existsSync as existsSync79 } from "node:fs";
|
|
320976
321329
|
import { join as join95 } from "node:path";
|
|
@@ -323923,8 +324276,12 @@ async function handleRequest(req2, res, ollamaUrl, verbose) {
|
|
|
323923
324276
|
const freeMem = os8.freemem();
|
|
323924
324277
|
const totalMem = os8.totalmem();
|
|
323925
324278
|
const ramUsedPct = Math.round((1 - freeMem / totalMem) * 100);
|
|
323926
|
-
const
|
|
323927
|
-
|
|
324279
|
+
const { instantaneousCpuPct: instantaneousCpuPct2 } = await Promise.resolve().then(() => (init_system_metrics(), system_metrics_exports));
|
|
324280
|
+
let cpuPct = instantaneousCpuPct2();
|
|
324281
|
+
if (cpuPct < 0) {
|
|
324282
|
+
const cpuLoad = os8.loadavg()[0] ?? 0;
|
|
324283
|
+
cpuPct = Math.max(0, Math.min(100, Math.round(cpuLoad / os8.cpus().length * 100)));
|
|
324284
|
+
}
|
|
323928
324285
|
let gpuUtil = [];
|
|
323929
324286
|
try {
|
|
323930
324287
|
const util2 = es("nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total --format=csv,noheader,nounits", { encoding: "utf8", timeout: 3e3, stdio: "pipe" });
|
|
@@ -324407,6 +324764,106 @@ ${historyLines}
|
|
|
324407
324764
|
jsonResponse(res, 200, { sessions: listSessions2() });
|
|
324408
324765
|
return;
|
|
324409
324766
|
}
|
|
324767
|
+
if (pathname === "/v1/chat/check-in" && method === "POST") {
|
|
324768
|
+
if (!checkAuth(req2, res, "run")) {
|
|
324769
|
+
status = 401;
|
|
324770
|
+
return;
|
|
324771
|
+
}
|
|
324772
|
+
const body = await parseJsonBody(req2);
|
|
324773
|
+
if (!body || typeof body.session_id !== "string" || typeof body.message !== "string") {
|
|
324774
|
+
jsonResponse(res, 400, { error: "Required: { session_id: string, message: string }" });
|
|
324775
|
+
return;
|
|
324776
|
+
}
|
|
324777
|
+
const checkinSession = lookupSession(body.session_id);
|
|
324778
|
+
if (!checkinSession) {
|
|
324779
|
+
jsonResponse(res, 404, { error: "Session not found", session_id: body.session_id });
|
|
324780
|
+
return;
|
|
324781
|
+
}
|
|
324782
|
+
try {
|
|
324783
|
+
addCheckinMessage(checkinSession, body.message);
|
|
324784
|
+
} catch {
|
|
324785
|
+
}
|
|
324786
|
+
const truncatedInput = body.message.length > 80 ? body.message.slice(0, 77) + "..." : body.message;
|
|
324787
|
+
let acknowledgment = `Acknowledged: ${truncatedInput}`;
|
|
324788
|
+
let steering = body.message;
|
|
324789
|
+
try {
|
|
324790
|
+
const cfg = loadConfig();
|
|
324791
|
+
const { OllamaAgenticBackend: OllamaAgenticBackend2, AgenticRunner: AgenticRunner2 } = await Promise.resolve().then(() => (init_dist8(), dist_exports4));
|
|
324792
|
+
const triageBackend = new OllamaAgenticBackend2(cfg.backendUrl, cfg.model, cfg.apiKey);
|
|
324793
|
+
const triage = new AgenticRunner2(triageBackend, {
|
|
324794
|
+
maxTurns: 3,
|
|
324795
|
+
maxTokens: 512,
|
|
324796
|
+
temperature: 0.3,
|
|
324797
|
+
requestTimeoutMs: 15e3,
|
|
324798
|
+
taskTimeoutMs: 3e4,
|
|
324799
|
+
streamEnabled: false
|
|
324800
|
+
});
|
|
324801
|
+
triage.registerTool({
|
|
324802
|
+
name: "task_complete",
|
|
324803
|
+
description: "Return the structured triage result.",
|
|
324804
|
+
parameters: {
|
|
324805
|
+
type: "object",
|
|
324806
|
+
properties: {
|
|
324807
|
+
acknowledgment: { type: "string", description: "Short spoken-style ack (1 sentence) referencing what the user asked." },
|
|
324808
|
+
summary: { type: "string", description: "Expanded steering instruction for the main agent." }
|
|
324809
|
+
},
|
|
324810
|
+
required: ["summary", "acknowledgment"]
|
|
324811
|
+
},
|
|
324812
|
+
async execute(args) {
|
|
324813
|
+
return { success: true, output: JSON.stringify(args) };
|
|
324814
|
+
}
|
|
324815
|
+
});
|
|
324816
|
+
const recentMessages = checkinSession.messages.filter((m2) => m2.role === "user" || m2.role === "assistant").slice(-6).map((m2) => `${m2.role}: ${m2.content.slice(0, 200)}`).join("\n");
|
|
324817
|
+
const triagePrompt = [
|
|
324818
|
+
"The user has typed a mid-run check-in message while the main agent is working.",
|
|
324819
|
+
"Your job: expand their raw input into a clear steering instruction the main agent can act on,",
|
|
324820
|
+
"and produce a brief spoken acknowledgment that REFERENCES what they actually said.",
|
|
324821
|
+
"",
|
|
324822
|
+
`Recent conversation:
|
|
324823
|
+
${recentMessages || "(none)"}`,
|
|
324824
|
+
"",
|
|
324825
|
+
`User check-in: "${body.message}"`,
|
|
324826
|
+
"",
|
|
324827
|
+
"Call task_complete with:",
|
|
324828
|
+
"- acknowledgment: 1 sentence specific to their request (no generic 'Got it')",
|
|
324829
|
+
"- summary: an expanded instruction for the main agent (~2-4 sentences)"
|
|
324830
|
+
].join("\n");
|
|
324831
|
+
const result = await triage.run(triagePrompt, "Triage check-in handler");
|
|
324832
|
+
try {
|
|
324833
|
+
const parsed = JSON.parse(result.summary || "{}");
|
|
324834
|
+
if (parsed.acknowledgment) acknowledgment = String(parsed.acknowledgment);
|
|
324835
|
+
if (parsed.summary) steering = String(parsed.summary);
|
|
324836
|
+
} catch {
|
|
324837
|
+
if (result.summary && result.summary.length > 10) steering = result.summary;
|
|
324838
|
+
}
|
|
324839
|
+
} catch (err) {
|
|
324840
|
+
}
|
|
324841
|
+
try {
|
|
324842
|
+
addTriageResponseMessage(checkinSession, acknowledgment, steering);
|
|
324843
|
+
} catch {
|
|
324844
|
+
}
|
|
324845
|
+
const markedSteering = `[USER CHECK-IN — incorporate during current run]
|
|
324846
|
+
${steering}`;
|
|
324847
|
+
try {
|
|
324848
|
+
appendCheckin(body.session_id, markedSteering);
|
|
324849
|
+
} catch {
|
|
324850
|
+
}
|
|
324851
|
+
try {
|
|
324852
|
+
publishEvent("memory.written", {
|
|
324853
|
+
kind: "chat_checkin",
|
|
324854
|
+
session_id: body.session_id,
|
|
324855
|
+
ack: acknowledgment.slice(0, 120)
|
|
324856
|
+
}, { subject: body.session_id });
|
|
324857
|
+
} catch {
|
|
324858
|
+
}
|
|
324859
|
+
jsonResponse(res, 200, {
|
|
324860
|
+
session_id: body.session_id,
|
|
324861
|
+
acknowledgment,
|
|
324862
|
+
steering,
|
|
324863
|
+
delivered: true
|
|
324864
|
+
});
|
|
324865
|
+
return;
|
|
324866
|
+
}
|
|
324410
324867
|
const chatSessionMatch = pathname.match(/^\/v1\/chat\/sessions\/([^/]+)$/);
|
|
324411
324868
|
if (chatSessionMatch) {
|
|
324412
324869
|
const sid = decodeURIComponent(chatSessionMatch[1]);
|
|
@@ -326049,6 +326506,23 @@ RULES:
|
|
|
326049
326506
|
});
|
|
326050
326507
|
runner.setWorkingDirectory(repoRoot);
|
|
326051
326508
|
_activeRunnerRef = runner;
|
|
326509
|
+
let _checkinPoller = null;
|
|
326510
|
+
try {
|
|
326511
|
+
const oaSessionId = process.env["OA_SESSION_ID"];
|
|
326512
|
+
if (oaSessionId) {
|
|
326513
|
+
const { drainCheckins: drainCheckins2 } = (init_chat_session(), __toCommonJS(chat_session_exports));
|
|
326514
|
+
_checkinPoller = setInterval(() => {
|
|
326515
|
+
try {
|
|
326516
|
+
const pending = drainCheckins2(oaSessionId);
|
|
326517
|
+
for (const steering of pending) {
|
|
326518
|
+
runner.injectUserMessage(steering);
|
|
326519
|
+
}
|
|
326520
|
+
} catch {
|
|
326521
|
+
}
|
|
326522
|
+
}, 1500);
|
|
326523
|
+
}
|
|
326524
|
+
} catch {
|
|
326525
|
+
}
|
|
326052
326526
|
const tools = buildTools(repoRoot, config, contextWindowSize, modelTier);
|
|
326053
326527
|
if (contextWindowSize && contextWindowSize > 0) {
|
|
326054
326528
|
for (const tool of tools) {
|
|
@@ -326925,6 +327399,13 @@ When done, either call task_complete with your answer, or use FINAL_VAR(variable
|
|
|
326925
327399
|
if (backend && typeof backend.stop === "function") {
|
|
326926
327400
|
backend.stop();
|
|
326927
327401
|
}
|
|
327402
|
+
if (_checkinPoller) {
|
|
327403
|
+
try {
|
|
327404
|
+
clearInterval(_checkinPoller);
|
|
327405
|
+
} catch {
|
|
327406
|
+
}
|
|
327407
|
+
_checkinPoller = null;
|
|
327408
|
+
}
|
|
326928
327409
|
});
|
|
326929
327410
|
return { runner, promise, filesTouched, get toolCallCount() {
|
|
326930
327411
|
return toolSequence.length;
|
|
@@ -330134,20 +330615,8 @@ ${result.text}`;
|
|
|
330134
330615
|
`- summary: expanded instruction for the main agent (e.g. "The user wants X instead of Y. Adjust your approach to prioritize Z. Specifically, they are asking you to...")`
|
|
330135
330616
|
].join("\n");
|
|
330136
330617
|
const result = await steerAgent.run(steerPrompt, "Steering sub-agent — interpret user input and produce instruction.");
|
|
330137
|
-
const
|
|
330138
|
-
|
|
330139
|
-
`On it, shifting approach.`,
|
|
330140
|
-
`Understood, recalibrating.`,
|
|
330141
|
-
`Right, let me rework that.`,
|
|
330142
|
-
`Okay, taking a different angle.`,
|
|
330143
|
-
`Heard you, pivoting now.`,
|
|
330144
|
-
`Sure thing, rethinking this.`,
|
|
330145
|
-
`Copy that, adapting.`,
|
|
330146
|
-
`Makes sense, re-routing.`,
|
|
330147
|
-
`Roger, new plan.`
|
|
330148
|
-
];
|
|
330149
|
-
const fbIdx = Math.floor(Math.random() * STEER_FALLBACKS.length);
|
|
330150
|
-
let acknowledgment = STEER_FALLBACKS[fbIdx];
|
|
330618
|
+
const truncatedInputForAck = input.length > 80 ? input.slice(0, 77) + "..." : input;
|
|
330619
|
+
let acknowledgment = `Acknowledged: ${truncatedInputForAck}`;
|
|
330151
330620
|
let steering = input;
|
|
330152
330621
|
try {
|
|
330153
330622
|
const parsed = JSON.parse(result.summary || "{}");
|