open-agents-ai 0.187.541 → 0.187.543

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -551057,6 +551057,29 @@ function tuiSelect(opts) {
551057
551057
  if (items.length === 0) {
551058
551058
  return Promise.resolve({ confirmed: false, key: null, index: -1 });
551059
551059
  }
551060
+ if (!process.stdin.isTTY && process.env["OA_TUI_FORCE_INTERACTIVE"] !== "1") {
551061
+ const lines = [];
551062
+ if (currentTitle) lines.push(currentTitle);
551063
+ if (lines.length) lines.push("");
551064
+ let idx = 1;
551065
+ for (const item of items) {
551066
+ const isSkip = skipSet.has(item.key);
551067
+ const labelPlain = stripAnsi3(item.label);
551068
+ const detailPlain = item.detail ? stripAnsi3(item.detail) : "";
551069
+ if (isSkip) {
551070
+ lines.push(labelPlain);
551071
+ } else {
551072
+ const num = String(idx).padStart(2, " ");
551073
+ const detail = detailPlain ? ` — ${detailPlain}` : "";
551074
+ lines.push(` ${num}. ${labelPlain}${detail}`);
551075
+ idx++;
551076
+ }
551077
+ }
551078
+ if (opts.customKeyHint) lines.push("", opts.customKeyHint);
551079
+ lines.push("", "(non-interactive: list shown above; pick options by re-running this command from the TUI)");
551080
+ process.stdout.write(lines.join("\n") + "\n");
551081
+ return Promise.resolve({ confirmed: false, key: null, index: -1 });
551082
+ }
551060
551083
  const isSkippable = (idx) => skipSet.has(items[idx].key);
551061
551084
  let filter2 = "";
551062
551085
  let matchSet = /* @__PURE__ */ new Set();
@@ -585939,6 +585962,171 @@ var init_graphical_sudo = __esm({
585939
585962
  }
585940
585963
  });
585941
585964
 
585965
+ // packages/cli/src/api/command-passthrough.ts
585966
+ var command_passthrough_exports = {};
585967
+ __export(command_passthrough_exports, {
585968
+ runCommand: () => runCommand
585969
+ });
585970
+ function stripAnsi5(s2) {
585971
+ return s2.replace(/\x1B(?:\[[\d;?]*[a-zA-Z]|\][^\x07\x1B]*[\x07\x1B]?|[@-Z\\-_])/g, "");
585972
+ }
585973
+ async function runCommand(input, opts) {
585974
+ const start2 = Date.now();
585975
+ const trimmed = input.trim();
585976
+ const slashCmd = trimmed.startsWith("/") ? trimmed : "/" + trimmed;
585977
+ const [rawCmd, ...rest] = slashCmd.slice(1).split(/\s+/);
585978
+ const cmdName = rawCmd ?? "";
585979
+ const argsStr = rest.join(" ");
585980
+ const release = await acquireLock2();
585981
+ try {
585982
+ const buf = [];
585983
+ setContentWriteHook({
585984
+ begin: () => {
585985
+ },
585986
+ end: () => {
585987
+ },
585988
+ redirect: () => (text) => {
585989
+ buf.push(text);
585990
+ }
585991
+ });
585992
+ const origWrite = process.stdout.write.bind(process.stdout);
585993
+ process.stdout.write = function(chunk, ...rest2) {
585994
+ if (typeof chunk === "string") buf.push(chunk);
585995
+ else if (chunk instanceof Buffer) buf.push(chunk.toString("utf-8"));
585996
+ const cb = rest2.find((r2) => typeof r2 === "function");
585997
+ if (cb) cb();
585998
+ return true;
585999
+ };
586000
+ let kind = "handled";
586001
+ let errMsg;
586002
+ try {
586003
+ const ctx3 = buildSyntheticContext(opts?.config, opts?.repoRoot);
586004
+ kind = await handleSlashCommand(slashCmd, ctx3);
586005
+ } catch (e2) {
586006
+ kind = "error";
586007
+ errMsg = e2 instanceof Error ? e2.message : String(e2);
586008
+ buf.push(`
586009
+ [error] ${errMsg}
586010
+ `);
586011
+ } finally {
586012
+ process.stdout.write = origWrite;
586013
+ setContentWriteHook({ begin: () => {
586014
+ }, end: () => {
586015
+ } });
586016
+ }
586017
+ const ansi5 = buf.join("");
586018
+ return {
586019
+ ok: kind !== "error" && kind !== "not_a_command",
586020
+ command: cmdName,
586021
+ args: argsStr,
586022
+ kind,
586023
+ output: stripAnsi5(ansi5).trim(),
586024
+ ansi: ansi5,
586025
+ durationMs: Date.now() - start2,
586026
+ error: errMsg
586027
+ };
586028
+ } finally {
586029
+ release();
586030
+ }
586031
+ }
586032
+ function acquireLock2() {
586033
+ let release;
586034
+ const next = new Promise((res) => {
586035
+ release = res;
586036
+ });
586037
+ const prev = _passthroughLock;
586038
+ _passthroughLock = next;
586039
+ return prev.then(() => release);
586040
+ }
586041
+ function buildSyntheticContext(config, repoRoot) {
586042
+ const cfg = config ?? loadConfig();
586043
+ return {
586044
+ config: cfg,
586045
+ repoRoot: repoRoot ?? process.cwd(),
586046
+ rl: makeRejectingReadline(),
586047
+ setModel: (_model) => {
586048
+ },
586049
+ setVerbose: (_verbose) => {
586050
+ },
586051
+ setEndpoint: (_url, _t, _k) => {
586052
+ },
586053
+ deactivateStatusBar: () => {
586054
+ },
586055
+ disableMouse: () => {
586056
+ },
586057
+ enableMouse: () => {
586058
+ },
586059
+ isMouseEnabled: () => false,
586060
+ lockFooter: () => {
586061
+ },
586062
+ unlockFooter: () => {
586063
+ },
586064
+ stopBanner: () => {
586065
+ },
586066
+ killEphemeral: () => {
586067
+ },
586068
+ setKeyPool: (_keys) => {
586069
+ },
586070
+ clearScreen: () => {
586071
+ },
586072
+ newSession: () => {
586073
+ },
586074
+ refreshBanner: () => {
586075
+ },
586076
+ exit: () => {
586077
+ renderError2("/quit and /exit are TUI-only — close the browser tab to end the GUI session.");
586078
+ },
586079
+ voiceToggle: async () => {
586080
+ renderError2(`voice ${TUI_ONLY_HINT} — use the GUI voice button instead.`);
586081
+ return "voice not available in GUI";
586082
+ },
586083
+ voiceSetModel: async (_id2) => {
586084
+ renderError2(`voice model ${TUI_ONLY_HINT}`);
586085
+ return "";
586086
+ },
586087
+ saveSettings: (_settings) => {
586088
+ }
586089
+ };
586090
+ }
586091
+ function makeRejectingReadline() {
586092
+ const reject = () => {
586093
+ throw new Error(
586094
+ "interactive prompts are not supported via the GUI command bridge — run this command from the TUI (open a terminal and type `oa`)."
586095
+ );
586096
+ };
586097
+ const noop2 = () => {
586098
+ };
586099
+ return {
586100
+ question: (_q, _cb) => reject(),
586101
+ close: noop2,
586102
+ write: noop2,
586103
+ on: () => noop2,
586104
+ once: () => noop2,
586105
+ off: () => noop2,
586106
+ removeListener: () => noop2,
586107
+ pause: noop2,
586108
+ resume: noop2,
586109
+ setPrompt: noop2,
586110
+ prompt: noop2,
586111
+ line: "",
586112
+ cursor: 0,
586113
+ terminal: false,
586114
+ input: { isTTY: false, on: noop2, once: noop2, removeListener: noop2, pause: noop2, resume: noop2 },
586115
+ output: { write: noop2, columns: 80, rows: 24, isTTY: false }
586116
+ };
586117
+ }
586118
+ var _passthroughLock, TUI_ONLY_HINT;
586119
+ var init_command_passthrough = __esm({
586120
+ "packages/cli/src/api/command-passthrough.ts"() {
586121
+ "use strict";
586122
+ init_render2();
586123
+ init_commands();
586124
+ init_config();
586125
+ _passthroughLock = Promise.resolve();
586126
+ TUI_ONLY_HINT = "(this command is TUI-only — no-op in GUI)";
586127
+ }
586128
+ });
586129
+
585942
586130
  // packages/cli/src/api/routes-v1.ts
585943
586131
  import { existsSync as existsSync103, readFileSync as readFileSync83, readdirSync as readdirSync34, statSync as statSync34 } from "node:fs";
585944
586132
  import { join as join119, resolve as pathResolve2 } from "node:path";
@@ -586026,6 +586214,8 @@ async function tryRouteV1(ctx3) {
586026
586214
  }
586027
586215
  if (pathname === "/v1/share/generate" && method === "POST") return handleGenerateShare(ctx3);
586028
586216
  if (pathname === "/v1/remote-proxy" && method === "POST") return handleRemoteProxy(ctx3);
586217
+ if (pathname === "/v1/command" && method === "POST") return handleCommandPassthrough(ctx3);
586218
+ if (pathname === "/v1/commands" && method === "GET") return handleCommandsList(ctx3);
586029
586219
  if (pathname === "/v1/tor/status" && method === "GET") return handleTorStatus(ctx3);
586030
586220
  if (pathname === "/v1/tor/enable" && method === "POST") return handleTorEnable(ctx3);
586031
586221
  if (pathname === "/v1/tor/disable" && method === "POST") return handleTorDisable(ctx3);
@@ -587704,6 +587894,104 @@ data: ${JSON.stringify({ error: err instanceof Error ? err.message : String(err)
587704
587894
  }
587705
587895
  return true;
587706
587896
  }
587897
+ async function handleCommandsList(ctx3) {
587898
+ const { res, requestId } = ctx3;
587899
+ try {
587900
+ const { SLASH_COMMANDS: SLASH_COMMANDS2 } = await Promise.resolve().then(() => (init_render2(), render_exports));
587901
+ const out = [];
587902
+ for (const [sig, desc] of SLASH_COMMANDS2) {
587903
+ const name10 = (sig.split(/\s+/)[0] || "").replace(/^\//, "");
587904
+ if (!name10) continue;
587905
+ out.push({ name: name10, signature: sig, description: desc, kind: "command" });
587906
+ }
587907
+ try {
587908
+ const { discoverSkills: discoverSkills2 } = await Promise.resolve().then(() => (init_dist5(), dist_exports));
587909
+ const skills = discoverSkills2(process.cwd());
587910
+ for (const s2 of Array.isArray(skills) ? skills : []) {
587911
+ const name10 = s2?.name;
587912
+ const desc = s2?.description || "";
587913
+ if (typeof name10 === "string" && name10) {
587914
+ out.push({
587915
+ name: name10,
587916
+ signature: `/${name10}`,
587917
+ description: desc || "(skill)",
587918
+ kind: "skill"
587919
+ });
587920
+ }
587921
+ }
587922
+ } catch {
587923
+ }
587924
+ const seen = /* @__PURE__ */ new Set();
587925
+ const dedup2 = out.filter((c9) => {
587926
+ const k = `${c9.kind}:${c9.signature}`;
587927
+ if (seen.has(k)) return false;
587928
+ seen.add(k);
587929
+ return true;
587930
+ });
587931
+ sendJson(res, 200, { commands: dedup2, count: dedup2.length });
587932
+ } catch (err) {
587933
+ sendProblem(res, problemDetails({
587934
+ type: P.internalError,
587935
+ status: 500,
587936
+ title: "Commands listing failed",
587937
+ detail: err instanceof Error ? err.message : String(err),
587938
+ instance: requestId
587939
+ }));
587940
+ }
587941
+ return true;
587942
+ }
587943
+ async function handleCommandPassthrough(ctx3) {
587944
+ const { req: req2, res, requestId } = ctx3;
587945
+ const reqAuth = req2;
587946
+ if (reqAuth._authScope !== "admin" && reqAuth._authScope !== "run") {
587947
+ sendProblem(res, problemDetails({
587948
+ type: P.forbidden,
587949
+ status: 403,
587950
+ title: "Auth required",
587951
+ detail: "Command passthrough requires 'run' or 'admin' scope.",
587952
+ instance: requestId
587953
+ }));
587954
+ return true;
587955
+ }
587956
+ let body;
587957
+ try {
587958
+ body = await parseJsonBodyStrict(req2);
587959
+ } catch {
587960
+ sendProblem(res, problemDetails({
587961
+ type: P.invalidRequest,
587962
+ status: 400,
587963
+ title: "Body must be JSON",
587964
+ detail: "Provide { input: string }",
587965
+ instance: requestId
587966
+ }));
587967
+ return true;
587968
+ }
587969
+ const input = typeof body?.input === "string" ? body.input : "";
587970
+ if (!input.trim()) {
587971
+ sendProblem(res, problemDetails({
587972
+ type: P.invalidRequest,
587973
+ status: 400,
587974
+ title: "input required",
587975
+ detail: "Provide a non-empty 'input' string (e.g. '/help')",
587976
+ instance: requestId
587977
+ }));
587978
+ return true;
587979
+ }
587980
+ try {
587981
+ const { runCommand: runCommand3 } = await Promise.resolve().then(() => (init_command_passthrough(), command_passthrough_exports));
587982
+ const result = await runCommand3(input);
587983
+ sendJson(res, 200, result);
587984
+ } catch (err) {
587985
+ sendProblem(res, problemDetails({
587986
+ type: P.internalError,
587987
+ status: 500,
587988
+ title: "Command dispatch failed",
587989
+ detail: err instanceof Error ? err.message : String(err),
587990
+ instance: requestId
587991
+ }));
587992
+ }
587993
+ return true;
587994
+ }
587707
587995
  async function handleRevokeKey(ctx3, prefix) {
587708
587996
  const { req: req2, res, requestId } = ctx3;
587709
587997
  const reqAuth = req2;
@@ -591439,15 +591727,88 @@ async function sendCheckin() {
591439
591727
  }
591440
591728
  window.sendCheckin = sendCheckin;
591441
591729
 
591730
+ // ─── Slash command inline runner ────────────────────────────────────────
591731
+ // POSTs /v1/command and renders the resulting captured TUI output as a
591732
+ // system message in the active chat. Output is plain text by default;
591733
+ // when the response carries ANSI we render that into a <pre> block with
591734
+ // minimal CSI→span color conversion so theme accents survive.
591735
+ async function runSlashCommandInline(rawInput) {
591736
+ const placeholder = addMessage('assistant', '');
591737
+ const head = document.createElement('div');
591738
+ head.style.cssText = 'font-size:0.72rem;color:var(--color-fg-muted);margin-bottom:4px';
591739
+ head.textContent = 'running ' + rawInput.split(/\\s+/)[0] + '…';
591740
+ placeholder.appendChild(head);
591741
+ const body = document.createElement('pre');
591742
+ body.style.cssText = 'margin:0;font-family:var(--font-mono);font-size:0.78rem;white-space:pre-wrap;word-break:break-word';
591743
+ placeholder.appendChild(body);
591744
+ try {
591745
+ const r = await fetch('/v1/command', {
591746
+ method: 'POST',
591747
+ headers: { ...headers(), 'Content-Type': 'application/json' },
591748
+ body: JSON.stringify({ input: rawInput }),
591749
+ });
591750
+ if (r.status === 403) {
591751
+ head.textContent = '✗ ' + rawInput;
591752
+ head.style.color = 'var(--color-error)';
591753
+ body.textContent = 'admin/run scope required';
591754
+ return;
591755
+ }
591756
+ const j = await r.json().catch(() => ({}));
591757
+ if (!r.ok) {
591758
+ head.textContent = '✗ ' + rawInput;
591759
+ head.style.color = 'var(--color-error)';
591760
+ body.textContent = (j && j.detail) || ('HTTP ' + r.status);
591761
+ return;
591762
+ }
591763
+ const cmdName = '/' + (j.command || '');
591764
+ const tookMs = j.durationMs || 0;
591765
+ if (j.kind === 'not_a_command') {
591766
+ head.textContent = '? unknown command ' + cmdName;
591767
+ head.style.color = 'var(--color-warning)';
591768
+ body.textContent = j.output || '(no output)';
591769
+ } else if (j.kind === 'exit') {
591770
+ head.textContent = cmdName + ' (exit requested — TUI-only, no-op in GUI)';
591771
+ head.style.color = 'var(--color-fg-muted)';
591772
+ body.textContent = j.output || '';
591773
+ } else if (j.error) {
591774
+ head.textContent = '✗ ' + cmdName;
591775
+ head.style.color = 'var(--color-error)';
591776
+ body.textContent = j.error + (j.output ? '\\n\\n' + j.output : '');
591777
+ } else {
591778
+ head.textContent = '✓ ' + cmdName + (tookMs ? ' (' + tookMs + 'ms)' : '');
591779
+ head.style.color = 'var(--color-success)';
591780
+ body.textContent = j.output || '(no output)';
591781
+ }
591782
+ } catch (e) {
591783
+ head.textContent = '✗ ' + rawInput;
591784
+ head.style.color = 'var(--color-error)';
591785
+ body.textContent = (e && e.message) ? e.message : String(e);
591786
+ }
591787
+ }
591788
+
591442
591789
  async function sendMessage() {
591443
591790
  const text = input.value.trim();
591444
591791
  if (!text || streaming) return;
591792
+
591793
+ // ─── Slash command passthrough ────────────────────────────────────────
591794
+ // /foo args → POST /v1/command, render result inline as a system
591795
+ // message, and skip the chat send entirely. Mirrors TUI behaviour:
591796
+ // commands don't enter the chat history.
591797
+ if (text.startsWith('/') && !text.startsWith('//')) {
591798
+ input.value = '';
591799
+ input.style.height = 'auto';
591800
+ addMessage('user', text);
591801
+ return runSlashCommandInline(text);
591802
+ }
591803
+ // Escape hatch: a literal /-message can be typed by prefixing //.
591804
+ // The leading slash is stripped so the agent sees the real text.
591805
+ const sendText = text.startsWith('//') ? text.slice(1) : text;
591445
591806
  input.value = '';
591446
591807
  input.style.height = 'auto';
591447
591808
 
591448
591809
  // Add user message
591449
- messages.push({ role: 'user', content: text });
591450
- addMessage('user', text);
591810
+ messages.push({ role: 'user', content: sendText });
591811
+ addMessage('user', sendText);
591451
591812
 
591452
591813
  // System prompt
591453
591814
  const sysPrompt = document.getElementById('system-prompt').value.trim();
@@ -591483,10 +591844,10 @@ async function sendMessage() {
591483
591844
  // Prepend context files as a FILES block so the agent can read them
591484
591845
  // without having to guess paths. The user picked these via right-click
591485
591846
  // "Add to context" on the workspace tree.
591486
- let messageWithContext = text;
591847
+ let messageWithContext = sendText;
591487
591848
  if (contextFiles.length > 0) {
591488
591849
  const filesBlock = 'FILES IN CONTEXT:\\n' + contextFiles.map(p => ' - ' + p).join('\\n') + '\\n\\n';
591489
- messageWithContext = filesBlock + text;
591850
+ messageWithContext = filesBlock + sendText;
591490
591851
  }
591491
591852
 
591492
591853
  // FRESH-SESSION: pull the one-shot fresh flag the newChatSession()
@@ -611858,7 +612219,7 @@ var init_interactive = __esm({
611858
612219
  var run_exports = {};
611859
612220
  __export(run_exports, {
611860
612221
  jobsCommand: () => jobsCommand,
611861
- runCommand: () => runCommand,
612222
+ runCommand: () => runCommand2,
611862
612223
  statusCommand: () => statusCommand
611863
612224
  });
611864
612225
  import { resolve as resolve40 } from "node:path";
@@ -611872,7 +612233,7 @@ function jobsDir2(repoPath) {
611872
612233
  mkdirSync66(dir, { recursive: true });
611873
612234
  return dir;
611874
612235
  }
611875
- async function runCommand(opts, config) {
612236
+ async function runCommand2(opts, config) {
611876
612237
  const mergedConfig = {
611877
612238
  ...config,
611878
612239
  ...opts.verbose !== void 0 ? { verbose: opts.verbose } : {},
@@ -613180,8 +613541,8 @@ async function main() {
613180
613541
  break;
613181
613542
  }
613182
613543
  case "run": {
613183
- const { runCommand: runCommand2 } = await Promise.resolve().then(() => (init_run(), run_exports));
613184
- await runCommand2(
613544
+ const { runCommand: runCommand3 } = await Promise.resolve().then(() => (init_run(), run_exports));
613545
+ await runCommand3(
613185
613546
  {
613186
613547
  task: parsed.task,
613187
613548
  // undefined = interactive mode
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.541",
3
+ "version": "0.187.543",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "open-agents-ai",
9
- "version": "0.187.541",
9
+ "version": "0.187.543",
10
10
  "hasInstallScript": true,
11
11
  "license": "CC-BY-NC-4.0",
12
12
  "dependencies": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "open-agents-ai",
3
- "version": "0.187.541",
3
+ "version": "0.187.543",
4
4
  "description": "AI coding agent powered by open-source models (Ollama/vLLM) — interactive TUI with agentic tool-calling loop",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",