@iinm/plain-agent 1.0.8 → 1.1.1
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/.config/config.predefined.json +15 -0
- package/README.md +10 -1
- package/package.json +1 -1
- package/src/cliArgs.mjs +23 -5
- package/src/cliBatch.mjs +115 -0
- package/src/config.mjs +28 -10
- package/src/main.mjs +57 -29
|
@@ -701,6 +701,21 @@
|
|
|
701
701
|
}
|
|
702
702
|
}
|
|
703
703
|
},
|
|
704
|
+
{
|
|
705
|
+
"name": "glm-5",
|
|
706
|
+
"variant": "bedrock",
|
|
707
|
+
"platform": {
|
|
708
|
+
"name": "bedrock",
|
|
709
|
+
"variant": "default"
|
|
710
|
+
},
|
|
711
|
+
"model": {
|
|
712
|
+
"format": "openai-messages",
|
|
713
|
+
"config": {
|
|
714
|
+
"model": "zai.glm-5",
|
|
715
|
+
"reasoning_effort": "high"
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
},
|
|
704
719
|
{
|
|
705
720
|
"name": "glm-5",
|
|
706
721
|
"variant": "ollama",
|
package/README.md
CHANGED
|
@@ -141,7 +141,16 @@ Run the agent.
|
|
|
141
141
|
plain
|
|
142
142
|
|
|
143
143
|
# Or
|
|
144
|
-
plain -m <model
|
|
144
|
+
plain -m <model+variant>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Run in batch mode (non-interactive).
|
|
148
|
+
In batch mode, config files are not loaded automatically. Only the files specified with `--config` are loaded.
|
|
149
|
+
|
|
150
|
+
```sh
|
|
151
|
+
plain --batch "Add tests for src/main.mjs" \
|
|
152
|
+
--config ~/.config/plain-agent/config.local.json \
|
|
153
|
+
--config .plain-agent/config.json
|
|
145
154
|
```
|
|
146
155
|
|
|
147
156
|
Display the help message.
|
package/package.json
CHANGED
package/src/cliArgs.mjs
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* @property {string|null} model - Model name with variant
|
|
4
4
|
* @property {boolean} showHelp - Whether to show help message
|
|
5
5
|
* @property {boolean} listModels - Whether to list available models
|
|
6
|
+
* @property {string|null} batch - Task instruction for batch mode
|
|
7
|
+
* @property {string[]} config - Paths to additional config files for batch mode
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
/**
|
|
@@ -13,15 +15,25 @@
|
|
|
13
15
|
export function parseCliArgs(argv) {
|
|
14
16
|
const args = argv.slice(2);
|
|
15
17
|
/** @type {CliArgs} */
|
|
16
|
-
const result = {
|
|
18
|
+
const result = {
|
|
19
|
+
model: null,
|
|
20
|
+
showHelp: false,
|
|
21
|
+
listModels: false,
|
|
22
|
+
batch: null,
|
|
23
|
+
config: [],
|
|
24
|
+
};
|
|
17
25
|
|
|
18
26
|
for (let i = 0; i < args.length; i++) {
|
|
19
|
-
if (args[i] === "-m" && args[i + 1]) {
|
|
27
|
+
if ((args[i] === "-m" || args[i] === "--model") && args[i + 1]) {
|
|
20
28
|
result.model = args[++i];
|
|
21
29
|
} else if (args[i] === "-h" || args[i] === "--help") {
|
|
22
30
|
result.showHelp = true;
|
|
23
31
|
} else if (args[i] === "-l" || args[i] === "--list-models") {
|
|
24
32
|
result.listModels = true;
|
|
33
|
+
} else if (args[i] === "--batch" && args[i + 1]) {
|
|
34
|
+
result.batch = args[++i];
|
|
35
|
+
} else if (args[i] === "--config" && args[i + 1]) {
|
|
36
|
+
result.config.push(args[++i]);
|
|
25
37
|
}
|
|
26
38
|
}
|
|
27
39
|
|
|
@@ -35,15 +47,21 @@ export function parseCliArgs(argv) {
|
|
|
35
47
|
export function printHelp(exitCode = 0) {
|
|
36
48
|
console.log(`
|
|
37
49
|
Usage: agent [options]
|
|
50
|
+
agent --batch "task instruction" [options]
|
|
38
51
|
|
|
39
52
|
Options:
|
|
40
|
-
-m <model+variant> Model to use
|
|
41
|
-
-l, --list-models
|
|
53
|
+
-m, --model <model+variant> Model to use
|
|
54
|
+
-l, --list-models List available models
|
|
42
55
|
-h, --help Show this help message
|
|
56
|
+
--batch <task> Run in batch mode with the given task instruction
|
|
57
|
+
--config <file> Config file to load (required in batch mode)
|
|
58
|
+
In batch mode, only explicitly specified config files are loaded
|
|
43
59
|
|
|
44
60
|
Examples:
|
|
45
|
-
agent -m claude-sonnet-4-6+thinking-16k
|
|
46
61
|
agent -m gpt-5.4+thinking-medium
|
|
62
|
+
plain --batch "Add tests for src/main.mjs" \\
|
|
63
|
+
--config ~/.config/plain-agent/config.local.json \\
|
|
64
|
+
--config .plain-agent/config.json
|
|
47
65
|
`);
|
|
48
66
|
process.exit(exitCode);
|
|
49
67
|
}
|
package/src/cliBatch.mjs
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { UserEventEmitter, AgentEventEmitter } from "./agent"
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} BatchSessionOptions
|
|
7
|
+
* @property {UserEventEmitter} userEventEmitter
|
|
8
|
+
* @property {AgentEventEmitter} agentEventEmitter
|
|
9
|
+
* @property {string} task - Task instruction to execute
|
|
10
|
+
* @property {string} sessionId
|
|
11
|
+
* @property {string} modelName
|
|
12
|
+
* @property {boolean} sandbox
|
|
13
|
+
* @property {() => Promise<void>} onStop
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Start a batch session and execute the task.
|
|
18
|
+
* Events are output as JSON Lines (1 line = 1 JSON object).
|
|
19
|
+
*
|
|
20
|
+
* @param {BatchSessionOptions} options
|
|
21
|
+
* @returns {Promise<void>}
|
|
22
|
+
*/
|
|
23
|
+
export async function startBatchSession({
|
|
24
|
+
userEventEmitter,
|
|
25
|
+
agentEventEmitter,
|
|
26
|
+
task,
|
|
27
|
+
sessionId,
|
|
28
|
+
modelName,
|
|
29
|
+
sandbox,
|
|
30
|
+
onStop,
|
|
31
|
+
}) {
|
|
32
|
+
setupEventHandlers(agentEventEmitter, { sessionId, modelName, sandbox });
|
|
33
|
+
|
|
34
|
+
userEventEmitter.emit("userInput", [{ type: "text", text: task }]);
|
|
35
|
+
|
|
36
|
+
await new Promise((/** @type {(value?: void) => void} */ resolve) => {
|
|
37
|
+
agentEventEmitter.on("turnEnd", async () => {
|
|
38
|
+
outputEvent({
|
|
39
|
+
type: "session_end",
|
|
40
|
+
timestamp: new Date().toISOString(),
|
|
41
|
+
});
|
|
42
|
+
await onStop();
|
|
43
|
+
resolve();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Setup event handlers for batch mode.
|
|
52
|
+
* Output events as JSON Lines.
|
|
53
|
+
*
|
|
54
|
+
* @param {AgentEventEmitter} agentEventEmitter
|
|
55
|
+
* @param {{ sessionId: string, modelName: string, sandbox: boolean }} meta
|
|
56
|
+
*/
|
|
57
|
+
function setupEventHandlers(
|
|
58
|
+
agentEventEmitter,
|
|
59
|
+
{ sessionId, modelName, sandbox },
|
|
60
|
+
) {
|
|
61
|
+
outputEvent({
|
|
62
|
+
type: "session_start",
|
|
63
|
+
sessionId,
|
|
64
|
+
modelName,
|
|
65
|
+
sandbox,
|
|
66
|
+
timestamp: new Date().toISOString(),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
agentEventEmitter.on("message", (message) => {
|
|
70
|
+
outputEvent({
|
|
71
|
+
type: "message",
|
|
72
|
+
message,
|
|
73
|
+
timestamp: new Date().toISOString(),
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
agentEventEmitter.on("error", (error) => {
|
|
78
|
+
outputEvent({
|
|
79
|
+
type: "error",
|
|
80
|
+
error: {
|
|
81
|
+
message: error.message,
|
|
82
|
+
stack: error.stack,
|
|
83
|
+
},
|
|
84
|
+
timestamp: new Date().toISOString(),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
process.exit(1);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
agentEventEmitter.on("subagentSwitched", (subagent) => {
|
|
91
|
+
outputEvent({
|
|
92
|
+
type: "subagent_switched",
|
|
93
|
+
subagent,
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
agentEventEmitter.on("providerTokenUsage", (usage) => {
|
|
99
|
+
outputEvent({
|
|
100
|
+
type: "token_usage",
|
|
101
|
+
usage,
|
|
102
|
+
timestamp: new Date().toISOString(),
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Output an event as JSON Lines format.
|
|
109
|
+
* Each event is a single line of JSON.
|
|
110
|
+
*
|
|
111
|
+
* @param {object} event
|
|
112
|
+
*/
|
|
113
|
+
function outputEvent(event) {
|
|
114
|
+
console.log(JSON.stringify(event));
|
|
115
|
+
}
|
package/src/config.mjs
CHANGED
|
@@ -16,19 +16,37 @@ import {
|
|
|
16
16
|
import { evalJSONConfig } from "./utils/evalJSONConfig.mjs";
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* @
|
|
20
|
-
* @
|
|
19
|
+
* @typedef {Object} LoadAppConfigOptions
|
|
20
|
+
* @property {boolean} [skipTrustCheck] - Skip trust check for config files
|
|
21
|
+
* @property {string[]} [configFiles] - Additional config files to load (for batch mode)
|
|
22
|
+
* @property {boolean} [skipUserConfig] - Skip default user config files (for batch mode)
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param {LoadAppConfigOptions} [options]
|
|
21
27
|
* @returns {Promise<{appConfig: AppConfig, loadedConfigPath: string[]}>}
|
|
22
28
|
*/
|
|
23
29
|
export async function loadAppConfig(options = {}) {
|
|
24
|
-
const {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
];
|
|
30
|
+
const {
|
|
31
|
+
skipTrustCheck = false,
|
|
32
|
+
configFiles = [],
|
|
33
|
+
skipUserConfig = false,
|
|
34
|
+
} = options;
|
|
35
|
+
|
|
36
|
+
// Always load predefined config
|
|
37
|
+
const paths = [`${AGENT_ROOT}/.config/config.predefined.json`];
|
|
38
|
+
|
|
39
|
+
if (!skipUserConfig) {
|
|
40
|
+
paths.push(
|
|
41
|
+
`${AGENT_USER_CONFIG_DIR}/config.json`,
|
|
42
|
+
`${AGENT_USER_CONFIG_DIR}/config.local.json`,
|
|
43
|
+
`${AGENT_PROJECT_METADATA_DIR}/config.json`,
|
|
44
|
+
`${AGENT_PROJECT_METADATA_DIR}/config.local.json`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Add explicitly specified config files
|
|
49
|
+
paths.push(...configFiles);
|
|
32
50
|
|
|
33
51
|
/** @type {string[]} */
|
|
34
52
|
const loadedConfigPath = [];
|
package/src/main.mjs
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { styleText } from "node:util";
|
|
6
6
|
import { createAgent } from "./agent.mjs";
|
|
7
7
|
import { parseCliArgs, printHelp } from "./cliArgs.mjs";
|
|
8
|
+
import { startBatchSession } from "./cliBatch.mjs";
|
|
8
9
|
import { startInteractiveSession } from "./cliInteractive.mjs";
|
|
9
10
|
import { loadAppConfig } from "./config.mjs";
|
|
10
11
|
import { loadAgentRoles } from "./context/loadAgentRoles.mjs";
|
|
@@ -56,22 +57,31 @@ if (cliArgs.listModels) {
|
|
|
56
57
|
`0${startTime.getMinutes()}`.slice(-2),
|
|
57
58
|
].join("-");
|
|
58
59
|
const tmuxSessionId = `agent-${sessionId}`;
|
|
59
|
-
const
|
|
60
|
+
const isBatchMode = Boolean(cliArgs.batch);
|
|
60
61
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
const { appConfig, loadedConfigPath } = await loadAppConfig({
|
|
63
|
+
skipUserConfig: isBatchMode,
|
|
64
|
+
skipTrustCheck: isBatchMode,
|
|
65
|
+
configFiles: cliArgs.config,
|
|
66
|
+
});
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
// In batch mode, skip human-readable output
|
|
69
|
+
if (!isBatchMode) {
|
|
70
|
+
if (loadedConfigPath.length > 0) {
|
|
71
|
+
console.log(styleText("green", "\n⚡ Loaded configuration files"));
|
|
72
|
+
console.log(loadedConfigPath.map((p) => ` ⤷ ${p}`).join("\n"));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (appConfig.sandbox) {
|
|
76
|
+
const sandboxStr = [
|
|
77
|
+
appConfig.sandbox.command,
|
|
78
|
+
...(appConfig.sandbox.args || []),
|
|
79
|
+
].join(" ");
|
|
80
|
+
console.log(styleText("green", "\n📦 Sandbox: on"));
|
|
81
|
+
console.log(` ⤷ ${sandboxStr}`);
|
|
82
|
+
} else {
|
|
83
|
+
console.log(styleText("yellow", "\n📦 Sandbox: off"));
|
|
84
|
+
}
|
|
75
85
|
}
|
|
76
86
|
|
|
77
87
|
/** @type {(() => Promise<void>)[]} */
|
|
@@ -82,11 +92,13 @@ if (cliArgs.listModels) {
|
|
|
82
92
|
if (appConfig.mcpServers) {
|
|
83
93
|
const mcpServerEntries = Object.entries(appConfig.mcpServers);
|
|
84
94
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
95
|
+
if (!isBatchMode) {
|
|
96
|
+
console.log();
|
|
97
|
+
for (const [serverName] of mcpServerEntries) {
|
|
98
|
+
console.log(
|
|
99
|
+
styleText("blue", `🔌 Connecting to MCP server: ${serverName}...`),
|
|
100
|
+
);
|
|
101
|
+
}
|
|
90
102
|
}
|
|
91
103
|
|
|
92
104
|
const mcpResults = await Promise.all(
|
|
@@ -99,12 +111,14 @@ if (cliArgs.listModels) {
|
|
|
99
111
|
for (const { serverName, tools, cleanup } of mcpResults) {
|
|
100
112
|
mcpTools.push(...tools);
|
|
101
113
|
mcpCleanups.push(cleanup);
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
if (!isBatchMode) {
|
|
115
|
+
console.log(
|
|
116
|
+
styleText(
|
|
117
|
+
"green",
|
|
118
|
+
`✅ Successfully connected to MCP server: ${serverName}`,
|
|
119
|
+
),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
|
|
@@ -196,21 +210,35 @@ if (cliArgs.listModels) {
|
|
|
196
210
|
agentRoles,
|
|
197
211
|
});
|
|
198
212
|
|
|
199
|
-
|
|
213
|
+
const sessionOptions = {
|
|
200
214
|
userEventEmitter,
|
|
201
215
|
agentEventEmitter,
|
|
202
216
|
agentCommands,
|
|
203
217
|
sessionId,
|
|
204
218
|
modelName: modelNameWithVariant,
|
|
205
|
-
notifyCmd: appConfig.notifyCmd || AGENT_NOTIFY_CMD_DEFAULT,
|
|
206
219
|
sandbox: Boolean(appConfig.sandbox),
|
|
207
220
|
onStop: async () => {
|
|
208
221
|
for (const cleanup of mcpCleanups) {
|
|
209
222
|
await cleanup();
|
|
210
223
|
}
|
|
211
224
|
},
|
|
212
|
-
|
|
213
|
-
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
if (isBatchMode) {
|
|
228
|
+
if (!cliArgs.batch) {
|
|
229
|
+
throw new Error("Batch task is required in batch mode");
|
|
230
|
+
}
|
|
231
|
+
await startBatchSession({
|
|
232
|
+
...sessionOptions,
|
|
233
|
+
task: cliArgs.batch,
|
|
234
|
+
});
|
|
235
|
+
} else {
|
|
236
|
+
startInteractiveSession({
|
|
237
|
+
...sessionOptions,
|
|
238
|
+
notifyCmd: appConfig.notifyCmd || AGENT_NOTIFY_CMD_DEFAULT,
|
|
239
|
+
claudeCodePlugins: appConfig.claudeCodePlugins,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
214
242
|
})().catch((err) => {
|
|
215
243
|
console.error(err);
|
|
216
244
|
process.exit(1);
|