@particle-academy/agent-integrations 0.11.1 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1953,19 +1953,39 @@ function serialize(entry) {
1953
1953
  var DEFAULT_AGENT8 = { id: "agent", name: "Agent", color: "#a855f7" };
1954
1954
  var truncate = (s, n = 60) => s.length > n ? s.slice(0, n) + "\u2026" : s;
1955
1955
  function registerTerminalBridge(host, options) {
1956
- const { adapter } = options;
1957
1956
  const agent = { ...DEFAULT_AGENT8, ...options.agent ?? {} };
1958
1957
  const pendingMode = options.pendingMode ?? false;
1958
+ const screenId = options.screenId ?? options.adapter?.screenId;
1959
1959
  const disposers = [];
1960
1960
  const staged = /* @__PURE__ */ new Map();
1961
1961
  let seq = 0;
1962
1962
  ensureUndoToolsRegistered(host);
1963
- const target = (label) => ({
1963
+ const listTerminals = () => {
1964
+ if (options.terminals) return options.terminals();
1965
+ if (options.adapter) return [{ id: "terminal", label: "Terminal", active: true, ...options.adapter }];
1966
+ return [];
1967
+ };
1968
+ const resolve = (id) => {
1969
+ const list = listTerminals();
1970
+ if (typeof id === "string" && id !== "") return list.find((t) => t.id === id);
1971
+ return list.find((t) => t.active) ?? list[0];
1972
+ };
1973
+ const anyMulti = !!options.terminals;
1974
+ const canClear = anyMulti || !!options.adapter?.clear;
1975
+ const canShells = anyMulti || !!options.adapter?.listShells;
1976
+ const canSetShell = anyMulti || !!options.adapter?.setShell;
1977
+ const target = (label, terminalId) => ({
1964
1978
  kind: "terminal",
1965
- screenId: adapter.screenId,
1966
- elementId: adapter.screenId ?? "terminal",
1979
+ screenId,
1980
+ elementId: terminalId ?? screenId ?? "terminal",
1967
1981
  label: label ?? "terminal"
1968
1982
  });
1983
+ const TERMINAL_ARG = {
1984
+ terminal: {
1985
+ type: "string",
1986
+ description: "Terminal id to target (call terminal_list for ids). Omit for the active / only terminal."
1987
+ }
1988
+ };
1969
1989
  const reg = (name, description, properties, required, handler, isMutation, resolveTarget) => {
1970
1990
  const wrapped = async (args) => {
1971
1991
  try {
@@ -1978,7 +1998,7 @@ function registerTerminalBridge(host, options) {
1978
1998
  toolName: name,
1979
1999
  agent,
1980
2000
  kind: "terminal",
1981
- screenId: adapter.screenId,
2001
+ screenId,
1982
2002
  resolveTarget: ({ args, result }) => resolveTarget?.(args, result) ?? target()
1983
2003
  }) : wrapped;
1984
2004
  disposers.push(
@@ -1992,38 +2012,66 @@ function registerTerminalBridge(host, options) {
1992
2012
  )
1993
2013
  );
1994
2014
  };
1995
- async function exec(kind, data) {
2015
+ const need = (args) => {
2016
+ const t = resolve(args.terminal);
2017
+ if (!t) {
2018
+ const ids = listTerminals().map((x) => x.id).join(", ") || "(none)";
2019
+ throw new Error(
2020
+ typeof args.terminal === "string" && args.terminal ? `Unknown terminal '${args.terminal}'. Available: ${ids}. Use terminal_list.` : "No terminal available."
2021
+ );
2022
+ }
2023
+ return t;
2024
+ };
2025
+ async function exec(t, kind, data) {
1996
2026
  if (kind === "run") {
1997
- if (adapter.runCommand) await adapter.runCommand(data);
1998
- else adapter.write(data + "\r");
2027
+ if (t.runCommand) await t.runCommand(data);
2028
+ else t.write(data + "\r");
1999
2029
  } else {
2000
- adapter.write(data);
2030
+ t.write(data);
2001
2031
  }
2002
2032
  }
2003
- async function stageOrExec(kind, data) {
2033
+ async function stageOrExec(t, kind, data) {
2004
2034
  if (!pendingMode) {
2005
- await exec(kind, data);
2006
- return textResult(`${kind === "run" ? "ran" : "wrote"}: ${truncate(data)}`, { kind, data, executed: true });
2035
+ await exec(t, kind, data);
2036
+ return textResult(`${kind === "run" ? "ran" : "wrote"} on ${t.id}: ${truncate(data)}`, {
2037
+ kind,
2038
+ data,
2039
+ terminal: t.id,
2040
+ executed: true
2041
+ });
2007
2042
  }
2008
2043
  const id = `t${++seq}`;
2009
- const entry = { id, kind, data };
2044
+ const entry = { id, kind, data, terminalId: t.id };
2010
2045
  staged.set(id, entry);
2011
2046
  options.onPending?.(entry);
2012
2047
  return textResult(
2013
- `Staged ${kind} (id ${id}) \u2014 awaiting human confirmation: ${truncate(data)}`,
2048
+ `Staged ${kind} on ${t.id} (id ${id}) \u2014 awaiting human confirmation: ${truncate(data)}`,
2014
2049
  { ...entry, pending: true }
2015
2050
  );
2016
2051
  }
2052
+ reg(
2053
+ "terminal_list",
2054
+ "List the terminals on this screen (id, label, which is active) \u2014 so you can reach into another terminal, not just the active one. Pass the chosen id as `terminal` to the other tools.",
2055
+ {},
2056
+ [],
2057
+ () => {
2058
+ const list = listTerminals().map((t) => ({ id: t.id, label: t.label ?? t.id, active: !!t.active }));
2059
+ const text = list.length ? list.map((t) => `${t.active ? "* " : " "}${t.id} \u2014 ${t.label}`).join("\n") : "(no terminals)";
2060
+ return textResult(text, { terminals: list });
2061
+ },
2062
+ false
2063
+ );
2017
2064
  reg(
2018
2065
  "terminal_read",
2019
- "Read the visible terminal buffer as text \u2014 what the user sees. Pass `tail` for only the last N lines.",
2020
- { tail: { type: "number", description: "Return only the last N lines." } },
2066
+ "Read a terminal's visible buffer as text \u2014 what the user sees. Pass `tail` for only the last N lines, `terminal` to read a specific one.",
2067
+ { ...TERMINAL_ARG, tail: { type: "number", description: "Return only the last N lines." } },
2021
2068
  [],
2022
2069
  (args) => {
2023
- let buf = adapter.getBuffer();
2070
+ const t = need(args);
2071
+ let buf = t.getBuffer();
2024
2072
  const tail = typeof args.tail === "number" ? args.tail : void 0;
2025
2073
  if (tail && tail > 0) buf = buf.split("\n").slice(-tail).join("\n");
2026
- return textResult(buf, { buffer: buf });
2074
+ return textResult(buf, { buffer: buf, terminal: t.id });
2027
2075
  },
2028
2076
  false
2029
2077
  );
@@ -2035,7 +2083,7 @@ function registerTerminalBridge(host, options) {
2035
2083
  () => {
2036
2084
  const list = [...staged.values()];
2037
2085
  return textResult(
2038
- list.length ? list.map((s) => `${s.id}: ${s.kind} ${truncate(s.data)}`).join("\n") : "(none)",
2086
+ list.length ? list.map((s) => `${s.id}: ${s.kind} on ${s.terminalId} ${truncate(s.data)}`).join("\n") : "(none)",
2039
2087
  { pending: list }
2040
2088
  );
2041
2089
  },
@@ -2043,20 +2091,21 @@ function registerTerminalBridge(host, options) {
2043
2091
  );
2044
2092
  reg(
2045
2093
  "terminal_write",
2046
- "Write raw data / keystrokes to the terminal (input, control chars, ANSI). In pendingMode this stages instead of executing.",
2047
- { data: { type: "string", description: "Raw bytes to write." } },
2094
+ "Write raw data / keystrokes to a terminal (input, control chars, ANSI). Pass `terminal` to target a specific one. In pendingMode this stages instead of executing.",
2095
+ { ...TERMINAL_ARG, data: { type: "string", description: "Raw bytes to write." } },
2048
2096
  ["data"],
2049
- (args) => stageOrExec("write", String(args.data)),
2050
- true
2097
+ (args) => stageOrExec(need(args), "write", String(args.data)),
2098
+ true,
2099
+ (args) => target(`write:${String(args.terminal ?? "")}`, resolve(args.terminal)?.id)
2051
2100
  );
2052
2101
  reg(
2053
2102
  "terminal_run",
2054
- "Run a shell command \u2014 writes the command followed by Enter (or the host's command runner). In pendingMode this stages it for confirmation.",
2055
- { command: { type: "string", description: "The command line to run." } },
2103
+ "Run a shell command in a terminal \u2014 writes the command + Enter (or the host's runner). Pass `terminal` to target a specific one. In pendingMode this stages it for confirmation.",
2104
+ { ...TERMINAL_ARG, command: { type: "string", description: "The command line to run." } },
2056
2105
  ["command"],
2057
- (args) => stageOrExec("run", String(args.command)),
2106
+ (args) => stageOrExec(need(args), "run", String(args.command)),
2058
2107
  true,
2059
- (args) => target(truncate(String(args.command ?? "")))
2108
+ (args) => target(truncate(String(args.command ?? "")), resolve(args.terminal)?.id)
2060
2109
  );
2061
2110
  reg(
2062
2111
  "terminal_confirm",
@@ -2067,11 +2116,17 @@ function registerTerminalBridge(host, options) {
2067
2116
  const id = String(args.id);
2068
2117
  const entry = staged.get(id);
2069
2118
  if (!entry) return errorResult(`No staged command ${id}`);
2119
+ const t = resolve(entry.terminalId);
2120
+ if (!t) return errorResult(`Terminal '${entry.terminalId}' is gone \u2014 cannot run ${id}`);
2070
2121
  staged.delete(id);
2071
- await exec(entry.kind, entry.data);
2072
- return textResult(`Confirmed ${id}: ${entry.kind} ${truncate(entry.data)}`, { ...entry, executed: true });
2122
+ await exec(t, entry.kind, entry.data);
2123
+ return textResult(`Confirmed ${id}: ${entry.kind} on ${t.id} ${truncate(entry.data)}`, { ...entry, executed: true });
2073
2124
  },
2074
- true
2125
+ true,
2126
+ (args) => {
2127
+ const e = staged.get(String(args.id));
2128
+ return target(`confirm:${String(args.id ?? "")}`, e?.terminalId);
2129
+ }
2075
2130
  );
2076
2131
  reg(
2077
2132
  "terminal_reject",
@@ -2085,51 +2140,58 @@ function registerTerminalBridge(host, options) {
2085
2140
  },
2086
2141
  false
2087
2142
  );
2088
- if (adapter.clear) {
2143
+ if (canClear) {
2089
2144
  reg(
2090
2145
  "terminal_clear",
2091
- "Clear the terminal viewport.",
2092
- {},
2146
+ "Clear a terminal's viewport. Pass `terminal` to target a specific one.",
2147
+ { ...TERMINAL_ARG },
2093
2148
  [],
2094
- () => {
2095
- adapter.clear();
2096
- return textResult("cleared");
2149
+ (args) => {
2150
+ const t = need(args);
2151
+ if (!t.clear) return errorResult(`Terminal '${t.id}' can't be cleared.`);
2152
+ t.clear();
2153
+ return textResult(`cleared ${t.id}`, { terminal: t.id });
2097
2154
  },
2098
- true
2155
+ true,
2156
+ (args) => target(`clear:${String(args.terminal ?? "")}`, resolve(args.terminal)?.id)
2099
2157
  );
2100
2158
  }
2101
- if (adapter.listShells) {
2159
+ if (canShells) {
2102
2160
  reg(
2103
2161
  "terminal_list_shells",
2104
- "List the shells the host can switch to (cmd, PowerShell, Git Bash, \u2026) \u2014 id + label, with the active one marked.",
2105
- {},
2162
+ "List the shells a terminal can switch to (cmd, PowerShell, Git Bash, \u2026) \u2014 id + label, active one marked. Pass `terminal` to target a specific one.",
2163
+ { ...TERMINAL_ARG },
2106
2164
  [],
2107
- () => {
2108
- const shells = adapter.listShells();
2109
- const active = adapter.getShell?.();
2165
+ (args) => {
2166
+ const t = need(args);
2167
+ if (!t.listShells) return errorResult(`Terminal '${t.id}' has no switchable shells.`);
2168
+ const shells = t.listShells();
2169
+ const active = t.getShell?.();
2110
2170
  const text = shells.length ? shells.map((s) => `${s.id === active ? "* " : " "}${s.id} \u2014 ${s.label}`).join("\n") : "(none)";
2111
- return textResult(text, { shells, active });
2171
+ return textResult(text, { shells, active, terminal: t.id });
2112
2172
  },
2113
2173
  false
2114
2174
  );
2115
2175
  }
2116
- if (adapter.setShell) {
2176
+ if (canSetShell) {
2117
2177
  reg(
2118
2178
  "terminal_set_shell",
2119
- "Switch the active shell by id (e.g. 'powershell', 'git-bash'). Call terminal_list_shells first for valid ids. The host reconnects its backend to the chosen shell.",
2120
- { id: { type: "string", description: "Shell id to switch to." } },
2179
+ "Switch a terminal's active shell by id (e.g. 'powershell', 'git-bash'). Call terminal_list_shells first for valid ids. Pass `terminal` to target a specific one.",
2180
+ { ...TERMINAL_ARG, id: { type: "string", description: "Shell id to switch to." } },
2121
2181
  ["id"],
2122
2182
  async (args) => {
2183
+ const t = need(args);
2184
+ if (!t.setShell) return errorResult(`Terminal '${t.id}' can't switch shells.`);
2123
2185
  const id = String(args.id);
2124
- const shells = adapter.listShells?.();
2186
+ const shells = t.listShells?.();
2125
2187
  if (shells && shells.length && !shells.some((s) => s.id === id)) {
2126
- return errorResult(`Unknown shell '${id}'. Use terminal_list_shells for valid ids.`);
2188
+ return errorResult(`Unknown shell '${id}' for ${t.id}. Use terminal_list_shells for valid ids.`);
2127
2189
  }
2128
- await adapter.setShell(id);
2129
- return textResult(`Switched shell to ${id}`, { shell: id });
2190
+ await t.setShell(id);
2191
+ return textResult(`Switched ${t.id} shell to ${id}`, { shell: id, terminal: t.id });
2130
2192
  },
2131
2193
  true,
2132
- (args) => target(`shell:${String(args.id ?? "")}`)
2194
+ (args) => target(`shell:${String(args.id ?? "")}`, resolve(args.terminal)?.id)
2133
2195
  );
2134
2196
  }
2135
2197
  return {
@@ -2143,8 +2205,9 @@ function registerTerminalBridge(host, options) {
2143
2205
  confirm: (id) => {
2144
2206
  const e = staged.get(id);
2145
2207
  if (e) {
2208
+ const t = resolve(e.terminalId);
2146
2209
  staged.delete(id);
2147
- void exec(e.kind, e.data);
2210
+ if (t) void exec(t, e.kind, e.data);
2148
2211
  }
2149
2212
  },
2150
2213
  reject: (id) => {