paseo-terminal-integration 0.2.0 → 0.4.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.
@@ -1,4 +1,4 @@
1
- import { useAgent, useRpc, useSettings, type PluginTimelineItemProps } from "@getpaseo/plugin/client";
1
+ import { useRpc, useSettings, type PluginTimelineItemProps } from "@getpaseo/plugin/client";
2
2
  import { copyText, Icon, useToast } from "@getpaseo/plugin/client/react-native";
3
3
  import { useQuery, useQueryClient } from "@tanstack/react-query";
4
4
  import { useMemo, useState } from "react";
@@ -11,10 +11,10 @@ import {
11
11
  startRun,
12
12
  TERMINAL_NAME,
13
13
  type RunCardData,
14
+ type RunMode,
14
15
  type RunSnapshot,
15
16
  } from "../shared/contracts";
16
17
  import { preferences } from "../shared/settings";
17
- import { openTerminalPanel } from "./panel-opener";
18
18
 
19
19
  type Theme = PluginTimelineItemProps["theme"];
20
20
  type Styles = ReturnType<typeof createStyles>;
@@ -44,24 +44,15 @@ export function RunCard({ item, agentId, theme, layout }: PluginTimelineItemProp
44
44
  const cancel = useRpc(cancelRun);
45
45
  const send = useRpc(sendRunOutput);
46
46
 
47
- const workspaceId = useAgent(agentId, (agent) => agent.workspaceId);
48
47
  const settings = useSettings(preferences);
49
- const saved = settings.status === "ready" ? settings.values : null;
50
- const panelLocation = saved?.panelLocation ?? "explorer";
51
48
  const [sendChoice, setSendChoice] = useState<boolean | null>(null);
52
- const sendToAgent = sendChoice ?? saved?.sendToAgent ?? true;
53
- const runVisibleLabel =
54
- layout.compact || panelLocation === "workspace" ? "Run in terminal tab" : "Run in side terminal";
49
+ const sendToAgent =
50
+ sendChoice ?? (settings.status === "ready" ? settings.values.sendToAgent : true);
55
51
 
56
- function showTerminal() {
57
- if (!workspaceId) return;
58
- openTerminalPanel(workspaceId, panelLocation, { compact: layout.compact, explicit: true });
59
- }
60
- function runBlock(key: string, block: RunCardData["blocks"][number], visible: boolean) {
61
- return act(key, async () => {
62
- await start({ agentId, key, lang: block.lang, code: block.code, send: sendToAgent });
63
- if (visible) showTerminal();
64
- });
52
+ function runBlock(key: string, block: RunCardData["blocks"][number], mode: RunMode) {
53
+ return act(key, () =>
54
+ start({ agentId, key, lang: block.lang, code: block.code, send: sendToAgent, mode }),
55
+ );
65
56
  }
66
57
 
67
58
  const queryKey = useMemo(() => ["terminal-run-status", cardId], [cardId]);
@@ -99,19 +90,10 @@ export function RunCard({ item, agentId, theme, layout }: PluginTimelineItemProp
99
90
  <Checkbox
100
91
  styles={styles}
101
92
  theme={theme}
102
- label="Send to agent"
93
+ label="Send output to agent"
103
94
  checked={sendToAgent}
104
95
  onChange={setSendChoice}
105
96
  />
106
- {workspaceId ? (
107
- <ActionButton
108
- styles={styles}
109
- theme={theme}
110
- icon="PanelRight"
111
- label="Terminal"
112
- onPress={showTerminal}
113
- />
114
- ) : null}
115
97
  </View>
116
98
  {blocks.map((block, index) => {
117
99
  const key = keys[index];
@@ -154,19 +136,19 @@ export function RunCard({ item, agentId, theme, layout }: PluginTimelineItemProp
154
136
  <ActionButton
155
137
  styles={styles}
156
138
  theme={theme}
157
- icon="PanelRight"
158
- label={runVisibleLabel}
139
+ icon="Play"
140
+ label="Run"
159
141
  tone="primary"
160
142
  disabled={busy || active}
161
- onPress={() => runBlock(key, block, true)}
143
+ onPress={() => runBlock(key, block, "terminal")}
162
144
  />
163
145
  <ActionButton
164
146
  styles={styles}
165
147
  theme={theme}
166
- icon="Play"
167
- label="Run silently"
148
+ icon="EyeOff"
149
+ label="Run in background"
168
150
  disabled={busy || active}
169
- onPress={() => runBlock(key, block, false)}
151
+ onPress={() => runBlock(key, block, "background")}
170
152
  />
171
153
  {run && !run.sent && !active ? (
172
154
  <ActionButton
@@ -205,7 +187,11 @@ function RunStatus({ run, styles }: { run: RunSnapshot; styles: Styles }) {
205
187
  const parts: string[] = [];
206
188
  let tone: TextStyle = styles.muted;
207
189
  if (run.status === "queued") parts.push("Queued, waiting for the terminal");
208
- if (run.status === "running") parts.push(`Running · ${elapsed(run)}`);
190
+ if (run.status === "running") {
191
+ parts.push(
192
+ `${run.mode === "background" ? "Running in background" : "Running"} · ${elapsed(run)}`,
193
+ );
194
+ }
209
195
  if (run.status === "done") {
210
196
  parts.push(`${run.exitCode === 0 ? "✓" : "✗"} exit ${run.exitCode}`);
211
197
  tone = run.exitCode === 0 ? styles.success : styles.danger;
@@ -0,0 +1,34 @@
1
+ // COMPAT(inlineCodeBlockActions): copies of the inline code-action SDK types until a published
2
+ // @getpaseo/plugin exports them; remove after 2027-04-01.
3
+ import type { ComponentType } from "react";
4
+ import type { PluginCleanup } from "@getpaseo/plugin";
5
+ import type { PluginClientContext, PluginSurfaceProps } from "@getpaseo/plugin/client";
6
+
7
+ type HostNavigation = NonNullable<PluginSurfaceProps["navigation"]>;
8
+
9
+ export interface PluginCodeBlockActionsProps extends Omit<PluginSurfaceProps, "navigation"> {
10
+ readonly navigation?: HostNavigation & {
11
+ readonly openTerminal?: (input: {
12
+ readonly workspaceId: string;
13
+ readonly terminalId: string;
14
+ readonly serverId?: string;
15
+ }) => void;
16
+ };
17
+ agentId: string;
18
+ messageId: string;
19
+ blockIndex: number;
20
+ code: string;
21
+ language: string;
22
+ phase: "streaming" | "complete";
23
+ }
24
+
25
+ export interface PluginCodeBlockActionsContribution {
26
+ id: string;
27
+ languages: readonly string[];
28
+ Component: ComponentType<PluginCodeBlockActionsProps>;
29
+ }
30
+
31
+ /** Older clients lack addCodeBlockActions; check for it before calling. */
32
+ export type InlineActionsClient = PluginClientContext & {
33
+ addCodeBlockActions?: (contribution: PluginCodeBlockActionsContribution) => PluginCleanup;
34
+ };
@@ -3,16 +3,11 @@ import {
3
3
  SettingsCard,
4
4
  SettingsRow,
5
5
  SettingsSection,
6
- SettingsSelect,
7
6
  SettingsSwitch,
8
7
  } from "@getpaseo/plugin/client/ui";
9
8
  import { Text } from "react-native";
10
- import { preferences, type PanelLocation } from "../shared/settings";
11
-
12
- const panelLocationChoices: { label: string; value: PanelLocation }[] = [
13
- { label: "Side panel (Explorer)", value: "explorer" },
14
- { label: "Workspace tab", value: "workspace" },
15
- ];
9
+ import { TERMINAL_NAME } from "../shared/contracts";
10
+ import { preferences } from "../shared/settings";
16
11
 
17
12
  export function SettingsScreen({ theme }: PluginSurfaceProps) {
18
13
  const settings = useSettings(preferences);
@@ -25,27 +20,19 @@ export function SettingsScreen({ theme }: PluginSurfaceProps) {
25
20
  const { values, revision } = settings;
26
21
 
27
22
  return (
28
- <SettingsSection title="Run cards">
23
+ <SettingsSection title="Code block actions">
29
24
  <SettingsCard>
30
- <SettingsSelect
31
- label="Show the terminal in"
32
- hint={`Used by "Run in side terminal", the Terminal button, and /run. Phones always use a tab and never switch to it automatically.`}
33
- value={values.panelLocation}
34
- options={panelLocationChoices}
35
- disabled={settings.saving}
36
- error={settings.saveError}
37
- onValueChange={(panelLocation) => void settings.save({ ...values, panelLocation }, revision)}
38
- />
39
25
  <SettingsSwitch
40
26
  label="Send output to the agent by default"
41
- hint={`Initial state of the "Send to agent" checkbox on each run card.`}
27
+ hint={`Initial state of the "Send output to agent" checkbox on each code block.`}
42
28
  value={values.sendToAgent}
43
29
  disabled={settings.saving}
30
+ error={settings.saveError}
44
31
  onValueChange={(sendToAgent) => void settings.save({ ...values, sendToAgent }, revision)}
45
32
  />
46
33
  <SettingsRow
47
- label="What the panel shows"
48
- hint={`The live "Agent commands" terminal with colors and keyboard input, for prompts such as sudo passwords, y/n questions, or 2FA codes. Ctrl-C copies while text is selected; Ctrl-Shift-C and Ctrl-Shift-V copy and paste. Phones show a plain-text copy with an input line.`}
34
+ label="Where the terminal appears"
35
+ hint={`Commands run in the "${TERMINAL_NAME}" terminal tab. Choose Main panel or On the side in Settings → Layout → Open location → Terminals. Manually moved tabs keep their location.`}
49
36
  />
50
37
  </SettingsCard>
51
38
  </SettingsSection>
package/index.client.tsx CHANGED
@@ -1,99 +1,35 @@
1
- import type { PluginClientContext } from "@getpaseo/plugin/client";
2
- import { setPanelOpener } from "./client/panel-opener";
3
- import { RunCard } from "./client/run-card";
1
+ import { z } from "zod";
2
+ import { contributeLegacy } from "./client/legacy";
3
+ import type { PluginCleanup } from "@getpaseo/plugin";
4
+ import type { InlineActionsClient } from "./client/sdk-compat";
5
+ import { CodeBlockActions } from "./client/code-block-actions";
4
6
  import { SettingsScreen } from "./client/settings-screen";
5
- import { TerminalPanel } from "./client/terminal-panel";
6
- import {
7
- RUN_CARD_KIND,
8
- RUN_CARD_VERSION,
9
- runAdhoc,
10
- runCardSchema,
11
- scanBlocks,
12
- TERMINAL_NAME,
13
- terminalOutputSource,
14
- } from "./shared/contracts";
15
- import { TERMINAL_PANEL_ID } from "./shared/settings";
16
-
17
- export default function contribute(client: PluginClientContext) {
18
- setPanelOpener((id, options) => client.openPanel(id, options));
19
-
20
- client.addTimelineRenderer({
21
- kind: RUN_CARD_KIND,
22
- version: RUN_CARD_VERSION,
23
- schema: runCardSchema,
24
- Component: RunCard,
25
- });
26
-
27
- client.addWorkspacePanel({
28
- id: TERMINAL_PANEL_ID,
29
- title: TERMINAL_NAME,
30
- icon: "SquareTerminal",
31
- context: "workspace",
32
- locations: ["explorer", "workspace"],
33
- Component: TerminalPanel,
34
- });
35
-
7
+ import { RUNNABLE_LANGUAGES } from "./shared/code-blocks";
8
+ import { terminalOutputSource } from "./shared/contracts";
9
+
10
+ export default function contribute(client: InlineActionsClient): PluginCleanup {
11
+ if (typeof client.addCodeBlockActions === "function") {
12
+ client.addCodeBlockActions({
13
+ id: "run",
14
+ languages: RUNNABLE_LANGUAGES,
15
+ Component: CodeBlockActions,
16
+ });
17
+ // COMPAT(legacyRunCards): added in the inline-actions fork, remove after 2027-04-01.
18
+ client.addTimelineRenderer({
19
+ kind: "terminal-run-card",
20
+ version: 1,
21
+ schema: z.unknown(),
22
+ Component: () => null,
23
+ });
24
+ } else {
25
+ contributeLegacy(client);
26
+ }
36
27
  client.addSettingsScreen({
37
28
  id: "preferences",
38
29
  title: "Terminal integration",
39
30
  icon: "SquareTerminal",
40
31
  Component: SettingsScreen,
41
32
  });
42
-
43
33
  client.addAttachmentSource(terminalOutputSource);
44
-
45
- client.addSlashCommand({
46
- name: "run",
47
- description: "Run a shell command in the workspace terminal and send its output to the agent",
48
- argumentHint: "<command>",
49
- context: "agent",
50
- async onSubmit({ args, agent, rpc, openPanel }) {
51
- if (!args) throw new Error("Usage: /run <command>");
52
- const { panelLocation } = await rpc(runAdhoc, { agentId: agent.id, command: args });
53
- try {
54
- openPanel(TERMINAL_PANEL_ID, { location: panelLocation });
55
- } catch {
56
- // Compact layouts have no Explorer; the card's Terminal button still opens a tab.
57
- }
58
- },
59
- });
60
-
61
- async function addRunButtons(agentId: string, rpc: PluginClientContext["rpc"]) {
62
- const { count } = await rpc(scanBlocks, { agentId });
63
- if (count === 0) throw new Error("The latest reply has no runnable code blocks");
64
- }
65
-
66
- client.addSlashCommand({
67
- name: "blocks",
68
- description: "Add terminal run buttons for code blocks in the agent's latest reply",
69
- argumentHint: "",
70
- context: "agent",
71
- onSubmit: ({ agent, rpc }) => addRunButtons(agent.id, rpc),
72
- });
73
-
74
- client.addCommandCenterItem({
75
- id: "scan-latest-reply",
76
- title: "Terminal: add run buttons to the latest reply",
77
- icon: "SquareTerminal",
78
- keywords: ["run", "code block", "terminal", "execute"],
79
- context: "agent",
80
- onSelect: ({ agent, rpc }) => addRunButtons(agent.id, rpc),
81
- });
82
-
83
- client.addCommandCenterItem({
84
- id: "open-terminal-panel",
85
- title: `Terminal: show "${TERMINAL_NAME}" in the side panel`,
86
- icon: "PanelRight",
87
- keywords: ["terminal", "explorer", "sidebar", "agent commands"],
88
- context: "workspace",
89
- onSelect({ openPanel }) {
90
- try {
91
- openPanel(TERMINAL_PANEL_ID, { location: "explorer" });
92
- } catch {
93
- openPanel(TERMINAL_PANEL_ID, { location: "workspace" });
94
- }
95
- },
96
- });
97
-
98
- return () => setPanelOpener(null);
34
+ return () => {};
99
35
  }
package/index.server.ts CHANGED
@@ -2,14 +2,10 @@ import type { PluginServerContext } from "@getpaseo/plugin/server";
2
2
  import { searchTerminals } from "./server/attachments";
3
3
  import { appendCardForTurn, appendRunCard, scanLatestReply } from "./server/cards";
4
4
  import { cleanupScripts, TerminalRunner } from "./server/runner";
5
- import { TerminalStreams } from "./server/streams";
6
5
  import { preferences } from "./shared/settings";
7
6
  import {
7
+ enableLegacyCards,
8
8
  cancelRun,
9
- closeTerminalView,
10
- openTerminalView,
11
- readTerminalView,
12
- resizeTerminalView,
13
9
  runAdhoc,
14
10
  runKey,
15
11
  runStatus,
@@ -20,13 +16,20 @@ import {
20
16
  } from "./shared/contracts";
21
17
 
22
18
  export default function contribute(server: PluginServerContext) {
23
- const settings = server.registerSettings(preferences);
19
+ server.registerSettings(preferences);
24
20
  let runner: TerminalRunner | null = null;
25
- const streams = new TerminalStreams();
26
21
  const runnerFor = (paseo: ConstructorParameters<typeof TerminalRunner>[0]) =>
27
22
  (runner ??= new TerminalRunner(paseo));
28
23
 
29
- server.on("agent.turn_ended", (event, { paseo }) => appendCardForTurn(paseo, event));
24
+ // COMPAT(legacyRunCards): only old clients opt into scanning; remove after 2027-04-01.
25
+ let legacyCardsRequested = false;
26
+ server.handle(enableLegacyCards, async () => {
27
+ legacyCardsRequested = true;
28
+ return { enabled: true };
29
+ });
30
+ server.on("agent.turn_ended", (event, { paseo }) => {
31
+ if (legacyCardsRequested) return appendCardForTurn(paseo, event);
32
+ });
30
33
 
31
34
  server.handle(startRun, (input, { paseo }) => runnerFor(paseo).start(input));
32
35
  server.handle(runStatus, ({ keys }, { paseo }) => ({ runs: runnerFor(paseo).status(keys) }));
@@ -37,9 +40,14 @@ export default function contribute(server: PluginServerContext) {
37
40
  const block = { lang: "bash", code: command };
38
41
  const cardId = await appendRunCard(paseo, agentId, [block]);
39
42
  if (!cardId) throw new Error("Command is too long");
40
- await runnerFor(paseo).start({ agentId, key: runKey(cardId, 0), ...block, send: true });
41
- const saved = await settings.read();
42
- return { cardId, panelLocation: saved.status === "ready" ? saved.values.panelLocation : "explorer" };
43
+ await runnerFor(paseo).start({
44
+ agentId,
45
+ key: runKey(cardId, 0),
46
+ ...block,
47
+ send: true,
48
+ mode: "terminal",
49
+ });
50
+ return { cardId };
43
51
  });
44
52
 
45
53
  server.handle(scanBlocks, async ({ agentId }, { paseo }) => ({
@@ -48,14 +56,8 @@ export default function contribute(server: PluginServerContext) {
48
56
 
49
57
  server.handle(searchTerminalOutput, searchTerminals);
50
58
 
51
- server.handle(openTerminalView, (input) => streams.open(input));
52
- server.handle(readTerminalView, (input) => streams.read(input));
53
- server.handle(resizeTerminalView, (input) => streams.resize(input));
54
- server.handle(closeTerminalView, (input) => streams.close(input));
55
-
56
59
  return async () => {
57
60
  runner?.stop();
58
- await streams.stop();
59
61
  await cleanupScripts();
60
62
  };
61
63
  }
package/package.json CHANGED
@@ -1,48 +1,49 @@
1
1
  {
2
2
  "name": "paseo-terminal-integration",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
+ "description": "Run agent code blocks in a Paseo terminal and send the output back to the agent; attach terminal output to prompts",
5
+ "keywords": [
6
+ "agents",
7
+ "paseo",
8
+ "paseo-plugin",
9
+ "terminal"
10
+ ],
11
+ "homepage": "https://github.com/keefeere/paseo-terminal-integration#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/keefeere/paseo-terminal-integration/issues"
14
+ },
15
+ "license": "MIT",
16
+ "author": "Chechulin Serhii",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/keefeere/paseo-terminal-integration.git"
20
+ },
4
21
  "files": [
5
22
  "paseo-plugin.json",
6
23
  "index.client.tsx",
7
24
  "index.server.ts",
8
25
  "client/",
9
26
  "server/",
10
- "shared/"
27
+ "shared/",
28
+ "!**/*.test.ts"
11
29
  ],
12
30
  "scripts": {
13
- "typecheck": "tsc --noEmit"
31
+ "typecheck": "tsc --noEmit",
32
+ "test": "vitest run",
33
+ "lint": "oxlint",
34
+ "format": "oxfmt ."
14
35
  },
15
36
  "devDependencies": {
16
- "@getpaseo/plugin": "0.10.1",
37
+ "@getpaseo/plugin": "0.10.2",
17
38
  "@tanstack/react-query": "^5.90.11",
18
39
  "@types/node": "^26.6.2",
19
40
  "@types/react": "~19.2.0",
41
+ "oxfmt": "0.46.0",
42
+ "oxlint": "1.61.0",
20
43
  "react": "19.1.0",
21
44
  "react-native": "0.81.5",
22
45
  "typescript": "^5.9.3",
23
- "zod": "^4.4.3"
24
- },
25
- "description": "Run agent code blocks in a Paseo terminal and send the output back to the agent; attach terminal output to prompts",
26
- "license": "MIT",
27
- "author": "Chechulin Serhii",
28
- "repository": {
29
- "type": "git",
30
- "url": "git+https://github.com/keefeere/paseo-terminal-integration.git"
31
- },
32
- "homepage": "https://github.com/keefeere/paseo-terminal-integration#readme",
33
- "bugs": {
34
- "url": "https://github.com/keefeere/paseo-terminal-integration/issues"
35
- },
36
- "keywords": [
37
- "paseo",
38
- "paseo-plugin",
39
- "terminal",
40
- "agents"
41
- ],
42
- "dependencies": {
43
- "@getpaseo/client": "0.10.1",
44
- "@xterm/addon-fit": "^0.11.0",
45
- "@xterm/addon-web-links": "^0.12.0",
46
- "@xterm/xterm": "^6.0.0"
46
+ "vitest": "^4.1.7",
47
+ "zod": "4.4.3"
47
48
  }
48
49
  }
@@ -32,7 +32,9 @@ export async function searchTerminals(
32
32
 
33
33
  const items = await Promise.all(
34
34
  matches.map(async (entry) => {
35
- const capture = await paseo.terminals.ref(entry).capture({ start: -lineCount, stripAnsi: true });
35
+ const capture = await paseo.terminals
36
+ .ref(entry)
37
+ .capture({ start: -lineCount, stripAnsi: true });
36
38
  const lines = trimBlankEdges(withoutRunMarkers(capture.lines));
37
39
  const name = entry.name || "Terminal";
38
40
  const workspace = workspaceNames.get(entry.workspaceId);