mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(p2): the terminal tool needs a turn the owner started
This commit is contained in:
parent
5c6ce2b32f
commit
2b78884e3a
3 changed files with 16 additions and 9 deletions
|
|
@ -888,8 +888,9 @@ translated phone drawer and intercept chat taps.
|
||||||
viewer or the agent); a panel showing a terminal keeps it, one showing none attaches the newest new one. Archiving a
|
viewer or the agent); a panel showing a terminal keeps it, one showing none attaches the newest new one. Archiving a
|
||||||
session closes its terminals (`SessionArchived`, a host-lifetime contribution; no host is started for it).
|
session closes its terminals (`SessionArchived`, a host-lifetime contribution; no host is started for it).
|
||||||
**Agent terminal tool** (owner decisions): the agent drives the same terminals the owner sees (shared: the owner
|
**Agent terminal tool** (owner decisions): the agent drives the same terminals the owner sees (shared: the owner
|
||||||
watches and types; the agent may read, type into and close any terminal of its session, the owner's too). Guest
|
watches and types; the agent may read, type into and close any terminal of its session, the owner's too). Only
|
||||||
turns may use it, like bash. Code mode only, like MCP tools (`deferred`, `callers: ["tools"]`; scripts find it with
|
turns the owner started may use it (`origin: "owner"`, owner decision): a terminal may be connected beyond the
|
||||||
|
session's environment (ssh, a waiting sudo prompt), where a guest's bash would not reach. Code mode only, like MCP tools (`deferred`, `callers: ["tools"]`; scripts find it with
|
||||||
`searchTools()`); while code mode is off `tool_search` loads it (installed once by `durableRegistry`, not by each
|
`searchTools()`); while code mode is off `tool_search` loads it (installed once by `durableRegistry`, not by each
|
||||||
plugin: two plugins contributing `tool-search` would be a registry conflict). The description says to use it only
|
plugin: two plugins contributing `tool-search` would be a registry conflict). The description says to use it only
|
||||||
when the user asks for a terminal or for driving an interactive program in one, never instead of bash; no prompt
|
when the user asks for a terminal or for driving an interactive program in one, never instead of bash; no prompt
|
||||||
|
|
@ -1065,7 +1066,8 @@ translated phone drawer and intercept chat taps.
|
||||||
`origin` (`"any"` default, or `"owner"`: only a turn the owner started) and `scope` (`"any"` default, `"main"`: only
|
`origin` (`"any"` default, or `"owner"`: only a turn the owner started) and `scope` (`"any"` default, `"main"`: only
|
||||||
top-level conversations, or `"subagent"`: given to subagents only). The worker's Durable registry wraps every tool
|
top-level conversations, or `"subagent"`: given to subagents only). The worker's Durable registry wraps every tool
|
||||||
with requirements (`guardTool`, in `durableRegistry`), so no tool checks them itself. `origin: "owner"`: the location
|
with requirements (`guardTool`, in `durableRegistry`), so no tool checks them itself. `origin: "owner"`: the location
|
||||||
tools, `reload_plugins`, `conversations`, `ports`, MCP tools (the owner's accounts are behind them). A turn's origin
|
tools, `reload_plugins`, `conversations`, `ports`, MCP tools (the owner's accounts are behind them), `terminal`
|
||||||
|
(the owner's terminals, possibly connected beyond the session's environment). A turn's origin
|
||||||
(`originOf`, `src/runtime/origin.ts`) is the conversation's current run (`pi.live.run.inputs`, each submission's
|
(`originOf`, `src/runtime/origin.ts`) is the conversation's current run (`pi.live.run.inputs`, each submission's
|
||||||
entry through `tx.submission`): the owner's only if every input the run placed is (`entryOrigin`): a person's input
|
entry through `tx.submission`): the owner's only if every input the run placed is (`entryOrigin`): a person's input
|
||||||
by its author in the entry data, an agent-written one by its `origin`. Agent-written inputs record the writing
|
by its author in the entry data, an agent-written one by its `origin`. Agent-written inputs record the writing
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
// The agent's `terminal` tool (worker): tmux commands as argv on the session's terminals, the ones the owner sees in
|
// The agent's `terminal` tool (worker): tmux commands as argv on the session's terminals, the ones the owner sees in
|
||||||
// the terminal panel. Code mode scripts call it (`tools.terminal({ command, args })`); without code mode `tool_search`
|
// the terminal panel. Code mode scripts call it (`tools.terminal({ command, args })`); without code mode `tool_search`
|
||||||
// loads it, as MCP tools. Replay-safe: the host does a call with the tool call's key once, and reads start where the
|
// loads it, as MCP tools. Only turns the owner started may call it (`origin: "owner"`): the terminals are the owner's
|
||||||
// first attempt's did (memoized), so a call a restart interrupted runs again without typing or opening twice.
|
// too, possibly connected where the session's `bash` would not reach (an ssh session, a waiting sudo prompt).
|
||||||
|
// Replay-safe: the host does a call with the tool call's key once, and reads start where the first attempt's did
|
||||||
|
// (memoized), so a call a restart interrupted runs again without typing or opening twice.
|
||||||
|
|
||||||
import {
|
import {
|
||||||
defineExtension,
|
defineExtension,
|
||||||
defineTool,
|
|
||||||
type Extension,
|
type Extension,
|
||||||
type ToolExecutionApi,
|
type ToolExecutionApi,
|
||||||
type ToolExecutionResult,
|
type ToolExecutionResult,
|
||||||
} from "@earendil-works/pi-durable";
|
} from "@earendil-works/pi-durable";
|
||||||
import { arr, bool, int, obj, opt, type Static, str, union } from "@earendil-works/pi-schema";
|
import { arr, bool, int, obj, opt, type Static, str, union } from "@earendil-works/pi-schema";
|
||||||
import { type Context, isRooted, joinPath, type Location, literal } from "@p2/app/v1";
|
import { type Context, isRooted, joinPath, type Location, literal } from "@p2/app/v1";
|
||||||
|
import { defineP2Tool, type P2Tool } from "@p2/app/v1/worker";
|
||||||
import {
|
import {
|
||||||
COMMANDS,
|
COMMANDS,
|
||||||
CommandError,
|
CommandError,
|
||||||
|
|
@ -99,11 +101,11 @@ export function createTerminalExtension(
|
||||||
reach: TerminalReach,
|
reach: TerminalReach,
|
||||||
backend: TerminalBackend,
|
backend: TerminalBackend,
|
||||||
locate: (api: ToolExecutionApi, context: Context) => Promise<Location>,
|
locate: (api: ToolExecutionApi, context: Context) => Promise<Location>,
|
||||||
): Extension {
|
): Extension<P2Tool> {
|
||||||
return defineExtension({
|
return defineExtension<P2Tool>({
|
||||||
name: TERMINAL_EXTENSION,
|
name: TERMINAL_EXTENSION,
|
||||||
tools: [
|
tools: [
|
||||||
defineTool({
|
defineP2Tool({
|
||||||
name: "terminal",
|
name: "terminal",
|
||||||
description: DESCRIPTION,
|
description: DESCRIPTION,
|
||||||
parameters: obj({
|
parameters: obj({
|
||||||
|
|
@ -116,6 +118,7 @@ export function createTerminalExtension(
|
||||||
...(reach === "codemode" ? { callers: ["tools" as const] } : {}),
|
...(reach === "codemode" ? { callers: ["tools" as const] } : {}),
|
||||||
outputLimits: { maxBytes: OUTPUT_CHARS + 1024, retain: "tail" },
|
outputLimits: { maxBytes: OUTPUT_CHARS + 1024, retain: "tail" },
|
||||||
replay: "safe",
|
replay: "safe",
|
||||||
|
origin: "owner",
|
||||||
execute: async (args, api, context) => {
|
execute: async (args, api, context) => {
|
||||||
let command: TerminalCommand;
|
let command: TerminalCommand;
|
||||||
try {
|
try {
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,8 @@ test("code mode only while code mode is on; tool_search reaches it otherwise", (
|
||||||
assert.deepEqual(terminal(backend).callers, ["tools"]);
|
assert.deepEqual(terminal(backend).callers, ["tools"]);
|
||||||
assert.equal(terminal(backend).deferred, true);
|
assert.equal(terminal(backend).deferred, true);
|
||||||
assert.equal(terminal(backend).replay, "safe");
|
assert.equal(terminal(backend).replay, "safe");
|
||||||
|
// The owner's terminals: only turns the owner started (checked by the worker's registry).
|
||||||
|
assert.equal(terminal(backend).origin, "owner");
|
||||||
assert.equal(terminal(backend, "tool_search").callers, undefined);
|
assert.equal(terminal(backend, "tool_search").callers, undefined);
|
||||||
assert.equal(terminal(backend, "tool_search").deferred, true);
|
assert.equal(terminal(backend, "tool_search").deferred, true);
|
||||||
assert.match(terminal(backend).description, /Only when the user asks for a terminal/);
|
assert.match(terminal(backend).description, /Only when the user asks for a terminal/);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue