@particle-academy/agent-integrations 0.9.0 → 0.10.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/README.md +8 -0
- package/dist/bridges/terminal.d.cts +63 -0
- package/dist/bridges/terminal.d.ts +63 -0
- package/dist/bridges-terminal.cjs +298 -0
- package/dist/bridges-terminal.cjs.map +1 -0
- package/dist/bridges-terminal.js +7 -0
- package/dist/bridges-terminal.js.map +1 -0
- package/dist/chunk-353O6IBR.js +178 -0
- package/dist/chunk-353O6IBR.js.map +1 -0
- package/dist/index.cjs +248 -76
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/docs/agent-hookable-demos.md +7 -2
- package/docs/relay-server.md +21 -0
- package/package.json +18 -8
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { ensureUndoToolsRegistered } from './chunk-KJ5AOOV7.js';
|
|
2
|
+
import { wrapToolWithActivity } from './chunk-ULJL53DL.js';
|
|
3
|
+
import { textResult, errorResult } from './chunk-4KAIV6OD.js';
|
|
4
|
+
|
|
5
|
+
// src/bridges/terminal.ts
|
|
6
|
+
var DEFAULT_AGENT = { id: "agent", name: "Agent", color: "#a855f7" };
|
|
7
|
+
var truncate = (s, n = 60) => s.length > n ? s.slice(0, n) + "\u2026" : s;
|
|
8
|
+
function registerTerminalBridge(host, options) {
|
|
9
|
+
const { adapter } = options;
|
|
10
|
+
const agent = { ...DEFAULT_AGENT, ...options.agent ?? {} };
|
|
11
|
+
const pendingMode = options.pendingMode ?? false;
|
|
12
|
+
const disposers = [];
|
|
13
|
+
const staged = /* @__PURE__ */ new Map();
|
|
14
|
+
let seq = 0;
|
|
15
|
+
ensureUndoToolsRegistered(host);
|
|
16
|
+
const target = (label) => ({
|
|
17
|
+
kind: "terminal",
|
|
18
|
+
screenId: adapter.screenId,
|
|
19
|
+
elementId: adapter.screenId ?? "terminal",
|
|
20
|
+
label: label ?? "terminal"
|
|
21
|
+
});
|
|
22
|
+
const reg = (name, description, properties, required, handler, isMutation, resolveTarget) => {
|
|
23
|
+
const wrapped = async (args) => {
|
|
24
|
+
try {
|
|
25
|
+
return await handler(args);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return errorResult(e instanceof Error ? e.message : String(e));
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const final = isMutation ? wrapToolWithActivity(wrapped, {
|
|
31
|
+
toolName: name,
|
|
32
|
+
agent,
|
|
33
|
+
kind: "terminal",
|
|
34
|
+
screenId: adapter.screenId,
|
|
35
|
+
resolveTarget: ({ args, result }) => resolveTarget?.(args, result) ?? target()
|
|
36
|
+
}) : wrapped;
|
|
37
|
+
disposers.push(
|
|
38
|
+
host.registerTool(
|
|
39
|
+
{
|
|
40
|
+
name,
|
|
41
|
+
description,
|
|
42
|
+
inputSchema: { type: "object", properties, required, additionalProperties: false }
|
|
43
|
+
},
|
|
44
|
+
final
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
};
|
|
48
|
+
async function exec(kind, data) {
|
|
49
|
+
if (kind === "run") {
|
|
50
|
+
if (adapter.runCommand) await adapter.runCommand(data);
|
|
51
|
+
else adapter.write(data + "\r");
|
|
52
|
+
} else {
|
|
53
|
+
adapter.write(data);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
async function stageOrExec(kind, data) {
|
|
57
|
+
if (!pendingMode) {
|
|
58
|
+
await exec(kind, data);
|
|
59
|
+
return textResult(`${kind === "run" ? "ran" : "wrote"}: ${truncate(data)}`, { kind, data, executed: true });
|
|
60
|
+
}
|
|
61
|
+
const id = `t${++seq}`;
|
|
62
|
+
const entry = { id, kind, data };
|
|
63
|
+
staged.set(id, entry);
|
|
64
|
+
options.onPending?.(entry);
|
|
65
|
+
return textResult(
|
|
66
|
+
`Staged ${kind} (id ${id}) \u2014 awaiting human confirmation: ${truncate(data)}`,
|
|
67
|
+
{ ...entry, pending: true }
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
reg(
|
|
71
|
+
"terminal_read",
|
|
72
|
+
"Read the visible terminal buffer as text \u2014 what the user sees. Pass `tail` for only the last N lines.",
|
|
73
|
+
{ tail: { type: "number", description: "Return only the last N lines." } },
|
|
74
|
+
[],
|
|
75
|
+
(args) => {
|
|
76
|
+
let buf = adapter.getBuffer();
|
|
77
|
+
const tail = typeof args.tail === "number" ? args.tail : void 0;
|
|
78
|
+
if (tail && tail > 0) buf = buf.split("\n").slice(-tail).join("\n");
|
|
79
|
+
return textResult(buf, { buffer: buf });
|
|
80
|
+
},
|
|
81
|
+
false
|
|
82
|
+
);
|
|
83
|
+
reg(
|
|
84
|
+
"terminal_pending",
|
|
85
|
+
"List commands staged for human confirmation (pendingMode).",
|
|
86
|
+
{},
|
|
87
|
+
[],
|
|
88
|
+
() => {
|
|
89
|
+
const list = [...staged.values()];
|
|
90
|
+
return textResult(
|
|
91
|
+
list.length ? list.map((s) => `${s.id}: ${s.kind} ${truncate(s.data)}`).join("\n") : "(none)",
|
|
92
|
+
{ pending: list }
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
false
|
|
96
|
+
);
|
|
97
|
+
reg(
|
|
98
|
+
"terminal_write",
|
|
99
|
+
"Write raw data / keystrokes to the terminal (input, control chars, ANSI). In pendingMode this stages instead of executing.",
|
|
100
|
+
{ data: { type: "string", description: "Raw bytes to write." } },
|
|
101
|
+
["data"],
|
|
102
|
+
(args) => stageOrExec("write", String(args.data)),
|
|
103
|
+
true
|
|
104
|
+
);
|
|
105
|
+
reg(
|
|
106
|
+
"terminal_run",
|
|
107
|
+
"Run a shell command \u2014 writes the command followed by Enter (or the host's command runner). In pendingMode this stages it for confirmation.",
|
|
108
|
+
{ command: { type: "string", description: "The command line to run." } },
|
|
109
|
+
["command"],
|
|
110
|
+
(args) => stageOrExec("run", String(args.command)),
|
|
111
|
+
true,
|
|
112
|
+
(args) => target(truncate(String(args.command ?? "")))
|
|
113
|
+
);
|
|
114
|
+
reg(
|
|
115
|
+
"terminal_confirm",
|
|
116
|
+
"Confirm + execute a staged command by id (pendingMode).",
|
|
117
|
+
{ id: { type: "string" } },
|
|
118
|
+
["id"],
|
|
119
|
+
async (args) => {
|
|
120
|
+
const id = String(args.id);
|
|
121
|
+
const entry = staged.get(id);
|
|
122
|
+
if (!entry) return errorResult(`No staged command ${id}`);
|
|
123
|
+
staged.delete(id);
|
|
124
|
+
await exec(entry.kind, entry.data);
|
|
125
|
+
return textResult(`Confirmed ${id}: ${entry.kind} ${truncate(entry.data)}`, { ...entry, executed: true });
|
|
126
|
+
},
|
|
127
|
+
true
|
|
128
|
+
);
|
|
129
|
+
reg(
|
|
130
|
+
"terminal_reject",
|
|
131
|
+
"Drop a staged command by id without executing it.",
|
|
132
|
+
{ id: { type: "string" } },
|
|
133
|
+
["id"],
|
|
134
|
+
(args) => {
|
|
135
|
+
const id = String(args.id);
|
|
136
|
+
if (!staged.delete(id)) return errorResult(`No staged command ${id}`);
|
|
137
|
+
return textResult(`Rejected ${id}`, { id, rejected: true });
|
|
138
|
+
},
|
|
139
|
+
false
|
|
140
|
+
);
|
|
141
|
+
if (adapter.clear) {
|
|
142
|
+
reg(
|
|
143
|
+
"terminal_clear",
|
|
144
|
+
"Clear the terminal viewport.",
|
|
145
|
+
{},
|
|
146
|
+
[],
|
|
147
|
+
() => {
|
|
148
|
+
adapter.clear();
|
|
149
|
+
return textResult("cleared");
|
|
150
|
+
},
|
|
151
|
+
true
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
return {
|
|
155
|
+
id: "terminal",
|
|
156
|
+
title: "Terminal",
|
|
157
|
+
dispose: () => {
|
|
158
|
+
disposers.forEach((d) => d());
|
|
159
|
+
disposers.length = 0;
|
|
160
|
+
staged.clear();
|
|
161
|
+
},
|
|
162
|
+
confirm: (id) => {
|
|
163
|
+
const e = staged.get(id);
|
|
164
|
+
if (e) {
|
|
165
|
+
staged.delete(id);
|
|
166
|
+
void exec(e.kind, e.data);
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
reject: (id) => {
|
|
170
|
+
staged.delete(id);
|
|
171
|
+
},
|
|
172
|
+
pending: () => [...staged.values()]
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export { registerTerminalBridge };
|
|
177
|
+
//# sourceMappingURL=chunk-353O6IBR.js.map
|
|
178
|
+
//# sourceMappingURL=chunk-353O6IBR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bridges/terminal.ts"],"names":[],"mappings":"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","file":"chunk-353O6IBR.js","sourcesContent":["import { textResult, errorResult } from \"../mcp/server\";\nimport type { ToolHost } from \"../mcp/tool-host\";\nimport type { JsonObject } from \"../mcp/types\";\nimport type { Bridge } from \"./types\";\nimport { wrapToolWithActivity } from \"../presence/wrap-tool-with-activity\";\nimport { ensureUndoToolsRegistered } from \"../undo/undo-tools\";\nimport type { AgentTarget } from \"../presence/types\";\n\n/**\n * Host-provided window into a terminal surface (e.g. a fancy-term `<Terminal>`'s\n * `TerminalHandle`). The bridge never touches the DOM — it reads + writes through\n * these functions, so it works with any terminal the host wires up.\n */\nexport type TerminalBridgeAdapter = {\n /** fancy-screens screen id (optional) so activity events know which screen the terminal lives in. */\n screenId?: string;\n /** Read the visible terminal buffer as text (wire to `TerminalHandle.getBuffer`). */\n getBuffer: () => string;\n /** Write raw data / keystrokes to the terminal (wire to `TerminalHandle.write`). */\n write: (data: string) => void;\n /** Run a command. Defaults to writing `${command}\\r` (submit to a PTY); override to call a real command runner. */\n runCommand?: (command: string) => void | Promise<void>;\n /** Optional: clear the terminal viewport (wire to `TerminalHandle.clear`). */\n clear?: () => void;\n};\n\ntype StagedKind = \"write\" | \"run\";\ntype Staged = { id: string; kind: StagedKind; data: string };\n\nexport type TerminalBridgeOptions = {\n adapter: TerminalBridgeAdapter;\n agent?: { id: string; name?: string; color?: string };\n /**\n * Trust-but-verify (Human+ contract for inhabited surfaces). When on,\n * `terminal_write` + `terminal_run` don't execute — they **stage** the command\n * (returning a pending id) and fire `onPending`. A human confirms via the\n * `terminal_confirm` tool or the returned bridge's `confirm(id)`. Default off.\n */\n pendingMode?: boolean;\n /** Notified when a command is staged (pendingMode) — show it + offer confirm / reject. */\n onPending?: (pending: Staged) => void;\n};\n\nexport type TerminalBridge = Bridge & {\n /** Execute a staged command by id — wire a host confirm button to this. No-op if not pending. */\n confirm: (id: string) => void;\n /** Drop a staged command by id without executing. */\n reject: (id: string) => void;\n /** Commands currently awaiting confirmation. */\n pending: () => Staged[];\n};\n\nconst DEFAULT_AGENT = { id: \"agent\", name: \"Agent\", color: \"#a855f7\" };\n\nconst truncate = (s: string, n = 60): string => (s.length > n ? s.slice(0, n) + \"…\" : s);\n\n/**\n * registerTerminalBridge — MCP access to a terminal surface. An agent reads the\n * visible buffer (`terminal_read`), writes input (`terminal_write`), and runs\n * commands (`terminal_run`) through the host adapter; every mutation broadcasts\n * an `AgentActivity` event. With `pendingMode`, destructive actions are staged\n * for human confirmation (`terminal_confirm` / `terminal_reject` /\n * `terminal_pending`). Tool prefix `terminal_*`.\n */\nexport function registerTerminalBridge(host: ToolHost, options: TerminalBridgeOptions): TerminalBridge {\n const { adapter } = options;\n const agent = { ...DEFAULT_AGENT, ...(options.agent ?? {}) };\n const pendingMode = options.pendingMode ?? false;\n const disposers: Array<() => void> = [];\n const staged = new Map<string, Staged>();\n let seq = 0;\n\n // Enables agent_history (a log of what agents did across every bridge).\n ensureUndoToolsRegistered(host);\n\n const target = (label?: string): AgentTarget => ({\n kind: \"terminal\",\n screenId: adapter.screenId,\n elementId: adapter.screenId ?? \"terminal\",\n label: label ?? \"terminal\",\n });\n\n const reg = (\n name: string,\n description: string,\n properties: Record<string, unknown>,\n required: string[],\n handler: (args: JsonObject) => Promise<any> | any,\n isMutation: boolean,\n resolveTarget?: (args: JsonObject, result: any) => AgentTarget | null,\n ) => {\n const wrapped = async (args: JsonObject) => {\n try {\n return await handler(args);\n } catch (e) {\n return errorResult(e instanceof Error ? e.message : String(e));\n }\n };\n const final = isMutation\n ? wrapToolWithActivity(wrapped, {\n toolName: name,\n agent,\n kind: \"terminal\",\n screenId: adapter.screenId,\n resolveTarget: ({ args, result }) => resolveTarget?.(args, result) ?? target(),\n })\n : wrapped;\n disposers.push(\n host.registerTool(\n {\n name,\n description,\n inputSchema: { type: \"object\", properties: properties as any, required, additionalProperties: false },\n },\n final as any,\n ),\n );\n };\n\n async function exec(kind: StagedKind, data: string): Promise<void> {\n if (kind === \"run\") {\n if (adapter.runCommand) await adapter.runCommand(data);\n else adapter.write(data + \"\\r\");\n } else {\n adapter.write(data);\n }\n }\n\n async function stageOrExec(kind: StagedKind, data: string) {\n if (!pendingMode) {\n await exec(kind, data);\n return textResult(`${kind === \"run\" ? \"ran\" : \"wrote\"}: ${truncate(data)}`, { kind, data, executed: true });\n }\n const id = `t${++seq}`;\n const entry: Staged = { id, kind, data };\n staged.set(id, entry);\n options.onPending?.(entry);\n return textResult(\n `Staged ${kind} (id ${id}) — awaiting human confirmation: ${truncate(data)}`,\n { ...entry, pending: true },\n );\n }\n\n // ── Read ──────────────────────────────────────────────────────────────────\n reg(\n \"terminal_read\",\n \"Read the visible terminal buffer as text — what the user sees. Pass `tail` for only the last N lines.\",\n { tail: { type: \"number\", description: \"Return only the last N lines.\" } },\n [],\n (args) => {\n let buf = adapter.getBuffer();\n const tail = typeof args.tail === \"number\" ? args.tail : undefined;\n if (tail && tail > 0) buf = buf.split(\"\\n\").slice(-tail).join(\"\\n\");\n return textResult(buf, { buffer: buf });\n },\n false,\n );\n\n reg(\n \"terminal_pending\",\n \"List commands staged for human confirmation (pendingMode).\",\n {},\n [],\n () => {\n const list = [...staged.values()];\n return textResult(\n list.length ? list.map((s) => `${s.id}: ${s.kind} ${truncate(s.data)}`).join(\"\\n\") : \"(none)\",\n { pending: list },\n );\n },\n false,\n );\n\n // ── Mutations ───────────────────────────────────────────────────────────────\n reg(\n \"terminal_write\",\n \"Write raw data / keystrokes to the terminal (input, control chars, ANSI). In pendingMode this stages instead of executing.\",\n { data: { type: \"string\", description: \"Raw bytes to write.\" } },\n [\"data\"],\n (args) => stageOrExec(\"write\", String(args.data)),\n true,\n );\n\n reg(\n \"terminal_run\",\n \"Run a shell command — writes the command followed by Enter (or the host's command runner). In pendingMode this stages it for confirmation.\",\n { command: { type: \"string\", description: \"The command line to run.\" } },\n [\"command\"],\n (args) => stageOrExec(\"run\", String(args.command)),\n true,\n (args) => target(truncate(String(args.command ?? \"\"))),\n );\n\n reg(\n \"terminal_confirm\",\n \"Confirm + execute a staged command by id (pendingMode).\",\n { id: { type: \"string\" } },\n [\"id\"],\n async (args) => {\n const id = String(args.id);\n const entry = staged.get(id);\n if (!entry) return errorResult(`No staged command ${id}`);\n staged.delete(id);\n await exec(entry.kind, entry.data);\n return textResult(`Confirmed ${id}: ${entry.kind} ${truncate(entry.data)}`, { ...entry, executed: true });\n },\n true,\n );\n\n reg(\n \"terminal_reject\",\n \"Drop a staged command by id without executing it.\",\n { id: { type: \"string\" } },\n [\"id\"],\n (args) => {\n const id = String(args.id);\n if (!staged.delete(id)) return errorResult(`No staged command ${id}`);\n return textResult(`Rejected ${id}`, { id, rejected: true });\n },\n false,\n );\n\n if (adapter.clear) {\n reg(\n \"terminal_clear\",\n \"Clear the terminal viewport.\",\n {},\n [],\n () => {\n adapter.clear!();\n return textResult(\"cleared\");\n },\n true,\n );\n }\n\n return {\n id: \"terminal\",\n title: \"Terminal\",\n dispose: () => {\n disposers.forEach((d) => d());\n disposers.length = 0;\n staged.clear();\n },\n confirm: (id: string) => {\n const e = staged.get(id);\n if (e) {\n staged.delete(id);\n void exec(e.kind, e.data);\n }\n },\n reject: (id: string) => {\n staged.delete(id);\n },\n pending: () => [...staged.values()],\n };\n}\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1872,6 +1872,253 @@ function firstTextContent(slide) {
|
|
|
1872
1872
|
}
|
|
1873
1873
|
return void 0;
|
|
1874
1874
|
}
|
|
1875
|
+
|
|
1876
|
+
// src/undo/undo-tools.ts
|
|
1877
|
+
var installedHosts = /* @__PURE__ */ new WeakSet();
|
|
1878
|
+
function ensureUndoToolsRegistered(host, options = {}) {
|
|
1879
|
+
if (installedHosts.has(host)) return;
|
|
1880
|
+
installedHosts.add(host);
|
|
1881
|
+
registerUndoTools(host, options);
|
|
1882
|
+
}
|
|
1883
|
+
function registerUndoTools(host, options = {}) {
|
|
1884
|
+
const defaultAgent = options.defaultAgentId ?? "agent";
|
|
1885
|
+
const disposers = [];
|
|
1886
|
+
const agentOf = (args) => typeof args?.agentId === "string" ? args.agentId : defaultAgent;
|
|
1887
|
+
disposers.push(
|
|
1888
|
+
host.registerTool(
|
|
1889
|
+
{
|
|
1890
|
+
name: "agent_undo",
|
|
1891
|
+
description: "Undo the most recent action on the agent's stack. Optional agentId targets a specific agent.",
|
|
1892
|
+
inputSchema: {
|
|
1893
|
+
type: "object",
|
|
1894
|
+
properties: { agentId: { type: "string" } },
|
|
1895
|
+
additionalProperties: false
|
|
1896
|
+
}
|
|
1897
|
+
},
|
|
1898
|
+
async (args) => {
|
|
1899
|
+
const entry = await fancyAutoCommon.undoOne(agentOf(args));
|
|
1900
|
+
if (!entry) return errorResult("Nothing to undo.");
|
|
1901
|
+
return textResult(`Undid: ${entry.label}`, { entry: serialize(entry) });
|
|
1902
|
+
}
|
|
1903
|
+
)
|
|
1904
|
+
);
|
|
1905
|
+
disposers.push(
|
|
1906
|
+
host.registerTool(
|
|
1907
|
+
{
|
|
1908
|
+
name: "agent_redo",
|
|
1909
|
+
description: "Redo the most recently undone action.",
|
|
1910
|
+
inputSchema: {
|
|
1911
|
+
type: "object",
|
|
1912
|
+
properties: { agentId: { type: "string" } },
|
|
1913
|
+
additionalProperties: false
|
|
1914
|
+
}
|
|
1915
|
+
},
|
|
1916
|
+
async (args) => {
|
|
1917
|
+
const entry = await fancyAutoCommon.redoOne(agentOf(args));
|
|
1918
|
+
if (!entry) return errorResult("Nothing to redo.");
|
|
1919
|
+
return textResult(`Redid: ${entry.label}`, { entry: serialize(entry) });
|
|
1920
|
+
}
|
|
1921
|
+
)
|
|
1922
|
+
);
|
|
1923
|
+
disposers.push(
|
|
1924
|
+
host.registerTool(
|
|
1925
|
+
{
|
|
1926
|
+
name: "agent_history",
|
|
1927
|
+
description: "List the agent's undo stack (oldest first). Useful for understanding what's reversible.",
|
|
1928
|
+
inputSchema: {
|
|
1929
|
+
type: "object",
|
|
1930
|
+
properties: { agentId: { type: "string" } },
|
|
1931
|
+
additionalProperties: false
|
|
1932
|
+
}
|
|
1933
|
+
},
|
|
1934
|
+
async (args) => {
|
|
1935
|
+
const history = fancyAutoCommon.readHistory(agentOf(args)).map(serialize);
|
|
1936
|
+
const text = history.map((e) => `${new Date(e.timestamp).toISOString()} ${e.bridgeId} ${e.action}: ${e.label}`).join("\n");
|
|
1937
|
+
return textResult(text || "(empty)", history);
|
|
1938
|
+
}
|
|
1939
|
+
)
|
|
1940
|
+
);
|
|
1941
|
+
return () => disposers.forEach((d) => d());
|
|
1942
|
+
}
|
|
1943
|
+
function serialize(entry) {
|
|
1944
|
+
return {
|
|
1945
|
+
timestamp: entry.timestamp,
|
|
1946
|
+
bridgeId: entry.bridgeId,
|
|
1947
|
+
action: entry.action,
|
|
1948
|
+
label: entry.label
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
// src/bridges/terminal.ts
|
|
1953
|
+
var DEFAULT_AGENT8 = { id: "agent", name: "Agent", color: "#a855f7" };
|
|
1954
|
+
var truncate = (s, n = 60) => s.length > n ? s.slice(0, n) + "\u2026" : s;
|
|
1955
|
+
function registerTerminalBridge(host, options) {
|
|
1956
|
+
const { adapter } = options;
|
|
1957
|
+
const agent = { ...DEFAULT_AGENT8, ...options.agent ?? {} };
|
|
1958
|
+
const pendingMode = options.pendingMode ?? false;
|
|
1959
|
+
const disposers = [];
|
|
1960
|
+
const staged = /* @__PURE__ */ new Map();
|
|
1961
|
+
let seq = 0;
|
|
1962
|
+
ensureUndoToolsRegistered(host);
|
|
1963
|
+
const target = (label) => ({
|
|
1964
|
+
kind: "terminal",
|
|
1965
|
+
screenId: adapter.screenId,
|
|
1966
|
+
elementId: adapter.screenId ?? "terminal",
|
|
1967
|
+
label: label ?? "terminal"
|
|
1968
|
+
});
|
|
1969
|
+
const reg = (name, description, properties, required, handler, isMutation, resolveTarget) => {
|
|
1970
|
+
const wrapped = async (args) => {
|
|
1971
|
+
try {
|
|
1972
|
+
return await handler(args);
|
|
1973
|
+
} catch (e) {
|
|
1974
|
+
return errorResult(e instanceof Error ? e.message : String(e));
|
|
1975
|
+
}
|
|
1976
|
+
};
|
|
1977
|
+
const final = isMutation ? wrapToolWithActivity(wrapped, {
|
|
1978
|
+
toolName: name,
|
|
1979
|
+
agent,
|
|
1980
|
+
kind: "terminal",
|
|
1981
|
+
screenId: adapter.screenId,
|
|
1982
|
+
resolveTarget: ({ args, result }) => resolveTarget?.(args, result) ?? target()
|
|
1983
|
+
}) : wrapped;
|
|
1984
|
+
disposers.push(
|
|
1985
|
+
host.registerTool(
|
|
1986
|
+
{
|
|
1987
|
+
name,
|
|
1988
|
+
description,
|
|
1989
|
+
inputSchema: { type: "object", properties, required, additionalProperties: false }
|
|
1990
|
+
},
|
|
1991
|
+
final
|
|
1992
|
+
)
|
|
1993
|
+
);
|
|
1994
|
+
};
|
|
1995
|
+
async function exec(kind, data) {
|
|
1996
|
+
if (kind === "run") {
|
|
1997
|
+
if (adapter.runCommand) await adapter.runCommand(data);
|
|
1998
|
+
else adapter.write(data + "\r");
|
|
1999
|
+
} else {
|
|
2000
|
+
adapter.write(data);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
async function stageOrExec(kind, data) {
|
|
2004
|
+
if (!pendingMode) {
|
|
2005
|
+
await exec(kind, data);
|
|
2006
|
+
return textResult(`${kind === "run" ? "ran" : "wrote"}: ${truncate(data)}`, { kind, data, executed: true });
|
|
2007
|
+
}
|
|
2008
|
+
const id = `t${++seq}`;
|
|
2009
|
+
const entry = { id, kind, data };
|
|
2010
|
+
staged.set(id, entry);
|
|
2011
|
+
options.onPending?.(entry);
|
|
2012
|
+
return textResult(
|
|
2013
|
+
`Staged ${kind} (id ${id}) \u2014 awaiting human confirmation: ${truncate(data)}`,
|
|
2014
|
+
{ ...entry, pending: true }
|
|
2015
|
+
);
|
|
2016
|
+
}
|
|
2017
|
+
reg(
|
|
2018
|
+
"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." } },
|
|
2021
|
+
[],
|
|
2022
|
+
(args) => {
|
|
2023
|
+
let buf = adapter.getBuffer();
|
|
2024
|
+
const tail = typeof args.tail === "number" ? args.tail : void 0;
|
|
2025
|
+
if (tail && tail > 0) buf = buf.split("\n").slice(-tail).join("\n");
|
|
2026
|
+
return textResult(buf, { buffer: buf });
|
|
2027
|
+
},
|
|
2028
|
+
false
|
|
2029
|
+
);
|
|
2030
|
+
reg(
|
|
2031
|
+
"terminal_pending",
|
|
2032
|
+
"List commands staged for human confirmation (pendingMode).",
|
|
2033
|
+
{},
|
|
2034
|
+
[],
|
|
2035
|
+
() => {
|
|
2036
|
+
const list = [...staged.values()];
|
|
2037
|
+
return textResult(
|
|
2038
|
+
list.length ? list.map((s) => `${s.id}: ${s.kind} ${truncate(s.data)}`).join("\n") : "(none)",
|
|
2039
|
+
{ pending: list }
|
|
2040
|
+
);
|
|
2041
|
+
},
|
|
2042
|
+
false
|
|
2043
|
+
);
|
|
2044
|
+
reg(
|
|
2045
|
+
"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." } },
|
|
2048
|
+
["data"],
|
|
2049
|
+
(args) => stageOrExec("write", String(args.data)),
|
|
2050
|
+
true
|
|
2051
|
+
);
|
|
2052
|
+
reg(
|
|
2053
|
+
"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." } },
|
|
2056
|
+
["command"],
|
|
2057
|
+
(args) => stageOrExec("run", String(args.command)),
|
|
2058
|
+
true,
|
|
2059
|
+
(args) => target(truncate(String(args.command ?? "")))
|
|
2060
|
+
);
|
|
2061
|
+
reg(
|
|
2062
|
+
"terminal_confirm",
|
|
2063
|
+
"Confirm + execute a staged command by id (pendingMode).",
|
|
2064
|
+
{ id: { type: "string" } },
|
|
2065
|
+
["id"],
|
|
2066
|
+
async (args) => {
|
|
2067
|
+
const id = String(args.id);
|
|
2068
|
+
const entry = staged.get(id);
|
|
2069
|
+
if (!entry) return errorResult(`No staged command ${id}`);
|
|
2070
|
+
staged.delete(id);
|
|
2071
|
+
await exec(entry.kind, entry.data);
|
|
2072
|
+
return textResult(`Confirmed ${id}: ${entry.kind} ${truncate(entry.data)}`, { ...entry, executed: true });
|
|
2073
|
+
},
|
|
2074
|
+
true
|
|
2075
|
+
);
|
|
2076
|
+
reg(
|
|
2077
|
+
"terminal_reject",
|
|
2078
|
+
"Drop a staged command by id without executing it.",
|
|
2079
|
+
{ id: { type: "string" } },
|
|
2080
|
+
["id"],
|
|
2081
|
+
(args) => {
|
|
2082
|
+
const id = String(args.id);
|
|
2083
|
+
if (!staged.delete(id)) return errorResult(`No staged command ${id}`);
|
|
2084
|
+
return textResult(`Rejected ${id}`, { id, rejected: true });
|
|
2085
|
+
},
|
|
2086
|
+
false
|
|
2087
|
+
);
|
|
2088
|
+
if (adapter.clear) {
|
|
2089
|
+
reg(
|
|
2090
|
+
"terminal_clear",
|
|
2091
|
+
"Clear the terminal viewport.",
|
|
2092
|
+
{},
|
|
2093
|
+
[],
|
|
2094
|
+
() => {
|
|
2095
|
+
adapter.clear();
|
|
2096
|
+
return textResult("cleared");
|
|
2097
|
+
},
|
|
2098
|
+
true
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
return {
|
|
2102
|
+
id: "terminal",
|
|
2103
|
+
title: "Terminal",
|
|
2104
|
+
dispose: () => {
|
|
2105
|
+
disposers.forEach((d) => d());
|
|
2106
|
+
disposers.length = 0;
|
|
2107
|
+
staged.clear();
|
|
2108
|
+
},
|
|
2109
|
+
confirm: (id) => {
|
|
2110
|
+
const e = staged.get(id);
|
|
2111
|
+
if (e) {
|
|
2112
|
+
staged.delete(id);
|
|
2113
|
+
void exec(e.kind, e.data);
|
|
2114
|
+
}
|
|
2115
|
+
},
|
|
2116
|
+
reject: (id) => {
|
|
2117
|
+
staged.delete(id);
|
|
2118
|
+
},
|
|
2119
|
+
pending: () => [...staged.values()]
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
1875
2122
|
function AgentPanel({ agent, activity, onSubmit, busy, actions, className, style }) {
|
|
1876
2123
|
const scrollRef = react.useRef(null);
|
|
1877
2124
|
const inputRef = react.useRef(null);
|
|
@@ -2340,82 +2587,6 @@ function useAgentActivityForScreen(screenId, options = {}) {
|
|
|
2340
2587
|
}, [latest, fadeAfter]);
|
|
2341
2588
|
return { events, latest, isAgentActive };
|
|
2342
2589
|
}
|
|
2343
|
-
|
|
2344
|
-
// src/undo/undo-tools.ts
|
|
2345
|
-
var installedHosts = /* @__PURE__ */ new WeakSet();
|
|
2346
|
-
function ensureUndoToolsRegistered(host, options = {}) {
|
|
2347
|
-
if (installedHosts.has(host)) return;
|
|
2348
|
-
installedHosts.add(host);
|
|
2349
|
-
registerUndoTools(host, options);
|
|
2350
|
-
}
|
|
2351
|
-
function registerUndoTools(host, options = {}) {
|
|
2352
|
-
const defaultAgent = options.defaultAgentId ?? "agent";
|
|
2353
|
-
const disposers = [];
|
|
2354
|
-
const agentOf = (args) => typeof args?.agentId === "string" ? args.agentId : defaultAgent;
|
|
2355
|
-
disposers.push(
|
|
2356
|
-
host.registerTool(
|
|
2357
|
-
{
|
|
2358
|
-
name: "agent_undo",
|
|
2359
|
-
description: "Undo the most recent action on the agent's stack. Optional agentId targets a specific agent.",
|
|
2360
|
-
inputSchema: {
|
|
2361
|
-
type: "object",
|
|
2362
|
-
properties: { agentId: { type: "string" } },
|
|
2363
|
-
additionalProperties: false
|
|
2364
|
-
}
|
|
2365
|
-
},
|
|
2366
|
-
async (args) => {
|
|
2367
|
-
const entry = await fancyAutoCommon.undoOne(agentOf(args));
|
|
2368
|
-
if (!entry) return errorResult("Nothing to undo.");
|
|
2369
|
-
return textResult(`Undid: ${entry.label}`, { entry: serialize(entry) });
|
|
2370
|
-
}
|
|
2371
|
-
)
|
|
2372
|
-
);
|
|
2373
|
-
disposers.push(
|
|
2374
|
-
host.registerTool(
|
|
2375
|
-
{
|
|
2376
|
-
name: "agent_redo",
|
|
2377
|
-
description: "Redo the most recently undone action.",
|
|
2378
|
-
inputSchema: {
|
|
2379
|
-
type: "object",
|
|
2380
|
-
properties: { agentId: { type: "string" } },
|
|
2381
|
-
additionalProperties: false
|
|
2382
|
-
}
|
|
2383
|
-
},
|
|
2384
|
-
async (args) => {
|
|
2385
|
-
const entry = await fancyAutoCommon.redoOne(agentOf(args));
|
|
2386
|
-
if (!entry) return errorResult("Nothing to redo.");
|
|
2387
|
-
return textResult(`Redid: ${entry.label}`, { entry: serialize(entry) });
|
|
2388
|
-
}
|
|
2389
|
-
)
|
|
2390
|
-
);
|
|
2391
|
-
disposers.push(
|
|
2392
|
-
host.registerTool(
|
|
2393
|
-
{
|
|
2394
|
-
name: "agent_history",
|
|
2395
|
-
description: "List the agent's undo stack (oldest first). Useful for understanding what's reversible.",
|
|
2396
|
-
inputSchema: {
|
|
2397
|
-
type: "object",
|
|
2398
|
-
properties: { agentId: { type: "string" } },
|
|
2399
|
-
additionalProperties: false
|
|
2400
|
-
}
|
|
2401
|
-
},
|
|
2402
|
-
async (args) => {
|
|
2403
|
-
const history = fancyAutoCommon.readHistory(agentOf(args)).map(serialize);
|
|
2404
|
-
const text = history.map((e) => `${new Date(e.timestamp).toISOString()} ${e.bridgeId} ${e.action}: ${e.label}`).join("\n");
|
|
2405
|
-
return textResult(text || "(empty)", history);
|
|
2406
|
-
}
|
|
2407
|
-
)
|
|
2408
|
-
);
|
|
2409
|
-
return () => disposers.forEach((d) => d());
|
|
2410
|
-
}
|
|
2411
|
-
function serialize(entry) {
|
|
2412
|
-
return {
|
|
2413
|
-
timestamp: entry.timestamp,
|
|
2414
|
-
bridgeId: entry.bridgeId,
|
|
2415
|
-
action: entry.action,
|
|
2416
|
-
label: entry.label
|
|
2417
|
-
};
|
|
2418
|
-
}
|
|
2419
2590
|
function useUndoStack(agentId, intervalMs = 500) {
|
|
2420
2591
|
const [history, setHistory] = react.useState(() => fancyAutoCommon.readHistory(agentId));
|
|
2421
2592
|
react.useEffect(() => {
|
|
@@ -2617,6 +2788,7 @@ exports.registerSceneBridge = registerSceneBridge;
|
|
|
2617
2788
|
exports.registerScreensBridge = registerScreensBridge;
|
|
2618
2789
|
exports.registerSheetsBridge = registerSheetsBridge;
|
|
2619
2790
|
exports.registerSlidesBridge = registerSlidesBridge;
|
|
2791
|
+
exports.registerTerminalBridge = registerTerminalBridge;
|
|
2620
2792
|
exports.registerUndoTools = registerUndoTools;
|
|
2621
2793
|
exports.rpcError = rpcError;
|
|
2622
2794
|
exports.textResult = textResult;
|