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.
Files changed (2) hide show
  1. package/dist/index.js +959 -490
  2. 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: Math.min(100, Math.round(l1 / cores * 100)),
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 mkdirSync44, appendFileSync as appendFileSync5, readFileSync as readFileSync58, existsSync as existsSync74 } from "node:fs";
313926
- import { join as join91 } from "node:path";
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 = join91(oaDir, "audit");
313929
- auditFile = join91(auditDir, "audit.jsonl");
314394
+ auditDir = join92(oaDir, "audit");
314395
+ auditFile = join92(auditDir, "audit.jsonl");
313930
314396
  try {
313931
- mkdirSync44(auditDir, { recursive: true });
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 || !existsSync74(auditFile)) return [];
314426
+ if (!initialized || !existsSync75(auditFile)) return [];
313961
314427
  try {
313962
- const raw = readFileSync58(auditFile, "utf-8");
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 existsSync75, mkdirSync as mkdirSync45, statSync as statSync21 } from "node:fs";
314000
- import { dirname as dirname25 } from "node:path";
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
- mkdirSync45(dirname25(outputPath), { recursive: true });
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 (!existsSync75(this.path)) {
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 existsSync76, readFileSync as readFileSync59, readdirSync as readdirSync24, statSync as statSync22 } from "node:fs";
314298
- import { join as join92 } from "node:path";
314299
- import { homedir as homedir31 } from "node:os";
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 && existsSync76(join92(envRoot, "package.json"))) {
314770
+ if (envRoot && existsSync77(join93(envRoot, "package.json"))) {
314305
314771
  _cachedAiwgRoot = envRoot;
314306
314772
  return envRoot;
314307
314773
  }
314308
- const shareDir = join92(homedir31(), ".local", "share", "ai-writing-guide");
314309
- if (existsSync76(join92(shareDir, "agentic"))) {
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 = join92(globalRoot, "aiwg");
314320
- if (existsSync76(join92(candidate, "package.json"))) {
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 (existsSync76(join92(p2, "package.json"))) {
314797
+ if (existsSync77(join93(p2, "package.json"))) {
314332
314798
  _cachedAiwgRoot = p2;
314333
314799
  return p2;
314334
314800
  }
314335
314801
  }
314336
314802
  const versionDirs = [
314337
- join92(homedir31(), ".nvm", "versions", "node"),
314338
- join92(homedir31(), ".local", "share", "fnm", "node-versions")
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 (!existsSync76(vdir)) continue;
314807
+ if (!existsSync77(vdir)) continue;
314342
314808
  try {
314343
- for (const ver of readdirSync24(vdir)) {
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 = join92(vdir, ver, prefix);
314346
- if (existsSync76(join92(cand, "package.json"))) {
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 = join92(cur, "..");
314365
- const pj = join92(cur, "package.json");
314366
- if (existsSync76(pj)) {
314830
+ cur = join93(cur, "..");
314831
+ const pj = join93(cur, "package.json");
314832
+ if (existsSync77(pj)) {
314367
314833
  try {
314368
- const pkg = JSON.parse(readFileSync59(pj, "utf-8"));
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 = join92(root, "agentic", "code", "frameworks");
314391
- if (!existsSync76(frameworksDir)) {
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 readdirSync24(frameworksDir)) {
314397
- const p2 = join92(frameworksDir, name10);
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 readdirSync24(dir, { withFileTypes: true })) {
314889
+ for (const e2 of readdirSync25(dir, { withFileTypes: true })) {
314424
314890
  if (e2.name.startsWith(".") || e2.name === "node_modules") continue;
314425
- const p2 = join92(dir, e2.name);
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 = join92(dir, candidate);
314456
- if (!existsSync76(p2)) continue;
314921
+ const p2 = join93(dir, candidate);
314922
+ if (!existsSync77(p2)) continue;
314457
314923
  try {
314458
- const txt = readFileSync59(p2, "utf-8");
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
- join92(root, "agentic", "code", "frameworks"),
314481
- join92(root, "agentic", "code", "addons"),
314482
- join92(root, "plugins")
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 (!existsSync76(wr)) continue;
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 readdirSync24(dir, { withFileTypes: true })) {
314960
+ for (const e2 of readdirSync25(dir, { withFileTypes: true })) {
314495
314961
  if (e2.name.startsWith(".") || e2.name === "node_modules") continue;
314496
- const p2 = join92(dir, e2.name);
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 = readFileSync59(p2, "utf-8");
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 (!existsSync76(path5)) return null;
314548
- const raw = readFileSync59(path5, "utf-8");
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 = join92(root, "agentic", "code", "addons");
314562
- if (!existsSync76(addonsDir)) {
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 readdirSync24(addonsDir)) {
314568
- const p2 = join92(addonsDir, name10);
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 existsSync77, readFileSync as readFileSync60, readdirSync as readdirSync25, statSync as statSync23 } from "node:fs";
315051
- import { join as join93, resolve as pathResolve } from "node:path";
315052
- import { homedir as homedir32 } from "node:os";
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
- join93(homedir32(), ".local", "share", "ai-writing-guide")
315723
+ join94(homedir33(), ".local", "share", "ai-writing-guide")
315258
315724
  ];
315259
315725
  const out = [];
315260
315726
  for (const root of roots) {
315261
- if (!existsSync77(root)) continue;
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 readdirSync25(dir, { withFileTypes: true })) {
315736
+ for (const e2 of readdirSync26(dir, { withFileTypes: true })) {
315271
315737
  if (e2.name.startsWith(".") || e2.name === "node_modules") continue;
315272
- const p2 = join93(dir, e2.name);
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 = readFileSync60(p2, "utf-8").slice(0, 2e3);
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 = join93(homedir32(), ".open-agents", "memory.db");
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 (!existsSync77(resolved)) {
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 = readFileSync60(resolved, "utf-8");
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
- join93(process.cwd(), ".oa", "nexus-peer-state.json"),
315983
- join93(homedir32(), ".open-agents", "nexus-peer-cache.json")
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 (!existsSync77(p2)) continue;
316453
+ if (!existsSync78(p2)) continue;
315988
316454
  try {
315989
- const raw = readFileSync60(p2, "utf-8");
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 = join93(homedir32(), ".open-agents", "agent-name");
316017
- if (existsSync77(p2)) return readFileSync60(p2, "utf-8").trim();
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
- join93(homedir32(), ".open-agents", "sponsor-cache.json"),
316027
- join93(homedir32(), ".open-agents", "sponsors.json")
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 (!existsSync77(p2)) continue;
316497
+ if (!existsSync78(p2)) continue;
316032
316498
  try {
316033
- const raw = JSON.parse(readFileSync60(p2, "utf-8"));
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 = join93(process.cwd(), ".oa", "jobs", `${runId}.json`);
316103
- if (!existsSync77(jobPath)) {
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(readFileSync60(jobPath, "utf-8"));
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 = homedir32();
316717
+ const home = homedir33();
316252
316718
  sendJson(res, 200, {
316253
316719
  engines: [
316254
- { name: "dream", state_file: join93(process.cwd(), ".oa", "dreams"), controllable_via: "SSE + slash commands" },
316255
- { name: "bless", state_file: join93(process.cwd(), ".oa", "bless-state.json"), controllable_via: "slash commands" },
316256
- { name: "call", state_file: join93(process.cwd(), ".oa", "call-state.json"), controllable_via: "slash commands" },
316257
- { name: "listen", state_file: join93(process.cwd(), ".oa", "listen-state.json"), controllable_via: "slash commands" },
316258
- { name: "telegram", state_file: join93(home, ".open-agents", "telegram-state.json"), controllable_via: "slash commands" },
316259
- { name: "expose", state_file: join93(process.cwd(), ".oa", "expose-state.json"), controllable_via: "/expose commands" },
316260
- { name: "nexus", state_file: join93(home, ".open-agents", "nexus-peer-cache.json"), controllable_via: "/nexus commands" },
316261
- { name: "ipfs", state_file: join93(process.cwd(), ".oa", "ipfs"), controllable_via: "slash commands" }
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 join93(homedir32(), ".open-agents", "aims");
316810
+ return join94(homedir33(), ".open-agents", "aims");
316345
316811
  }
316346
316812
  function readAimsFile(name10, fallback) {
316347
316813
  try {
316348
- const p2 = join93(aimsDir(), name10);
316349
- if (existsSync77(p2)) return JSON.parse(readFileSync60(p2, "utf-8"));
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 = join93(dir, name10);
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
- join93(homedir32(), ".open-agents", "sponsor-cache.json")
317154
+ join94(homedir33(), ".open-agents", "sponsor-cache.json")
316689
317155
  ];
316690
317156
  for (const p2 of sponsorPaths) {
316691
- if (!existsSync77(p2)) continue;
317157
+ if (!existsSync78(p2)) continue;
316692
317158
  try {
316693
- const raw = JSON.parse(readFileSync60(p2, "utf-8"));
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 &darr;</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
- // Auto-resize textarea
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) { e.preventDefault(); sendMessage(); }
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
- conv.scrollTop = conv.scrollHeight;
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
- conv.scrollTop = conv.scrollHeight;
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
- conv.scrollTop = conv.scrollHeight;
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
- conv.scrollTop = conv.scrollHeight;
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
- msgDiv.innerHTML = '<span style="color:#ff4444">Error: ' + escHtml(err.message) + '</span>';
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
- conv.scrollTop = conv.scrollHeight;
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">Error: ' + escHtml(err.message) + '</div>';
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
- alert('Kill ' + id + ': ' + r.status + (text ? ' — ' + text.slice(0, 100) : ''));
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
- alert('Kill failed: ' + e.message);
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
- alert('Fetch failed: ' + e.message);
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
- // If no assistant bubble yet (tool fired before any text),
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
- if (!currentAssistantTools) {
319990
- const msgDiv = addMessage('assistant', '');
319991
- currentAssistantTools = document.createElement('div');
319992
- currentAssistantTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
319993
- msgDiv.appendChild(currentAssistantTools);
319994
- }
319995
- renderToolResultEvent(currentAssistantTools, { output: m.output, success: m.success });
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 cpuLoad = os8.loadavg()[0] ?? 0;
323927
- const cpuPct = Math.min(100, Math.round(cpuLoad / os8.cpus().length * 100));
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 STEER_FALLBACKS = [
330138
- `Noted, changing course.`,
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 || "{}");