@iinm/plain-agent 1.8.3 → 1.8.4

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.
Files changed (86) hide show
  1. package/README.md +2 -2
  2. package/bin/plain +1 -1
  3. package/config/config.predefined.json +1 -1
  4. package/config/prompts.predefined/shortcuts/configure.md +1 -1
  5. package/dist/main.mjs +473 -0
  6. package/dist/main.mjs.map +7 -0
  7. package/package.json +5 -7
  8. package/src/agent.d.ts +0 -52
  9. package/src/agent.mjs +0 -204
  10. package/src/agentLoop.mjs +0 -419
  11. package/src/agentState.mjs +0 -41
  12. package/src/claudeCodePlugin.mjs +0 -164
  13. package/src/cliArgs.mjs +0 -175
  14. package/src/cliBatch.mjs +0 -147
  15. package/src/cliCommands.mjs +0 -283
  16. package/src/cliCompleter.mjs +0 -227
  17. package/src/cliCost.mjs +0 -309
  18. package/src/cliFormatter.mjs +0 -413
  19. package/src/cliInteractive.mjs +0 -529
  20. package/src/cliInterruptTransform.mjs +0 -51
  21. package/src/cliMuteTransform.mjs +0 -26
  22. package/src/cliPasteTransform.mjs +0 -183
  23. package/src/config.d.ts +0 -36
  24. package/src/config.mjs +0 -197
  25. package/src/context/loadAgentRoles.mjs +0 -283
  26. package/src/context/loadPrompts.mjs +0 -324
  27. package/src/context/loadUserMessageContext.mjs +0 -147
  28. package/src/costTracker.mjs +0 -210
  29. package/src/env.mjs +0 -44
  30. package/src/main.mjs +0 -279
  31. package/src/mcpClient.mjs +0 -351
  32. package/src/mcpIntegration.mjs +0 -160
  33. package/src/model.d.ts +0 -109
  34. package/src/modelCaller.mjs +0 -32
  35. package/src/modelDefinition.d.ts +0 -92
  36. package/src/prompt.mjs +0 -138
  37. package/src/providers/anthropic.d.ts +0 -248
  38. package/src/providers/anthropic.mjs +0 -587
  39. package/src/providers/bedrock.d.ts +0 -249
  40. package/src/providers/bedrock.mjs +0 -700
  41. package/src/providers/gemini.d.ts +0 -208
  42. package/src/providers/gemini.mjs +0 -754
  43. package/src/providers/openai.d.ts +0 -281
  44. package/src/providers/openai.mjs +0 -544
  45. package/src/providers/openaiCompatible.d.ts +0 -147
  46. package/src/providers/openaiCompatible.mjs +0 -652
  47. package/src/providers/platform/awsSigV4.mjs +0 -184
  48. package/src/providers/platform/azure.mjs +0 -42
  49. package/src/providers/platform/bedrock.mjs +0 -78
  50. package/src/providers/platform/googleCloud.mjs +0 -34
  51. package/src/subagent.mjs +0 -265
  52. package/src/tmpfile.mjs +0 -27
  53. package/src/tool.d.ts +0 -74
  54. package/src/toolExecutor.mjs +0 -236
  55. package/src/toolInputValidator.mjs +0 -183
  56. package/src/toolUseApprover.mjs +0 -99
  57. package/src/tools/askURL.mjs +0 -209
  58. package/src/tools/askWeb.mjs +0 -208
  59. package/src/tools/compactContext.d.ts +0 -4
  60. package/src/tools/compactContext.mjs +0 -87
  61. package/src/tools/delegateToSubagent.d.ts +0 -4
  62. package/src/tools/delegateToSubagent.mjs +0 -48
  63. package/src/tools/execCommand.d.ts +0 -22
  64. package/src/tools/execCommand.mjs +0 -200
  65. package/src/tools/patchFile.d.ts +0 -4
  66. package/src/tools/patchFile.mjs +0 -133
  67. package/src/tools/reportAsSubagent.d.ts +0 -3
  68. package/src/tools/reportAsSubagent.mjs +0 -44
  69. package/src/tools/tmuxCommand.d.ts +0 -14
  70. package/src/tools/tmuxCommand.mjs +0 -194
  71. package/src/tools/writeFile.d.ts +0 -4
  72. package/src/tools/writeFile.mjs +0 -56
  73. package/src/usageStore.mjs +0 -167
  74. package/src/utils/evalJSONConfig.mjs +0 -72
  75. package/src/utils/matchValue.d.ts +0 -6
  76. package/src/utils/matchValue.mjs +0 -40
  77. package/src/utils/noThrow.mjs +0 -31
  78. package/src/utils/notify.mjs +0 -29
  79. package/src/utils/parseFileRange.mjs +0 -18
  80. package/src/utils/readFileRange.mjs +0 -33
  81. package/src/utils/retryOnError.mjs +0 -41
  82. package/src/voiceInput.mjs +0 -61
  83. package/src/voiceInputGemini.mjs +0 -105
  84. package/src/voiceInputOpenAI.mjs +0 -104
  85. package/src/voiceInputSession.mjs +0 -543
  86. package/src/voiceToggleKey.mjs +0 -62
