@iinm/plain-agent 1.0.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.
Files changed (79) hide show
  1. package/.config/agents.library/code-simplifier.md +5 -0
  2. package/.config/agents.library/qa-engineer.md +74 -0
  3. package/.config/agents.library/software-architect.md +278 -0
  4. package/.config/agents.predefined/worker.md +3 -0
  5. package/.config/config.predefined.json +825 -0
  6. package/.config/prompts.library/code-review.md +8 -0
  7. package/.config/prompts.library/feature-dev.md +6 -0
  8. package/.config/prompts.predefined/shortcuts/commit-by-user.md +9 -0
  9. package/.config/prompts.predefined/shortcuts/commit.md +10 -0
  10. package/.config/prompts.predefined/shortcuts/general-question.md +6 -0
  11. package/LICENSE +21 -0
  12. package/README.md +624 -0
  13. package/bin/plain +3 -0
  14. package/bin/plain-interrupt +6 -0
  15. package/bin/plain-notify-desktop +19 -0
  16. package/bin/plain-notify-terminal-bell +3 -0
  17. package/package.json +57 -0
  18. package/sandbox/bin/plain-sandbox +972 -0
  19. package/src/agent.d.ts +48 -0
  20. package/src/agent.mjs +159 -0
  21. package/src/agentLoop.mjs +369 -0
  22. package/src/agentState.mjs +41 -0
  23. package/src/cliArgs.mjs +45 -0
  24. package/src/cliFormatter.mjs +217 -0
  25. package/src/cliInteractive.mjs +739 -0
  26. package/src/config.d.ts +48 -0
  27. package/src/config.mjs +168 -0
  28. package/src/context/consumeInterruptMessage.mjs +30 -0
  29. package/src/context/loadAgentRoles.mjs +272 -0
  30. package/src/context/loadPrompts.mjs +312 -0
  31. package/src/context/loadUserMessageContext.mjs +147 -0
  32. package/src/env.mjs +46 -0
  33. package/src/main.mjs +202 -0
  34. package/src/mcp.mjs +202 -0
  35. package/src/model.d.ts +109 -0
  36. package/src/modelCaller.mjs +29 -0
  37. package/src/modelDefinition.d.ts +73 -0
  38. package/src/prompt.mjs +128 -0
  39. package/src/providers/anthropic.d.ts +248 -0
  40. package/src/providers/anthropic.mjs +596 -0
  41. package/src/providers/gemini.d.ts +208 -0
  42. package/src/providers/gemini.mjs +752 -0
  43. package/src/providers/openai.d.ts +281 -0
  44. package/src/providers/openai.mjs +551 -0
  45. package/src/providers/openaiCompatible.d.ts +147 -0
  46. package/src/providers/openaiCompatible.mjs +658 -0
  47. package/src/providers/platform/azure.mjs +42 -0
  48. package/src/providers/platform/bedrock.mjs +74 -0
  49. package/src/providers/platform/googleCloud.mjs +34 -0
  50. package/src/subagent.mjs +247 -0
  51. package/src/tmpfile.mjs +27 -0
  52. package/src/tool.d.ts +74 -0
  53. package/src/toolExecutor.mjs +236 -0
  54. package/src/toolInputValidator.mjs +183 -0
  55. package/src/toolUseApprover.mjs +98 -0
  56. package/src/tools/askGoogle.mjs +135 -0
  57. package/src/tools/delegateToSubagent.d.ts +4 -0
  58. package/src/tools/delegateToSubagent.mjs +48 -0
  59. package/src/tools/execCommand.d.ts +22 -0
  60. package/src/tools/execCommand.mjs +200 -0
  61. package/src/tools/fetchWebPage.mjs +96 -0
  62. package/src/tools/patchFile.d.ts +4 -0
  63. package/src/tools/patchFile.mjs +96 -0
  64. package/src/tools/reportAsSubagent.d.ts +3 -0
  65. package/src/tools/reportAsSubagent.mjs +44 -0
  66. package/src/tools/tavilySearch.d.ts +6 -0
  67. package/src/tools/tavilySearch.mjs +57 -0
  68. package/src/tools/tmuxCommand.d.ts +14 -0
  69. package/src/tools/tmuxCommand.mjs +194 -0
  70. package/src/tools/writeFile.d.ts +4 -0
  71. package/src/tools/writeFile.mjs +56 -0
  72. package/src/utils/evalJSONConfig.mjs +48 -0
  73. package/src/utils/matchValue.d.ts +6 -0
  74. package/src/utils/matchValue.mjs +40 -0
  75. package/src/utils/noThrow.mjs +31 -0
  76. package/src/utils/notify.mjs +28 -0
  77. package/src/utils/parseFileRange.mjs +18 -0
  78. package/src/utils/readFileRange.mjs +33 -0
  79. package/src/utils/retryOnError.mjs +41 -0
