@iinm/plain-agent 1.8.4 → 1.8.6
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.
- package/bin/plain +1 -1
- package/package.json +8 -9
- package/sandbox/bin/plain-sandbox +13 -0
- package/src/agent.d.ts +52 -0
- package/src/agent.mjs +204 -0
- package/src/agentLoop.mjs +419 -0
- package/src/agentState.mjs +41 -0
- package/src/claudeCodePlugin.mjs +164 -0
- package/src/cliArgs.mjs +175 -0
- package/src/cliBatch.mjs +147 -0
- package/src/cliCommands.mjs +283 -0
- package/src/cliCompleter.mjs +227 -0
- package/src/cliCost.mjs +309 -0
- package/src/cliFormatter.mjs +518 -0
- package/src/cliInteractive.mjs +533 -0
- package/src/cliInterruptTransform.mjs +51 -0
- package/src/cliMuteTransform.mjs +26 -0
- package/src/cliPasteTransform.mjs +183 -0
- package/src/config.d.ts +36 -0
- package/src/config.mjs +197 -0
- package/src/context/loadAgentRoles.mjs +267 -0
- package/src/context/loadPrompts.mjs +303 -0
- package/src/context/loadUserMessageContext.mjs +147 -0
- package/src/costTracker.mjs +210 -0
- package/src/env.mjs +44 -0
- package/src/main.mjs +281 -0
- package/src/mcpClient.mjs +351 -0
- package/src/mcpIntegration.mjs +160 -0
- package/src/model.d.ts +109 -0
- package/src/modelCaller.mjs +32 -0
- package/src/modelDefinition.d.ts +92 -0
- package/src/prompt.mjs +138 -0
- package/src/providers/anthropic.d.ts +248 -0
- package/src/providers/anthropic.mjs +587 -0
- package/src/providers/bedrock.d.ts +249 -0
- package/src/providers/bedrock.mjs +700 -0
- package/src/providers/gemini.d.ts +208 -0
- package/src/providers/gemini.mjs +754 -0
- package/src/providers/openai.d.ts +281 -0
- package/src/providers/openai.mjs +544 -0
- package/src/providers/openaiCompatible.d.ts +147 -0
- package/src/providers/openaiCompatible.mjs +652 -0
- package/src/providers/platform/awsSigV4.mjs +184 -0
- package/src/providers/platform/azure.mjs +42 -0
- package/src/providers/platform/bedrock.mjs +78 -0
- package/src/providers/platform/googleCloud.mjs +34 -0
- package/src/subagent.mjs +265 -0
- package/src/tmpfile.mjs +27 -0
- package/src/tool.d.ts +74 -0
- package/src/toolExecutor.mjs +236 -0
- package/src/toolInputValidator.mjs +183 -0
- package/src/toolUseApprover.mjs +99 -0
- package/src/tools/askURL.mjs +209 -0
- package/src/tools/askWeb.mjs +208 -0
- package/src/tools/compactContext.d.ts +4 -0
- package/src/tools/compactContext.mjs +87 -0
- package/src/tools/execCommand.d.ts +22 -0
- package/src/tools/execCommand.mjs +200 -0
- package/src/tools/patchFile.d.ts +4 -0
- package/src/tools/patchFile.mjs +133 -0
- package/src/tools/switchToMainAgent.d.ts +3 -0
- package/src/tools/switchToMainAgent.mjs +43 -0
- package/src/tools/switchToSubagent.d.ts +4 -0
- package/src/tools/switchToSubagent.mjs +59 -0
- package/src/tools/tmuxCommand.d.ts +14 -0
- package/src/tools/tmuxCommand.mjs +194 -0
- package/src/tools/writeFile.d.ts +4 -0
- package/src/tools/writeFile.mjs +56 -0
- package/src/usageStore.mjs +167 -0
- package/src/utils/evalJSONConfig.mjs +72 -0
- package/src/utils/matchValue.d.ts +6 -0
- package/src/utils/matchValue.mjs +40 -0
- package/src/utils/noThrow.mjs +31 -0
- package/src/utils/notify.mjs +29 -0
- package/src/utils/parseFileRange.mjs +18 -0
- package/src/utils/parseFrontmatter.mjs +19 -0
- package/src/utils/readFileRange.mjs +33 -0
- package/src/utils/retryOnError.mjs +41 -0
- package/src/voiceInput.mjs +61 -0
- package/src/voiceInputGemini.mjs +105 -0
- package/src/voiceInputOpenAI.mjs +104 -0
- package/src/voiceInputSession.mjs +543 -0
- package/src/voiceToggleKey.mjs +62 -0
- package/dist/main.mjs +0 -473
- package/dist/main.mjs.map +0 -7
package/bin/plain
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iinm/plain-agent",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.6",
|
|
4
4
|
"description": "A lightweight CLI-based coding agent",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -17,27 +17,26 @@
|
|
|
17
17
|
"bin",
|
|
18
18
|
"sandbox/bin",
|
|
19
19
|
"config",
|
|
20
|
-
"
|
|
20
|
+
"src/**/*.mjs",
|
|
21
|
+
"src/**/*.d.ts",
|
|
22
|
+
"!src/**/*.test.mjs",
|
|
23
|
+
"!src/**/*.test.*.mjs",
|
|
24
|
+
"!src/**/*.playground.mjs",
|
|
25
|
+
"!src/**/test/"
|
|
21
26
|
],
|
|
22
27
|
"engines": {
|
|
23
28
|
"node": ">=22"
|
|
24
29
|
},
|
|
25
30
|
"scripts": {
|
|
26
|
-
"build": "esbuild src/main.mjs --bundle --platform=node --target=node22 --outfile=dist/main.mjs --minify --sourcemap --format=esm --banner:js='import { createRequire } from \"node:module\";const require=createRequire(import.meta.url);'",
|
|
27
|
-
"prepublishOnly": "npm run build",
|
|
28
31
|
"check": "npm run lint && tsc && npm run test",
|
|
29
32
|
"test": "node --test",
|
|
30
33
|
"lint": "npx @biomejs/biome check",
|
|
31
34
|
"fix": "npx @biomejs/biome check --fix"
|
|
32
35
|
},
|
|
33
|
-
"dependencies": {
|
|
34
|
-
"diff": "^8.0.4",
|
|
35
|
-
"yaml": "^2.8.3"
|
|
36
|
-
},
|
|
36
|
+
"dependencies": {},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@biomejs/biome": "^2.4.12",
|
|
39
39
|
"@types/node": "^22.19.17",
|
|
40
|
-
"esbuild": "^0.28.0",
|
|
41
40
|
"typescript": "^6.0.2"
|
|
42
41
|
}
|
|
43
42
|
}
|
|
@@ -731,6 +731,14 @@ setup_container_firewall() {
|
|
|
731
731
|
# allow established connections
|
|
732
732
|
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
733
733
|
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
734
|
+
|
|
735
|
+
# allow localhost on IPv6
|
|
736
|
+
ip6tables -A INPUT -i lo -j ACCEPT
|
|
737
|
+
ip6tables -A OUTPUT -o lo -j ACCEPT
|
|
738
|
+
|
|
739
|
+
# allow established connections on IPv6
|
|
740
|
+
ip6tables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
741
|
+
ip6tables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
|
734
742
|
|
|
735
743
|
# allow given addresses
|
|
736
744
|
for address in "${addresses[@]}"; do
|
|
@@ -750,6 +758,11 @@ setup_container_firewall() {
|
|
|
750
758
|
fi
|
|
751
759
|
done
|
|
752
760
|
iptables -A OUTPUT -p tcp -m set --match-set allow_list dst,dst -j ACCEPT
|
|
761
|
+
|
|
762
|
+
# Reject unauthorized TCP connections immediately with RST
|
|
763
|
+
# instead of silently dropping and causing timeout
|
|
764
|
+
iptables -A OUTPUT -p tcp -j REJECT --reject-with tcp-reset
|
|
765
|
+
ip6tables -A OUTPUT -p tcp -j REJECT --reject-with tcp-reset
|
|
753
766
|
}
|
|
754
767
|
|
|
755
768
|
setup_container_user() {
|
package/src/agent.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { EventEmitter } from "node:events";
|
|
2
|
+
import type { AgentRole } from "./context/loadAgentRoles.mjs";
|
|
3
|
+
import type { CostConfig, CostSummary } from "./costTracker.mjs";
|
|
4
|
+
import type {
|
|
5
|
+
CallModel,
|
|
6
|
+
Message,
|
|
7
|
+
MessageContentImage,
|
|
8
|
+
MessageContentText,
|
|
9
|
+
PartialMessageContent,
|
|
10
|
+
ProviderTokenUsage,
|
|
11
|
+
} from "./model";
|
|
12
|
+
import type { Tool, ToolUseApprover } from "./tool";
|
|
13
|
+
|
|
14
|
+
export type Agent = {
|
|
15
|
+
userEventEmitter: UserEventEmitter;
|
|
16
|
+
agentEventEmitter: AgentEventEmitter;
|
|
17
|
+
agentCommands: AgentCommands;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type AgentCommands = {
|
|
21
|
+
dumpMessages: () => Promise<void>;
|
|
22
|
+
loadMessages: () => Promise<void>;
|
|
23
|
+
getCostSummary: () => CostSummary;
|
|
24
|
+
pauseAutoApprove: () => void;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type UserEventMap = {
|
|
28
|
+
userInput: [(MessageContentText | MessageContentImage)[]];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type UserEventEmitter = EventEmitter<UserEventMap>;
|
|
32
|
+
|
|
33
|
+
type AgentEventMap = {
|
|
34
|
+
message: [Message];
|
|
35
|
+
partialMessageContent: [PartialMessageContent];
|
|
36
|
+
error: [Error];
|
|
37
|
+
toolUseRequest: [];
|
|
38
|
+
turnEnd: [];
|
|
39
|
+
providerTokenUsage: [ProviderTokenUsage];
|
|
40
|
+
subagentSwitched: [{ name: string } | null];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type AgentEventEmitter = EventEmitter<AgentEventMap>;
|
|
44
|
+
|
|
45
|
+
export type AgentConfig = {
|
|
46
|
+
callModel: CallModel;
|
|
47
|
+
prompt: string;
|
|
48
|
+
tools: Tool[];
|
|
49
|
+
toolUseApprover: ToolUseApprover;
|
|
50
|
+
agentRoles: Map<string, AgentRole>;
|
|
51
|
+
modelCostConfig?: CostConfig;
|
|
52
|
+
};
|
package/src/agent.mjs
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { Agent, AgentConfig, AgentEventEmitter, UserEventEmitter } from "./agent"
|
|
3
|
+
* @import { Tool, ToolDefinition } from "./tool"
|
|
4
|
+
* @import { CompactContextInput } from "./tools/compactContext"
|
|
5
|
+
* @import { SwitchToSubagentInput } from "./tools/switchToSubagent"
|
|
6
|
+
* @import { SwitchToMainAgentInput } from "./tools/switchToMainAgent"
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { EventEmitter } from "node:events";
|
|
10
|
+
import fs from "node:fs/promises";
|
|
11
|
+
import { createAgentLoop } from "./agentLoop.mjs";
|
|
12
|
+
import { createStateManager } from "./agentState.mjs";
|
|
13
|
+
import { createCostTracker } from "./costTracker.mjs";
|
|
14
|
+
import { MESSAGES_DUMP_FILE_PATH } from "./env.mjs";
|
|
15
|
+
import { createSubagentManager } from "./subagent.mjs";
|
|
16
|
+
import { createToolExecutor } from "./toolExecutor.mjs";
|
|
17
|
+
import {
|
|
18
|
+
compactContextToolName,
|
|
19
|
+
readMemoryForCompaction,
|
|
20
|
+
} from "./tools/compactContext.mjs";
|
|
21
|
+
import { switchToMainAgentToolName } from "./tools/switchToMainAgent.mjs";
|
|
22
|
+
import { switchToSubagentToolName } from "./tools/switchToSubagent.mjs";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @param {AgentConfig} config
|
|
26
|
+
* @returns {Agent}
|
|
27
|
+
*/
|
|
28
|
+
export function createAgent({
|
|
29
|
+
callModel,
|
|
30
|
+
prompt,
|
|
31
|
+
tools,
|
|
32
|
+
toolUseApprover,
|
|
33
|
+
agentRoles,
|
|
34
|
+
modelCostConfig,
|
|
35
|
+
}) {
|
|
36
|
+
/** @type {UserEventEmitter} */
|
|
37
|
+
const userEventEmitter = new EventEmitter();
|
|
38
|
+
/** @type {AgentEventEmitter} */
|
|
39
|
+
const agentEventEmitter = new EventEmitter();
|
|
40
|
+
|
|
41
|
+
const costTracker = createCostTracker(modelCostConfig);
|
|
42
|
+
|
|
43
|
+
agentEventEmitter.on("providerTokenUsage", (usage) => {
|
|
44
|
+
costTracker.recordUsage(usage);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const stateManager = createStateManager(
|
|
48
|
+
[
|
|
49
|
+
{
|
|
50
|
+
role: "system",
|
|
51
|
+
content: [{ type: "text", text: prompt }],
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
{
|
|
55
|
+
onMessagesAppended: (newMessages) => {
|
|
56
|
+
const lastMessage = newMessages.at(-1);
|
|
57
|
+
if (!lastMessage) {
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
agentEventEmitter.emit("message", lastMessage);
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const subagentManager = createSubagentManager(agentRoles, {
|
|
66
|
+
onSubagentSwitched: (subagent) => {
|
|
67
|
+
agentEventEmitter.emit("subagentSwitched", subagent);
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* @param {SwitchToSubagentInput} input
|
|
73
|
+
*/
|
|
74
|
+
const switchToSubagentImpl = async (input) => {
|
|
75
|
+
const result = subagentManager.switchToSubagent(
|
|
76
|
+
input.name,
|
|
77
|
+
input.goal,
|
|
78
|
+
stateManager.getMessages().length - 1,
|
|
79
|
+
);
|
|
80
|
+
if (!result.success) {
|
|
81
|
+
return new Error(result.error);
|
|
82
|
+
}
|
|
83
|
+
return result.value;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @param {SwitchToMainAgentInput} input
|
|
88
|
+
*/
|
|
89
|
+
const switchToMainAgentImpl = async (input) => {
|
|
90
|
+
const result = await subagentManager.switchToMainAgent(input.memoryPath);
|
|
91
|
+
if (!result.success) {
|
|
92
|
+
return new Error(result.error);
|
|
93
|
+
}
|
|
94
|
+
return result.memoryContent;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {Record<string, unknown>} rawInput
|
|
99
|
+
*/
|
|
100
|
+
const compactContextImpl = async (rawInput) => {
|
|
101
|
+
if (subagentManager.isSubagentActive()) {
|
|
102
|
+
return new Error(
|
|
103
|
+
"compact_context cannot be used while running as a subagent. " +
|
|
104
|
+
"Call switch_to_main_agent to return to the main agent first.",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const input = /** @type {CompactContextInput} */ (rawInput);
|
|
108
|
+
return await readMemoryForCompaction(input);
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/** @type {Map<string, Tool>} */
|
|
112
|
+
const toolByName = new Map();
|
|
113
|
+
for (const tool of tools) {
|
|
114
|
+
if (tool.def.name === switchToSubagentToolName && tool.injectImpl) {
|
|
115
|
+
tool.injectImpl(switchToSubagentImpl);
|
|
116
|
+
}
|
|
117
|
+
if (tool.def.name === switchToMainAgentToolName && tool.injectImpl) {
|
|
118
|
+
tool.injectImpl(switchToMainAgentImpl);
|
|
119
|
+
}
|
|
120
|
+
if (tool.def.name === compactContextToolName && tool.injectImpl) {
|
|
121
|
+
tool.injectImpl(compactContextImpl);
|
|
122
|
+
}
|
|
123
|
+
toolByName.set(tool.def.name, tool);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** @type {ToolDefinition[]} */
|
|
127
|
+
const toolDefs = tools.map(({ def }) => def);
|
|
128
|
+
|
|
129
|
+
const toolExecutor = createToolExecutor(toolByName, {
|
|
130
|
+
exclusiveToolNames: [switchToSubagentToolName, switchToMainAgentToolName],
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
async function dumpMessages() {
|
|
134
|
+
const filePath = MESSAGES_DUMP_FILE_PATH;
|
|
135
|
+
try {
|
|
136
|
+
await fs.writeFile(
|
|
137
|
+
filePath,
|
|
138
|
+
JSON.stringify(stateManager.getMessages(), null, 2),
|
|
139
|
+
);
|
|
140
|
+
console.log(`Messages dumped to ${filePath}`);
|
|
141
|
+
} catch (error) {
|
|
142
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
143
|
+
console.error(`Error dumping messages: ${message}`);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function loadMessages() {
|
|
148
|
+
const filePath = MESSAGES_DUMP_FILE_PATH;
|
|
149
|
+
try {
|
|
150
|
+
const data = await fs.readFile(filePath, "utf-8");
|
|
151
|
+
const loadedMessages = JSON.parse(data);
|
|
152
|
+
if (Array.isArray(loadedMessages)) {
|
|
153
|
+
// Keep the system message (index 0) and replace the rest
|
|
154
|
+
stateManager.setMessages([
|
|
155
|
+
stateManager.getMessageAt(0),
|
|
156
|
+
...loadedMessages.slice(1),
|
|
157
|
+
]);
|
|
158
|
+
console.log(`Messages loaded from ${filePath}`);
|
|
159
|
+
} else {
|
|
160
|
+
console.error("Error loading messages: Invalid format in file.");
|
|
161
|
+
}
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error instanceof Error) {
|
|
164
|
+
console.error(`Error loading messages: ${error.message}`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Pause signal: set by Ctrl-C during agent execution, checked after each tool batch completes
|
|
170
|
+
let paused = false;
|
|
171
|
+
/** @type {import("./agentLoop.mjs").PauseSignal} */
|
|
172
|
+
const pauseSignal = {
|
|
173
|
+
isPaused: () => paused,
|
|
174
|
+
reset: () => {
|
|
175
|
+
paused = false;
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const agentLoop = createAgentLoop({
|
|
180
|
+
callModel,
|
|
181
|
+
stateManager,
|
|
182
|
+
toolDefs,
|
|
183
|
+
toolExecutor,
|
|
184
|
+
agentEventEmitter,
|
|
185
|
+
toolUseApprover,
|
|
186
|
+
subagentManager,
|
|
187
|
+
pauseSignal,
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
userEventEmitter.on("userInput", agentLoop.handleUserInput);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
userEventEmitter,
|
|
194
|
+
agentEventEmitter,
|
|
195
|
+
agentCommands: {
|
|
196
|
+
dumpMessages,
|
|
197
|
+
loadMessages,
|
|
198
|
+
getCostSummary: () => costTracker.calculateCost(),
|
|
199
|
+
pauseAutoApprove: () => {
|
|
200
|
+
paused = true;
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
}
|