chainlesschain 0.37.9 → 0.37.10
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/README.md +309 -19
- package/bin/chainlesschain.js +4 -0
- package/package.json +1 -1
- package/src/commands/audit.js +286 -0
- package/src/commands/auth.js +387 -0
- package/src/commands/browse.js +184 -0
- package/src/commands/did.js +376 -0
- package/src/commands/encrypt.js +233 -0
- package/src/commands/export.js +125 -0
- package/src/commands/git.js +215 -0
- package/src/commands/import.js +259 -0
- package/src/commands/instinct.js +202 -0
- package/src/commands/llm.js +155 -4
- package/src/commands/mcp.js +302 -0
- package/src/commands/memory.js +282 -0
- package/src/commands/note.js +187 -0
- package/src/commands/org.js +505 -0
- package/src/commands/p2p.js +274 -0
- package/src/commands/plugin.js +398 -0
- package/src/commands/search.js +237 -0
- package/src/commands/session.js +238 -0
- package/src/commands/sync.js +249 -0
- package/src/commands/tokens.js +214 -0
- package/src/commands/wallet.js +416 -0
- package/src/index.js +49 -1
- package/src/lib/audit-logger.js +364 -0
- package/src/lib/bm25-search.js +322 -0
- package/src/lib/browser-automation.js +216 -0
- package/src/lib/crypto-manager.js +246 -0
- package/src/lib/did-manager.js +270 -0
- package/src/lib/ensure-utf8.js +59 -0
- package/src/lib/git-integration.js +220 -0
- package/src/lib/instinct-manager.js +190 -0
- package/src/lib/knowledge-exporter.js +302 -0
- package/src/lib/knowledge-importer.js +293 -0
- package/src/lib/llm-providers.js +325 -0
- package/src/lib/mcp-client.js +413 -0
- package/src/lib/memory-manager.js +211 -0
- package/src/lib/note-versioning.js +244 -0
- package/src/lib/org-manager.js +424 -0
- package/src/lib/p2p-manager.js +317 -0
- package/src/lib/pdf-parser.js +96 -0
- package/src/lib/permission-engine.js +374 -0
- package/src/lib/plan-mode.js +333 -0
- package/src/lib/plugin-manager.js +312 -0
- package/src/lib/response-cache.js +156 -0
- package/src/lib/session-manager.js +189 -0
- package/src/lib/sync-manager.js +347 -0
- package/src/lib/token-tracker.js +200 -0
- package/src/lib/wallet-manager.js +348 -0
- package/src/repl/agent-repl.js +142 -12
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wallet Manager — Digital wallet and asset management for CLI.
|
|
3
|
+
* Uses Node.js crypto for key generation. Blockchain operations
|
|
4
|
+
* are local-only (no real chain interaction without bridge).
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import crypto from "crypto";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Ensure wallet tables exist.
|
|
11
|
+
*/
|
|
12
|
+
export function ensureWalletTables(db) {
|
|
13
|
+
db.exec(`
|
|
14
|
+
CREATE TABLE IF NOT EXISTS wallets (
|
|
15
|
+
address TEXT PRIMARY KEY,
|
|
16
|
+
name TEXT,
|
|
17
|
+
wallet_type TEXT DEFAULT 'standard',
|
|
18
|
+
public_key TEXT NOT NULL,
|
|
19
|
+
encrypted_key TEXT NOT NULL,
|
|
20
|
+
balance TEXT DEFAULT '0',
|
|
21
|
+
is_default INTEGER DEFAULT 0,
|
|
22
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
23
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
24
|
+
)
|
|
25
|
+
`);
|
|
26
|
+
db.exec(`
|
|
27
|
+
CREATE TABLE IF NOT EXISTS digital_assets (
|
|
28
|
+
id TEXT PRIMARY KEY,
|
|
29
|
+
wallet_address TEXT NOT NULL,
|
|
30
|
+
asset_type TEXT NOT NULL,
|
|
31
|
+
name TEXT NOT NULL,
|
|
32
|
+
description TEXT,
|
|
33
|
+
metadata TEXT,
|
|
34
|
+
amount TEXT DEFAULT '1',
|
|
35
|
+
status TEXT DEFAULT 'active',
|
|
36
|
+
created_at TEXT DEFAULT (datetime('now')),
|
|
37
|
+
updated_at TEXT DEFAULT (datetime('now'))
|
|
38
|
+
)
|
|
39
|
+
`);
|
|
40
|
+
db.exec(`
|
|
41
|
+
CREATE TABLE IF NOT EXISTS transactions (
|
|
42
|
+
id TEXT PRIMARY KEY,
|
|
43
|
+
from_address TEXT,
|
|
44
|
+
to_address TEXT,
|
|
45
|
+
asset_id TEXT,
|
|
46
|
+
amount TEXT NOT NULL,
|
|
47
|
+
tx_type TEXT NOT NULL,
|
|
48
|
+
status TEXT DEFAULT 'pending',
|
|
49
|
+
metadata TEXT,
|
|
50
|
+
created_at TEXT DEFAULT (datetime('now'))
|
|
51
|
+
)
|
|
52
|
+
`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate a wallet address from public key.
|
|
57
|
+
*/
|
|
58
|
+
export function generateAddress(publicKeyHex) {
|
|
59
|
+
const hash = crypto
|
|
60
|
+
.createHash("sha256")
|
|
61
|
+
.update(Buffer.from(publicKeyHex, "hex"))
|
|
62
|
+
.digest();
|
|
63
|
+
return `0x${hash.toString("hex").slice(0, 40)}`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Create a new wallet.
|
|
68
|
+
*/
|
|
69
|
+
export function createWallet(db, name, password) {
|
|
70
|
+
ensureWalletTables(db);
|
|
71
|
+
|
|
72
|
+
const { publicKey, privateKey } = crypto.generateKeyPairSync("ed25519", {
|
|
73
|
+
publicKeyEncoding: { type: "spki", format: "der" },
|
|
74
|
+
privateKeyEncoding: { type: "pkcs8", format: "der" },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const publicKeyHex = publicKey.toString("hex");
|
|
78
|
+
const address = generateAddress(publicKeyHex);
|
|
79
|
+
|
|
80
|
+
// Encrypt private key with password
|
|
81
|
+
const salt = crypto.randomBytes(16);
|
|
82
|
+
const key = crypto.pbkdf2Sync(
|
|
83
|
+
password || "default",
|
|
84
|
+
salt,
|
|
85
|
+
100000,
|
|
86
|
+
32,
|
|
87
|
+
"sha256",
|
|
88
|
+
);
|
|
89
|
+
const iv = crypto.randomBytes(12);
|
|
90
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", key, iv);
|
|
91
|
+
const encrypted = Buffer.concat([cipher.update(privateKey), cipher.final()]);
|
|
92
|
+
const tag = cipher.getAuthTag();
|
|
93
|
+
|
|
94
|
+
const encryptedKey = JSON.stringify({
|
|
95
|
+
salt: salt.toString("hex"),
|
|
96
|
+
iv: iv.toString("hex"),
|
|
97
|
+
data: encrypted.toString("hex"),
|
|
98
|
+
tag: tag.toString("hex"),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// First wallet is default
|
|
102
|
+
const count = db.prepare("SELECT COUNT(*) as c FROM wallets").get().c;
|
|
103
|
+
const isDefault = count === 0 ? 1 : 0;
|
|
104
|
+
|
|
105
|
+
db.prepare(
|
|
106
|
+
`INSERT INTO wallets (address, name, wallet_type, public_key, encrypted_key, balance, is_default)
|
|
107
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
108
|
+
).run(
|
|
109
|
+
address,
|
|
110
|
+
name || null,
|
|
111
|
+
"standard",
|
|
112
|
+
publicKeyHex,
|
|
113
|
+
encryptedKey,
|
|
114
|
+
"0",
|
|
115
|
+
isDefault,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
address,
|
|
120
|
+
name,
|
|
121
|
+
walletType: "standard",
|
|
122
|
+
publicKey: publicKeyHex,
|
|
123
|
+
balance: "0",
|
|
124
|
+
isDefault: isDefault === 1,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get a wallet by address.
|
|
130
|
+
*/
|
|
131
|
+
export function getWallet(db, address) {
|
|
132
|
+
ensureWalletTables(db);
|
|
133
|
+
return db.prepare("SELECT * FROM wallets WHERE address = ?").get(address);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get the default wallet.
|
|
138
|
+
*/
|
|
139
|
+
export function getDefaultWallet(db) {
|
|
140
|
+
ensureWalletTables(db);
|
|
141
|
+
return db.prepare("SELECT * FROM wallets WHERE is_default = 1").get();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get all wallets.
|
|
146
|
+
*/
|
|
147
|
+
export function getAllWallets(db) {
|
|
148
|
+
ensureWalletTables(db);
|
|
149
|
+
return db
|
|
150
|
+
.prepare("SELECT * FROM wallets ORDER BY is_default DESC, created_at DESC")
|
|
151
|
+
.all();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Set a wallet as default.
|
|
156
|
+
*/
|
|
157
|
+
export function setDefaultWallet(db, address) {
|
|
158
|
+
ensureWalletTables(db);
|
|
159
|
+
const wallet = getWallet(db, address);
|
|
160
|
+
if (!wallet) return false;
|
|
161
|
+
|
|
162
|
+
db.prepare("UPDATE wallets SET is_default = 0 WHERE address LIKE ?").run("%");
|
|
163
|
+
db.prepare("UPDATE wallets SET is_default = 1 WHERE address = ?").run(
|
|
164
|
+
address,
|
|
165
|
+
);
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Delete a wallet.
|
|
171
|
+
*/
|
|
172
|
+
export function deleteWallet(db, address) {
|
|
173
|
+
ensureWalletTables(db);
|
|
174
|
+
const result = db
|
|
175
|
+
.prepare("DELETE FROM wallets WHERE address = ?")
|
|
176
|
+
.run(address);
|
|
177
|
+
if (result.changes > 0) {
|
|
178
|
+
// Promote next wallet to default
|
|
179
|
+
const next = db
|
|
180
|
+
.prepare("SELECT address FROM wallets ORDER BY created_at ASC LIMIT 1")
|
|
181
|
+
.get();
|
|
182
|
+
if (next) {
|
|
183
|
+
db.prepare("UPDATE wallets SET is_default = 1 WHERE address = ?").run(
|
|
184
|
+
next.address,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return result.changes > 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get wallet balance.
|
|
193
|
+
*/
|
|
194
|
+
export function getBalance(db, address) {
|
|
195
|
+
ensureWalletTables(db);
|
|
196
|
+
const wallet = getWallet(db, address);
|
|
197
|
+
if (!wallet) return null;
|
|
198
|
+
return { address, balance: wallet.balance, name: wallet.name };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Create a digital asset.
|
|
203
|
+
*/
|
|
204
|
+
export function createAsset(
|
|
205
|
+
db,
|
|
206
|
+
walletAddress,
|
|
207
|
+
assetType,
|
|
208
|
+
name,
|
|
209
|
+
description,
|
|
210
|
+
metadata,
|
|
211
|
+
) {
|
|
212
|
+
ensureWalletTables(db);
|
|
213
|
+
const wallet = getWallet(db, walletAddress);
|
|
214
|
+
if (!wallet) throw new Error(`Wallet not found: ${walletAddress}`);
|
|
215
|
+
|
|
216
|
+
const id = `asset-${crypto.randomBytes(8).toString("hex")}`;
|
|
217
|
+
|
|
218
|
+
db.prepare(
|
|
219
|
+
`INSERT INTO digital_assets (id, wallet_address, asset_type, name, description, metadata, amount, status)
|
|
220
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
221
|
+
).run(
|
|
222
|
+
id,
|
|
223
|
+
walletAddress,
|
|
224
|
+
assetType,
|
|
225
|
+
name,
|
|
226
|
+
description || null,
|
|
227
|
+
metadata ? JSON.stringify(metadata) : null,
|
|
228
|
+
"1",
|
|
229
|
+
"active",
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
id,
|
|
234
|
+
walletAddress,
|
|
235
|
+
assetType,
|
|
236
|
+
name,
|
|
237
|
+
description,
|
|
238
|
+
amount: "1",
|
|
239
|
+
status: "active",
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get assets for a wallet.
|
|
245
|
+
*/
|
|
246
|
+
export function getAssets(db, walletAddress) {
|
|
247
|
+
ensureWalletTables(db);
|
|
248
|
+
return db
|
|
249
|
+
.prepare(
|
|
250
|
+
"SELECT * FROM digital_assets WHERE wallet_address = ? ORDER BY created_at DESC",
|
|
251
|
+
)
|
|
252
|
+
.all(walletAddress);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Get all assets across all wallets.
|
|
257
|
+
*/
|
|
258
|
+
export function getAllAssets(db) {
|
|
259
|
+
ensureWalletTables(db);
|
|
260
|
+
return db
|
|
261
|
+
.prepare("SELECT * FROM digital_assets ORDER BY created_at DESC")
|
|
262
|
+
.all();
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Get asset by ID.
|
|
267
|
+
*/
|
|
268
|
+
export function getAsset(db, assetId) {
|
|
269
|
+
ensureWalletTables(db);
|
|
270
|
+
return db.prepare("SELECT * FROM digital_assets WHERE id = ?").get(assetId);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Transfer an asset to another wallet.
|
|
275
|
+
*/
|
|
276
|
+
export function transferAsset(db, assetId, toAddress, amount) {
|
|
277
|
+
ensureWalletTables(db);
|
|
278
|
+
const asset = getAsset(db, assetId);
|
|
279
|
+
if (!asset) throw new Error(`Asset not found: ${assetId}`);
|
|
280
|
+
|
|
281
|
+
const txId = `tx-${crypto.randomBytes(8).toString("hex")}`;
|
|
282
|
+
const fromAddress = asset.wallet_address;
|
|
283
|
+
const txAmount = amount || asset.amount;
|
|
284
|
+
|
|
285
|
+
db.prepare(
|
|
286
|
+
`INSERT INTO transactions (id, from_address, to_address, asset_id, amount, tx_type, status)
|
|
287
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
288
|
+
).run(
|
|
289
|
+
txId,
|
|
290
|
+
fromAddress,
|
|
291
|
+
toAddress,
|
|
292
|
+
assetId,
|
|
293
|
+
txAmount,
|
|
294
|
+
"transfer",
|
|
295
|
+
"confirmed",
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
// Update asset ownership
|
|
299
|
+
db.prepare("UPDATE digital_assets SET wallet_address = ? WHERE id = ?").run(
|
|
300
|
+
toAddress,
|
|
301
|
+
assetId,
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
txId,
|
|
306
|
+
from: fromAddress,
|
|
307
|
+
to: toAddress,
|
|
308
|
+
assetId,
|
|
309
|
+
amount: txAmount,
|
|
310
|
+
status: "confirmed",
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get transaction history.
|
|
316
|
+
*/
|
|
317
|
+
export function getTransactions(db, options = {}) {
|
|
318
|
+
ensureWalletTables(db);
|
|
319
|
+
const { address, limit = 50 } = options;
|
|
320
|
+
|
|
321
|
+
if (address) {
|
|
322
|
+
return db
|
|
323
|
+
.prepare(
|
|
324
|
+
"SELECT * FROM transactions WHERE from_address = ? OR to_address = ? ORDER BY created_at DESC LIMIT ?",
|
|
325
|
+
)
|
|
326
|
+
.all(address, address, limit);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
return db
|
|
330
|
+
.prepare("SELECT * FROM transactions ORDER BY created_at DESC LIMIT ?")
|
|
331
|
+
.all(limit);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Get wallet summary.
|
|
336
|
+
*/
|
|
337
|
+
export function getWalletSummary(db) {
|
|
338
|
+
ensureWalletTables(db);
|
|
339
|
+
const wallets = db.prepare("SELECT COUNT(*) as c FROM wallets").get();
|
|
340
|
+
const assets = db.prepare("SELECT COUNT(*) as c FROM digital_assets").get();
|
|
341
|
+
const txns = db.prepare("SELECT COUNT(*) as c FROM transactions").get();
|
|
342
|
+
|
|
343
|
+
return {
|
|
344
|
+
walletCount: wallets?.c || 0,
|
|
345
|
+
assetCount: assets?.c || 0,
|
|
346
|
+
transactionCount: txns?.c || 0,
|
|
347
|
+
};
|
|
348
|
+
}
|
package/src/repl/agent-repl.js
CHANGED
|
@@ -24,6 +24,7 @@ import path from "path";
|
|
|
24
24
|
import { execSync } from "child_process";
|
|
25
25
|
import { fileURLToPath } from "url";
|
|
26
26
|
import { logger } from "../lib/logger.js";
|
|
27
|
+
import { getPlanModeManager, PlanState } from "../lib/plan-mode.js";
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Tool definitions for function calling
|
|
@@ -261,9 +262,29 @@ function loadSkillList(skillsDir) {
|
|
|
261
262
|
}
|
|
262
263
|
|
|
263
264
|
/**
|
|
264
|
-
* Execute a tool call
|
|
265
|
+
* Execute a tool call (with plan mode filtering)
|
|
265
266
|
*/
|
|
266
267
|
async function executeTool(name, args) {
|
|
268
|
+
// Plan mode: check if tool is allowed
|
|
269
|
+
const planManager = getPlanModeManager();
|
|
270
|
+
if (planManager.isActive() && !planManager.isToolAllowed(name)) {
|
|
271
|
+
// In plan mode, log the blocked tool as a plan item
|
|
272
|
+
planManager.addPlanItem({
|
|
273
|
+
title: `${name}: ${formatToolArgs(name, args)}`,
|
|
274
|
+
tool: name,
|
|
275
|
+
params: args,
|
|
276
|
+
estimatedImpact:
|
|
277
|
+
name === "run_shell"
|
|
278
|
+
? "high"
|
|
279
|
+
: name === "write_file"
|
|
280
|
+
? "medium"
|
|
281
|
+
: "low",
|
|
282
|
+
});
|
|
283
|
+
return {
|
|
284
|
+
error: `[Plan Mode] Tool "${name}" is blocked during planning. It has been added to the plan. Use /plan approve to execute.`,
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
267
288
|
switch (name) {
|
|
268
289
|
case "read_file": {
|
|
269
290
|
const filePath = path.resolve(args.path);
|
|
@@ -520,6 +541,9 @@ async function chatWithTools(messages, options) {
|
|
|
520
541
|
|
|
521
542
|
const data = await response.json();
|
|
522
543
|
// Normalize to Ollama-like format
|
|
544
|
+
if (!data.choices || !data.choices[0]) {
|
|
545
|
+
throw new Error("Invalid API response: no choices returned");
|
|
546
|
+
}
|
|
523
547
|
const choice = data.choices[0];
|
|
524
548
|
return {
|
|
525
549
|
message: choice.message,
|
|
@@ -537,7 +561,11 @@ async function agentLoop(messages, options) {
|
|
|
537
561
|
|
|
538
562
|
for (let i = 0; i < MAX_ITERATIONS; i++) {
|
|
539
563
|
const result = await chatWithTools(messages, options);
|
|
540
|
-
const msg = result
|
|
564
|
+
const msg = result?.message;
|
|
565
|
+
|
|
566
|
+
if (!msg) {
|
|
567
|
+
return "(No response from LLM)";
|
|
568
|
+
}
|
|
541
569
|
|
|
542
570
|
// Check for tool calls
|
|
543
571
|
const toolCalls = msg.tool_calls;
|
|
@@ -643,10 +671,22 @@ export async function startAgentRepl(options = {}) {
|
|
|
643
671
|
|
|
644
672
|
const messages = [{ role: "system", content: SYSTEM_PROMPT }];
|
|
645
673
|
|
|
674
|
+
const getPrompt = () => {
|
|
675
|
+
const planManager = getPlanModeManager();
|
|
676
|
+
if (planManager.isActive()) {
|
|
677
|
+
const state = planManager.state;
|
|
678
|
+
if (state === PlanState.APPROVED || state === PlanState.EXECUTING) {
|
|
679
|
+
return chalk.green("[plan:exec] > ");
|
|
680
|
+
}
|
|
681
|
+
return chalk.yellow("[plan] > ");
|
|
682
|
+
}
|
|
683
|
+
return chalk.green("> ");
|
|
684
|
+
};
|
|
685
|
+
|
|
646
686
|
const rl = readline.createInterface({
|
|
647
687
|
input: process.stdin,
|
|
648
688
|
output: process.stdout,
|
|
649
|
-
prompt:
|
|
689
|
+
prompt: getPrompt(),
|
|
650
690
|
terminal: true,
|
|
651
691
|
});
|
|
652
692
|
|
|
@@ -661,12 +701,16 @@ export async function startAgentRepl(options = {}) {
|
|
|
661
701
|
);
|
|
662
702
|
logger.log(chalk.gray("Type /exit to quit, /help for commands\n"));
|
|
663
703
|
|
|
664
|
-
|
|
704
|
+
const prompt = () => {
|
|
705
|
+
rl.setPrompt(getPrompt());
|
|
706
|
+
rl.prompt();
|
|
707
|
+
};
|
|
708
|
+
prompt();
|
|
665
709
|
|
|
666
710
|
rl.on("line", async (input) => {
|
|
667
711
|
const trimmed = input.trim();
|
|
668
712
|
if (!trimmed) {
|
|
669
|
-
|
|
713
|
+
prompt();
|
|
670
714
|
return;
|
|
671
715
|
}
|
|
672
716
|
|
|
@@ -686,13 +730,21 @@ export async function startAgentRepl(options = {}) {
|
|
|
686
730
|
logger.log(` ${chalk.cyan("/provider")} Show/change provider`);
|
|
687
731
|
logger.log(` ${chalk.cyan("/clear")} Clear conversation`);
|
|
688
732
|
logger.log(` ${chalk.cyan("/compact")} Keep only last 4 messages`);
|
|
733
|
+
logger.log(
|
|
734
|
+
` ${chalk.cyan("/plan")} Enter plan mode (read-only analysis first)`,
|
|
735
|
+
);
|
|
736
|
+
logger.log(` ${chalk.cyan("/plan show")} Show current plan`);
|
|
737
|
+
logger.log(
|
|
738
|
+
` ${chalk.cyan("/plan approve")} Approve and execute the plan`,
|
|
739
|
+
);
|
|
740
|
+
logger.log(` ${chalk.cyan("/plan reject")} Reject the plan`);
|
|
689
741
|
logger.log(chalk.bold("\nCapabilities:"));
|
|
690
742
|
logger.log(" Read, write, and edit files");
|
|
691
743
|
logger.log(" Run shell commands (git, npm, etc.)");
|
|
692
744
|
logger.log(" Search codebase by filename or content");
|
|
693
745
|
logger.log(" Run 138 built-in skills (code-review, summarize, etc.)");
|
|
694
|
-
logger.log("
|
|
695
|
-
|
|
746
|
+
logger.log(" Plan mode: analyze first, execute after approval\n");
|
|
747
|
+
prompt();
|
|
696
748
|
return;
|
|
697
749
|
}
|
|
698
750
|
|
|
@@ -704,7 +756,7 @@ export async function startAgentRepl(options = {}) {
|
|
|
704
756
|
} else {
|
|
705
757
|
logger.info(`Current model: ${chalk.cyan(model)}`);
|
|
706
758
|
}
|
|
707
|
-
|
|
759
|
+
prompt();
|
|
708
760
|
return;
|
|
709
761
|
}
|
|
710
762
|
|
|
@@ -716,14 +768,14 @@ export async function startAgentRepl(options = {}) {
|
|
|
716
768
|
} else {
|
|
717
769
|
logger.info(`Current provider: ${chalk.cyan(provider)}`);
|
|
718
770
|
}
|
|
719
|
-
|
|
771
|
+
prompt();
|
|
720
772
|
return;
|
|
721
773
|
}
|
|
722
774
|
|
|
723
775
|
if (trimmed === "/clear") {
|
|
724
776
|
messages.length = 1; // Keep system prompt
|
|
725
777
|
logger.info("Conversation cleared");
|
|
726
|
-
|
|
778
|
+
prompt();
|
|
727
779
|
return;
|
|
728
780
|
}
|
|
729
781
|
|
|
@@ -736,7 +788,85 @@ export async function startAgentRepl(options = {}) {
|
|
|
736
788
|
messages.push(systemMsg, ...recent);
|
|
737
789
|
logger.info("Conversation compacted to last 4 messages");
|
|
738
790
|
}
|
|
739
|
-
|
|
791
|
+
prompt();
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// Plan mode commands
|
|
796
|
+
if (trimmed.startsWith("/plan")) {
|
|
797
|
+
const planManager = getPlanModeManager();
|
|
798
|
+
const subCmd = trimmed.slice(5).trim();
|
|
799
|
+
|
|
800
|
+
if (!subCmd || subCmd === "enter") {
|
|
801
|
+
if (planManager.isActive()) {
|
|
802
|
+
logger.info(
|
|
803
|
+
"Already in plan mode. Use /plan show, /plan approve, or /plan reject.",
|
|
804
|
+
);
|
|
805
|
+
} else {
|
|
806
|
+
planManager.enterPlanMode({ title: "Agent Plan" });
|
|
807
|
+
logger.success(
|
|
808
|
+
"Entered plan mode. Write tools are blocked until you approve the plan.",
|
|
809
|
+
);
|
|
810
|
+
logger.info(
|
|
811
|
+
"The AI can still read files and search. Blocked tools become plan items.",
|
|
812
|
+
);
|
|
813
|
+
logger.info(
|
|
814
|
+
"Use /plan show to see the plan, /plan approve to execute.",
|
|
815
|
+
);
|
|
816
|
+
// Inject plan mode context into system prompt
|
|
817
|
+
messages.push({
|
|
818
|
+
role: "system",
|
|
819
|
+
content:
|
|
820
|
+
"[PLAN MODE ACTIVE] You are now in plan mode. You can read files, search, and analyze — but write/execute tools are blocked. Any blocked tool calls will be recorded as plan items. Analyze the task thoroughly, then the user will approve your plan.",
|
|
821
|
+
});
|
|
822
|
+
}
|
|
823
|
+
} else if (subCmd === "show") {
|
|
824
|
+
if (!planManager.isActive()) {
|
|
825
|
+
logger.info("Not in plan mode. Use /plan to enter.");
|
|
826
|
+
} else {
|
|
827
|
+
logger.log("\n" + planManager.generatePlanSummary() + "\n");
|
|
828
|
+
}
|
|
829
|
+
} else if (subCmd === "approve" || subCmd === "yes") {
|
|
830
|
+
if (!planManager.isActive()) {
|
|
831
|
+
logger.info("No plan to approve.");
|
|
832
|
+
} else if (planManager.currentPlan.items.length === 0) {
|
|
833
|
+
logger.info(
|
|
834
|
+
"Plan has no items yet. Let the AI analyze the task first.",
|
|
835
|
+
);
|
|
836
|
+
} else {
|
|
837
|
+
planManager.approvePlan();
|
|
838
|
+
logger.success(
|
|
839
|
+
`Plan approved! ${planManager.currentPlan.items.length} items ready for execution.`,
|
|
840
|
+
);
|
|
841
|
+
logger.info(
|
|
842
|
+
"Write/execute tools are now unlocked. The AI can proceed.",
|
|
843
|
+
);
|
|
844
|
+
messages.push({
|
|
845
|
+
role: "system",
|
|
846
|
+
content: `[PLAN APPROVED] The user has approved your plan with ${planManager.currentPlan.items.length} items. You can now use all tools including write_file, edit_file, run_shell, and run_skill. Execute the plan items in order.`,
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
} else if (subCmd === "reject" || subCmd === "no") {
|
|
850
|
+
if (!planManager.isActive()) {
|
|
851
|
+
logger.info("No plan to reject.");
|
|
852
|
+
} else {
|
|
853
|
+
planManager.rejectPlan("User rejected");
|
|
854
|
+
logger.info("Plan rejected. Exited plan mode.");
|
|
855
|
+
}
|
|
856
|
+
} else if (subCmd === "exit") {
|
|
857
|
+
if (planManager.isActive()) {
|
|
858
|
+
planManager.exitPlanMode({ savePlan: true });
|
|
859
|
+
logger.info("Exited plan mode.");
|
|
860
|
+
} else {
|
|
861
|
+
logger.info("Not in plan mode.");
|
|
862
|
+
}
|
|
863
|
+
} else {
|
|
864
|
+
logger.info(
|
|
865
|
+
"Unknown /plan subcommand. Try: /plan, /plan show, /plan approve, /plan reject, /plan exit",
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
prompt();
|
|
740
870
|
return;
|
|
741
871
|
}
|
|
742
872
|
|
|
@@ -773,7 +903,7 @@ export async function startAgentRepl(options = {}) {
|
|
|
773
903
|
}
|
|
774
904
|
}
|
|
775
905
|
|
|
776
|
-
|
|
906
|
+
prompt();
|
|
777
907
|
});
|
|
778
908
|
|
|
779
909
|
rl.on("close", () => {
|