open-agents-ai 0.187.224 → 0.187.225

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 +1200 -797
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -313404,210 +313404,643 @@ var init_task_manager_singleton = __esm({
313404
313404
  }
313405
313405
  });
313406
313406
 
313407
- // packages/cli/src/tui/mouse-filter.ts
313408
- var mouse_filter_exports = {};
313409
- __export(mouse_filter_exports, {
313410
- MouseFilterStream: () => MouseFilterStream
313407
+ // packages/cli/src/api/chat-session.ts
313408
+ var chat_session_exports = {};
313409
+ __export(chat_session_exports, {
313410
+ addAssistantMessage: () => addAssistantMessage,
313411
+ addCheckinMessage: () => addCheckinMessage,
313412
+ addToolCallMessage: () => addToolCallMessage,
313413
+ addToolResultMessage: () => addToolResultMessage,
313414
+ addTriageResponseMessage: () => addTriageResponseMessage,
313415
+ addUserMessage: () => addUserMessage,
313416
+ appendCheckin: () => appendCheckin,
313417
+ appendInFlightOutput: () => appendInFlightOutput,
313418
+ compactSession: () => compactSession,
313419
+ deleteSession: () => deleteSession2,
313420
+ drainCheckins: () => drainCheckins,
313421
+ finishInFlightChat: () => finishInFlightChat,
313422
+ getInFlightChat: () => getInFlightChat,
313423
+ getSession: () => getSession,
313424
+ listSessions: () => listSessions2,
313425
+ loadPersistedSessions: () => loadPersistedSessions,
313426
+ lookupSession: () => lookupSession,
313427
+ startInFlightChat: () => startInFlightChat,
313428
+ trackSessionTokens: () => trackSessionTokens
313411
313429
  });
313412
- import { Transform } from "node:stream";
313413
- var MouseFilterStream;
313414
- var init_mouse_filter = __esm({
313415
- "packages/cli/src/tui/mouse-filter.ts"() {
313416
- "use strict";
313417
- MouseFilterStream = class extends Transform {
313418
- buffer = "";
313419
- onScroll = null;
313420
- onActivity = null;
313421
- onPointer = null;
313422
- flushTimer = null;
313423
- constructor(scrollHandler, activityHandler, pointerHandler) {
313424
- super();
313425
- this.onScroll = scrollHandler;
313426
- this.onActivity = activityHandler ?? null;
313427
- this.onPointer = pointerHandler ?? null;
313428
- }
313429
- _transform(chunk, _encoding, callback) {
313430
- this.buffer += chunk.toString();
313431
- this.processBuffer(callback);
313432
- }
313433
- processBuffer(callback) {
313434
- let output = "";
313435
- let i2 = 0;
313436
- while (i2 < this.buffer.length) {
313437
- if (this.buffer[i2] === "\x1B") {
313438
- const remaining = this.buffer.slice(i2);
313439
- const mouseMatch = remaining.match(/^\x1B\[<(\d+);(\d+);(\d+)([Mm])/);
313440
- if (mouseMatch) {
313441
- const btn = parseInt(mouseMatch[1]);
313442
- const col = parseInt(mouseMatch[2]);
313443
- const row = parseInt(mouseMatch[3]);
313444
- const suffix = mouseMatch[4];
313445
- if ((btn === 64 || btn === 96) && this.onScroll) this.onScroll("up", 3, row);
313446
- else if ((btn === 65 || btn === 97) && this.onScroll) this.onScroll("down", 3, row);
313447
- else if (this.onPointer) {
313448
- if ((btn === 0 || btn === 1 || btn === 2) && suffix === "M") this.onPointer("press", col, row);
313449
- else if (btn >= 32 && btn <= 35 && suffix === "M") this.onPointer("drag", col, row);
313450
- else if (suffix === "m") this.onPointer("release", col, row);
313451
- }
313452
- if (this.onActivity) this.onActivity();
313453
- i2 += mouseMatch[0].length;
313454
- continue;
313455
- }
313456
- if (remaining.startsWith("\x1B[M")) {
313457
- if (remaining.length >= 6) {
313458
- if (this.onActivity) this.onActivity();
313459
- i2 += 6;
313460
- continue;
313430
+ import { randomUUID as randomUUID10 } from "node:crypto";
313431
+ import {
313432
+ existsSync as existsSync74,
313433
+ readFileSync as readFileSync58,
313434
+ readdirSync as readdirSync24,
313435
+ writeFileSync as writeFileSync39,
313436
+ renameSync as renameSync4,
313437
+ mkdirSync as mkdirSync44,
313438
+ unlinkSync as unlinkSync20
313439
+ } from "node:fs";
313440
+ import { join as join91 } from "node:path";
313441
+ import { homedir as homedir31 } from "node:os";
313442
+ function sessionsDir() {
313443
+ return join91(homedir31(), ".open-agents", "chat-sessions");
313444
+ }
313445
+ function sessionPath(id) {
313446
+ const safe = id.replace(/[^a-zA-Z0-9_.-]/g, "_");
313447
+ return join91(sessionsDir(), `${safe}.json`);
313448
+ }
313449
+ function inFlightPath(id) {
313450
+ const safe = id.replace(/[^a-zA-Z0-9_.-]/g, "_");
313451
+ return join91(sessionsDir(), `${safe}.inflight.json`);
313452
+ }
313453
+ function persistSession(s2) {
313454
+ try {
313455
+ mkdirSync44(sessionsDir(), { recursive: true });
313456
+ const final2 = sessionPath(s2.id);
313457
+ const tmp = `${final2}.tmp.${process.pid}.${Date.now()}`;
313458
+ writeFileSync39(tmp, JSON.stringify(s2, null, 2), "utf-8");
313459
+ renameSync4(tmp, final2);
313460
+ } catch {
313461
+ }
313462
+ }
313463
+ function persistInFlight(j) {
313464
+ try {
313465
+ mkdirSync44(sessionsDir(), { recursive: true });
313466
+ const final2 = inFlightPath(j.sessionId);
313467
+ const tmp = `${final2}.tmp.${process.pid}.${Date.now()}`;
313468
+ writeFileSync39(tmp, JSON.stringify(j, null, 2), "utf-8");
313469
+ renameSync4(tmp, final2);
313470
+ } catch {
313471
+ }
313472
+ }
313473
+ function deleteInFlightFile(id) {
313474
+ try {
313475
+ const p2 = inFlightPath(id);
313476
+ if (existsSync74(p2)) unlinkSync20(p2);
313477
+ } catch {
313478
+ }
313479
+ }
313480
+ function loadPersistedSessions() {
313481
+ const report = { restored: 0, staleInFlight: 0 };
313482
+ try {
313483
+ const dir = sessionsDir();
313484
+ if (!existsSync74(dir)) return report;
313485
+ const cutoff = Date.now() - SESSION_TTL_MS;
313486
+ for (const f2 of readdirSync24(dir)) {
313487
+ if (!f2.endsWith(".json") || f2.includes(".tmp.")) continue;
313488
+ const fp = join91(dir, f2);
313489
+ try {
313490
+ const parsed = JSON.parse(readFileSync58(fp, "utf-8"));
313491
+ if (f2.endsWith(".inflight.json")) {
313492
+ if (parsed && parsed.pid && parsed.status === "running") {
313493
+ try {
313494
+ process.kill(parsed.pid, 0);
313495
+ } catch {
313496
+ parsed.status = "failed";
313497
+ parsed.error = "Daemon restart while subprocess was running";
313498
+ parsed.completedAt = Date.now();
313499
+ try {
313500
+ writeFileSync39(fp, JSON.stringify(parsed, null, 2), "utf-8");
313501
+ } catch {
313461
313502
  }
313462
- break;
313463
- }
313464
- if (remaining.startsWith("\x1B[<") && remaining.length < 15) {
313465
- break;
313466
- }
313467
- if (remaining.startsWith("\x1B[") && remaining.length === 2) {
313468
- break;
313469
- }
313470
- if (remaining.length === 1) {
313471
- break;
313503
+ report.staleInFlight++;
313472
313504
  }
313473
313505
  }
313474
- output += this.buffer[i2];
313475
- i2++;
313506
+ continue;
313476
313507
  }
313477
- this.buffer = this.buffer.slice(i2);
313478
- if (output.length > 0) {
313479
- this.push(output);
313508
+ if (parsed && typeof parsed === "object" && parsed.id) {
313509
+ if ((parsed.lastActivity ?? 0) >= cutoff) {
313510
+ sessions.set(parsed.id, parsed);
313511
+ report.restored++;
313512
+ }
313480
313513
  }
313481
- if (this.buffer.length > 0) {
313482
- if (this.flushTimer) clearTimeout(this.flushTimer);
313483
- this.flushTimer = setTimeout(() => {
313484
- if (this.buffer.length > 0) {
313485
- if (this.buffer.startsWith("\x1B[<") || this.buffer.startsWith("\x1B[M")) {
313486
- this.buffer = "";
313487
- } else if (this.buffer === "\x1B" || this.buffer === "\x1B[") {
313488
- this.push(this.buffer);
313489
- this.buffer = "";
313490
- } else {
313491
- this.push(this.buffer);
313492
- this.buffer = "";
313493
- }
313514
+ } catch {
313515
+ }
313516
+ }
313517
+ } catch {
313518
+ }
313519
+ return report;
313520
+ }
313521
+ function buildSystemPrompt(cwd4) {
313522
+ const parts = [];
313523
+ parts.push(
313524
+ "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."
313525
+ );
313526
+ parts.push(`\\nEnvironment: ${process.platform}, Node ${process.version}, CWD: ${cwd4}`);
313527
+ const diaryPath = join91(cwd4, ".oa", "context", "session-diary.md");
313528
+ if (existsSync74(diaryPath)) {
313529
+ try {
313530
+ const diary = readFileSync58(diaryPath, "utf-8").slice(0, 1e3);
313531
+ parts.push(`\\nPrevious session history:\\n${diary}`);
313532
+ } catch {
313533
+ }
313534
+ }
313535
+ const memDir = join91(cwd4, ".oa", "memory");
313536
+ if (existsSync74(memDir)) {
313537
+ try {
313538
+ const files = readdirSync24(memDir).filter((f2) => f2.endsWith(".json")).slice(0, 5);
313539
+ if (files.length > 0) {
313540
+ parts.push("\\nPersistent memory topics: " + files.map((f2) => f2.replace(".json", "")).join(", "));
313541
+ for (const f2 of files.slice(0, 3)) {
313542
+ try {
313543
+ const data = JSON.parse(readFileSync58(join91(memDir, f2), "utf-8"));
313544
+ const entries = Object.entries(data).slice(0, 3);
313545
+ if (entries.length > 0) {
313546
+ parts.push(`\\nMemory [${f2.replace(".json", "")}]: ` + entries.map(([k, v]) => `${k}: ${String(v.value ?? v).slice(0, 100)}`).join("; "));
313494
313547
  }
313495
- }, 50);
313548
+ } catch {
313549
+ }
313496
313550
  }
313497
- callback();
313498
313551
  }
313499
- _flush(callback) {
313500
- if (this.flushTimer) {
313501
- clearTimeout(this.flushTimer);
313502
- this.flushTimer = null;
313503
- }
313504
- if (this.buffer.length > 0) {
313505
- this.push(this.buffer);
313506
- this.buffer = "";
313507
- }
313508
- callback();
313552
+ } catch {
313553
+ }
313554
+ }
313555
+ for (const name10 of ["AGENTS.md", "OA.md", ".open-agents.md"]) {
313556
+ const p2 = join91(cwd4, name10);
313557
+ if (existsSync74(p2)) {
313558
+ try {
313559
+ const content = readFileSync58(p2, "utf-8").slice(0, 500);
313560
+ parts.push(`\\nProject instructions (${name10}):\\n${content}`);
313561
+ } catch {
313509
313562
  }
313510
- };
313563
+ }
313511
313564
  }
313512
- });
313513
-
313514
- // packages/cli/src/tui/direct-input.ts
313515
- var direct_input_exports = {};
313516
- __export(direct_input_exports, {
313517
- DirectInput: () => DirectInput
313518
- });
313519
- import { EventEmitter as EventEmitter9 } from "node:events";
313520
- var DirectInput;
313521
- var init_direct_input = __esm({
313522
- "packages/cli/src/tui/direct-input.ts"() {
313523
- "use strict";
313524
- DirectInput = class extends EventEmitter9 {
313525
- /** Current input line text */
313526
- line = "";
313527
- /** Cursor position within .line (0-based) */
313528
- cursor = 0;
313529
- _history;
313530
- _historySize;
313531
- _historyIndex = -1;
313532
- _savedLine = "";
313533
- // saved current input when navigating history
313534
- _completer = null;
313535
- _paused = false;
313536
- _closed = false;
313537
- _input;
313538
- _buffer = "";
313539
- // partial escape sequence buffer
313540
- _flushTimer = null;
313541
- constructor(input, options2) {
313542
- super();
313543
- this._input = input;
313544
- this._history = [...options2?.history ?? []];
313545
- this._historySize = options2?.historySize ?? 500;
313546
- this._completer = options2?.completer ?? null;
313547
- input.on("data", (chunk) => {
313548
- if (this._paused || this._closed) return;
313549
- this.feed(chunk.toString("utf8"));
313550
- });
313551
- input.on("end", () => {
313552
- if (!this._closed) this.close();
313553
- });
313565
+ return parts.join("");
313566
+ }
313567
+ function getSession(sessionId, model, cwd4) {
313568
+ if (sessionId && sessions.has(sessionId)) {
313569
+ const s2 = sessions.get(sessionId);
313570
+ s2.lastActivity = Date.now();
313571
+ return s2;
313572
+ }
313573
+ if (sessionId) {
313574
+ try {
313575
+ const fp = sessionPath(sessionId);
313576
+ if (existsSync74(fp)) {
313577
+ const parsed = JSON.parse(readFileSync58(fp, "utf-8"));
313578
+ if (parsed && parsed.id === sessionId) {
313579
+ parsed.lastActivity = Date.now();
313580
+ sessions.set(sessionId, parsed);
313581
+ return parsed;
313582
+ }
313554
313583
  }
313555
- /** Process raw input data — parse escape sequences and printable chars */
313556
- feed(data) {
313557
- this._buffer += data;
313558
- this._processBuffer();
313559
- }
313560
- /** Pause input processing (for overlay transitions) */
313561
- pause() {
313562
- this._paused = true;
313563
- }
313564
- /** Resume input processing */
313565
- resume() {
313566
- this._paused = false;
313567
- }
313568
- /** Close the input handler */
313569
- close() {
313570
- if (this._closed) return;
313571
- this._closed = true;
313572
- if (this._flushTimer) {
313573
- clearTimeout(this._flushTimer);
313574
- this._flushTimer = null;
313584
+ } catch {
313585
+ }
313586
+ }
313587
+ const id = sessionId || randomUUID10();
313588
+ const systemPrompt = buildSystemPrompt(cwd4);
313589
+ const session = {
313590
+ id,
313591
+ messages: [{ role: "system", content: systemPrompt }],
313592
+ model,
313593
+ createdAt: Date.now(),
313594
+ lastActivity: Date.now(),
313595
+ tokensIn: 0,
313596
+ tokensOut: 0
313597
+ };
313598
+ sessions.set(id, session);
313599
+ persistSession(session);
313600
+ return session;
313601
+ }
313602
+ function addUserMessage(session, content) {
313603
+ session.messages.push({ role: "user", content });
313604
+ session.lastActivity = Date.now();
313605
+ persistSession(session);
313606
+ return session.messages.filter((m2) => INFERENCE_ROLES.includes(m2.role));
313607
+ }
313608
+ function addAssistantMessage(session, content) {
313609
+ session.messages.push({ role: "assistant", content });
313610
+ session.lastActivity = Date.now();
313611
+ persistSession(session);
313612
+ }
313613
+ function addToolCallMessage(session, tool, args) {
313614
+ let cappedArgs = args;
313615
+ try {
313616
+ const json = JSON.stringify(args);
313617
+ if (json && json.length > 4e3) {
313618
+ if (args && typeof args === "object" && !Array.isArray(args)) {
313619
+ const pruned = {};
313620
+ for (const [k, v] of Object.entries(args)) {
313621
+ if (typeof v === "string" && v.length > 1e3) {
313622
+ pruned[k] = v.slice(0, 800) + `… [+${v.length - 800} chars truncated for storage]`;
313623
+ } else {
313624
+ pruned[k] = v;
313625
+ }
313575
313626
  }
313576
- this.emit("close");
313577
- }
313578
- /** No-op readline compat (StatusBar renders the prompt, not us) */
313579
- setPrompt(_prompt) {
313627
+ cappedArgs = pruned;
313628
+ } else {
313629
+ cappedArgs = { _truncated: true, _bytes: json.length };
313580
313630
  }
313581
- /** No-op — readline compat */
313582
- prompt(_preserveCursor) {
313631
+ }
313632
+ } catch {
313633
+ }
313634
+ session.messages.push({
313635
+ role: "tool_call",
313636
+ content: "",
313637
+ tool,
313638
+ args: cappedArgs,
313639
+ ts: Date.now()
313640
+ });
313641
+ session.lastActivity = Date.now();
313642
+ persistSession(session);
313643
+ }
313644
+ function addToolResultMessage(session, tool, output, success) {
313645
+ const capped = output && output.length > 2048 ? output.slice(0, 2e3) + `… [+${output.length - 2e3} chars truncated for storage]` : output;
313646
+ session.messages.push({
313647
+ role: "tool_result",
313648
+ content: "",
313649
+ tool,
313650
+ output: capped,
313651
+ success,
313652
+ ts: Date.now()
313653
+ });
313654
+ session.lastActivity = Date.now();
313655
+ persistSession(session);
313656
+ }
313657
+ function addCheckinMessage(session, content) {
313658
+ session.messages.push({
313659
+ role: "user_checkin",
313660
+ content,
313661
+ ts: Date.now()
313662
+ });
313663
+ session.lastActivity = Date.now();
313664
+ persistSession(session);
313665
+ }
313666
+ function addTriageResponseMessage(session, acknowledgment, steering) {
313667
+ session.messages.push({
313668
+ role: "triage_response",
313669
+ content: acknowledgment,
313670
+ acknowledgment,
313671
+ steering,
313672
+ ts: Date.now()
313673
+ });
313674
+ session.lastActivity = Date.now();
313675
+ persistSession(session);
313676
+ }
313677
+ function checkinPath(sessionId) {
313678
+ const safe = sessionId.replace(/[^a-zA-Z0-9_.-]/g, "_");
313679
+ return join91(sessionsDir(), `${safe}.checkins.jsonl`);
313680
+ }
313681
+ function appendCheckin(sessionId, steering) {
313682
+ try {
313683
+ mkdirSync44(sessionsDir(), { recursive: true });
313684
+ const fp = checkinPath(sessionId);
313685
+ const entry = JSON.stringify({ ts: Date.now(), steering }) + "\n";
313686
+ const { appendFileSync: appendFileSync7 } = __require("node:fs");
313687
+ appendFileSync7(fp, entry, "utf-8");
313688
+ } catch {
313689
+ }
313690
+ }
313691
+ function drainCheckins(sessionId) {
313692
+ const fp = checkinPath(sessionId);
313693
+ if (!existsSync74(fp)) return [];
313694
+ try {
313695
+ const raw = readFileSync58(fp, "utf-8");
313696
+ try {
313697
+ unlinkSync20(fp);
313698
+ } catch {
313699
+ }
313700
+ if (!raw.trim()) return [];
313701
+ const out = [];
313702
+ for (const line of raw.split("\n")) {
313703
+ if (!line.trim()) continue;
313704
+ try {
313705
+ const parsed = JSON.parse(line);
313706
+ if (typeof parsed.steering === "string" && parsed.steering) {
313707
+ out.push(parsed.steering);
313708
+ }
313709
+ } catch {
313583
313710
  }
313584
- /** Set the line content and cursor position (for Esc-to-recall or suggestion apply) */
313585
- setLine(text, cursorPos) {
313586
- this.line = text;
313587
- this.cursor = cursorPos ?? text.length;
313711
+ }
313712
+ return out;
313713
+ } catch {
313714
+ return [];
313715
+ }
313716
+ }
313717
+ function trackSessionTokens(session, tokensIn, tokensOut) {
313718
+ session.tokensIn += tokensIn;
313719
+ session.tokensOut += tokensOut;
313720
+ }
313721
+ function listSessions2() {
313722
+ return Array.from(sessions.values()).map((s2) => ({
313723
+ id: s2.id,
313724
+ model: s2.model,
313725
+ messages: s2.messages.length - 1,
313726
+ // exclude system prompt
313727
+ tokensIn: s2.tokensIn,
313728
+ tokensOut: s2.tokensOut,
313729
+ lastActivity: new Date(s2.lastActivity).toISOString()
313730
+ }));
313731
+ }
313732
+ function deleteSession2(id) {
313733
+ try {
313734
+ const p2 = sessionPath(id);
313735
+ if (existsSync74(p2)) unlinkSync20(p2);
313736
+ } catch {
313737
+ }
313738
+ deleteInFlightFile(id);
313739
+ inFlight.delete(id);
313740
+ return sessions.delete(id);
313741
+ }
313742
+ function lookupSession(id) {
313743
+ const cached = sessions.get(id);
313744
+ if (cached) return cached;
313745
+ try {
313746
+ const fp = sessionPath(id);
313747
+ if (existsSync74(fp)) {
313748
+ const parsed = JSON.parse(readFileSync58(fp, "utf-8"));
313749
+ if (parsed && parsed.id === id) {
313750
+ sessions.set(id, parsed);
313751
+ return parsed;
313588
313752
  }
313589
- /** Pre-submit hook — called before Enter submits. Return true to consume Enter. */
313590
- _preSubmit = null;
313591
- setPreSubmit(hook) {
313592
- this._preSubmit = hook;
313753
+ }
313754
+ } catch {
313755
+ }
313756
+ return null;
313757
+ }
313758
+ function startInFlightChat(opts) {
313759
+ const job = {
313760
+ sessionId: opts.sessionId,
313761
+ runId: opts.runId,
313762
+ pid: opts.pid,
313763
+ startedAt: Date.now(),
313764
+ partialOutput: "",
313765
+ status: "running",
313766
+ tailBytes: 0
313767
+ };
313768
+ inFlight.set(opts.sessionId, job);
313769
+ persistInFlight(job);
313770
+ return job;
313771
+ }
313772
+ function appendInFlightOutput(sessionId, chunk) {
313773
+ const job = inFlight.get(sessionId);
313774
+ if (!job) return;
313775
+ job.partialOutput += chunk;
313776
+ job.tailBytes += chunk.length;
313777
+ if (job.partialOutput.length > PARTIAL_TAIL_BUDGET) {
313778
+ job.partialOutput = job.partialOutput.slice(-PARTIAL_TAIL_BUDGET);
313779
+ }
313780
+ persistInFlight(job);
313781
+ }
313782
+ function finishInFlightChat(sessionId, status, finalContent, error) {
313783
+ const job = inFlight.get(sessionId);
313784
+ if (!job) return;
313785
+ job.status = status;
313786
+ job.completedAt = Date.now();
313787
+ if (finalContent !== void 0) job.finalContent = finalContent;
313788
+ if (error !== void 0) job.error = error;
313789
+ persistInFlight(job);
313790
+ setTimeout(() => {
313791
+ inFlight.delete(sessionId);
313792
+ deleteInFlightFile(sessionId);
313793
+ }, 3e4).unref?.();
313794
+ }
313795
+ function getInFlightChat(sessionId) {
313796
+ const cached = inFlight.get(sessionId);
313797
+ if (cached) return cached;
313798
+ try {
313799
+ const p2 = inFlightPath(sessionId);
313800
+ if (existsSync74(p2)) {
313801
+ const parsed = JSON.parse(readFileSync58(p2, "utf-8"));
313802
+ if (parsed && parsed.sessionId === sessionId) {
313803
+ return parsed;
313593
313804
  }
313594
- /** Navigate history up (older) */
313595
- historyUp() {
313596
- if (this._history.length === 0) return;
313597
- if (this._historyIndex === -1) {
313598
- this._savedLine = this.line;
313599
- }
313600
- if (this._historyIndex < this._history.length - 1) {
313601
- this._historyIndex++;
313602
- this.line = this._history[this._historyIndex];
313603
- this.cursor = this.line.length;
313604
- }
313805
+ }
313806
+ } catch {
313807
+ }
313808
+ return null;
313809
+ }
313810
+ function compactSession(session, maxMessages = 40) {
313811
+ if (session.messages.length <= maxMessages) return;
313812
+ const system = session.messages[0];
313813
+ const recent = session.messages.slice(-20);
313814
+ const middle = session.messages.slice(1, -20);
313815
+ 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(", ") + "...]";
313816
+ session.messages = [
313817
+ system,
313818
+ { role: "system", content: summary },
313819
+ ...recent
313820
+ ];
313821
+ }
313822
+ var sessions, inFlight, SESSION_TTL_MS, INFERENCE_ROLES, PARTIAL_TAIL_BUDGET;
313823
+ var init_chat_session = __esm({
313824
+ "packages/cli/src/api/chat-session.ts"() {
313825
+ "use strict";
313826
+ sessions = /* @__PURE__ */ new Map();
313827
+ inFlight = /* @__PURE__ */ new Map();
313828
+ SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
313829
+ setInterval(() => {
313830
+ const now = Date.now();
313831
+ for (const [id, s2] of sessions) {
313832
+ if (now - s2.lastActivity > SESSION_TTL_MS) sessions.delete(id);
313605
313833
  }
313606
- /** Navigate history down (newer) */
313607
- historyDown() {
313608
- if (this._historyIndex <= -1) return;
313609
- this._historyIndex--;
313610
- if (this._historyIndex === -1) {
313834
+ }, 5 * 60 * 1e3);
313835
+ INFERENCE_ROLES = ["system", "user", "assistant"];
313836
+ PARTIAL_TAIL_BUDGET = 8 * 1024;
313837
+ }
313838
+ });
313839
+
313840
+ // packages/cli/src/tui/mouse-filter.ts
313841
+ var mouse_filter_exports = {};
313842
+ __export(mouse_filter_exports, {
313843
+ MouseFilterStream: () => MouseFilterStream
313844
+ });
313845
+ import { Transform } from "node:stream";
313846
+ var MouseFilterStream;
313847
+ var init_mouse_filter = __esm({
313848
+ "packages/cli/src/tui/mouse-filter.ts"() {
313849
+ "use strict";
313850
+ MouseFilterStream = class extends Transform {
313851
+ buffer = "";
313852
+ onScroll = null;
313853
+ onActivity = null;
313854
+ onPointer = null;
313855
+ flushTimer = null;
313856
+ constructor(scrollHandler, activityHandler, pointerHandler) {
313857
+ super();
313858
+ this.onScroll = scrollHandler;
313859
+ this.onActivity = activityHandler ?? null;
313860
+ this.onPointer = pointerHandler ?? null;
313861
+ }
313862
+ _transform(chunk, _encoding, callback) {
313863
+ this.buffer += chunk.toString();
313864
+ this.processBuffer(callback);
313865
+ }
313866
+ processBuffer(callback) {
313867
+ let output = "";
313868
+ let i2 = 0;
313869
+ while (i2 < this.buffer.length) {
313870
+ if (this.buffer[i2] === "\x1B") {
313871
+ const remaining = this.buffer.slice(i2);
313872
+ const mouseMatch = remaining.match(/^\x1B\[<(\d+);(\d+);(\d+)([Mm])/);
313873
+ if (mouseMatch) {
313874
+ const btn = parseInt(mouseMatch[1]);
313875
+ const col = parseInt(mouseMatch[2]);
313876
+ const row = parseInt(mouseMatch[3]);
313877
+ const suffix = mouseMatch[4];
313878
+ if ((btn === 64 || btn === 96) && this.onScroll) this.onScroll("up", 3, row);
313879
+ else if ((btn === 65 || btn === 97) && this.onScroll) this.onScroll("down", 3, row);
313880
+ else if (this.onPointer) {
313881
+ if ((btn === 0 || btn === 1 || btn === 2) && suffix === "M") this.onPointer("press", col, row);
313882
+ else if (btn >= 32 && btn <= 35 && suffix === "M") this.onPointer("drag", col, row);
313883
+ else if (suffix === "m") this.onPointer("release", col, row);
313884
+ }
313885
+ if (this.onActivity) this.onActivity();
313886
+ i2 += mouseMatch[0].length;
313887
+ continue;
313888
+ }
313889
+ if (remaining.startsWith("\x1B[M")) {
313890
+ if (remaining.length >= 6) {
313891
+ if (this.onActivity) this.onActivity();
313892
+ i2 += 6;
313893
+ continue;
313894
+ }
313895
+ break;
313896
+ }
313897
+ if (remaining.startsWith("\x1B[<") && remaining.length < 15) {
313898
+ break;
313899
+ }
313900
+ if (remaining.startsWith("\x1B[") && remaining.length === 2) {
313901
+ break;
313902
+ }
313903
+ if (remaining.length === 1) {
313904
+ break;
313905
+ }
313906
+ }
313907
+ output += this.buffer[i2];
313908
+ i2++;
313909
+ }
313910
+ this.buffer = this.buffer.slice(i2);
313911
+ if (output.length > 0) {
313912
+ this.push(output);
313913
+ }
313914
+ if (this.buffer.length > 0) {
313915
+ if (this.flushTimer) clearTimeout(this.flushTimer);
313916
+ this.flushTimer = setTimeout(() => {
313917
+ if (this.buffer.length > 0) {
313918
+ if (this.buffer.startsWith("\x1B[<") || this.buffer.startsWith("\x1B[M")) {
313919
+ this.buffer = "";
313920
+ } else if (this.buffer === "\x1B" || this.buffer === "\x1B[") {
313921
+ this.push(this.buffer);
313922
+ this.buffer = "";
313923
+ } else {
313924
+ this.push(this.buffer);
313925
+ this.buffer = "";
313926
+ }
313927
+ }
313928
+ }, 50);
313929
+ }
313930
+ callback();
313931
+ }
313932
+ _flush(callback) {
313933
+ if (this.flushTimer) {
313934
+ clearTimeout(this.flushTimer);
313935
+ this.flushTimer = null;
313936
+ }
313937
+ if (this.buffer.length > 0) {
313938
+ this.push(this.buffer);
313939
+ this.buffer = "";
313940
+ }
313941
+ callback();
313942
+ }
313943
+ };
313944
+ }
313945
+ });
313946
+
313947
+ // packages/cli/src/tui/direct-input.ts
313948
+ var direct_input_exports = {};
313949
+ __export(direct_input_exports, {
313950
+ DirectInput: () => DirectInput
313951
+ });
313952
+ import { EventEmitter as EventEmitter9 } from "node:events";
313953
+ var DirectInput;
313954
+ var init_direct_input = __esm({
313955
+ "packages/cli/src/tui/direct-input.ts"() {
313956
+ "use strict";
313957
+ DirectInput = class extends EventEmitter9 {
313958
+ /** Current input line text */
313959
+ line = "";
313960
+ /** Cursor position within .line (0-based) */
313961
+ cursor = 0;
313962
+ _history;
313963
+ _historySize;
313964
+ _historyIndex = -1;
313965
+ _savedLine = "";
313966
+ // saved current input when navigating history
313967
+ _completer = null;
313968
+ _paused = false;
313969
+ _closed = false;
313970
+ _input;
313971
+ _buffer = "";
313972
+ // partial escape sequence buffer
313973
+ _flushTimer = null;
313974
+ constructor(input, options2) {
313975
+ super();
313976
+ this._input = input;
313977
+ this._history = [...options2?.history ?? []];
313978
+ this._historySize = options2?.historySize ?? 500;
313979
+ this._completer = options2?.completer ?? null;
313980
+ input.on("data", (chunk) => {
313981
+ if (this._paused || this._closed) return;
313982
+ this.feed(chunk.toString("utf8"));
313983
+ });
313984
+ input.on("end", () => {
313985
+ if (!this._closed) this.close();
313986
+ });
313987
+ }
313988
+ /** Process raw input data — parse escape sequences and printable chars */
313989
+ feed(data) {
313990
+ this._buffer += data;
313991
+ this._processBuffer();
313992
+ }
313993
+ /** Pause input processing (for overlay transitions) */
313994
+ pause() {
313995
+ this._paused = true;
313996
+ }
313997
+ /** Resume input processing */
313998
+ resume() {
313999
+ this._paused = false;
314000
+ }
314001
+ /** Close the input handler */
314002
+ close() {
314003
+ if (this._closed) return;
314004
+ this._closed = true;
314005
+ if (this._flushTimer) {
314006
+ clearTimeout(this._flushTimer);
314007
+ this._flushTimer = null;
314008
+ }
314009
+ this.emit("close");
314010
+ }
314011
+ /** No-op — readline compat (StatusBar renders the prompt, not us) */
314012
+ setPrompt(_prompt) {
314013
+ }
314014
+ /** No-op — readline compat */
314015
+ prompt(_preserveCursor) {
314016
+ }
314017
+ /** Set the line content and cursor position (for Esc-to-recall or suggestion apply) */
314018
+ setLine(text, cursorPos) {
314019
+ this.line = text;
314020
+ this.cursor = cursorPos ?? text.length;
314021
+ }
314022
+ /** Pre-submit hook — called before Enter submits. Return true to consume Enter. */
314023
+ _preSubmit = null;
314024
+ setPreSubmit(hook) {
314025
+ this._preSubmit = hook;
314026
+ }
314027
+ /** Navigate history up (older) */
314028
+ historyUp() {
314029
+ if (this._history.length === 0) return;
314030
+ if (this._historyIndex === -1) {
314031
+ this._savedLine = this.line;
314032
+ }
314033
+ if (this._historyIndex < this._history.length - 1) {
314034
+ this._historyIndex++;
314035
+ this.line = this._history[this._historyIndex];
314036
+ this.cursor = this.line.length;
314037
+ }
314038
+ }
314039
+ /** Navigate history down (newer) */
314040
+ historyDown() {
314041
+ if (this._historyIndex <= -1) return;
314042
+ this._historyIndex--;
314043
+ if (this._historyIndex === -1) {
313611
314044
  this.line = this._savedLine;
313612
314045
  } else {
313613
314046
  this.line = this._history[this._historyIndex];
@@ -313922,13 +314355,13 @@ __export(audit_log_exports, {
313922
314355
  recordAudit: () => recordAudit,
313923
314356
  sanitizeBody: () => sanitizeBody
313924
314357
  });
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";
314358
+ import { mkdirSync as mkdirSync45, appendFileSync as appendFileSync5, readFileSync as readFileSync59, existsSync as existsSync75 } from "node:fs";
314359
+ import { join as join92 } from "node:path";
313927
314360
  function initAuditLog(oaDir) {
313928
- auditDir = join91(oaDir, "audit");
313929
- auditFile = join91(auditDir, "audit.jsonl");
314361
+ auditDir = join92(oaDir, "audit");
314362
+ auditFile = join92(auditDir, "audit.jsonl");
313930
314363
  try {
313931
- mkdirSync44(auditDir, { recursive: true });
314364
+ mkdirSync45(auditDir, { recursive: true });
313932
314365
  initialized = true;
313933
314366
  } catch {
313934
314367
  }
@@ -313957,9 +314390,9 @@ function sanitizeBody(body, maxLen = 200) {
313957
314390
  return safe.length > maxLen ? safe.slice(0, maxLen) + "..." : safe;
313958
314391
  }
313959
314392
  function queryAudit(opts) {
313960
- if (!initialized || !existsSync74(auditFile)) return [];
314393
+ if (!initialized || !existsSync75(auditFile)) return [];
313961
314394
  try {
313962
- const raw = readFileSync58(auditFile, "utf-8");
314395
+ const raw = readFileSync59(auditFile, "utf-8");
313963
314396
  const lines = raw.split("\n").filter(Boolean);
313964
314397
  let records = lines.map((l2) => {
313965
314398
  try {
@@ -313996,8 +314429,8 @@ var init_audit_log = __esm({
313996
314429
 
313997
314430
  // packages/cli/src/api/disk-task-output.ts
313998
314431
  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";
314432
+ import { existsSync as existsSync76, mkdirSync as mkdirSync46, statSync as statSync21 } from "node:fs";
314433
+ import { dirname as dirname26 } from "node:path";
314001
314434
  import * as fsConstants from "node:constants";
314002
314435
  var O_NOFOLLOW2, O_APPEND2, O_CREAT2, O_WRONLY2, OPEN_FLAGS_WRITE, OPEN_MODE, DiskTaskOutput;
314003
314436
  var init_disk_task_output = __esm({
@@ -314016,7 +314449,7 @@ var init_disk_task_output = __esm({
314016
314449
  fileSize = 0;
314017
314450
  constructor(outputPath) {
314018
314451
  this.path = outputPath;
314019
- mkdirSync45(dirname25(outputPath), { recursive: true });
314452
+ mkdirSync46(dirname26(outputPath), { recursive: true });
314020
314453
  }
314021
314454
  /** Queue content for async append. Non-blocking. */
314022
314455
  append(chunk) {
@@ -314092,7 +314525,7 @@ var init_disk_task_output = __esm({
314092
314525
  async readFrom(offset, limit = 65536) {
314093
314526
  let handle2 = null;
314094
314527
  try {
314095
- if (!existsSync75(this.path)) {
314528
+ if (!existsSync76(this.path)) {
314096
314529
  return { content: "", nextOffset: offset, eof: true, size: 0 };
314097
314530
  }
314098
314531
  const st = statSync21(this.path);
@@ -314294,19 +314727,19 @@ __export(aiwg_exports, {
314294
314727
  resolveAiwgRoot: () => resolveAiwgRoot,
314295
314728
  tryRouteAiwg: () => tryRouteAiwg
314296
314729
  });
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";
314730
+ import { existsSync as existsSync77, readFileSync as readFileSync60, readdirSync as readdirSync25, statSync as statSync22 } from "node:fs";
314731
+ import { join as join93 } from "node:path";
314732
+ import { homedir as homedir32 } from "node:os";
314300
314733
  import { execSync as execSync53 } from "node:child_process";
314301
314734
  function resolveAiwgRoot() {
314302
314735
  if (_cachedAiwgRoot !== void 0) return _cachedAiwgRoot;
314303
314736
  const envRoot = process.env["OA_AIWG_ROOT"];
314304
- if (envRoot && existsSync76(join92(envRoot, "package.json"))) {
314737
+ if (envRoot && existsSync77(join93(envRoot, "package.json"))) {
314305
314738
  _cachedAiwgRoot = envRoot;
314306
314739
  return envRoot;
314307
314740
  }
314308
- const shareDir = join92(homedir31(), ".local", "share", "ai-writing-guide");
314309
- if (existsSync76(join92(shareDir, "agentic"))) {
314741
+ const shareDir = join93(homedir32(), ".local", "share", "ai-writing-guide");
314742
+ if (existsSync77(join93(shareDir, "agentic"))) {
314310
314743
  _cachedAiwgRoot = shareDir;
314311
314744
  return shareDir;
314312
314745
  }
@@ -314316,8 +314749,8 @@ function resolveAiwgRoot() {
314316
314749
  timeout: 5e3,
314317
314750
  stdio: ["pipe", "pipe", "pipe"]
314318
314751
  }).trim();
314319
- const candidate = join92(globalRoot, "aiwg");
314320
- if (existsSync76(join92(candidate, "package.json"))) {
314752
+ const candidate = join93(globalRoot, "aiwg");
314753
+ if (existsSync77(join93(candidate, "package.json"))) {
314321
314754
  _cachedAiwgRoot = candidate;
314322
314755
  return candidate;
314323
314756
  }
@@ -314328,22 +314761,22 @@ function resolveAiwgRoot() {
314328
314761
  "/usr/lib/node_modules/aiwg",
314329
314762
  "/opt/homebrew/lib/node_modules/aiwg"
314330
314763
  ]) {
314331
- if (existsSync76(join92(p2, "package.json"))) {
314764
+ if (existsSync77(join93(p2, "package.json"))) {
314332
314765
  _cachedAiwgRoot = p2;
314333
314766
  return p2;
314334
314767
  }
314335
314768
  }
314336
314769
  const versionDirs = [
314337
- join92(homedir31(), ".nvm", "versions", "node"),
314338
- join92(homedir31(), ".local", "share", "fnm", "node-versions")
314770
+ join93(homedir32(), ".nvm", "versions", "node"),
314771
+ join93(homedir32(), ".local", "share", "fnm", "node-versions")
314339
314772
  ];
314340
314773
  for (const vdir of versionDirs) {
314341
- if (!existsSync76(vdir)) continue;
314774
+ if (!existsSync77(vdir)) continue;
314342
314775
  try {
314343
- for (const ver of readdirSync24(vdir)) {
314776
+ for (const ver of readdirSync25(vdir)) {
314344
314777
  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"))) {
314778
+ const cand = join93(vdir, ver, prefix);
314779
+ if (existsSync77(join93(cand, "package.json"))) {
314347
314780
  _cachedAiwgRoot = cand;
314348
314781
  return cand;
314349
314782
  }
@@ -314361,11 +314794,11 @@ function resolveAiwgRoot() {
314361
314794
  if (whichAiwg) {
314362
314795
  let cur = whichAiwg;
314363
314796
  for (let i2 = 0; i2 < 8; i2++) {
314364
- cur = join92(cur, "..");
314365
- const pj = join92(cur, "package.json");
314366
- if (existsSync76(pj)) {
314797
+ cur = join93(cur, "..");
314798
+ const pj = join93(cur, "package.json");
314799
+ if (existsSync77(pj)) {
314367
314800
  try {
314368
- const pkg = JSON.parse(readFileSync59(pj, "utf-8"));
314801
+ const pkg = JSON.parse(readFileSync60(pj, "utf-8"));
314369
314802
  if (pkg.name === "aiwg") {
314370
314803
  _cachedAiwgRoot = cur;
314371
314804
  return cur;
@@ -314387,14 +314820,14 @@ function listAiwgFrameworks() {
314387
314820
  _cachedFrameworks = [];
314388
314821
  return _cachedFrameworks;
314389
314822
  }
314390
- const frameworksDir = join92(root, "agentic", "code", "frameworks");
314391
- if (!existsSync76(frameworksDir)) {
314823
+ const frameworksDir = join93(root, "agentic", "code", "frameworks");
314824
+ if (!existsSync77(frameworksDir)) {
314392
314825
  _cachedFrameworks = [];
314393
314826
  return _cachedFrameworks;
314394
314827
  }
314395
314828
  const out = [];
314396
- for (const name10 of readdirSync24(frameworksDir)) {
314397
- const p2 = join92(frameworksDir, name10);
314829
+ for (const name10 of readdirSync25(frameworksDir)) {
314830
+ const p2 = join93(frameworksDir, name10);
314398
314831
  try {
314399
314832
  const st = statSync22(p2);
314400
314833
  if (!st.isDirectory()) continue;
@@ -314420,9 +314853,9 @@ function aggregateDir(dir, depth = 0) {
314420
314853
  const out = { files: 0, bytes: 0, mdChars: 0, skills: 0, agents: 0, commands: 0 };
314421
314854
  if (depth > 8) return out;
314422
314855
  try {
314423
- for (const e2 of readdirSync24(dir, { withFileTypes: true })) {
314856
+ for (const e2 of readdirSync25(dir, { withFileTypes: true })) {
314424
314857
  if (e2.name.startsWith(".") || e2.name === "node_modules") continue;
314425
- const p2 = join92(dir, e2.name);
314858
+ const p2 = join93(dir, e2.name);
314426
314859
  if (e2.isDirectory()) {
314427
314860
  const sub = aggregateDir(p2, depth + 1);
314428
314861
  out.files += sub.files;
@@ -314452,10 +314885,10 @@ function aggregateDir(dir, depth = 0) {
314452
314885
  }
314453
314886
  function readFirstLineDescription(dir) {
314454
314887
  for (const candidate of ["README.md", "SKILL.md", "INDEX.md"]) {
314455
- const p2 = join92(dir, candidate);
314456
- if (!existsSync76(p2)) continue;
314888
+ const p2 = join93(dir, candidate);
314889
+ if (!existsSync77(p2)) continue;
314457
314890
  try {
314458
- const txt = readFileSync59(p2, "utf-8");
314891
+ const txt = readFileSync60(p2, "utf-8");
314459
314892
  const descMatch = txt.match(/^description:\s*(.+)$/m);
314460
314893
  if (descMatch) return descMatch[1].trim().slice(0, 200);
314461
314894
  for (const line of txt.split("\n")) {
@@ -314477,12 +314910,12 @@ function listAiwgItems() {
314477
314910
  }
314478
314911
  const out = [];
314479
314912
  const walkRoots = [
314480
- join92(root, "agentic", "code", "frameworks"),
314481
- join92(root, "agentic", "code", "addons"),
314482
- join92(root, "plugins")
314913
+ join93(root, "agentic", "code", "frameworks"),
314914
+ join93(root, "agentic", "code", "addons"),
314915
+ join93(root, "plugins")
314483
314916
  ];
314484
314917
  for (const wr of walkRoots) {
314485
- if (!existsSync76(wr)) continue;
314918
+ if (!existsSync77(wr)) continue;
314486
314919
  walkForItems(wr, out, 0);
314487
314920
  }
314488
314921
  _cachedItems = out;
@@ -314491,9 +314924,9 @@ function listAiwgItems() {
314491
314924
  function walkForItems(dir, out, depth) {
314492
314925
  if (depth > 10) return;
314493
314926
  try {
314494
- for (const e2 of readdirSync24(dir, { withFileTypes: true })) {
314927
+ for (const e2 of readdirSync25(dir, { withFileTypes: true })) {
314495
314928
  if (e2.name.startsWith(".") || e2.name === "node_modules") continue;
314496
- const p2 = join92(dir, e2.name);
314929
+ const p2 = join93(dir, e2.name);
314497
314930
  if (e2.isDirectory()) {
314498
314931
  walkForItems(p2, out, depth + 1);
314499
314932
  } else if (e2.isFile() && e2.name.endsWith(".md")) {
@@ -314506,7 +314939,7 @@ function walkForItems(dir, out, depth) {
314506
314939
  }
314507
314940
  function parseItem(p2) {
314508
314941
  try {
314509
- const raw = readFileSync59(p2, "utf-8");
314942
+ const raw = readFileSync60(p2, "utf-8");
314510
314943
  const header = raw.slice(0, 3e3);
314511
314944
  const nameMatch = header.match(/^name:\s*(.+)$/m);
314512
314945
  const descMatch = header.match(/^description:\s*(.+)$/m);
@@ -314544,8 +314977,8 @@ function deriveSource(p2) {
314544
314977
  }
314545
314978
  function loadAiwgItemContent(path5, maxBytes = 2e4) {
314546
314979
  try {
314547
- if (!existsSync76(path5)) return null;
314548
- const raw = readFileSync59(path5, "utf-8");
314980
+ if (!existsSync77(path5)) return null;
314981
+ const raw = readFileSync60(path5, "utf-8");
314549
314982
  return raw.length > maxBytes ? raw.slice(0, maxBytes) + "\n\n...(truncated for context budget)" : raw;
314550
314983
  } catch {
314551
314984
  return null;
@@ -314558,14 +314991,14 @@ function listAiwgAddons() {
314558
314991
  _cachedAddons = [];
314559
314992
  return _cachedAddons;
314560
314993
  }
314561
- const addonsDir = join92(root, "agentic", "code", "addons");
314562
- if (!existsSync76(addonsDir)) {
314994
+ const addonsDir = join93(root, "agentic", "code", "addons");
314995
+ if (!existsSync77(addonsDir)) {
314563
314996
  _cachedAddons = [];
314564
314997
  return _cachedAddons;
314565
314998
  }
314566
314999
  const out = [];
314567
- for (const name10 of readdirSync24(addonsDir)) {
314568
- const p2 = join92(addonsDir, name10);
315000
+ for (const name10 of readdirSync25(addonsDir)) {
315001
+ const p2 = join93(addonsDir, name10);
314569
315002
  try {
314570
315003
  const st = statSync22(p2);
314571
315004
  if (!st.isDirectory()) continue;
@@ -315047,9 +315480,9 @@ var init_aiwg = __esm({
315047
315480
  });
315048
315481
 
315049
315482
  // 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";
315483
+ import { existsSync as existsSync78, readFileSync as readFileSync61, readdirSync as readdirSync26, statSync as statSync23 } from "node:fs";
315484
+ import { join as join94, resolve as pathResolve } from "node:path";
315485
+ import { homedir as homedir33 } from "node:os";
315053
315486
  async function tryRouteV1(ctx3) {
315054
315487
  const { pathname, method } = ctx3;
315055
315488
  if (pathname === "/v1/skills" && method === "GET") {
@@ -315254,11 +315687,11 @@ async function handleGetSkill(ctx3, name10) {
315254
315687
  async function fallbackDiscoverSkills() {
315255
315688
  return (_root) => {
315256
315689
  const roots = [
315257
- join93(homedir32(), ".local", "share", "ai-writing-guide")
315690
+ join94(homedir33(), ".local", "share", "ai-writing-guide")
315258
315691
  ];
315259
315692
  const out = [];
315260
315693
  for (const root of roots) {
315261
- if (!existsSync77(root)) continue;
315694
+ if (!existsSync78(root)) continue;
315262
315695
  walkForSkills(root, out, 0);
315263
315696
  }
315264
315697
  return out;
@@ -315267,14 +315700,14 @@ async function fallbackDiscoverSkills() {
315267
315700
  function walkForSkills(dir, out, depth) {
315268
315701
  if (depth > 6) return;
315269
315702
  try {
315270
- for (const e2 of readdirSync25(dir, { withFileTypes: true })) {
315703
+ for (const e2 of readdirSync26(dir, { withFileTypes: true })) {
315271
315704
  if (e2.name.startsWith(".") || e2.name === "node_modules") continue;
315272
- const p2 = join93(dir, e2.name);
315705
+ const p2 = join94(dir, e2.name);
315273
315706
  if (e2.isDirectory()) {
315274
315707
  walkForSkills(p2, out, depth + 1);
315275
315708
  } else if (e2.isFile() && e2.name === "SKILL.md") {
315276
315709
  try {
315277
- const content = readFileSync60(p2, "utf-8").slice(0, 2e3);
315710
+ const content = readFileSync61(p2, "utf-8").slice(0, 2e3);
315278
315711
  const nameMatch = content.match(/^name:\s*(.+)$/m);
315279
315712
  const descMatch = content.match(/^description:\s*(.+)$/m);
315280
315713
  out.push({
@@ -315458,7 +315891,7 @@ async function getMemoryStores() {
315458
315891
  if (memoryInitTried) return null;
315459
315892
  memoryInitTried = true;
315460
315893
  try {
315461
- const dbPath = join93(homedir32(), ".open-agents", "memory.db");
315894
+ const dbPath = join94(homedir33(), ".open-agents", "memory.db");
315462
315895
  const sharedDb = initDb(dbPath);
315463
315896
  memoryStoresCache = {
315464
315897
  episode: new EpisodeStore(dbPath),
@@ -315716,7 +316149,7 @@ async function handleFilesRead(ctx3) {
315716
316149
  }));
315717
316150
  return true;
315718
316151
  }
315719
- if (!existsSync77(resolved)) {
316152
+ if (!existsSync78(resolved)) {
315720
316153
  sendProblem(res, problemDetails({
315721
316154
  type: P.notFound,
315722
316155
  status: 404,
@@ -315748,7 +316181,7 @@ async function handleFilesRead(ctx3) {
315748
316181
  }));
315749
316182
  return true;
315750
316183
  }
315751
- const content = readFileSync60(resolved, "utf-8");
316184
+ const content = readFileSync61(resolved, "utf-8");
315752
316185
  const offset = typeof body.offset === "number" && body.offset >= 0 ? body.offset : 0;
315753
316186
  const limit = typeof body.limit === "number" && body.limit > 0 ? body.limit : content.length;
315754
316187
  const slice2 = content.slice(offset, offset + limit);
@@ -315979,14 +316412,14 @@ async function handleNexusStatus(ctx3) {
315979
316412
  const { res, requestId } = ctx3;
315980
316413
  try {
315981
316414
  const statePaths = [
315982
- join93(process.cwd(), ".oa", "nexus-peer-state.json"),
315983
- join93(homedir32(), ".open-agents", "nexus-peer-cache.json")
316415
+ join94(process.cwd(), ".oa", "nexus-peer-state.json"),
316416
+ join94(homedir33(), ".open-agents", "nexus-peer-cache.json")
315984
316417
  ];
315985
316418
  const states = [];
315986
316419
  for (const p2 of statePaths) {
315987
- if (!existsSync77(p2)) continue;
316420
+ if (!existsSync78(p2)) continue;
315988
316421
  try {
315989
- const raw = readFileSync60(p2, "utf-8");
316422
+ const raw = readFileSync61(p2, "utf-8");
315990
316423
  states.push({ source: p2, data: JSON.parse(raw) });
315991
316424
  } catch (e2) {
315992
316425
  states.push({ source: p2, error: String(e2) });
@@ -316013,8 +316446,8 @@ async function handleNexusStatus(ctx3) {
316013
316446
  }
316014
316447
  function loadAgentName() {
316015
316448
  try {
316016
- const p2 = join93(homedir32(), ".open-agents", "agent-name");
316017
- if (existsSync77(p2)) return readFileSync60(p2, "utf-8").trim();
316449
+ const p2 = join94(homedir33(), ".open-agents", "agent-name");
316450
+ if (existsSync78(p2)) return readFileSync61(p2, "utf-8").trim();
316018
316451
  } catch {
316019
316452
  }
316020
316453
  return null;
@@ -316023,14 +316456,14 @@ async function handleSponsors(ctx3) {
316023
316456
  const { req: req2, res, url, requestId } = ctx3;
316024
316457
  try {
316025
316458
  const candidates = [
316026
- join93(homedir32(), ".open-agents", "sponsor-cache.json"),
316027
- join93(homedir32(), ".open-agents", "sponsors.json")
316459
+ join94(homedir33(), ".open-agents", "sponsor-cache.json"),
316460
+ join94(homedir33(), ".open-agents", "sponsors.json")
316028
316461
  ];
316029
316462
  let sponsors = [];
316030
316463
  for (const p2 of candidates) {
316031
- if (!existsSync77(p2)) continue;
316464
+ if (!existsSync78(p2)) continue;
316032
316465
  try {
316033
- const raw = JSON.parse(readFileSync60(p2, "utf-8"));
316466
+ const raw = JSON.parse(readFileSync61(p2, "utf-8"));
316034
316467
  if (Array.isArray(raw)) {
316035
316468
  sponsors = raw;
316036
316469
  break;
@@ -316099,8 +316532,8 @@ async function handleEvaluate(ctx3) {
316099
316532
  }));
316100
316533
  return true;
316101
316534
  }
316102
- const jobPath = join93(process.cwd(), ".oa", "jobs", `${runId}.json`);
316103
- if (!existsSync77(jobPath)) {
316535
+ const jobPath = join94(process.cwd(), ".oa", "jobs", `${runId}.json`);
316536
+ if (!existsSync78(jobPath)) {
316104
316537
  sendProblem(res, problemDetails({
316105
316538
  type: P.notFound,
316106
316539
  status: 404,
@@ -316110,7 +316543,7 @@ async function handleEvaluate(ctx3) {
316110
316543
  }));
316111
316544
  return true;
316112
316545
  }
316113
- const job = JSON.parse(readFileSync60(jobPath, "utf-8"));
316546
+ const job = JSON.parse(readFileSync61(jobPath, "utf-8"));
316114
316547
  sendJson(res, 200, {
316115
316548
  run_id: runId,
316116
316549
  task: job.task,
@@ -316248,17 +316681,17 @@ async function handleListAgentTypes(ctx3) {
316248
316681
  }
316249
316682
  async function handleListEngines(ctx3) {
316250
316683
  const { res } = ctx3;
316251
- const home = homedir32();
316684
+ const home = homedir33();
316252
316685
  sendJson(res, 200, {
316253
316686
  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" }
316687
+ { name: "dream", state_file: join94(process.cwd(), ".oa", "dreams"), controllable_via: "SSE + slash commands" },
316688
+ { name: "bless", state_file: join94(process.cwd(), ".oa", "bless-state.json"), controllable_via: "slash commands" },
316689
+ { name: "call", state_file: join94(process.cwd(), ".oa", "call-state.json"), controllable_via: "slash commands" },
316690
+ { name: "listen", state_file: join94(process.cwd(), ".oa", "listen-state.json"), controllable_via: "slash commands" },
316691
+ { name: "telegram", state_file: join94(home, ".open-agents", "telegram-state.json"), controllable_via: "slash commands" },
316692
+ { name: "expose", state_file: join94(process.cwd(), ".oa", "expose-state.json"), controllable_via: "/expose commands" },
316693
+ { name: "nexus", state_file: join94(home, ".open-agents", "nexus-peer-cache.json"), controllable_via: "/nexus commands" },
316694
+ { name: "ipfs", state_file: join94(process.cwd(), ".oa", "ipfs"), controllable_via: "slash commands" }
316262
316695
  ],
316263
316696
  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
316697
  });
@@ -316341,12 +316774,12 @@ async function tryAimsRoute(ctx3) {
316341
316774
  return false;
316342
316775
  }
316343
316776
  function aimsDir() {
316344
- return join93(homedir32(), ".open-agents", "aims");
316777
+ return join94(homedir33(), ".open-agents", "aims");
316345
316778
  }
316346
316779
  function readAimsFile(name10, fallback) {
316347
316780
  try {
316348
- const p2 = join93(aimsDir(), name10);
316349
- if (existsSync77(p2)) return JSON.parse(readFileSync60(p2, "utf-8"));
316781
+ const p2 = join94(aimsDir(), name10);
316782
+ if (existsSync78(p2)) return JSON.parse(readFileSync61(p2, "utf-8"));
316350
316783
  } catch {
316351
316784
  }
316352
316785
  return fallback;
@@ -316355,7 +316788,7 @@ function writeAimsFile(name10, data) {
316355
316788
  const dir = aimsDir();
316356
316789
  const { mkdirSync: mkdirSync54, writeFileSync: wf, renameSync: rn } = __require("node:fs");
316357
316790
  mkdirSync54(dir, { recursive: true });
316358
- const finalPath = join93(dir, name10);
316791
+ const finalPath = join94(dir, name10);
316359
316792
  const tmpPath = `${finalPath}.tmp.${process.pid}.${Date.now()}`;
316360
316793
  try {
316361
316794
  wf(tmpPath, JSON.stringify(data, null, 2) + "\n", { encoding: "utf-8", mode: 384 });
@@ -316685,12 +317118,12 @@ async function handleAimsSuppliers(ctx3) {
316685
317118
  }
316686
317119
  ];
316687
317120
  const sponsorPaths = [
316688
- join93(homedir32(), ".open-agents", "sponsor-cache.json")
317121
+ join94(homedir33(), ".open-agents", "sponsor-cache.json")
316689
317122
  ];
316690
317123
  for (const p2 of sponsorPaths) {
316691
- if (!existsSync77(p2)) continue;
317124
+ if (!existsSync78(p2)) continue;
316692
317125
  try {
316693
- const raw = JSON.parse(readFileSync60(p2, "utf-8"));
317126
+ const raw = JSON.parse(readFileSync61(p2, "utf-8"));
316694
317127
  const list = Array.isArray(raw) ? raw : raw?.sponsors ?? [];
316695
317128
  for (const s2 of list) {
316696
317129
  suppliers.push({
@@ -317536,6 +317969,11 @@ body {
317536
317969
  <!-- Popover for process details (absolute positioned above the process row) -->
317537
317970
  <div id="proc-popover"></div>
317538
317971
 
317972
+ <!-- WO-CHAT-AUTOSCROLL — tiny tag that appears far-right just above
317973
+ the tasks-row when the user has scrolled up from live. Click
317974
+ jumps to bottom and re-enables auto-scroll. -->
317975
+ <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>
317976
+
317539
317977
  <!-- WO-TASK-06: Compact aggregated pill label above the dots row.
317540
317978
  Hidden when no processes are active; click to toggle dots row visibility. -->
317541
317979
  <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 +317998,12 @@ body {
317560
317998
  <textarea id="input-area" placeholder="Type a message..." rows="1"></textarea>
317561
317999
  <button id="send-btn" onclick="sendMessage()">send</button>
317562
318000
  <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>
318001
+ <!-- WO-CHAT-CHECKIN — teal accent button that takes the place of stop
318002
+ when the user starts typing during an active run. Click sends a
318003
+ side-channel check-in to the triage sub-agent (route /v1/chat/check-in)
318004
+ which expands the input into a steering instruction the main agent
318005
+ picks up at its next turn. -->
318006
+ <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
318007
  </div>
317564
318008
  </div>
317565
318009
 
@@ -317595,13 +318039,72 @@ let chatAbortController = null; // for stop button
317595
318039
  // state OR the user navigates away.
317596
318040
  let chatInFlightPoller = null;
317597
318041
 
317598
- // Auto-resize textarea
318042
+ // WO-CHAT-AUTOSCROLL — track whether the user is parked at the bottom.
318043
+ // When they scroll up by more than ~SCROLL_TOLERANCE px, we stop auto-
318044
+ // scrolling on new content and show the "scroll to bottom" tag. When
318045
+ // they scroll back to the bottom (or click the tag), auto-scroll
318046
+ // resumes. autoScrollPinned defaults to true so first-load works.
318047
+ let autoScrollPinned = true;
318048
+ const SCROLL_TOLERANCE = 80;
318049
+ function isAtBottom() {
318050
+ if (!conv) return true;
318051
+ return (conv.scrollHeight - conv.scrollTop - conv.clientHeight) < SCROLL_TOLERANCE;
318052
+ }
318053
+ function maybeAutoScroll() {
318054
+ if (autoScrollPinned && conv) conv.scrollTop = conv.scrollHeight;
318055
+ }
318056
+ function scrollChatToBottom() {
318057
+ if (!conv) return;
318058
+ conv.scrollTop = conv.scrollHeight;
318059
+ autoScrollPinned = true;
318060
+ const tag = document.getElementById('scroll-bottom-tag');
318061
+ if (tag) tag.style.display = 'none';
318062
+ }
318063
+ window.scrollChatToBottom = scrollChatToBottom;
318064
+ if (conv) {
318065
+ conv.addEventListener('scroll', () => {
318066
+ const atBottom = isAtBottom();
318067
+ if (atBottom) {
318068
+ autoScrollPinned = true;
318069
+ const tag = document.getElementById('scroll-bottom-tag');
318070
+ if (tag) tag.style.display = 'none';
318071
+ } else {
318072
+ autoScrollPinned = false;
318073
+ const tag = document.getElementById('scroll-bottom-tag');
318074
+ if (tag) tag.style.display = 'block';
318075
+ }
318076
+ }, { passive: true });
318077
+ }
318078
+
318079
+ // Auto-resize textarea + WO-CHAT-CHECKIN typing detection
317599
318080
  input.addEventListener('input', () => {
317600
318081
  input.style.height = 'auto';
317601
318082
  input.style.height = Math.min(input.scrollHeight, 120) + 'px';
318083
+ // While a run is streaming, swap stop button → check-in button as
318084
+ // soon as the user types anything. When the input is cleared, swap back.
318085
+ if (streaming) {
318086
+ const checkinBtn = document.getElementById('checkin-btn');
318087
+ const stopBtn = document.getElementById('stop-btn');
318088
+ if (input.value.trim().length > 0) {
318089
+ if (checkinBtn) checkinBtn.style.display = 'inline-block';
318090
+ if (stopBtn) stopBtn.style.display = 'none';
318091
+ } else {
318092
+ if (checkinBtn) checkinBtn.style.display = 'none';
318093
+ if (stopBtn) stopBtn.style.display = 'inline-block';
318094
+ }
318095
+ }
317602
318096
  });
317603
318097
  input.addEventListener('keydown', (e) => {
317604
- if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); }
318098
+ if (e.key === 'Enter' && !e.shiftKey) {
318099
+ e.preventDefault();
318100
+ // While a run is streaming, Enter dispatches a check-in instead of
318101
+ // a new top-level send (which would block on streaming === true).
318102
+ if (streaming && input.value.trim().length > 0) {
318103
+ sendCheckin();
318104
+ } else {
318105
+ sendMessage();
318106
+ }
318107
+ }
317605
318108
  });
317606
318109
 
317607
318110
  function headers() {
@@ -317705,7 +318208,7 @@ function addMessage(role, content) {
317705
318208
  div.appendChild(actions);
317706
318209
  }
317707
318210
  conv.appendChild(div);
317708
- conv.scrollTop = conv.scrollHeight;
318211
+ maybeAutoScroll();
317709
318212
  return div;
317710
318213
  }
317711
318214
 
@@ -317954,31 +318457,155 @@ function renderToolResultEvent(parent, chunkLike) {
317954
318457
  return resultEl;
317955
318458
  }
317956
318459
 
317957
- async function sendMessage() {
318460
+ // WO-CHAT-CHECKIN render a teal-accent user check-in entry. The
318461
+ // content is the raw message the user typed during an active run,
318462
+ // before the triage sub-agent expanded it. Sits inline with the
318463
+ // active assistant turn's tool dropdowns.
318464
+ function renderCheckinEvent(parent, content) {
318465
+ const el = document.createElement('div');
318466
+ 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';
318467
+ const label = document.createElement('div');
318468
+ label.style.cssText = 'color:#2db4b4;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:2px';
318469
+ label.textContent = '\\u25B8 user check-in';
318470
+ el.appendChild(label);
318471
+ const body = document.createElement('div');
318472
+ body.style.cssText = 'color:#b0d4d4;white-space:pre-wrap;word-break:break-word';
318473
+ body.textContent = String(content || '');
318474
+ el.appendChild(body);
318475
+ parent.appendChild(el);
318476
+ return el;
318477
+ }
318478
+
318479
+ // WO-CHAT-CHECKIN — render the triage sub-agent's structured response
318480
+ // (acknowledgment + steering instruction). Uses the same teal accent
318481
+ // so the user can visually trace each side-channel exchange in the
318482
+ // stack of dropdowns.
318483
+ function renderTriageResponseEvent(parent, ack, steering) {
318484
+ const el = document.createElement('div');
318485
+ el.style.cssText = 'background:#0a2628;border-left:3px solid #2db4b4;color:#7fdada;padding:6px 10px 6px 14px;margin:3px 0;font-size:0.7rem';
318486
+ const label = document.createElement('div');
318487
+ label.style.cssText = 'color:#2db4b4;font-size:0.6rem;text-transform:uppercase;letter-spacing:0.08em;margin-bottom:2px';
318488
+ label.textContent = '\\u25B8 triage \\u2192 main agent';
318489
+ el.appendChild(label);
318490
+ if (ack) {
318491
+ const ackEl = document.createElement('div');
318492
+ ackEl.style.cssText = 'color:#9fe4e4;font-style:italic;margin-bottom:3px';
318493
+ ackEl.textContent = ack;
318494
+ el.appendChild(ackEl);
318495
+ }
318496
+ if (steering) {
318497
+ const steerWrap = document.createElement('div');
318498
+ steerWrap.style.cssText = 'color:#7fdada;font-size:0.65rem';
318499
+ appendExpandableContent(steerWrap, steering, { truncateAt: 200, baseStyle: 'color:#7fdada;' });
318500
+ el.appendChild(steerWrap);
318501
+ }
318502
+ parent.appendChild(el);
318503
+ return el;
318504
+ }
318505
+
318506
+ // WO-CHAT-CHECKIN — POST a side-channel check-in to /v1/chat/check-in
318507
+ // while a chat run is streaming. Renders the raw input + the triage
318508
+ // response as teal entries inline with the live assistant turn, and
318509
+ // the triage's steering string is forwarded into the agent's
318510
+ // pendingUserMessages queue via the daemon-side mailbox file.
318511
+ async function sendCheckin() {
317958
318512
  const text = input.value.trim();
317959
- if (!text || streaming) return;
318513
+ if (!text || !streaming || !chatSessionId) return;
317960
318514
  input.value = '';
317961
318515
  input.style.height = 'auto';
317962
-
317963
- // Add user message
317964
- messages.push({ role: 'user', content: text });
317965
- addMessage('user', text);
317966
-
317967
- // System prompt
317968
- const sysPrompt = document.getElementById('system-prompt').value.trim();
317969
-
317970
- streaming = true;
317971
- chatAbortController = new AbortController();
317972
- document.getElementById('send-btn').style.display = 'none';
317973
- document.getElementById('stop-btn').style.display = 'inline-block';
317974
-
317975
- const msgDiv = addMessage('assistant', '');
317976
- let fullContent = '';
317977
- let chatTools = []; // tool calls collected during streaming
317978
- let metaInfo = null; // completion metadata
317979
-
317980
- // Tool calls container — shown live during streaming
318516
+ // Swap back to stop button after submission
318517
+ const checkinBtn = document.getElementById('checkin-btn');
318518
+ const stopBtn = document.getElementById('stop-btn');
318519
+ if (checkinBtn) checkinBtn.style.display = 'none';
318520
+ if (stopBtn) stopBtn.style.display = 'inline-block';
318521
+ // Find the active assistant bubble — the last assistant message
318522
+ // currently in the conversation. We attach the teal entries inline
318523
+ // with its tool dropdowns so the side-channel exchange interleaves
318524
+ // with the main agent's actions.
318525
+ const conv = document.getElementById('conversation');
318526
+ let toolsContainer = null;
318527
+ if (conv) {
318528
+ const lastAssistant = conv.querySelector('.msg.assistant:last-of-type');
318529
+ if (lastAssistant) {
318530
+ // Reuse the existing tools container if any, else create one
318531
+ toolsContainer = lastAssistant.querySelector('.tools-container');
318532
+ if (!toolsContainer) {
318533
+ toolsContainer = document.createElement('div');
318534
+ toolsContainer.className = 'tools-container';
318535
+ toolsContainer.style.cssText = 'margin:4px 0;font-size:0.7rem';
318536
+ lastAssistant.appendChild(toolsContainer);
318537
+ }
318538
+ } else {
318539
+ // No active assistant bubble — create a fresh one
318540
+ const md = addMessage('assistant', '');
318541
+ toolsContainer = document.createElement('div');
318542
+ toolsContainer.className = 'tools-container';
318543
+ toolsContainer.style.cssText = 'margin:4px 0;font-size:0.7rem';
318544
+ md.appendChild(toolsContainer);
318545
+ }
318546
+ }
318547
+ if (toolsContainer) renderCheckinEvent(toolsContainer, text);
318548
+ conv && maybeAutoScroll();
318549
+ // Fire the request — non-blocking from the user's perspective
318550
+ try {
318551
+ const r = await fetch('/v1/chat/check-in', {
318552
+ method: 'POST',
318553
+ headers: headers(),
318554
+ body: JSON.stringify({ session_id: chatSessionId, message: text }),
318555
+ });
318556
+ if (!r.ok) {
318557
+ if (toolsContainer) {
318558
+ const errEl = document.createElement('div');
318559
+ errEl.style.cssText = 'color:#b25f5f;font-size:0.6rem;padding:2px 14px';
318560
+ errEl.textContent = 'Check-in failed: HTTP ' + r.status;
318561
+ toolsContainer.appendChild(errEl);
318562
+ }
318563
+ return;
318564
+ }
318565
+ const data = await r.json();
318566
+ if (toolsContainer) {
318567
+ renderTriageResponseEvent(toolsContainer, data.acknowledgment || '', data.steering || '');
318568
+ conv && maybeAutoScroll();
318569
+ }
318570
+ } catch (err) {
318571
+ if (toolsContainer) {
318572
+ const errEl = document.createElement('div');
318573
+ errEl.style.cssText = 'color:#b25f5f;font-size:0.6rem;padding:2px 14px';
318574
+ errEl.textContent = 'Check-in network error: ' + (err && err.message || String(err));
318575
+ toolsContainer.appendChild(errEl);
318576
+ }
318577
+ }
318578
+ }
318579
+ window.sendCheckin = sendCheckin;
318580
+
318581
+ async function sendMessage() {
318582
+ const text = input.value.trim();
318583
+ if (!text || streaming) return;
318584
+ input.value = '';
318585
+ input.style.height = 'auto';
318586
+
318587
+ // Add user message
318588
+ messages.push({ role: 'user', content: text });
318589
+ addMessage('user', text);
318590
+
318591
+ // System prompt
318592
+ const sysPrompt = document.getElementById('system-prompt').value.trim();
318593
+
318594
+ streaming = true;
318595
+ chatAbortController = new AbortController();
318596
+ document.getElementById('send-btn').style.display = 'none';
318597
+ document.getElementById('stop-btn').style.display = 'inline-block';
318598
+
318599
+ const msgDiv = addMessage('assistant', '');
318600
+ let fullContent = '';
318601
+ let chatTools = []; // tool calls collected during streaming
318602
+ let metaInfo = null; // completion metadata
318603
+
318604
+ // Tool calls container — shown live during streaming. Class name lets
318605
+ // the WO-CHAT-CHECKIN sendCheckin() helper find it and attach teal
318606
+ // entries inline with the active assistant's tool dropdowns.
317981
318607
  const toolsContainer = document.createElement('div');
318608
+ toolsContainer.className = 'tools-container';
317982
318609
  toolsContainer.style.cssText = 'margin:4px 0;font-size:0.7rem';
317983
318610
  msgDiv.appendChild(toolsContainer);
317984
318611
 
@@ -318066,7 +318693,7 @@ async function sendMessage() {
318066
318693
  if (fullContent && !fullContent.endsWith('\\n')) fullContent += '\\n\\n';
318067
318694
  fullContent += summaryText;
318068
318695
  contentDiv.innerHTML = renderMarkdown(fullContent);
318069
- conv.scrollTop = conv.scrollHeight;
318696
+ maybeAutoScroll();
318070
318697
  }
318071
318698
  // fall through so the dropdown still renders for inspection
318072
318699
  }
@@ -318178,7 +318805,7 @@ async function sendMessage() {
318178
318805
  details.appendChild(argsDiv);
318179
318806
  }
318180
318807
  toolsContainer.appendChild(details);
318181
- conv.scrollTop = conv.scrollHeight;
318808
+ maybeAutoScroll();
318182
318809
  continue;
318183
318810
  }
318184
318811
 
@@ -318204,7 +318831,7 @@ async function sendMessage() {
318204
318831
  if (delta) {
318205
318832
  fullContent += delta;
318206
318833
  contentDiv.innerHTML = renderMarkdown(fullContent);
318207
- conv.scrollTop = conv.scrollHeight;
318834
+ maybeAutoScroll();
318208
318835
  }
318209
318836
  } catch {}
318210
318837
  }
@@ -318267,7 +318894,7 @@ async function sendMessage() {
318267
318894
  chatAbortController = null;
318268
318895
  document.getElementById('send-btn').style.display = 'inline-block';
318269
318896
  document.getElementById('stop-btn').style.display = 'none';
318270
- conv.scrollTop = conv.scrollHeight;
318897
+ maybeAutoScroll();
318271
318898
  }
318272
318899
 
318273
318900
  function toggleSystemPrompt() {
@@ -319962,37 +320589,36 @@ async function restoreChatSession() {
319962
320589
  // assistant bubble's tools container so consecutive tool events
319963
320590
  // attach to the same bubble (the live SSE handler does the same).
319964
320591
  let currentAssistantTools = null;
320592
+ const ensureTools = () => {
320593
+ if (!currentAssistantTools) {
320594
+ const md = addMessage('assistant', '');
320595
+ currentAssistantTools = document.createElement('div');
320596
+ currentAssistantTools.className = 'tools-container';
320597
+ currentAssistantTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
320598
+ md.appendChild(currentAssistantTools);
320599
+ }
320600
+ return currentAssistantTools;
320601
+ };
319965
320602
  for (const m of allMessages) {
319966
320603
  if (m.role === 'user') {
319967
320604
  addMessage('user', m.content);
319968
320605
  currentAssistantTools = null;
319969
320606
  } else if (m.role === 'assistant') {
319970
320607
  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
320608
  currentAssistantTools = document.createElement('div');
320609
+ currentAssistantTools.className = 'tools-container';
319976
320610
  currentAssistantTools.style.cssText = 'margin:4px 0;font-size:0.7rem';
319977
320611
  msgDiv.appendChild(currentAssistantTools);
319978
320612
  } 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 });
320613
+ renderToolCallEvent(ensureTools(), { tool: m.tool, args: m.args });
319988
320614
  } 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 });
320615
+ renderToolResultEvent(ensureTools(), { output: m.output, success: m.success });
320616
+ } else if (m.role === 'user_checkin') {
320617
+ // WO-CHAT-CHECKIN — replay the teal user check-in entry
320618
+ renderCheckinEvent(ensureTools(), m.content);
320619
+ } else if (m.role === 'triage_response') {
320620
+ // WO-CHAT-CHECKIN — replay the triage sub-agent's structured response
320621
+ renderTriageResponseEvent(ensureTools(), m.acknowledgment || m.content || '', m.steering || '');
319996
320622
  }
319997
320623
  }
319998
320624
  }
@@ -320489,485 +321115,139 @@ function getOpenApiSpec() {
320489
321115
  responses: { 200: { description: "Search results" } }
320490
321116
  }
320491
321117
  },
320492
- "/v1/memory/write": { post: { summary: "Write a memory entry (PT-04)", tags: ["Memory"], responses: { 201: { description: "Written" }, 403: { description: "Requires run/admin scope" } } } },
320493
- "/v1/memory/episodes": { get: { summary: "List episodes", tags: ["Memory"], responses: { 200: { description: "Paginated episodes" } } } },
320494
- "/v1/memory/failures": { get: { summary: "List failures", tags: ["Memory"], responses: { 200: { description: "Paginated failures" } } } },
320495
- // ───── PT-06: Events ─────
320496
- "/v1/events": {
320497
- get: {
320498
- summary: "Subscribe to the event bus (PT-06)",
320499
- description: "Server-sent event stream. Event types: config.changed, run.started, run.completed, run.aborted, run.failed, tool.called, memory.written, memory.searched, skill.invoked, mcp.called, engine.state_changed, auth.failed, rate_limit.hit, incident.raised, incident.resolved, session.created, session.ended, aims.policy_changed, aims.decision_recorded. Filter with ?type=foo.bar or ?type=foo.*",
320500
- tags: ["Events"],
320501
- parameters: [{ name: "type", in: "query", required: false, schema: { type: "string" }, description: "Filter by event type (supports wildcard suffix .*)" }],
320502
- responses: { 200: { description: "text/event-stream with event frames" } }
320503
- }
320504
- },
320505
- // ───── P1: Sessions ─────
320506
- "/v1/sessions": { get: { summary: "List OA task sessions", tags: ["Sessions"], responses: { 200: { description: "Paginated sessions" } } } },
320507
- "/v1/sessions/{id}": { get: { summary: "Get session history", tags: ["Sessions"], parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], responses: { 200: { description: "Session history" }, 404: { description: "Not found" } } } },
320508
- // ───── P1: Context ─────
320509
- "/v1/context": { get: { summary: "Show current session context", tags: ["Context"], responses: { 200: { description: "Context snapshot" } } } },
320510
- "/v1/context/save": { post: { summary: "Save a session context entry", tags: ["Context"], responses: { 201: { description: "Saved" } } } },
320511
- "/v1/context/restore": { get: { summary: "Build a restore prompt from saved context", tags: ["Context"], responses: { 200: { description: "Restore prompt" } } } },
320512
- "/v1/context/compact": { post: { summary: "Request context compaction (event-driven)", tags: ["Context"], responses: { 202: { description: "Compaction requested" } } } },
320513
- // ───── P1: Nexus / Sponsors ─────
320514
- "/v1/nexus/status": { get: { summary: "Nexus peer state", tags: ["Nexus"], responses: { 200: { description: "Peer cache snapshot" } } } },
320515
- "/v1/sponsors": { get: { summary: "Local sponsor directory cache", tags: ["Nexus"], responses: { 200: { description: "Paginated sponsors" } } } },
320516
- // ───── P1: Cost / Evaluate / Index ─────
320517
- "/v1/cost": { get: { summary: "Cost tracker (pricing model)", tags: ["Metering"], responses: { 200: { description: "Provider pricing" } } } },
320518
- "/v1/evaluate": { post: { summary: "Evaluate a run by ID", tags: ["Agentic"], responses: { 200: { description: "Evaluation metrics" }, 404: { description: "Run not found" } } } },
320519
- "/v1/index": { post: { summary: "Trigger repository indexing", tags: ["Agentic"], responses: { 202: { description: "Indexing started" } } } },
320520
- // ───── P2: Tools / Hooks / Agents / Engines ─────
320521
- "/v1/tools": { get: { summary: "List agentic tool registry", tags: ["Tools"], responses: { 200: { description: "Paginated tools" } } } },
320522
- "/v1/hooks": { get: { summary: "List hook types and counts", tags: ["Tools"], responses: { 200: { description: "Hook registry" } } } },
320523
- "/v1/agents": { get: { summary: "List agent types", tags: ["Tools"], responses: { 200: { description: "Agent type registry" } } } },
320524
- "/v1/engines": { get: { summary: "List long-running engines", tags: ["Engines"], responses: { 200: { description: "Engine status + state files" } } } },
320525
- // ───── P2: Voice / Vision (deferred) ─────
320526
- "/v1/voice/tts": { post: { summary: "Text-to-speech (deferred to PT-07)", tags: ["Voice"], responses: { 501: { description: "Not yet daemon-resident" } } } },
320527
- "/v1/voice/asr": { post: { summary: "Automatic speech recognition (deferred)", tags: ["Voice"], responses: { 501: { description: "Not yet daemon-resident" } } } },
320528
- "/v1/vision/describe": { post: { summary: "Vision describe (deferred to PT-07)", tags: ["Vision"], responses: { 501: { description: "Not yet daemon-resident" } } } },
320529
- // ───── ISO/IEC 42001:2023 AIMS ─────
320530
- "/v1/aims": { get: { summary: "AIMS root — control map and endpoint index", tags: ["AIMS"], responses: { 200: { description: "AIMS overview" } } } },
320531
- "/v1/aims/policies": {
320532
- get: { summary: "AI policies (ISO 42001 A.2)", tags: ["AIMS"], responses: { 200: { description: "Policy register" } } },
320533
- put: { summary: "Replace the policy register (ISO 42001 A.2)", tags: ["AIMS"], responses: { 200: { description: "Updated" }, 403: { description: "Admin scope required" } } }
320534
- },
320535
- "/v1/aims/roles": { get: { summary: "Roles and responsibilities (A.3)", tags: ["AIMS"], responses: { 200: { description: "Role register" } } } },
320536
- "/v1/aims/resources": { get: { summary: "Resources inventory (A.4)", tags: ["AIMS"], responses: { 200: { description: "Compute + backend inventory" } } } },
320537
- "/v1/aims/impact-assessments": {
320538
- get: { summary: "Impact assessment register (A.5)", tags: ["AIMS"], responses: { 200: { description: "Paginated impact assessments" } } },
320539
- post: { summary: "File an impact assessment (A.5)", tags: ["AIMS"], responses: { 201: { description: "Recorded" }, 403: { description: "Admin scope required" } } }
320540
- },
320541
- "/v1/aims/lifecycle": { get: { summary: "AI system lifecycle state (A.6)", tags: ["AIMS"], responses: { 200: { description: "Lifecycle snapshot" } } } },
320542
- "/v1/aims/data-quality": { get: { summary: "Data quality controls (A.7.2)", tags: ["AIMS"], responses: { 200: { description: "Data quality register" } } } },
320543
- "/v1/aims/transparency": { get: { summary: "Model cards and transparency info (A.8)", tags: ["AIMS"], responses: { 200: { description: "Model cards" } } } },
320544
- "/v1/aims/usage": { get: { summary: "AI system usage (A.9 alias of /v1/usage)", tags: ["AIMS"], responses: { 200: { description: "Usage snapshot" } } } },
320545
- "/v1/aims/suppliers": { get: { summary: "Third-party supplier inventory (A.10)", tags: ["AIMS"], responses: { 200: { description: "Suppliers" } } } },
320546
- "/v1/aims/incidents": {
320547
- get: { summary: "Incident register (A.6.2.8)", tags: ["AIMS"], responses: { 200: { description: "Paginated incidents" } } },
320548
- post: { summary: "Raise an incident (A.6.2.8)", tags: ["AIMS"], responses: { 201: { description: "Recorded" } } }
320549
- },
320550
- "/v1/aims/oversight": { get: { summary: "Human oversight gates (A.6.2.7)", tags: ["AIMS"], responses: { 200: { description: "Oversight configuration" } } } },
320551
- "/v1/aims/decisions": { get: { summary: "Consequential decision log (A.9)", tags: ["AIMS"], responses: { 200: { description: "Paginated decisions" } } } },
320552
- "/v1/aims/config-history": { get: { summary: "Configuration change history (A.6.2.8)", tags: ["AIMS"], responses: { 200: { description: "Config change records from audit log" } } } }
320553
- }
320554
- };
320555
- }
320556
- function getSwaggerUI() {
320557
- return `<!DOCTYPE html><html><head><title>OA API Docs</title>
320558
- <meta charset="utf-8">
320559
- <style>
320560
- body{font-family:'SF Mono',ui-monospace,monospace;background:#1a1a1e;color:#b0b0b0;margin:0;padding:20px;font-size:13px}
320561
- h1{color:#b2920a;font-size:1.3rem;margin-bottom:4px}
320562
- h2{color:#b2920a;font-size:0.85rem;margin-top:20px;text-transform:uppercase;letter-spacing:1px}
320563
- .subtitle{color:#888;font-size:0.85rem;margin-bottom:20px}
320564
- .endpoint{background:#1e1e22;border:1px solid #2a2a30;border-radius:3px;padding:8px 12px;margin:4px 0;display:flex;align-items:center;gap:12px}
320565
- .method{font-weight:bold;padding:2px 8px;border-radius:2px;font-size:0.7rem;min-width:50px;text-align:center}
320566
- .get{color:#4ec94e;border:1px solid #4ec94e}.post{color:#b2920a;border:1px solid #b2920a}.put{color:#4e94c9;border:1px solid #4e94c9}
320567
- .patch{color:#c9944e;border:1px solid #c9944e}.delete{color:#c94e4e;border:1px solid #c94e4e}
320568
- .path{color:#b0b0b0;font-weight:600}.summary{color:#888;font-size:0.75rem;flex:1;text-align:right}
320569
- .tag{margin-top:18px;border-bottom:1px solid #2a2a30;padding-bottom:4px}
320570
- a{color:#b2920a;text-decoration:none}a:hover{text-decoration:underline}
320571
- .iso-note{background:#222;border-left:3px solid #b2920a;padding:10px 14px;margin:12px 0;color:#ccc;font-size:0.8rem}
320572
- </style></head><body>
320573
- <h1>Open Agents REST API</h1>
320574
- <p class="subtitle">Enterprise-grade REST surface. Errors follow RFC 7807. Responses carry X-API-Version. <a href="/openapi.json">Full OpenAPI 3.0 spec</a></p>
320575
- <div class="iso-note">
320576
- <strong>ISO/IEC 42001:2023 AIMS layer:</strong> The /v1/aims/* endpoints expose the
320577
- AI Management System controls an auditor expects — policies, roles, resources, impact
320578
- assessments, lifecycle, data quality, transparency, usage, suppliers, incidents, oversight,
320579
- decisions, and config history. Events published to /v1/events are tagged with the matching
320580
- Annex A control reference (aims:control field).
320581
- </div>
320582
- <div id="docs"></div>
320583
- <script>
320584
- fetch('/openapi.json').then(r=>r.json()).then(spec=>{
320585
- const docs=document.getElementById('docs');
320586
- const tagOrder=(spec.tags||[]).map(t=>t.name);
320587
- const byTag={};
320588
- for(const[path,methods]of Object.entries(spec.paths)){
320589
- for(const[method,op]of Object.entries(methods)){
320590
- const tag=(op.tags||['Other'])[0];
320591
- if(!byTag[tag])byTag[tag]=[];
320592
- byTag[tag].push({method,path,summary:op.summary||''});
320593
- }
320594
- }
320595
- const orderedTags=[...tagOrder.filter(t=>byTag[t]),...Object.keys(byTag).filter(t=>!tagOrder.includes(t))];
320596
- for(const tag of orderedTags){
320597
- const endpoints=byTag[tag];
320598
- const tagObj=(spec.tags||[]).find(t=>t.name===tag);
320599
- let html='<div class="tag"><h2>'+tag+'</h2>';
320600
- if(tagObj&&tagObj.description)html+='<div style="color:#777;font-size:0.75rem;margin-bottom:8px">'+tagObj.description+'</div>';
320601
- html+='</div>';
320602
- docs.innerHTML+=html;
320603
- for(const ep of endpoints){
320604
- docs.innerHTML+='<div class="endpoint"><span class="method '+ep.method+'">'+ep.method.toUpperCase()+'</span><span class="path">'+ep.path+'</span><span class="summary">'+ep.summary+'</span></div>';
320605
- }
320606
- }
320607
- });
320608
- </script></body></html>`;
320609
- }
320610
- var init_openapi = __esm({
320611
- "packages/cli/src/api/openapi.ts"() {
320612
- "use strict";
320613
- init_http2();
320614
- }
320615
- });
320616
-
320617
- // packages/cli/src/api/auth-oidc.ts
320618
- var OIDC_ISSUER, OIDC_AUDIENCE, OIDC_SCOPE_CLAIM;
320619
- var init_auth_oidc = __esm({
320620
- "packages/cli/src/api/auth-oidc.ts"() {
320621
- "use strict";
320622
- OIDC_ISSUER = process.env["OA_OIDC_ISSUER"] || "";
320623
- OIDC_AUDIENCE = process.env["OA_OIDC_AUDIENCE"] || "";
320624
- OIDC_SCOPE_CLAIM = process.env["OA_OIDC_SCOPE_CLAIM"] || "scope";
320625
- }
320626
- });
320627
-
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
- }
321118
+ "/v1/memory/write": { post: { summary: "Write a memory entry (PT-04)", tags: ["Memory"], responses: { 201: { description: "Written" }, 403: { description: "Requires run/admin scope" } } } },
321119
+ "/v1/memory/episodes": { get: { summary: "List episodes", tags: ["Memory"], responses: { 200: { description: "Paginated episodes" } } } },
321120
+ "/v1/memory/failures": { get: { summary: "List failures", tags: ["Memory"], responses: { 200: { description: "Paginated failures" } } } },
321121
+ // ───── PT-06: Events ─────
321122
+ "/v1/events": {
321123
+ get: {
321124
+ summary: "Subscribe to the event bus (PT-06)",
321125
+ description: "Server-sent event stream. Event types: config.changed, run.started, run.completed, run.aborted, run.failed, tool.called, memory.written, memory.searched, skill.invoked, mcp.called, engine.state_changed, auth.failed, rate_limit.hit, incident.raised, incident.resolved, session.created, session.ended, aims.policy_changed, aims.decision_recorded. Filter with ?type=foo.bar or ?type=foo.*",
321126
+ tags: ["Events"],
321127
+ parameters: [{ name: "type", in: "query", required: false, schema: { type: "string" }, description: "Filter by event type (supports wildcard suffix .*)" }],
321128
+ responses: { 200: { description: "text/event-stream with event frames" } }
321129
+ }
321130
+ },
321131
+ // ───── P1: Sessions ─────
321132
+ "/v1/sessions": { get: { summary: "List OA task sessions", tags: ["Sessions"], responses: { 200: { description: "Paginated sessions" } } } },
321133
+ "/v1/sessions/{id}": { get: { summary: "Get session history", tags: ["Sessions"], parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }], responses: { 200: { description: "Session history" }, 404: { description: "Not found" } } } },
321134
+ // ───── P1: Context ─────
321135
+ "/v1/context": { get: { summary: "Show current session context", tags: ["Context"], responses: { 200: { description: "Context snapshot" } } } },
321136
+ "/v1/context/save": { post: { summary: "Save a session context entry", tags: ["Context"], responses: { 201: { description: "Saved" } } } },
321137
+ "/v1/context/restore": { get: { summary: "Build a restore prompt from saved context", tags: ["Context"], responses: { 200: { description: "Restore prompt" } } } },
321138
+ "/v1/context/compact": { post: { summary: "Request context compaction (event-driven)", tags: ["Context"], responses: { 202: { description: "Compaction requested" } } } },
321139
+ // ───── P1: Nexus / Sponsors ─────
321140
+ "/v1/nexus/status": { get: { summary: "Nexus peer state", tags: ["Nexus"], responses: { 200: { description: "Peer cache snapshot" } } } },
321141
+ "/v1/sponsors": { get: { summary: "Local sponsor directory cache", tags: ["Nexus"], responses: { 200: { description: "Paginated sponsors" } } } },
321142
+ // ───── P1: Cost / Evaluate / Index ─────
321143
+ "/v1/cost": { get: { summary: "Cost tracker (pricing model)", tags: ["Metering"], responses: { 200: { description: "Provider pricing" } } } },
321144
+ "/v1/evaluate": { post: { summary: "Evaluate a run by ID", tags: ["Agentic"], responses: { 200: { description: "Evaluation metrics" }, 404: { description: "Run not found" } } } },
321145
+ "/v1/index": { post: { summary: "Trigger repository indexing", tags: ["Agentic"], responses: { 202: { description: "Indexing started" } } } },
321146
+ // ───── P2: Tools / Hooks / Agents / Engines ─────
321147
+ "/v1/tools": { get: { summary: "List agentic tool registry", tags: ["Tools"], responses: { 200: { description: "Paginated tools" } } } },
321148
+ "/v1/hooks": { get: { summary: "List hook types and counts", tags: ["Tools"], responses: { 200: { description: "Hook registry" } } } },
321149
+ "/v1/agents": { get: { summary: "List agent types", tags: ["Tools"], responses: { 200: { description: "Agent type registry" } } } },
321150
+ "/v1/engines": { get: { summary: "List long-running engines", tags: ["Engines"], responses: { 200: { description: "Engine status + state files" } } } },
321151
+ // ───── P2: Voice / Vision (deferred) ─────
321152
+ "/v1/voice/tts": { post: { summary: "Text-to-speech (deferred to PT-07)", tags: ["Voice"], responses: { 501: { description: "Not yet daemon-resident" } } } },
321153
+ "/v1/voice/asr": { post: { summary: "Automatic speech recognition (deferred)", tags: ["Voice"], responses: { 501: { description: "Not yet daemon-resident" } } } },
321154
+ "/v1/vision/describe": { post: { summary: "Vision describe (deferred to PT-07)", tags: ["Vision"], responses: { 501: { description: "Not yet daemon-resident" } } } },
321155
+ // ───── ISO/IEC 42001:2023 AIMS ─────
321156
+ "/v1/aims": { get: { summary: "AIMS root — control map and endpoint index", tags: ["AIMS"], responses: { 200: { description: "AIMS overview" } } } },
321157
+ "/v1/aims/policies": {
321158
+ get: { summary: "AI policies (ISO 42001 A.2)", tags: ["AIMS"], responses: { 200: { description: "Policy register" } } },
321159
+ put: { summary: "Replace the policy register (ISO 42001 A.2)", tags: ["AIMS"], responses: { 200: { description: "Updated" }, 403: { description: "Admin scope required" } } }
321160
+ },
321161
+ "/v1/aims/roles": { get: { summary: "Roles and responsibilities (A.3)", tags: ["AIMS"], responses: { 200: { description: "Role register" } } } },
321162
+ "/v1/aims/resources": { get: { summary: "Resources inventory (A.4)", tags: ["AIMS"], responses: { 200: { description: "Compute + backend inventory" } } } },
321163
+ "/v1/aims/impact-assessments": {
321164
+ get: { summary: "Impact assessment register (A.5)", tags: ["AIMS"], responses: { 200: { description: "Paginated impact assessments" } } },
321165
+ post: { summary: "File an impact assessment (A.5)", tags: ["AIMS"], responses: { 201: { description: "Recorded" }, 403: { description: "Admin scope required" } } }
321166
+ },
321167
+ "/v1/aims/lifecycle": { get: { summary: "AI system lifecycle state (A.6)", tags: ["AIMS"], responses: { 200: { description: "Lifecycle snapshot" } } } },
321168
+ "/v1/aims/data-quality": { get: { summary: "Data quality controls (A.7.2)", tags: ["AIMS"], responses: { 200: { description: "Data quality register" } } } },
321169
+ "/v1/aims/transparency": { get: { summary: "Model cards and transparency info (A.8)", tags: ["AIMS"], responses: { 200: { description: "Model cards" } } } },
321170
+ "/v1/aims/usage": { get: { summary: "AI system usage (A.9 alias of /v1/usage)", tags: ["AIMS"], responses: { 200: { description: "Usage snapshot" } } } },
321171
+ "/v1/aims/suppliers": { get: { summary: "Third-party supplier inventory (A.10)", tags: ["AIMS"], responses: { 200: { description: "Suppliers" } } } },
321172
+ "/v1/aims/incidents": {
321173
+ get: { summary: "Incident register (A.6.2.8)", tags: ["AIMS"], responses: { 200: { description: "Paginated incidents" } } },
321174
+ post: { summary: "Raise an incident (A.6.2.8)", tags: ["AIMS"], responses: { 201: { description: "Recorded" } } }
321175
+ },
321176
+ "/v1/aims/oversight": { get: { summary: "Human oversight gates (A.6.2.7)", tags: ["AIMS"], responses: { 200: { description: "Oversight configuration" } } } },
321177
+ "/v1/aims/decisions": { get: { summary: "Consequential decision log (A.9)", tags: ["AIMS"], responses: { 200: { description: "Paginated decisions" } } } },
321178
+ "/v1/aims/config-history": { get: { summary: "Configuration change history (A.6.2.8)", tags: ["AIMS"], responses: { 200: { description: "Config change records from audit log" } } } }
320888
321179
  }
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
321180
  };
320903
- inFlight.set(opts.sessionId, job);
320904
- persistInFlight(job);
320905
- return job;
320906
321181
  }
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);
321182
+ function getSwaggerUI() {
321183
+ return `<!DOCTYPE html><html><head><title>OA API Docs</title>
321184
+ <meta charset="utf-8">
321185
+ <style>
321186
+ body{font-family:'SF Mono',ui-monospace,monospace;background:#1a1a1e;color:#b0b0b0;margin:0;padding:20px;font-size:13px}
321187
+ h1{color:#b2920a;font-size:1.3rem;margin-bottom:4px}
321188
+ h2{color:#b2920a;font-size:0.85rem;margin-top:20px;text-transform:uppercase;letter-spacing:1px}
321189
+ .subtitle{color:#888;font-size:0.85rem;margin-bottom:20px}
321190
+ .endpoint{background:#1e1e22;border:1px solid #2a2a30;border-radius:3px;padding:8px 12px;margin:4px 0;display:flex;align-items:center;gap:12px}
321191
+ .method{font-weight:bold;padding:2px 8px;border-radius:2px;font-size:0.7rem;min-width:50px;text-align:center}
321192
+ .get{color:#4ec94e;border:1px solid #4ec94e}.post{color:#b2920a;border:1px solid #b2920a}.put{color:#4e94c9;border:1px solid #4e94c9}
321193
+ .patch{color:#c9944e;border:1px solid #c9944e}.delete{color:#c94e4e;border:1px solid #c94e4e}
321194
+ .path{color:#b0b0b0;font-weight:600}.summary{color:#888;font-size:0.75rem;flex:1;text-align:right}
321195
+ .tag{margin-top:18px;border-bottom:1px solid #2a2a30;padding-bottom:4px}
321196
+ a{color:#b2920a;text-decoration:none}a:hover{text-decoration:underline}
321197
+ .iso-note{background:#222;border-left:3px solid #b2920a;padding:10px 14px;margin:12px 0;color:#ccc;font-size:0.8rem}
321198
+ </style></head><body>
321199
+ <h1>Open Agents REST API</h1>
321200
+ <p class="subtitle">Enterprise-grade REST surface. Errors follow RFC 7807. Responses carry X-API-Version. <a href="/openapi.json">Full OpenAPI 3.0 spec</a></p>
321201
+ <div class="iso-note">
321202
+ <strong>ISO/IEC 42001:2023 AIMS layer:</strong> The /v1/aims/* endpoints expose the
321203
+ AI Management System controls an auditor expects — policies, roles, resources, impact
321204
+ assessments, lifecycle, data quality, transparency, usage, suppliers, incidents, oversight,
321205
+ decisions, and config history. Events published to /v1/events are tagged with the matching
321206
+ Annex A control reference (aims:control field).
321207
+ </div>
321208
+ <div id="docs"></div>
321209
+ <script>
321210
+ fetch('/openapi.json').then(r=>r.json()).then(spec=>{
321211
+ const docs=document.getElementById('docs');
321212
+ const tagOrder=(spec.tags||[]).map(t=>t.name);
321213
+ const byTag={};
321214
+ for(const[path,methods]of Object.entries(spec.paths)){
321215
+ for(const[method,op]of Object.entries(methods)){
321216
+ const tag=(op.tags||['Other'])[0];
321217
+ if(!byTag[tag])byTag[tag]=[];
321218
+ byTag[tag].push({method,path,summary:op.summary||''});
321219
+ }
320914
321220
  }
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
- }
321221
+ const orderedTags=[...tagOrder.filter(t=>byTag[t]),...Object.keys(byTag).filter(t=>!tagOrder.includes(t))];
321222
+ for(const tag of orderedTags){
321223
+ const endpoints=byTag[tag];
321224
+ const tagObj=(spec.tags||[]).find(t=>t.name===tag);
321225
+ let html='<div class="tag"><h2>'+tag+'</h2>';
321226
+ if(tagObj&&tagObj.description)html+='<div style="color:#777;font-size:0.75rem;margin-bottom:8px">'+tagObj.description+'</div>';
321227
+ html+='</div>';
321228
+ docs.innerHTML+=html;
321229
+ for(const ep of endpoints){
321230
+ docs.innerHTML+='<div class="endpoint"><span class="method '+ep.method+'">'+ep.method.toUpperCase()+'</span><span class="path">'+ep.path+'</span><span class="summary">'+ep.summary+'</span></div>';
320940
321231
  }
320941
- } catch {
320942
321232
  }
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
- ];
321233
+ });
321234
+ </script></body></html>`;
320956
321235
  }
320957
- var sessions, inFlight, SESSION_TTL_MS, PARTIAL_TAIL_BUDGET;
320958
- var init_chat_session = __esm({
320959
- "packages/cli/src/api/chat-session.ts"() {
321236
+ var init_openapi = __esm({
321237
+ "packages/cli/src/api/openapi.ts"() {
320960
321238
  "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;
321239
+ init_http2();
321240
+ }
321241
+ });
321242
+
321243
+ // packages/cli/src/api/auth-oidc.ts
321244
+ var OIDC_ISSUER, OIDC_AUDIENCE, OIDC_SCOPE_CLAIM;
321245
+ var init_auth_oidc = __esm({
321246
+ "packages/cli/src/api/auth-oidc.ts"() {
321247
+ "use strict";
321248
+ OIDC_ISSUER = process.env["OA_OIDC_ISSUER"] || "";
321249
+ OIDC_AUDIENCE = process.env["OA_OIDC_AUDIENCE"] || "";
321250
+ OIDC_SCOPE_CLAIM = process.env["OA_OIDC_SCOPE_CLAIM"] || "scope";
320971
321251
  }
320972
321252
  });
320973
321253
 
@@ -324407,6 +324687,105 @@ ${historyLines}
324407
324687
  jsonResponse(res, 200, { sessions: listSessions2() });
324408
324688
  return;
324409
324689
  }
324690
+ if (pathname === "/v1/chat/check-in" && method === "POST") {
324691
+ if (!checkAuth(req2, res, "run")) {
324692
+ status = 401;
324693
+ return;
324694
+ }
324695
+ const body = await parseJsonBody(req2);
324696
+ if (!body || typeof body.session_id !== "string" || typeof body.message !== "string") {
324697
+ jsonResponse(res, 400, { error: "Required: { session_id: string, message: string }" });
324698
+ return;
324699
+ }
324700
+ const checkinSession = lookupSession(body.session_id);
324701
+ if (!checkinSession) {
324702
+ jsonResponse(res, 404, { error: "Session not found", session_id: body.session_id });
324703
+ return;
324704
+ }
324705
+ try {
324706
+ addCheckinMessage(checkinSession, body.message);
324707
+ } catch {
324708
+ }
324709
+ let acknowledgment = "Got it, adapting course.";
324710
+ let steering = body.message;
324711
+ try {
324712
+ const cfg = loadConfig();
324713
+ const { OllamaAgenticBackend: OllamaAgenticBackend2, AgenticRunner: AgenticRunner2 } = await Promise.resolve().then(() => (init_dist8(), dist_exports4));
324714
+ const triageBackend = new OllamaAgenticBackend2(cfg.backendUrl, cfg.model, cfg.apiKey);
324715
+ const triage = new AgenticRunner2(triageBackend, {
324716
+ maxTurns: 3,
324717
+ maxTokens: 512,
324718
+ temperature: 0.3,
324719
+ requestTimeoutMs: 15e3,
324720
+ taskTimeoutMs: 3e4,
324721
+ streamEnabled: false
324722
+ });
324723
+ triage.registerTool({
324724
+ name: "task_complete",
324725
+ description: "Return the structured triage result.",
324726
+ parameters: {
324727
+ type: "object",
324728
+ properties: {
324729
+ acknowledgment: { type: "string", description: "Short spoken-style ack (1 sentence) referencing what the user asked." },
324730
+ summary: { type: "string", description: "Expanded steering instruction for the main agent." }
324731
+ },
324732
+ required: ["summary", "acknowledgment"]
324733
+ },
324734
+ async execute(args) {
324735
+ return { success: true, output: JSON.stringify(args) };
324736
+ }
324737
+ });
324738
+ 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");
324739
+ const triagePrompt = [
324740
+ "The user has typed a mid-run check-in message while the main agent is working.",
324741
+ "Your job: expand their raw input into a clear steering instruction the main agent can act on,",
324742
+ "and produce a brief spoken acknowledgment that REFERENCES what they actually said.",
324743
+ "",
324744
+ `Recent conversation:
324745
+ ${recentMessages || "(none)"}`,
324746
+ "",
324747
+ `User check-in: "${body.message}"`,
324748
+ "",
324749
+ "Call task_complete with:",
324750
+ "- acknowledgment: 1 sentence specific to their request (no generic 'Got it')",
324751
+ "- summary: an expanded instruction for the main agent (~2-4 sentences)"
324752
+ ].join("\n");
324753
+ const result = await triage.run(triagePrompt, "Triage check-in handler");
324754
+ try {
324755
+ const parsed = JSON.parse(result.summary || "{}");
324756
+ if (parsed.acknowledgment) acknowledgment = String(parsed.acknowledgment);
324757
+ if (parsed.summary) steering = String(parsed.summary);
324758
+ } catch {
324759
+ if (result.summary && result.summary.length > 10) steering = result.summary;
324760
+ }
324761
+ } catch (err) {
324762
+ }
324763
+ try {
324764
+ addTriageResponseMessage(checkinSession, acknowledgment, steering);
324765
+ } catch {
324766
+ }
324767
+ const markedSteering = `[USER CHECK-IN — incorporate during current run]
324768
+ ${steering}`;
324769
+ try {
324770
+ appendCheckin(body.session_id, markedSteering);
324771
+ } catch {
324772
+ }
324773
+ try {
324774
+ publishEvent("memory.written", {
324775
+ kind: "chat_checkin",
324776
+ session_id: body.session_id,
324777
+ ack: acknowledgment.slice(0, 120)
324778
+ }, { subject: body.session_id });
324779
+ } catch {
324780
+ }
324781
+ jsonResponse(res, 200, {
324782
+ session_id: body.session_id,
324783
+ acknowledgment,
324784
+ steering,
324785
+ delivered: true
324786
+ });
324787
+ return;
324788
+ }
324410
324789
  const chatSessionMatch = pathname.match(/^\/v1\/chat\/sessions\/([^/]+)$/);
324411
324790
  if (chatSessionMatch) {
324412
324791
  const sid = decodeURIComponent(chatSessionMatch[1]);
@@ -326049,6 +326428,23 @@ RULES:
326049
326428
  });
326050
326429
  runner.setWorkingDirectory(repoRoot);
326051
326430
  _activeRunnerRef = runner;
326431
+ let _checkinPoller = null;
326432
+ try {
326433
+ const oaSessionId = process.env["OA_SESSION_ID"];
326434
+ if (oaSessionId) {
326435
+ const { drainCheckins: drainCheckins2 } = (init_chat_session(), __toCommonJS(chat_session_exports));
326436
+ _checkinPoller = setInterval(() => {
326437
+ try {
326438
+ const pending = drainCheckins2(oaSessionId);
326439
+ for (const steering of pending) {
326440
+ runner.injectUserMessage(steering);
326441
+ }
326442
+ } catch {
326443
+ }
326444
+ }, 1500);
326445
+ }
326446
+ } catch {
326447
+ }
326052
326448
  const tools = buildTools(repoRoot, config, contextWindowSize, modelTier);
326053
326449
  if (contextWindowSize && contextWindowSize > 0) {
326054
326450
  for (const tool of tools) {
@@ -326925,6 +327321,13 @@ When done, either call task_complete with your answer, or use FINAL_VAR(variable
326925
327321
  if (backend && typeof backend.stop === "function") {
326926
327322
  backend.stop();
326927
327323
  }
327324
+ if (_checkinPoller) {
327325
+ try {
327326
+ clearInterval(_checkinPoller);
327327
+ } catch {
327328
+ }
327329
+ _checkinPoller = null;
327330
+ }
326928
327331
  });
326929
327332
  return { runner, promise, filesTouched, get toolCallCount() {
326930
327333
  return toolSequence.length;