0agent 1.0.38 → 1.0.40
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/chat.js +67 -24
- package/dist/daemon.mjs +130 -25
- package/package.json +1 -1
package/bin/chat.js
CHANGED
|
@@ -23,33 +23,64 @@ class Spinner {
|
|
|
23
23
|
this._msg = msg;
|
|
24
24
|
this._frames = ['⠋','⠙','⠹','⠸','⠼','⠴','⠦','⠧','⠇','⠏'];
|
|
25
25
|
this._i = 0; this._timer = null; this._active = false;
|
|
26
|
+
this._sessionMode = false; // true during agent sessions — no \r animation
|
|
26
27
|
}
|
|
28
|
+
|
|
29
|
+
// Animated spinner — safe only at startup when user cannot type yet.
|
|
30
|
+
// Uses \r which conflicts with readline when user is typing.
|
|
27
31
|
start(msg) {
|
|
28
32
|
if (this._active) return;
|
|
29
33
|
if (msg) this._msg = msg;
|
|
30
34
|
this._active = true;
|
|
35
|
+
this._sessionMode = false;
|
|
31
36
|
this._timer = setInterval(() => {
|
|
32
37
|
process.stdout.write(`\r \x1b[36m${this._frames[this._i++ % this._frames.length]}\x1b[0m \x1b[2m${this._msg}\x1b[0m `);
|
|
33
38
|
}, 80);
|
|
34
39
|
}
|
|
40
|
+
|
|
41
|
+
// Session mode — prints a one-time static status line, NO \r animation.
|
|
42
|
+
// readline owns the cursor; call rl.prompt(true) after this to show › .
|
|
43
|
+
startSession(msg) {
|
|
44
|
+
if (this._active) return;
|
|
45
|
+
if (msg) this._msg = msg;
|
|
46
|
+
this._active = true;
|
|
47
|
+
this._sessionMode = true;
|
|
48
|
+
process.stdout.write(` \x1b[2m⠋ ${this._msg}...\x1b[0m\n`);
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
update(msg) { this._msg = msg; }
|
|
52
|
+
|
|
36
53
|
stop(clearIt = true) {
|
|
37
54
|
if (!this._active) return;
|
|
38
|
-
clearInterval(this._timer); this._timer = null;
|
|
39
|
-
|
|
55
|
+
if (this._timer) { clearInterval(this._timer); this._timer = null; }
|
|
56
|
+
const wasSession = this._sessionMode;
|
|
57
|
+
this._active = false;
|
|
58
|
+
this._sessionMode = false;
|
|
59
|
+
// Only clear the \r-based animation line; session mode output flows naturally
|
|
60
|
+
if (clearIt && !wasSession) {
|
|
61
|
+
process.stdout.write('\r\x1b[2K');
|
|
62
|
+
}
|
|
40
63
|
}
|
|
64
|
+
|
|
41
65
|
get active() { return this._active; }
|
|
42
66
|
|
|
43
|
-
|
|
44
|
-
* Pause spinner, run fn() (which may print lines), then resume.
|
|
45
|
-
* Prevents spinner from overwriting printed content.
|
|
46
|
-
*/
|
|
67
|
+
// Pause to print something cleanly, then resume.
|
|
47
68
|
pauseFor(fn) {
|
|
48
|
-
const wasActive
|
|
49
|
-
const
|
|
50
|
-
|
|
69
|
+
const wasActive = this._active;
|
|
70
|
+
const wasSession = this._sessionMode;
|
|
71
|
+
const savedMsg = this._msg;
|
|
72
|
+
this.stop(!wasSession); // clear animated spinner; session mode: just deactivate
|
|
51
73
|
fn();
|
|
52
|
-
if (wasActive)
|
|
74
|
+
if (wasActive) {
|
|
75
|
+
if (wasSession) {
|
|
76
|
+
// Re-mark active without printing again — readline is showing the prompt
|
|
77
|
+
this._active = true;
|
|
78
|
+
this._sessionMode = true;
|
|
79
|
+
this._msg = savedMsg;
|
|
80
|
+
} else {
|
|
81
|
+
this.start(savedMsg);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
53
84
|
}
|
|
54
85
|
}
|
|
55
86
|
|
|
@@ -192,13 +223,20 @@ function handleWsEvent(event) {
|
|
|
192
223
|
case 'session.step': {
|
|
193
224
|
spinner.stop();
|
|
194
225
|
if (streaming) { process.stdout.write('\n'); streaming = false; }
|
|
226
|
+
// Clear current readline line, print step, then restore › prompt
|
|
227
|
+
process.stdout.write('\r\x1b[2K');
|
|
195
228
|
console.log(` ${fmt(C.dim, '›')} ${event.step}`);
|
|
196
|
-
spinner.
|
|
229
|
+
spinner.startSession(event.step.slice(0, 50));
|
|
230
|
+
rl.prompt(true); // restore › so user can keep typing
|
|
197
231
|
break;
|
|
198
232
|
}
|
|
199
233
|
case 'session.token': {
|
|
200
234
|
spinner.stop();
|
|
201
|
-
if (!streaming) {
|
|
235
|
+
if (!streaming) {
|
|
236
|
+
// Clear › prompt line before streaming response
|
|
237
|
+
process.stdout.write('\r\x1b[2K\n ');
|
|
238
|
+
streaming = true;
|
|
239
|
+
}
|
|
202
240
|
process.stdout.write(event.token);
|
|
203
241
|
lineBuffer += event.token;
|
|
204
242
|
break;
|
|
@@ -383,9 +421,10 @@ async function runTask(input) {
|
|
|
383
421
|
});
|
|
384
422
|
const s = await res.json();
|
|
385
423
|
sessionId = s.session_id ?? s.id;
|
|
386
|
-
//
|
|
387
|
-
process.stdout.write(
|
|
388
|
-
spinner.
|
|
424
|
+
// Start session-mode status (no \r animation) then restore › so user can type
|
|
425
|
+
process.stdout.write('\n');
|
|
426
|
+
spinner.startSession('Thinking');
|
|
427
|
+
rl.prompt(true); // keep › visible — user can queue next message while agent works
|
|
389
428
|
|
|
390
429
|
// Polling fallback — runs concurrently with WS events.
|
|
391
430
|
// Catches completion when WS is disconnected (e.g. daemon just restarted).
|
|
@@ -416,8 +455,10 @@ async function runTask(input) {
|
|
|
416
455
|
const steps = session.steps ?? [];
|
|
417
456
|
for (let j = lastPolledStep; j < steps.length; j++) {
|
|
418
457
|
spinner.stop();
|
|
458
|
+
process.stdout.write('\r\x1b[2K');
|
|
419
459
|
console.log(` \x1b[2m›\x1b[0m ${steps[j].description}`);
|
|
420
|
-
spinner.
|
|
460
|
+
spinner.startSession(steps[j].description.slice(0, 50));
|
|
461
|
+
rl.prompt(true);
|
|
421
462
|
}
|
|
422
463
|
lastPolledStep = steps.length;
|
|
423
464
|
|
|
@@ -968,14 +1009,16 @@ rl.on('line', async (input) => {
|
|
|
968
1009
|
if (pendingResolve) {
|
|
969
1010
|
messageQueue.push(line);
|
|
970
1011
|
const qLen = messageQueue.length;
|
|
971
|
-
spinner.pauseFor(
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1012
|
+
// No spinner.pauseFor() needed — session mode has no \r animation
|
|
1013
|
+
// Just print the queued confirmation and restore the › prompt
|
|
1014
|
+
process.stdout.write('\r\x1b[2K');
|
|
1015
|
+
process.stdout.write(
|
|
1016
|
+
` ${fmt(C.magenta, '↳')} ${fmt(C.bold, `[queued #${qLen}]`)} ${fmt(C.dim, line.slice(0, 70))}\n`
|
|
1017
|
+
);
|
|
1018
|
+
if (qLen > 1) {
|
|
1019
|
+
process.stdout.write(` ${fmt(C.dim, `${qLen} tasks waiting`)}\n`);
|
|
1020
|
+
}
|
|
1021
|
+
rl.prompt(true); // keep › visible
|
|
979
1022
|
return;
|
|
980
1023
|
}
|
|
981
1024
|
|
package/dist/daemon.mjs
CHANGED
|
@@ -2406,14 +2406,14 @@ var init_FileCapability = __esm({
|
|
|
2406
2406
|
"use strict";
|
|
2407
2407
|
FileCapability = class {
|
|
2408
2408
|
name = "file_op";
|
|
2409
|
-
description = "Read, write, or
|
|
2409
|
+
description = "Read, write, list files, or create directories. Scoped to working directory.";
|
|
2410
2410
|
toolDefinition = {
|
|
2411
2411
|
name: "file_op",
|
|
2412
|
-
description: "Read, write,
|
|
2412
|
+
description: "Read, write, list files, or create directories in the working directory.",
|
|
2413
2413
|
input_schema: {
|
|
2414
2414
|
type: "object",
|
|
2415
2415
|
properties: {
|
|
2416
|
-
op: { type: "string", description: '"read", "write", or "
|
|
2416
|
+
op: { type: "string", description: '"read", "write", "list", or "mkdir"' },
|
|
2417
2417
|
path: { type: "string", description: "File or directory path (relative to cwd)" },
|
|
2418
2418
|
content: { type: "string", description: "Content for write operation" }
|
|
2419
2419
|
},
|
|
@@ -2448,7 +2448,11 @@ var init_FileCapability = __esm({
|
|
|
2448
2448
|
const entries = readdirSync(safe, { withFileTypes: true }).filter((e) => !e.name.startsWith(".") && e.name !== "node_modules").map((e) => `${e.isDirectory() ? "d" : "f"} ${e.name}`).join("\n");
|
|
2449
2449
|
return { success: true, output: entries || "(empty)", duration_ms: Date.now() - start };
|
|
2450
2450
|
}
|
|
2451
|
-
|
|
2451
|
+
if (op === "mkdir") {
|
|
2452
|
+
mkdirSync(safe, { recursive: true });
|
|
2453
|
+
return { success: true, output: `Directory created: ${rel}`, duration_ms: Date.now() - start };
|
|
2454
|
+
}
|
|
2455
|
+
return { success: false, output: `Unknown op: ${op}. Use "read", "write", "list", or "mkdir"`, duration_ms: Date.now() - start };
|
|
2452
2456
|
} catch (err) {
|
|
2453
2457
|
return { success: false, output: `Error: ${err instanceof Error ? err.message : String(err)}`, duration_ms: Date.now() - start };
|
|
2454
2458
|
}
|
|
@@ -2457,6 +2461,74 @@ var init_FileCapability = __esm({
|
|
|
2457
2461
|
}
|
|
2458
2462
|
});
|
|
2459
2463
|
|
|
2464
|
+
// packages/daemon/src/capabilities/MemoryCapability.ts
|
|
2465
|
+
var MemoryCapability;
|
|
2466
|
+
var init_MemoryCapability = __esm({
|
|
2467
|
+
"packages/daemon/src/capabilities/MemoryCapability.ts"() {
|
|
2468
|
+
"use strict";
|
|
2469
|
+
init_src();
|
|
2470
|
+
MemoryCapability = class {
|
|
2471
|
+
constructor(graph) {
|
|
2472
|
+
this.graph = graph;
|
|
2473
|
+
}
|
|
2474
|
+
name = "memory_write";
|
|
2475
|
+
description = "Persist a discovered fact to long-term memory so it survives across sessions.";
|
|
2476
|
+
toolDefinition = {
|
|
2477
|
+
name: "memory_write",
|
|
2478
|
+
description: 'Write an important fact to long-term memory. Call this whenever you discover something worth remembering: URLs (ngrok, live servers), file paths, port numbers, API endpoints, configuration values, decisions made, or task outcomes. Examples: memory_write({label:"ngrok_url", content:"https://abc.ngrok.io", type:"url"}) or memory_write({label:"project_port", content:"3000", type:"config"})',
|
|
2479
|
+
input_schema: {
|
|
2480
|
+
type: "object",
|
|
2481
|
+
properties: {
|
|
2482
|
+
label: { type: "string", description: 'Short name for this fact (e.g. "ngrok_url", "project_port")' },
|
|
2483
|
+
content: { type: "string", description: "The value to remember" },
|
|
2484
|
+
type: { type: "string", description: 'Category: "url", "path", "config", "note", "outcome"' }
|
|
2485
|
+
},
|
|
2486
|
+
required: ["label", "content"]
|
|
2487
|
+
}
|
|
2488
|
+
};
|
|
2489
|
+
async execute(input, _cwd) {
|
|
2490
|
+
const label = String(input.label ?? "").trim();
|
|
2491
|
+
const content = String(input.content ?? "").trim();
|
|
2492
|
+
const type = String(input.type ?? "note").trim();
|
|
2493
|
+
const start = Date.now();
|
|
2494
|
+
if (!label || !content) {
|
|
2495
|
+
return { success: false, output: "label and content are required", duration_ms: 0 };
|
|
2496
|
+
}
|
|
2497
|
+
try {
|
|
2498
|
+
const nodeId = `memory:${label.toLowerCase().replace(/[^a-z0-9_]/g, "_")}`;
|
|
2499
|
+
const existing = this.graph.getNode(nodeId);
|
|
2500
|
+
if (existing) {
|
|
2501
|
+
this.graph.updateNode(nodeId, {
|
|
2502
|
+
label,
|
|
2503
|
+
metadata: { ...existing.metadata, content, type, updated_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
2504
|
+
});
|
|
2505
|
+
} else {
|
|
2506
|
+
const node = createNode({
|
|
2507
|
+
id: nodeId,
|
|
2508
|
+
graph_id: "root",
|
|
2509
|
+
label,
|
|
2510
|
+
type: "context" /* CONTEXT */,
|
|
2511
|
+
metadata: { content, type, saved_at: (/* @__PURE__ */ new Date()).toISOString() }
|
|
2512
|
+
});
|
|
2513
|
+
this.graph.addNode(node);
|
|
2514
|
+
}
|
|
2515
|
+
return {
|
|
2516
|
+
success: true,
|
|
2517
|
+
output: `Remembered: "${label}" = ${content.slice(0, 120)}${content.length > 120 ? "\u2026" : ""}`,
|
|
2518
|
+
duration_ms: Date.now() - start
|
|
2519
|
+
};
|
|
2520
|
+
} catch (err) {
|
|
2521
|
+
return {
|
|
2522
|
+
success: false,
|
|
2523
|
+
output: `Memory write failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
2524
|
+
duration_ms: Date.now() - start
|
|
2525
|
+
};
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2528
|
+
};
|
|
2529
|
+
}
|
|
2530
|
+
});
|
|
2531
|
+
|
|
2460
2532
|
// packages/daemon/src/capabilities/CodespaceBrowserCapability.ts
|
|
2461
2533
|
var CodespaceBrowserCapability_exports = {};
|
|
2462
2534
|
__export(CodespaceBrowserCapability_exports, {
|
|
@@ -2541,6 +2613,7 @@ var init_CapabilityRegistry = __esm({
|
|
|
2541
2613
|
init_ScraperCapability();
|
|
2542
2614
|
init_ShellCapability();
|
|
2543
2615
|
init_FileCapability();
|
|
2616
|
+
init_MemoryCapability();
|
|
2544
2617
|
CapabilityRegistry = class {
|
|
2545
2618
|
capabilities = /* @__PURE__ */ new Map();
|
|
2546
2619
|
/**
|
|
@@ -2553,7 +2626,7 @@ var init_CapabilityRegistry = __esm({
|
|
|
2553
2626
|
* task_type: browser_task). The main agent does NOT have direct access
|
|
2554
2627
|
* to browser_open without going through a subagent spawn.
|
|
2555
2628
|
*/
|
|
2556
|
-
constructor(codespaceManager) {
|
|
2629
|
+
constructor(codespaceManager, graph) {
|
|
2557
2630
|
this.register(new WebSearchCapability());
|
|
2558
2631
|
if (codespaceManager) {
|
|
2559
2632
|
try {
|
|
@@ -2568,6 +2641,9 @@ var init_CapabilityRegistry = __esm({
|
|
|
2568
2641
|
this.register(new ScraperCapability());
|
|
2569
2642
|
this.register(new ShellCapability());
|
|
2570
2643
|
this.register(new FileCapability());
|
|
2644
|
+
if (graph) {
|
|
2645
|
+
this.register(new MemoryCapability(graph));
|
|
2646
|
+
}
|
|
2571
2647
|
}
|
|
2572
2648
|
register(cap) {
|
|
2573
2649
|
this.capabilities.set(cap.name, cap);
|
|
@@ -2633,7 +2709,7 @@ var init_AgentExecutor = __esm({
|
|
|
2633
2709
|
this.maxIterations = config.max_iterations ?? 20;
|
|
2634
2710
|
this.maxCommandMs = config.max_command_ms ?? 3e4;
|
|
2635
2711
|
this.agentRoot = config.agent_root;
|
|
2636
|
-
this.registry = new CapabilityRegistry();
|
|
2712
|
+
this.registry = new CapabilityRegistry(void 0, config.graph);
|
|
2637
2713
|
}
|
|
2638
2714
|
cwd;
|
|
2639
2715
|
maxIterations;
|
|
@@ -2658,23 +2734,39 @@ var init_AgentExecutor = __esm({
|
|
|
2658
2734
|
for (let i = 0; i < this.maxIterations; i++) {
|
|
2659
2735
|
this.onStep(i === 0 ? "Thinking\u2026" : "Continuing\u2026");
|
|
2660
2736
|
let response;
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2737
|
+
let llmFailed = false;
|
|
2738
|
+
{
|
|
2739
|
+
let llmRetry = 0;
|
|
2740
|
+
while (true) {
|
|
2741
|
+
try {
|
|
2742
|
+
response = await this.llm.completeWithTools(
|
|
2743
|
+
messages,
|
|
2744
|
+
this.registry.getToolDefinitions(),
|
|
2745
|
+
systemPrompt,
|
|
2746
|
+
// Only stream tokens on the final (non-tool) turn
|
|
2747
|
+
(token) => {
|
|
2748
|
+
this.onToken(token);
|
|
2749
|
+
finalOutput += token;
|
|
2750
|
+
}
|
|
2751
|
+
);
|
|
2752
|
+
break;
|
|
2753
|
+
} catch (err) {
|
|
2754
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2755
|
+
const isTimeout = /timeout|AbortError|aborted/i.test(msg);
|
|
2756
|
+
if (isTimeout && llmRetry < 2) {
|
|
2757
|
+
llmRetry++;
|
|
2758
|
+
this.onStep(`LLM timeout \u2014 retrying (${llmRetry}/2)\u2026`);
|
|
2759
|
+
await new Promise((r) => setTimeout(r, 2e3 * llmRetry));
|
|
2760
|
+
continue;
|
|
2761
|
+
}
|
|
2762
|
+
this.onStep(`LLM error: ${msg}`);
|
|
2763
|
+
finalOutput = `Error: ${msg}`;
|
|
2764
|
+
llmFailed = true;
|
|
2765
|
+
break;
|
|
2670
2766
|
}
|
|
2671
|
-
|
|
2672
|
-
} catch (err) {
|
|
2673
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
2674
|
-
this.onStep(`LLM error: ${msg}`);
|
|
2675
|
-
finalOutput = `Error: ${msg}`;
|
|
2676
|
-
break;
|
|
2767
|
+
}
|
|
2677
2768
|
}
|
|
2769
|
+
if (llmFailed) break;
|
|
2678
2770
|
totalTokens += response.tokens_used;
|
|
2679
2771
|
modelName = response.model;
|
|
2680
2772
|
if (response.stop_reason === "end_turn" || !response.tool_calls?.length) {
|
|
@@ -2889,6 +2981,7 @@ content = element.text if element else page.get_all_text()` : `content = page.ge
|
|
|
2889
2981
|
}
|
|
2890
2982
|
buildSystemPrompt(extra, task) {
|
|
2891
2983
|
const isSelfMod = !!(task && SELF_MOD_PATTERN.test(task));
|
|
2984
|
+
const hasMemory = !!this.config.graph;
|
|
2892
2985
|
const lines = [
|
|
2893
2986
|
`You are 0agent, an AI software engineer. You can execute shell commands and manage files.`,
|
|
2894
2987
|
`Working directory: ${this.cwd}`,
|
|
@@ -2899,12 +2992,24 @@ content = element.text if element else page.get_all_text()` : `content = page.ge
|
|
|
2899
2992
|
` cmd > /tmp/0agent-server.log 2>&1 &`,
|
|
2900
2993
|
` Example: python3 -m http.server 3000 > /tmp/0agent-server.log 2>&1 &`,
|
|
2901
2994
|
` NEVER run background commands without redirecting output.`,
|
|
2902
|
-
`-
|
|
2903
|
-
`-
|
|
2995
|
+
`- To create a folder: use file_op with op="mkdir" and path="folder/name"`,
|
|
2996
|
+
`- To create a file (and its parent folders): use file_op with op="write" \u2014 parent dirs are created automatically`,
|
|
2997
|
+
`- For npm/node projects: check package.json first with file_op op="list"`,
|
|
2998
|
+
`- After writing files, verify with file_op op="read" if needed`,
|
|
2904
2999
|
`- After shell_exec, check output for errors and retry if needed`,
|
|
2905
3000
|
`- For research tasks: use web_search first, then scrape_url for full page content`,
|
|
2906
3001
|
`- Use relative paths from the working directory`,
|
|
2907
|
-
`- Be concise in your final response: state what was done and where to find it
|
|
3002
|
+
`- Be concise in your final response: state what was done and where to find it`,
|
|
3003
|
+
...hasMemory ? [
|
|
3004
|
+
``,
|
|
3005
|
+
`Memory (IMPORTANT):`,
|
|
3006
|
+
`- ALWAYS call memory_write after discovering important facts:`,
|
|
3007
|
+
` \xB7 Live URLs (ngrok, deployed apps, APIs): memory_write({label:"ngrok_url", content:"https://...", type:"url"})`,
|
|
3008
|
+
` \xB7 Server ports: memory_write({label:"dev_server_port", content:"3000", type:"config"})`,
|
|
3009
|
+
` \xB7 File paths of created projects: memory_write({label:"project_path", content:"/path/to/project", type:"path"})`,
|
|
3010
|
+
` \xB7 Task outcomes: memory_write({label:"last_task_result", content:"...", type:"outcome"})`,
|
|
3011
|
+
`- Call memory_write immediately when you find the value, not just at the end`
|
|
3012
|
+
] : []
|
|
2908
3013
|
];
|
|
2909
3014
|
if (isSelfMod && this.agentRoot) {
|
|
2910
3015
|
lines.push(
|
|
@@ -4597,7 +4702,7 @@ var SessionManager = class {
|
|
|
4597
4702
|
if (activeLLM?.isConfigured) {
|
|
4598
4703
|
const executor = new AgentExecutor(
|
|
4599
4704
|
activeLLM,
|
|
4600
|
-
{ cwd: this.cwd, agent_root: this.agentRoot },
|
|
4705
|
+
{ cwd: this.cwd, agent_root: this.agentRoot, graph: this.graph },
|
|
4601
4706
|
// step callback → emit session.step events
|
|
4602
4707
|
(step) => this.addStep(sessionId, step),
|
|
4603
4708
|
// token callback → emit session.token events
|