@meowlynxsea/koi 0.1.2 → 0.1.3

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.
@@ -0,0 +1,158 @@
1
+ /**
2
+ * SendToMonitor Tool — 向 Monitor 内进程发送输入
3
+ *
4
+ * 允许 agent 向运行中的 monitor PTY 会话发送输入,
5
+ * 支持交互式命令(如 sudo 密码、确认提示等)。
6
+ */
7
+
8
+ import { Type } from "typebox";
9
+ import type { ToolDefinition } from "@mariozechner/pi-coding-agent";
10
+ import type { TextContent } from "@mariozechner/pi-ai";
11
+ import { monitorRegistry } from "../agent/monitor-registry.js";
12
+
13
+ export const sendToMonitorSchema = Type.Object({
14
+ monitorId: Type.String({
15
+ description: "The ID of the monitor to send input to.",
16
+ }),
17
+ input: Type.String({
18
+ description:
19
+ "The input string to send to the monitor's PTY. " +
20
+ "For passwords, type the password and it will be sent to the PTY. " +
21
+ "For newlines, include \\n in the string or use sendLine: true.",
22
+ }),
23
+ sendLine: Type.Optional(
24
+ Type.Boolean({
25
+ description:
26
+ "If true, append a newline after the input (equivalent to pressing Enter). " +
27
+ "Default: false (raw input without newline).",
28
+ default: false,
29
+ })
30
+ ),
31
+ interrupt: Type.Optional(
32
+ Type.Boolean({
33
+ description:
34
+ "If true, send Ctrl+C to interrupt the running process instead of sending text input. " +
35
+ "When set to true, the 'input' parameter is ignored.",
36
+ default: false,
37
+ })
38
+ ),
39
+ });
40
+
41
+ export type SendToMonitorInput = {
42
+ monitorId: string;
43
+ input: string;
44
+ sendLine?: boolean;
45
+ interrupt?: boolean;
46
+ };
47
+
48
+ export function createSendToMonitorToolDefinition(): ToolDefinition<
49
+ typeof sendToMonitorSchema,
50
+ { success: boolean; monitorId: string }
51
+ > {
52
+ return {
53
+ name: "SendToMonitor",
54
+ label: "SendToMonitor",
55
+ description:
56
+ "Send input to a running monitor's PTY process.\n\n" +
57
+ "Use this tool when:\n" +
58
+ "- A monitor prompts for a password (e.g., sudo)\n" +
59
+ "- A monitor asks for confirmation (y/n)\n" +
60
+ "- You need to interact with an interactive command running in a monitor\n\n" +
61
+ "The input is sent directly to the PTY, completely bypassing any terminal UI,\n" +
62
+ "ensuring no sensitive information is leaked to the outer environment.",
63
+ promptSnippet: "SendToMonitor: send input to a background monitor process",
64
+ parameters: sendToMonitorSchema,
65
+ executionMode: "parallel",
66
+ async execute(
67
+ _toolCallId,
68
+ params: SendToMonitorInput
69
+ ): Promise<{
70
+ content: TextContent[];
71
+ details: { success: boolean; monitorId: string };
72
+ isError?: boolean;
73
+ }> {
74
+ const monitor = monitorRegistry.get(params.monitorId);
75
+
76
+ if (!monitor) {
77
+ return {
78
+ content: [
79
+ {
80
+ type: "text",
81
+ text: `Monitor not found: ${params.monitorId}`,
82
+ } satisfies TextContent,
83
+ ],
84
+ details: { success: false, monitorId: params.monitorId },
85
+ isError: true,
86
+ };
87
+ }
88
+
89
+ if (monitor.status !== "running") {
90
+ return {
91
+ content: [
92
+ {
93
+ type: "text",
94
+ text: `Monitor ${params.monitorId} is not running (status: ${monitor.status}).`,
95
+ } satisfies TextContent,
96
+ ],
97
+ details: { success: false, monitorId: params.monitorId },
98
+ isError: true,
99
+ };
100
+ }
101
+
102
+ const session = monitorRegistry.getSession(params.monitorId);
103
+ if (!session) {
104
+ return {
105
+ content: [
106
+ {
107
+ type: "text",
108
+ text: `Monitor ${params.monitorId} session not found (may have already exited).`,
109
+ } satisfies TextContent,
110
+ ],
111
+ details: { success: false, monitorId: params.monitorId },
112
+ isError: true,
113
+ };
114
+ }
115
+
116
+ let success = false;
117
+
118
+ // Send interrupt (Ctrl+C) if requested
119
+ if (params.interrupt) {
120
+ success = monitorRegistry.interrupt(params.monitorId);
121
+ return {
122
+ content: [
123
+ {
124
+ type: "text",
125
+ text: success
126
+ ? `Interrupt (Ctrl+C) sent to monitor ${params.monitorId}.`
127
+ : `Monitor ${params.monitorId} could not be interrupted.`,
128
+ } satisfies TextContent,
129
+ ],
130
+ details: { success, monitorId: params.monitorId },
131
+ };
132
+ }
133
+
134
+ // Send input to PTY
135
+ if (params.sendLine) {
136
+ monitorRegistry.sendLine(params.monitorId, params.input);
137
+ } else {
138
+ monitorRegistry.write(params.monitorId, params.input);
139
+ }
140
+ success = true;
141
+
142
+ // Mask password in output for display (but keep it in details if needed for debugging)
143
+ const displayInput = params.input.length > 0 && params.input === params.input.trim()
144
+ ? "••••••••"
145
+ : params.input;
146
+
147
+ return {
148
+ content: [
149
+ {
150
+ type: "text",
151
+ text: `Input sent to monitor ${params.monitorId}: ${params.sendLine ? displayInput + "\\n" : displayInput}`,
152
+ } satisfies TextContent,
153
+ ],
154
+ details: { success, monitorId: params.monitorId },
155
+ };
156
+ },
157
+ };
158
+ }
@@ -6,7 +6,8 @@
6
6
  */
7
7
 
8
8
  import { useState, useEffect } from "react";
9
- import { createTextAttributes, type SpinnerVariant } from "@opentui/core";
9
+ import { createTextAttributes } from "@opentui/core";
10
+ type SpinnerVariant = "dots" | "arc" | "circle" | "line";
10
11
  import type { McpConnectionProgress } from "../../services/mcp/index.js";
11
12
 
12
13
  interface ConnectingModalProps {
@@ -131,7 +131,7 @@ interface SubagentItem {
131
131
  interface MonitorItem {
132
132
  id: string;
133
133
  description: string;
134
- status: "running" | "completed" | "killed" | "error";
134
+ status: "running" | "completed" | "killed" | "error" | "detached";
135
135
  lastOutput?: string;
136
136
  }
137
137