@@ -1,283 +0,0 @@
1
- /**
2
- * @import { UserEventEmitter, AgentCommands } from "./agent"
3
- * @import { ClaudeCodePlugin } from "./claudeCodePlugin.mjs"
4
- */
5
-
6
- import { execFileSync } from "node:child_process";
7
- import { styleText } from "node:util";
8
- import { formatCostSummary } from "./cliFormatter.mjs";
9
- import { loadAgentRoles } from "./context/loadAgentRoles.mjs";
10
- import { loadPrompts } from "./context/loadPrompts.mjs";
11
- import { loadUserMessageContext } from "./context/loadUserMessageContext.mjs";
12
- import { CLAUDE_CODE_COMPATIBILITY_NOTES } from "./prompt.mjs";
13
- import { parseFileRange } from "./utils/parseFileRange.mjs";
14
- import { readFileRange } from "./utils/readFileRange.mjs";
15
-
16
- /**
17
- * @typedef {"prompt" | "continue"} CommandResult
18
- * - "prompt": return control to prompt (state.turn = true; cli.prompt())
19
- * - "continue": agent is now running, do nothing
20
- */
21
-
22
- /**
23
- * @typedef {object} CommandHandlerDeps
24
- * @property {AgentCommands} agentCommands
25
- * @property {UserEventEmitter} userEventEmitter
26
- * @property {ClaudeCodePlugin[] | undefined} claudeCodePlugins
27
- * @property {string} helpMessage
28
- */
29
-
30
- /**
31
- * Create command handler function for processing slash commands.
32
- *
33
- * @param {CommandHandlerDeps} deps
34
- * @returns {(input: string) => Promise<CommandResult>}
35
- */
36
- export function createCommandHandler({
37
- agentCommands,
38
- userEventEmitter,
39
- claudeCodePlugins,
40
- helpMessage,
41
- }) {
42
- /**
43
- * Invoke an agent with the given id and goal.
44
- * @param {string} id
45
- * @param {string} goal
46
- * @returns {Promise<CommandResult>}
47
- */
48
- async function invokeAgent(id, goal) {
49
- const agentRoles = await loadAgentRoles(claudeCodePlugins);
50
- const agent = agentRoles.get(id);
51
- const name = agent ? id : `custom:${id}`;
52
-
53
- const [goalTextContent, ...goalImages] = await loadUserMessageContext(goal);
54
- const goalText =
55
- goalTextContent?.type === "text" ? goalTextContent.text : goal;
56
-
57
- const messageText = `Delegate to "${name}" agent with goal: ${goalText}`;
58
- userEventEmitter.emit("userInput", [
59
- { type: "text", text: messageText },
60
- ...goalImages,
61
- ]);
62
- return "continue";
63
- }
64
-
65
- /**
66
- * Invoke a prompt with the given id, args, and display invocation.
67
- * @param {string} id
68
- * @param {string} args
69
- * @param {string} displayInvocation
70
- * @returns {Promise<CommandResult>}
71
- */
72
- async function invokePrompt(id, args, displayInvocation) {
73
- const prompts = await loadPrompts(claudeCodePlugins);
74
- const prompt = prompts.get(id);
75
-
76
- if (!prompt) {
77
- console.log(styleText("red", `\nPrompt not found: ${id}`));
78
- return "prompt";
79
- }
80
-
81
- const [argsTextContent, ...argsImages] = args
82
- ? await loadUserMessageContext(args)
83
- : [];
84
- const argsText =
85
- argsTextContent?.type === "text" ? argsTextContent.text : args;
86
-
87
- const invocation = `${displayInvocation}${argsText ? ` ${argsText}` : ""}`;
88
- const promptContent = prompt.claudeOriginated
89
- ? `${prompt.content}\n\n---\n\n${CLAUDE_CODE_COMPATIBILITY_NOTES}`
90
- : prompt.content;
91
- const message = prompt.isSkill
92
- ? `System: This prompt was invoked as "${invocation}".\nPrompt path: ${prompt.filePath}\n\n${promptContent}`
93
- : `System: This prompt was invoked as "${invocation}".\n\n${promptContent}`;
94
-
95
- userEventEmitter.emit("userInput", [
96
- { type: "text", text: message },
97
- ...argsImages,
98
- ]);
99
- return "continue";
100
- }
101
-
102
- /**
103
- * Handle a complete user input string and return a CommandResult.
104
- * @param {string} inputTrimmed
105
- * @returns {Promise<CommandResult>}
106
- */
107
- return async function handleCommand(inputTrimmed) {
108
- // /help or help
109
- if (["/help", "help"].includes(inputTrimmed.toLowerCase())) {
110
- console.log(`\n${helpMessage}`);
111
- return "prompt";
112
- }
113
-
114
- // !path — read file content and emit as user input
115
- if (inputTrimmed.startsWith("!")) {
116
- const fileRange = parseFileRange(inputTrimmed.slice(1));
117
- if (fileRange instanceof Error) {
118
- console.log(styleText("red", `\n${fileRange.message}`));
119
- return "prompt";
120
- }
121
-
122
- const fileContent = await readFileRange(fileRange);
123
- if (fileContent instanceof Error) {
124
- console.log(styleText("red", `\n${fileContent.message}`));
125
- return "prompt";
126
- }
127
-
128
- const messageWithContext = await loadUserMessageContext(fileContent);
129
- userEventEmitter.emit("userInput", messageWithContext);
130
- return "continue";
131
- }
132
-
133
- // /dump
134
- if (inputTrimmed.toLowerCase() === "/dump") {
135
- await agentCommands.dumpMessages();
136
- return "prompt";
137
- }
138
-
139
- // /load
140
- if (inputTrimmed.toLowerCase() === "/load") {
141
- await agentCommands.loadMessages();
142
- return "prompt";
143
- }
144
-
145
- // /cost
146
- if (inputTrimmed.toLowerCase() === "/cost") {
147
- const summary = agentCommands.getCostSummary();
148
- console.log(formatCostSummary(summary));
149
- return "prompt";
150
- }
151
-
152
- // /compact
153
- if (inputTrimmed.toLowerCase() === "/compact") {
154
- const message = [
155
- 'System: This prompt was invoked as "/compact".',
156
- "",
157
- "Compact the conversation context:",
158
- "1. Update the memory file for the current task so it fully captures the task overview, progress, decisions, and next steps in a self-contained way.",
159
- '2. Then call the "compact_context" tool alone with that memory file path and a brief reason.',
160
- ].join("\n");
161
- userEventEmitter.emit("userInput", [{ type: "text", text: message }]);
162
- return "continue";
163
- }
164
-
165
- // /agents or /agents:id
166
- if (inputTrimmed === "/agents") {
167
- const agentRoles = await loadAgentRoles(claudeCodePlugins);
168
-
169
- console.log(styleText("bold", "\nAvailable Agent Roles:"));
170
- if (agentRoles.size === 0) {
171
- console.log(" No agent roles found.");
172
- } else {
173
- for (const role of agentRoles.values()) {
174
- const maxLength = process.stdout.columns ?? 100;
175
- const line = ` ${styleText("cyan", role.id.padEnd(20))} - ${role.description}`;
176
- console.log(
177
- line.length > maxLength ? `${line.slice(0, maxLength)}...` : line,
178
- );
179
- }
180
- }
181
- return "prompt";
182
- }
183
-
184
- if (inputTrimmed.startsWith("/agents:")) {
185
- const match = inputTrimmed.match(/^\/agents:([^ ]+)(?:\s+(.*))?$/s);
186
- if (!match) {
187
- console.log(styleText("red", "\nInvalid agent invocation format."));
188
- return "prompt";
189
- }
190
- return await invokeAgent(match[1], match[2] || "");
191
- }
192
-
193
- // /prompts or /prompts:id
194
- if (inputTrimmed.startsWith("/prompts")) {
195
- const prompts = await loadPrompts(claudeCodePlugins);
196
-
197
- if (inputTrimmed === "/prompts") {
198
- console.log(styleText("bold", "\nAvailable Prompts:"));
199
- if (prompts.size === 0) {
200
- console.log(" No prompts found.");
201
- } else {
202
- for (const prompt of prompts.values()) {
203
- const maxLength = process.stdout.columns ?? 100;
204
- const line = ` ${styleText("cyan", prompt.id.padEnd(20))} - ${prompt.description}`;
205
- console.log(
206
- line.length > maxLength ? `${line.slice(0, maxLength)}...` : line,
207
- );
208
- }
209
- }
210
- return "prompt";
211
- }
212
-
213
- if (inputTrimmed.startsWith("/prompts:")) {
214
- const match = inputTrimmed.match(/^\/prompts:([^ ]+)(?:\s+(.*))?$/s);
215
- if (!match) {
216
- console.log(styleText("red", "\nInvalid prompt invocation format."));
217
- return "prompt";
218
- }
219
- return await invokePrompt(
220
- match[1],
221
- match[2] || "",
222
- `/prompts:${match[1]}`,
223
- );
224
- }
225
- }
226
-
227
- // /paste — read clipboard and emit as user input
228
- if (inputTrimmed.startsWith("/paste")) {
229
- const prompt = inputTrimmed.slice("/paste".length).trim();
230
- let clipboard;
231
- try {
232
- if (process.platform === "darwin") {
233
- clipboard = execFileSync("pbpaste", { encoding: "utf8" });
234
- } else if (process.platform === "linux") {
235
- clipboard = execFileSync("xsel", ["--clipboard", "--output"], {
236
- encoding: "utf8",
237
- });
238
- } else {
239
- console.log(
240
- styleText(
241
- "red",
242
- `\nUnsupported platform for /paste: ${process.platform}`,
243
- ),
244
- );
245
- return "prompt";
246
- }
247
- } catch (e) {
248
- const errorMessage = e instanceof Error ? e.message : String(e);
249
- console.log(
250
- styleText(
251
- "red",
252
- `\nFailed to get clipboard content: ${errorMessage}`,
253
- ),
254
- );
255
- return "prompt";
256
- }
257
-
258
- const combinedInput = prompt ? `${prompt}\n\n${clipboard}` : clipboard;
259
- const messageWithContext = await loadUserMessageContext(combinedInput);
260
- userEventEmitter.emit("userInput", messageWithContext);
261
- return "continue";
262
- }
263
-
264
- // /<id> — shortcut for prompts in shortcuts/ directory
265
- if (inputTrimmed.startsWith("/")) {
266
- const match = inputTrimmed.match(/^\/([^ ]+)(?:\s+(.*))?$/);
267
- if (match) {
268
- const id = match[1];
269
- const prompts = await loadPrompts(claudeCodePlugins);
270
- const prompt = prompts.get(id);
271
-
272
- if (prompt?.isShortcut) {
273
- return await invokePrompt(id, match[2] || "", `/${id}`);
274
- }
275
- }
276
- }
277
-
278
- // Default: emit as plain user input
279
- const messageWithContext = await loadUserMessageContext(inputTrimmed);
280
- userEventEmitter.emit("userInput", messageWithContext);
281
- return "continue";
282
- };
283
- }
@@ -1,227 +0,0 @@
1
- /**
2
- * @import { ClaudeCodePlugin } from "./claudeCodePlugin.mjs"
3
- */
4
-
5
- import { styleText } from "node:util";
6
- import { loadAgentRoles } from "./context/loadAgentRoles.mjs";
7
- import { loadPrompts } from "./context/loadPrompts.mjs";
8
-
9
- // Define available slash commands for tab completion
10
- export const SLASH_COMMANDS = [
11
- { name: "/help", description: "Display this help message" },
12
- { name: "/agents", description: "List available agent roles" },
13
- {
14
- name: "/agents:<id>",
15
- description:
16
- "Delegate to an agent with the given ID (e.g., /agents:code-simplifier)",
17
- },
18
- { name: "/prompts", description: "List available prompts" },
19
- {
20
- name: "/prompts:<id>",
21
- description:
22
- "Invoke a prompt with the given ID (e.g., /prompts:feature-dev)",
23
- },
24
- {
25
- name: "/<id>",
26
- description:
27
- "Shortcut for prompts in the shortcuts/ directory (e.g., /commit)",
28
- },
29
- { name: "/paste", description: "Paste content from clipboard" },
30
- {
31
- name: "/resume",
32
- description: "Resume conversation after an LLM provider error",
33
- },
34
- { name: "/dump", description: "Save current messages to a JSON file" },
35
- { name: "/load", description: "Load messages from a JSON file" },
36
- { name: "/cost", description: "Display session cost and token usage" },
37
- {
38
- name: "/compact",
39
- description:
40
- "Ask the agent to compact the context by reloading from a memory file",
41
- },
42
- ];
43
-
44
- /**
45
- * @typedef {Object} CompletionCandidate
46
- * @property {string} name
47
- * @property {string} description
48
- */
49
-
50
- /**
51
- * Find candidates that match the line, prioritizing prefix matches.
52
- * @param {(string | CompletionCandidate)[]} candidates
53
- * @param {string} line
54
- * @param {number} queryStartIndex
55
- * @returns {(string | CompletionCandidate)[]}
56
- */
57
- function findMatches(candidates, line, queryStartIndex) {
58
- const query = line.slice(queryStartIndex);
59
- const prefixMatches = [];
60
- const partialMatches = [];
61
-
62
- for (const candidate of candidates) {
63
- const name = typeof candidate === "string" ? candidate : candidate.name;
64
- if (name.startsWith(line)) {
65
- prefixMatches.push(candidate);
66
- } else if (
67
- query.length > 0 &&
68
- name.slice(queryStartIndex).includes(query)
69
- ) {
70
- partialMatches.push(candidate);
71
- }
72
- }
73
-
74
- return [...prefixMatches, ...partialMatches];
75
- }
76
-
77
- /**
78
- * Return the longest common prefix of the given strings.
79
- * @param {string[]} strings
80
- * @returns {string}
81
- */
82
- function commonPrefix(strings) {
83
- if (strings.length === 0) return "";
84
- let prefix = strings[0];
85
- for (let i = 1; i < strings.length; i++) {
86
- while (!strings[i].startsWith(prefix)) {
87
- prefix = prefix.slice(0, -1);
88
- }
89
- }
90
- return prefix;
91
- }
92
-
93
- /**
94
- * Display completion candidates and invoke the readline callback.
95
- *
96
- * Node.js readline normally requires two consecutive Tab presses to show the
97
- * candidate list. This helper lets readline handle the common-prefix
98
- * auto-completion first, then prints the candidate list on the next tick and
99
- * redraws the prompt so the display stays clean.
100
- *
101
- * @param {import("node:readline").Interface} rl
102
- * @param {(string | CompletionCandidate)[]} candidates
103
- * @param {string} line
104
- * @param {(err: Error | null, result: [string[], string]) => void} callback
105
- */
106
- function showCompletions(rl, candidates, line, callback) {
107
- const names = candidates.map((c) => (typeof c === "string" ? c : c.name));
108
- if (candidates.length <= 1) {
109
- callback(null, [names, line]);
110
- return;
111
- }
112
- const prefix = commonPrefix(names);
113
- if (prefix.length > line.length) {
114
- // Let readline insert the common prefix.
115
- callback(null, [[prefix], line]);
116
- } else {
117
- // Nothing new to insert.
118
- callback(null, [[], line]);
119
- }
120
- // After readline finishes its own refresh, print the candidate list and
121
- // redraw the prompt line. We cannot use rl.prompt(true) because its
122
- // internal _refreshLine clears everything below the prompt start, which
123
- // erases the candidate list we just wrote. Instead we manually re-output
124
- // the prompt and current line content.
125
- setTimeout(() => {
126
- const maxLength = process.stdout.columns ?? 100;
127
- const list = candidates
128
- .map((c) => {
129
- if (typeof c === "string") return c;
130
- const nameText = c.name.padEnd(25);
131
- const separator = " - ";
132
- const descText = c.description;
133
-
134
- // 画面幅に合わせて説明文をカット(色を付ける前に計算)
135
- const availableWidth =
136
- maxLength - nameText.length - separator.length - 3;
137
- const displayDesc =
138
- descText.length > availableWidth && availableWidth > 0
139
- ? `${descText.slice(0, availableWidth)}...`
140
- : descText;
141
-
142
- const name = styleText("cyan", nameText);
143
- const description = styleText("dim", displayDesc);
144
- return `${name}${separator}${description}`;
145
- })
146
- .join("\r\n");
147
- process.stdout.write(`\r\n${list}\r\n`);
148
- process.stdout.write(`${rl.getPrompt()}${rl.line}`);
149
- }, 0);
150
- }
151
-
152
- /**
153
- * Create a completer function for readline.
154
- *
155
- * Because the readline.Interface instance (`cli`) is not available until after
156
- * `readline.createInterface` returns, we accept a getter function so the
157
- * completer can resolve the reference lazily at call time.
158
- *
159
- * @param {() => import("node:readline").Interface} getCliRef - A function that returns the readline Interface
160
- * @param {ClaudeCodePlugin[] | undefined} claudeCodePlugins
161
- * @returns {(line: string, callback: (err?: Error | null, result?: [string[], string]) => void) => void}
162
- */
163
- export function createCompleter(getCliRef, claudeCodePlugins) {
164
- return (line, callback) => {
165
- (async () => {
166
- try {
167
- const cli = getCliRef();
168
- const prompts = await loadPrompts(claudeCodePlugins);
169
- const agentRoles = await loadAgentRoles(claudeCodePlugins);
170
-
171
- if (line.startsWith("/agents:")) {
172
- const prefix = "/agents:";
173
- const candidates = Array.from(agentRoles.values()).map((a) => ({
174
- name: `${prefix}${a.id}`,
175
- description: a.description,
176
- }));
177
- const hits = findMatches(candidates, line, prefix.length);
178
-
179
- showCompletions(cli, hits, line, callback);
180
- return;
181
- }
182
-
183
- if (line.startsWith("/prompts:")) {
184
- const prefix = "/prompts:";
185
- const candidates = Array.from(prompts.values()).map((p) => ({
186
- name: `${prefix}${p.id}`,
187
- description: p.description,
188
- }));
189
- const hits = findMatches(candidates, line, prefix.length);
190
-
191
- showCompletions(cli, hits, line, callback);
192
- return;
193
- }
194
-
195
- if (line.startsWith("/")) {
196
- const shortcuts = Array.from(prompts.values())
197
- .filter((p) => p.isShortcut)
198
- .map((p) => ({
199
- name: `/${p.id}`,
200
- description: p.description,
201
- }));
202
-
203
- const allCommands = [...SLASH_COMMANDS, ...shortcuts].filter(
204
- (cmd) => {
205
- const name = typeof cmd === "string" ? cmd : cmd.name;
206
- return (
207
- name !== "/<id>" &&
208
- (name === "/agents:" || !name.startsWith("/agents:")) &&
209
- (name === "/prompts:" || !name.startsWith("/prompts:"))
210
- );
211
- },
212
- );
213
-
214
- const hits = findMatches(allCommands, line, 1);
215
-
216
- showCompletions(cli, hits, line, callback);
217
- return;
218
- }
219
-
220
- callback(null, [[], line]);
221
- } catch (err) {
222
- const error = err instanceof Error ? err : new Error(String(err));
223
- callback(error, [[], line]);
224
- }
225
- })();
226
- };
227
- }