@@ -0,0 +1,217 @@
1
+ /**
2
+ * @import { MessageContentToolUse, MessageContentToolResult, ProviderTokenUsage } from "./model"
3
+ * @import { ExecCommandInput } from "./tools/execCommand"
4
+ * @import { PatchFileInput } from "./tools/patchFile"
5
+ * @import { WriteFileInput } from "./tools/writeFile"
6
+ * @import { TmuxCommandInput } from "./tools/tmuxCommand"
7
+ * @import { TavilySearchInput } from "./tools/tavilySearch"
8
+ * @import { DelegateToSubagentInput } from "./tools/delegateToSubagent"
9
+ */
10
+
11
+ import { styleText } from "node:util";
12
+ import { createPatch } from "diff";
13
+
14
+ /**
15
+ * Format tool use for display.
16
+ * @param {MessageContentToolUse} toolUse
17
+ * @returns {string}
18
+ */
19
+ export function formatToolUse(toolUse) {
20
+ const { toolName, input } = toolUse;
21
+
22
+ if (toolName === "exec_command") {
23
+ /** @type {Partial<ExecCommandInput>} */
24
+ const execCommandInput = input;
25
+ return [
26
+ `tool: ${toolName}`,
27
+ `command: ${JSON.stringify(execCommandInput.command)}`,
28
+ `args: ${JSON.stringify(execCommandInput.args)}`,
29
+ ].join("\n");
30
+ }
31
+
32
+ if (toolName === "write_file") {
33
+ /** @type {Partial<WriteFileInput>} */
34
+ const writeFileInput = input;
35
+ return [
36
+ `tool: ${toolName}`,
37
+ `filePath: ${writeFileInput.filePath}`,
38
+ `content:\n${writeFileInput.content}`,
39
+ ].join("\n");
40
+ }
41
+
42
+ if (toolName === "patch_file") {
43
+ /** @type {Partial<PatchFileInput>} */
44
+ const patchFileInput = input;
45
+ const diff = patchFileInput.diff || "";
46
+
47
+ /** @type {{search:string; replace:string}[]} */
48
+ const diffs = [];
49
+ const matches = Array.from(
50
+ diff.matchAll(
51
+ /<<<<<<< SEARCH\n(.*?)\n?=======\n(.*?)\n?>>>>>>> REPLACE/gs,
52
+ ),
53
+ );
54
+ for (const match of matches) {
55
+ const [_, search, replace] = match;
56
+ diffs.push({ search, replace });
57
+ }
58
+
59
+ const highlightedDiff = diffs
60
+ .map(
61
+ ({ search, replace }) =>
62
+ `${createPatch(patchFileInput.filePath || "", search, replace)
63
+ .replace(/^-.+$/gm, (match) => styleText("red", match))
64
+ .replace(/^\+.+$/gm, (match) => styleText("green", match))
65
+ .replace(/^@@.+$/gm, (match) => styleText("gray", match))
66
+ .replace(/^\$/gm, (match) =>
67
+ styleText("gray", match),
68
+ )}\n-------\n${replace}`,
69
+ )
70
+ .join("\n\n");
71
+
72
+ return [
73
+ `tool: ${toolName}`,
74
+ `path: ${patchFileInput.filePath}`,
75
+ `diff:\n${highlightedDiff}`,
76
+ ].join("\n");
77
+ }
78
+
79
+ if (toolName === "tmux_command") {
80
+ /** @type {Partial<TmuxCommandInput>} */
81
+ const tmuxCommandInput = input;
82
+ return [
83
+ `tool: ${toolName}`,
84
+ `command: ${tmuxCommandInput.command}`,
85
+ `args: ${JSON.stringify(tmuxCommandInput.args)}`,
86
+ ].join("\n");
87
+ }
88
+
89
+ if (toolName === "search_web") {
90
+ /** @type {Partial<TavilySearchInput>} */
91
+ const tavilySearchInput = input;
92
+ return [`tool: ${toolName}`, `query: ${tavilySearchInput.query}`].join(
93
+ "\n",
94
+ );
95
+ }
96
+
97
+ if (toolName === "delegate_to_subagent") {
98
+ /** @type {Partial<DelegateToSubagentInput>} */
99
+ const delegateInput = input;
100
+ return [
101
+ `tool: ${toolName}`,
102
+ `name: ${delegateInput.name}`,
103
+ `goal: ${delegateInput.goal}`,
104
+ ].join("\n");
105
+ }
106
+
107
+ const { provider: _, ...filteredToolUse } = toolUse;
108
+
109
+ return JSON.stringify(filteredToolUse, null, 2);
110
+ }
111
+
112
+ /** Maximum length of output to display */
113
+ const MAX_DISPLAY_OUTPUT_LENGTH = 1024;
114
+
115
+ /**
116
+ * Format tool result for display.
117
+ * @param {MessageContentToolResult} toolResult
118
+ * @returns {string}
119
+ */
120
+ export function formatToolResult(toolResult) {
121
+ const { content, isError } = toolResult;
122
+
123
+ /** @type {string[]} */
124
+ const contentStringParts = [];
125
+ for (const part of content) {
126
+ switch (part.type) {
127
+ case "text":
128
+ contentStringParts.push(part.text);
129
+ break;
130
+ case "image":
131
+ contentStringParts.push(
132
+ `data:${part.mimeType};base64,${part.data.slice(0, 20)}...`,
133
+ );
134
+ break;
135
+ default:
136
+ console.log(`Unsupported content part: ${JSON.stringify(part)}`);
137
+ break;
138
+ }
139
+ }
140
+
141
+ const contentString = contentStringParts.join("\n\n");
142
+
143
+ if (isError) {
144
+ return styleText("red", contentString);
145
+ }
146
+
147
+ if (toolResult.toolName === "exec_command") {
148
+ return contentString
149
+ .replace(/(^<stdout>|<\/stdout>$)/gm, styleText("blue", "$1"))
150
+ .replace(
151
+ /(<truncated_output.+?>|<\/truncated_output>)/g,
152
+ styleText("yellow", "$1"),
153
+ )
154
+ .replace(/(^<stderr>|<\/stderr>$)/gm, styleText("magenta", "$1"))
155
+ .replace(/(^<error>|<\/error>$)/gm, styleText("red", "$1"));
156
+ }
157
+
158
+ if (toolResult.toolName === "tmux_command") {
159
+ return contentString
160
+ .replace(/(^<stdout>|<\/stdout>$)/gm, styleText("blue", "$1"))
161
+ .replace(/(^<stderr>|<\/stderr>$)/gm, styleText("magenta", "$1"))
162
+ .replace(/(^<error>|<\/error>$)/gm, styleText("red", "$1"))
163
+ .replace(/(^<tmux:.*?>|<\/tmux:.*?>$)/gm, styleText("green", "$1"));
164
+ }
165
+
166
+ if (contentString.length > MAX_DISPLAY_OUTPUT_LENGTH) {
167
+ return [
168
+ contentString.slice(0, MAX_DISPLAY_OUTPUT_LENGTH),
169
+ styleText("yellow", "... (Output truncated for display)"),
170
+ "\n",
171
+ ].join("");
172
+ }
173
+
174
+ return contentString;
175
+ }
176
+
177
+ /**
178
+ * Format provider token usage for display.
179
+ * @param {ProviderTokenUsage} usage
180
+ * @returns {string}
181
+ */
182
+ export function formatProviderTokenUsage(usage) {
183
+ /** @type {string[]} */
184
+ const lines = [];
185
+ /** @type {string[]} */
186
+ const header = [];
187
+ for (const [key, value] of Object.entries(usage)) {
188
+ if (typeof value === "number") {
189
+ header.push(`${key}: ${value}`);
190
+ } else if (typeof value === "string") {
191
+ header.push(`${key}: ${value}`);
192
+ } else if (value) {
193
+ lines.push(
194
+ `(${key}) ${Object.entries(value)
195
+ .filter(
196
+ ([k]) =>
197
+ ![
198
+ // OpenAI
199
+ "audio_tokens",
200
+ "accepted_prediction_tokens",
201
+ "rejected_prediction_tokens",
202
+ ].includes(k),
203
+ )
204
+ .map(([k, v]) => `${k}: ${v}`)
205
+ .join(", ")}`,
206
+ );
207
+ }
208
+ }
209
+
210
+ const outputLines = [`\n${header.join(", ")}`];
211
+
212
+ if (lines.length) {
213
+ outputLines.push(lines.join(" / "));
214
+ }
215
+
216
+ return styleText("gray", outputLines.join("\n"));
217
+ }