@oh-my-pi/pi-coding-agent 15.7.1 → 15.7.3
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/CHANGELOG.md +95 -6
- package/dist/types/auto-thinking/classifier.d.ts +35 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/extension-flags.d.ts +36 -0
- package/dist/types/config/config-file.d.ts +4 -0
- package/dist/types/config/file-lock.d.ts +23 -0
- package/dist/types/config/keybindings.d.ts +2 -1
- package/dist/types/config/model-registry.d.ts +6 -0
- package/dist/types/config/settings-schema.d.ts +112 -69
- package/dist/types/edit/hashline/diff.d.ts +9 -3
- package/dist/types/eval/__tests__/agent-bridge.test.d.ts +1 -0
- package/dist/types/eval/__tests__/budget-bridge.test.d.ts +1 -0
- package/dist/types/eval/__tests__/idle-timeout.test.d.ts +1 -0
- package/dist/types/eval/agent-bridge.d.ts +25 -0
- package/dist/types/eval/backend.d.ts +17 -2
- package/dist/types/eval/budget-bridge.d.ts +29 -0
- package/dist/types/eval/idle-timeout.d.ts +28 -0
- package/dist/types/eval/js/executor.d.ts +8 -0
- package/dist/types/eval/js/tool-bridge.d.ts +2 -1
- package/dist/types/eval/py/executor.d.ts +13 -0
- package/dist/types/exec/bash-executor.d.ts +1 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +7 -0
- package/dist/types/extensibility/plugins/git-url.d.ts +11 -1
- package/dist/types/extensibility/plugins/manager.d.ts +12 -1
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/memory-backend/index.d.ts +1 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +3 -3
- package/dist/types/mnemopi/backend.d.ts +4 -0
- package/dist/types/mnemopi/config.d.ts +29 -0
- package/dist/types/mnemopi/state.d.ts +72 -0
- package/dist/types/modes/components/custom-editor.d.ts +2 -2
- package/dist/types/modes/components/model-selector.d.ts +3 -2
- package/dist/types/modes/components/omfg-panel.d.ts +19 -0
- package/dist/types/modes/controllers/command-controller.d.ts +7 -0
- package/dist/types/modes/controllers/input-controller.d.ts +1 -3
- package/dist/types/modes/controllers/omfg-controller.d.ts +10 -0
- package/dist/types/modes/controllers/omfg-rule.d.ts +26 -0
- package/dist/types/modes/gradient-highlight.d.ts +5 -1
- package/dist/types/modes/interactive-mode.d.ts +7 -3
- package/dist/types/modes/magic-keywords.d.ts +14 -0
- package/dist/types/modes/markdown-prose.d.ts +27 -0
- package/dist/types/modes/orchestrate.d.ts +7 -2
- package/dist/types/modes/shared.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/turn-budget.d.ts +18 -0
- package/dist/types/modes/types.d.ts +7 -3
- package/dist/types/modes/ultrathink.d.ts +7 -2
- package/dist/types/modes/workflow.d.ts +15 -0
- package/dist/types/sdk.d.ts +15 -4
- package/dist/types/session/agent-session.d.ts +59 -23
- package/dist/types/session/session-manager.d.ts +18 -0
- package/dist/types/session/session-storage.d.ts +6 -0
- package/dist/types/session/shake-types.d.ts +24 -0
- package/dist/types/task/executor.d.ts +2 -2
- package/dist/types/thinking.d.ts +39 -1
- package/dist/types/tiny/device.d.ts +3 -3
- package/dist/types/tiny/models.d.ts +34 -1
- package/dist/types/tiny/title-protocol.d.ts +4 -0
- package/dist/types/tools/index.d.ts +19 -3
- package/dist/types/tools/memory-edit.d.ts +1 -1
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/package.json +10 -10
- package/src/auto-thinking/classifier.ts +180 -0
- package/src/autoresearch/tools/run-experiment.ts +45 -113
- package/src/cli/args.ts +39 -16
- package/src/cli/extension-flags.ts +48 -0
- package/src/cli/plugin-cli.ts +11 -2
- package/src/config/config-file.ts +98 -13
- package/src/config/file-lock.ts +60 -17
- package/src/config/keybindings.ts +78 -27
- package/src/config/model-registry.ts +7 -1
- package/src/config/settings-schema.ts +118 -71
- package/src/config/settings.ts +12 -0
- package/src/edit/hashline/diff.ts +87 -22
- package/src/edit/renderer.ts +16 -12
- package/src/edit/streaming.ts +17 -6
- package/src/eval/__tests__/agent-bridge.test.ts +433 -0
- package/src/eval/__tests__/budget-bridge.test.ts +69 -0
- package/src/eval/__tests__/idle-timeout.test.ts +66 -0
- package/src/eval/__tests__/shared-executors.test.ts +53 -0
- package/src/eval/agent-bridge.ts +295 -0
- package/src/eval/backend.ts +17 -2
- package/src/eval/budget-bridge.ts +48 -0
- package/src/eval/idle-timeout.ts +80 -0
- package/src/eval/js/executor.ts +35 -7
- package/src/eval/js/index.ts +2 -1
- package/src/eval/js/shared/local-module-loader.ts +75 -10
- package/src/eval/js/shared/prelude.txt +85 -1
- package/src/eval/js/tool-bridge.ts +9 -0
- package/src/eval/py/executor.ts +41 -14
- package/src/eval/py/index.ts +2 -1
- package/src/eval/py/prelude.py +132 -1
- package/src/exec/bash-executor.ts +2 -3
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/runner.ts +12 -2
- package/src/extensibility/plugins/git-url.ts +90 -4
- package/src/extensibility/plugins/manager.ts +103 -7
- package/src/extensibility/shared-events.ts +2 -2
- package/src/internal-urls/docs-index.generated.ts +88 -88
- package/src/main.ts +50 -56
- package/src/memory-backend/index.ts +1 -1
- package/src/memory-backend/resolve.ts +3 -3
- package/src/memory-backend/types.ts +3 -3
- package/src/{mnemosyne → mnemopi}/backend.ts +70 -70
- package/src/{mnemosyne → mnemopi}/config.ts +36 -36
- package/src/{mnemosyne → mnemopi}/state.ts +67 -67
- package/src/modes/acp/acp-agent.ts +13 -3
- package/src/modes/components/agent-dashboard.ts +6 -6
- package/src/modes/components/custom-editor.ts +4 -11
- package/src/modes/components/extensions/state-manager.ts +3 -4
- package/src/modes/components/footer.ts +18 -12
- package/src/modes/components/hook-selector.ts +86 -20
- package/src/modes/components/model-selector.ts +20 -11
- package/src/modes/components/oauth-selector.ts +93 -21
- package/src/modes/components/omfg-panel.ts +141 -0
- package/src/modes/components/settings-defs.ts +9 -2
- package/src/modes/components/settings-selector.ts +4 -1
- package/src/modes/components/status-line/segments.ts +13 -5
- package/src/modes/components/tips.txt +2 -1
- package/src/modes/components/tool-execution.ts +38 -19
- package/src/modes/components/tree-selector.ts +4 -3
- package/src/modes/components/user-message-selector.ts +94 -19
- package/src/modes/components/user-message.ts +8 -1
- package/src/modes/controllers/command-controller.ts +57 -0
- package/src/modes/controllers/event-controller.ts +65 -3
- package/src/modes/controllers/input-controller.ts +14 -11
- package/src/modes/controllers/omfg-controller.ts +283 -0
- package/src/modes/controllers/omfg-rule.ts +647 -0
- package/src/modes/controllers/selector-controller.ts +21 -6
- package/src/modes/gradient-highlight.ts +23 -6
- package/src/modes/interactive-mode.ts +41 -7
- package/src/modes/magic-keywords.ts +20 -0
- package/src/modes/markdown-prose.ts +247 -0
- package/src/modes/orchestrate.ts +17 -11
- package/src/modes/shared.ts +3 -11
- package/src/modes/theme/theme.ts +6 -0
- package/src/modes/turn-budget.ts +31 -0
- package/src/modes/types.ts +7 -1
- package/src/modes/ultrathink.ts +16 -10
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/workflow.ts +42 -0
- package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
- package/src/prompts/system/auto-thinking-difficulty.md +12 -0
- package/src/prompts/system/omfg-user.md +51 -0
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +70 -0
- package/src/prompts/tools/eval.md +13 -1
- package/src/prompts/tools/memory-edit.md +1 -1
- package/src/sdk.ts +86 -38
- package/src/session/agent-session.ts +558 -80
- package/src/session/session-manager.ts +32 -0
- package/src/session/session-storage.ts +68 -8
- package/src/session/shake-types.ts +44 -0
- package/src/slash-commands/builtin-registry.ts +41 -16
- package/src/task/executor.ts +3 -3
- package/src/task/index.ts +6 -6
- package/src/thinking.ts +73 -1
- package/src/tiny/device.ts +4 -10
- package/src/tiny/models.ts +54 -2
- package/src/tiny/title-protocol.ts +11 -1
- package/src/tiny/worker.ts +19 -7
- package/src/tools/eval.ts +202 -26
- package/src/tools/grouped-file-output.ts +9 -2
- package/src/tools/index.ts +17 -5
- package/src/tools/memory-edit.ts +4 -4
- package/src/tools/memory-recall.ts +5 -5
- package/src/tools/memory-reflect.ts +5 -5
- package/src/tools/memory-retain.ts +4 -4
- package/src/tools/render-utils.ts +2 -1
- package/src/tools/search.ts +480 -76
- package/dist/types/mnemosyne/backend.d.ts +0 -4
- package/dist/types/mnemosyne/config.d.ts +0 -29
- package/dist/types/mnemosyne/state.d.ts +0 -72
- /package/dist/types/{mnemosyne → mnemopi}/index.d.ts +0 -0
- /package/src/{mnemosyne → mnemopi}/index.ts +0 -0
package/src/tiny/worker.ts
CHANGED
|
@@ -461,19 +461,20 @@ async function generateTitle(
|
|
|
461
461
|
return extractTinyTitle(output[0]?.generated_text ?? "");
|
|
462
462
|
}
|
|
463
463
|
|
|
464
|
-
function buildCompletionPrompt(generator: TextGenerationPipeline, promptText: string): string {
|
|
464
|
+
function buildCompletionPrompt(generator: TextGenerationPipeline, promptText: string, prefill?: string): string {
|
|
465
465
|
const chat = [{ role: "user", content: promptText }];
|
|
466
466
|
const chatTemplateOptions = {
|
|
467
467
|
add_generation_prompt: true,
|
|
468
468
|
tokenize: false,
|
|
469
469
|
enable_thinking: false,
|
|
470
470
|
};
|
|
471
|
-
|
|
471
|
+
const base = generator.tokenizer.apply_chat_template(chat, chatTemplateOptions) as string;
|
|
472
|
+
return prefill ? `${base}${prefill}` : base;
|
|
472
473
|
}
|
|
473
474
|
|
|
474
475
|
/**
|
|
475
|
-
* Generic single-turn completion used by
|
|
476
|
-
* and consolidation). The caller (
|
|
476
|
+
* Generic single-turn completion used by Mnemopi memory tasks (fact extraction
|
|
477
|
+
* and consolidation). The caller (Mnemopi) supplies the full task prompt; we
|
|
477
478
|
* wrap it as the user turn, decode greedily, and return the raw text for the
|
|
478
479
|
* caller's own parser. Output is capped to keep local inference latency bounded.
|
|
479
480
|
*/
|
|
@@ -483,18 +484,27 @@ async function generateCompletion(
|
|
|
483
484
|
modelKey: TinyLocalModelKey,
|
|
484
485
|
promptText: string,
|
|
485
486
|
maxTokens: number | undefined,
|
|
487
|
+
prefill?: string,
|
|
488
|
+
stop?: string,
|
|
486
489
|
): Promise<string | null> {
|
|
487
490
|
const generator = await loadPipeline(modelKey, transport, requestId);
|
|
488
|
-
const text = buildCompletionPrompt(generator, promptText);
|
|
491
|
+
const text = buildCompletionPrompt(generator, promptText, prefill);
|
|
489
492
|
const requested = maxTokens ?? MEMORY_COMPLETION_MAX_NEW_TOKENS;
|
|
490
493
|
const maxNewTokens = Math.min(Math.max(1, requested), MEMORY_COMPLETION_MAX_NEW_TOKENS);
|
|
494
|
+
const transformers = stop ? await loadTransformers(transport, requestId, modelKey) : undefined;
|
|
491
495
|
const output = (await generator(text, {
|
|
492
496
|
max_new_tokens: maxNewTokens,
|
|
493
497
|
do_sample: false,
|
|
494
498
|
return_full_text: false,
|
|
499
|
+
...(transformers && stop
|
|
500
|
+
? { stopping_criteria: createStopOnTextCriteria(transformers, generator.tokenizer, stop) }
|
|
501
|
+
: {}),
|
|
495
502
|
})) as TextGenerationStringOutput;
|
|
496
|
-
const generated =
|
|
497
|
-
|
|
503
|
+
const generated = output[0]?.generated_text ?? "";
|
|
504
|
+
// Re-attach the forced prefix so the caller's parser sees the full assistant turn,
|
|
505
|
+
// including the opening tag it pinned via `prefill`.
|
|
506
|
+
const full = `${prefill ?? ""}${generated}`.trim();
|
|
507
|
+
return full === "" ? null : full;
|
|
498
508
|
}
|
|
499
509
|
|
|
500
510
|
function releasePipelines(): void {
|
|
@@ -538,6 +548,8 @@ async function handleQueuedRequest(
|
|
|
538
548
|
request.modelKey,
|
|
539
549
|
request.prompt,
|
|
540
550
|
request.maxTokens,
|
|
551
|
+
request.prefill,
|
|
552
|
+
request.stop,
|
|
541
553
|
);
|
|
542
554
|
transport.send({ type: "completion", id: request.id, text });
|
|
543
555
|
return;
|
package/src/tools/eval.ts
CHANGED
|
@@ -2,13 +2,16 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
2
2
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Markdown, Text } from "@oh-my-pi/pi-tui";
|
|
5
|
-
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
|
+
import { formatNumber, prompt } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import * as z from "zod/v4";
|
|
7
|
+
import { settings } from "../config/settings";
|
|
7
8
|
import { jsBackend, pythonBackend } from "../eval";
|
|
8
|
-
import type { ExecutorBackend } from "../eval/backend";
|
|
9
|
+
import type { ExecutorBackend, ExecutorBackendResult } from "../eval/backend";
|
|
10
|
+
import { IdleTimeout } from "../eval/idle-timeout";
|
|
9
11
|
import { defaultEvalSessionId } from "../eval/session-id";
|
|
10
12
|
import type { EvalCellResult, EvalDisplayOutput, EvalLanguage, EvalStatusEvent, EvalToolDetails } from "../eval/types";
|
|
11
13
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
14
|
+
import { formatContextUsage } from "../modes/components/status-line/context-thresholds";
|
|
12
15
|
import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
13
16
|
import { shimmerEnabled } from "../modes/theme/shimmer";
|
|
14
17
|
import { getMarkdownTheme, type Theme } from "../modes/theme/theme";
|
|
@@ -33,7 +36,16 @@ import {
|
|
|
33
36
|
resolveOutputSinkHeadBytes,
|
|
34
37
|
stripOutputNotice,
|
|
35
38
|
} from "./output-meta";
|
|
36
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
formatBadge,
|
|
41
|
+
formatDuration,
|
|
42
|
+
formatStatusIcon,
|
|
43
|
+
formatTitle,
|
|
44
|
+
replaceTabs,
|
|
45
|
+
shortenPath,
|
|
46
|
+
truncateToWidth,
|
|
47
|
+
wrapBrackets,
|
|
48
|
+
} from "./render-utils";
|
|
37
49
|
import { ToolAbortError, ToolError } from "./tool-errors";
|
|
38
50
|
import { toolResult } from "./tool-result";
|
|
39
51
|
import { clampTimeout } from "./tool-timeouts";
|
|
@@ -335,12 +347,20 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
335
347
|
for (let i = 0; i < cells.length; i++) {
|
|
336
348
|
const cell = cells[i];
|
|
337
349
|
const backend = cell.resolved.backend;
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
350
|
+
// The per-cell `timeout` is an *inactivity* budget, not a hard
|
|
351
|
+
// wall-clock cap: it bounds the gap between progress signals
|
|
352
|
+
// (status events — agent() updates, log()/phase(), tool-bridge
|
|
353
|
+
// activity), so a long fanout that keeps reporting progress runs to
|
|
354
|
+
// completion while a genuinely stalled cell (no progress for the
|
|
355
|
+
// whole window) is still interrupted. Raw stdout deliberately does
|
|
356
|
+
// NOT re-arm it, so pure-compute runaway loops stay bounded. The
|
|
357
|
+
// watchdog drives `combinedSignal`; we pass no wall-clock deadline
|
|
358
|
+
// downstream so the backends never arm a competing fixed timer.
|
|
359
|
+
const idleTimeoutMs = timeoutSecondsFromMs(cell.timeoutMs) * 1000;
|
|
360
|
+
const idle = new IdleTimeout(idleTimeoutMs);
|
|
341
361
|
const combinedSignal = signal
|
|
342
|
-
? AbortSignal.any([signal,
|
|
343
|
-
: AbortSignal.any([
|
|
362
|
+
? AbortSignal.any([signal, idle.signal, sessionAbortController.signal])
|
|
363
|
+
: AbortSignal.any([idle.signal, sessionAbortController.signal]);
|
|
344
364
|
|
|
345
365
|
const cellResult = cellResults[i];
|
|
346
366
|
cellResult.status = "running";
|
|
@@ -351,21 +371,32 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
351
371
|
pushUpdate();
|
|
352
372
|
|
|
353
373
|
const startTime = Date.now();
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
374
|
+
let result: ExecutorBackendResult;
|
|
375
|
+
try {
|
|
376
|
+
result = await backend.execute(cell.code, {
|
|
377
|
+
cwd: session.cwd,
|
|
378
|
+
sessionId,
|
|
379
|
+
sessionFile: sessionFile ?? undefined,
|
|
380
|
+
kernelOwnerId,
|
|
381
|
+
signal: combinedSignal,
|
|
382
|
+
session,
|
|
383
|
+
idleTimeoutMs,
|
|
384
|
+
reset: cell.reset,
|
|
385
|
+
artifactPath,
|
|
386
|
+
artifactId,
|
|
387
|
+
onChunk: chunk => {
|
|
388
|
+
outputSink!.push(chunk);
|
|
389
|
+
},
|
|
390
|
+
onStatus: event => {
|
|
391
|
+
idle.bump();
|
|
392
|
+
cellResult.statusEvents ??= [];
|
|
393
|
+
upsertStatusEvent(cellResult.statusEvents, event);
|
|
394
|
+
pushUpdate();
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
} finally {
|
|
398
|
+
idle.dispose();
|
|
399
|
+
}
|
|
369
400
|
const durationMs = Date.now() - startTime;
|
|
370
401
|
|
|
371
402
|
const cellStatusEvents: EvalStatusEvent[] = [];
|
|
@@ -400,8 +431,8 @@ export class EvalTool implements AgentTool<typeof evalSchema> {
|
|
|
400
431
|
}
|
|
401
432
|
}
|
|
402
433
|
if (output.type === "status") {
|
|
403
|
-
statusEvents
|
|
404
|
-
cellStatusEvents
|
|
434
|
+
upsertStatusEvent(statusEvents, output.event);
|
|
435
|
+
upsertStatusEvent(cellStatusEvents, output.event);
|
|
405
436
|
}
|
|
406
437
|
if (output.type === "markdown") {
|
|
407
438
|
cellHasMarkdown = true;
|
|
@@ -608,6 +639,137 @@ function getRenderCells(args: EvalRenderArgs | undefined): EvalRenderCell[] {
|
|
|
608
639
|
return out;
|
|
609
640
|
}
|
|
610
641
|
|
|
642
|
+
type AgentEventStatus = "pending" | "running" | "completed" | "failed" | "aborted";
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Append or replace a status event. `agent` events are progress snapshots keyed
|
|
646
|
+
* by `id`, so they coalesce in place (preserving first-seen order); every other
|
|
647
|
+
* op is a discrete action and simply appends. Keeps the persisted event list
|
|
648
|
+
* bounded even when a subagent emits hundreds of throttled progress ticks.
|
|
649
|
+
*/
|
|
650
|
+
function upsertStatusEvent(events: EvalStatusEvent[], event: EvalStatusEvent): void {
|
|
651
|
+
if (event.op === "agent" && typeof event.id === "string") {
|
|
652
|
+
const id = event.id;
|
|
653
|
+
const idx = events.findIndex(e => e.op === "agent" && e.id === id);
|
|
654
|
+
if (idx >= 0) {
|
|
655
|
+
events[idx] = event;
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
events.push(event);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
function eventString(value: unknown): string | undefined {
|
|
663
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
function eventNumber(value: unknown): number {
|
|
667
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
function agentEventStatus(value: unknown): AgentEventStatus {
|
|
671
|
+
switch (value) {
|
|
672
|
+
case "pending":
|
|
673
|
+
case "running":
|
|
674
|
+
case "completed":
|
|
675
|
+
case "failed":
|
|
676
|
+
case "aborted":
|
|
677
|
+
return value;
|
|
678
|
+
default:
|
|
679
|
+
return "running";
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/** Append the toolCount · context · cost · model stat run, mirroring the task tool. */
|
|
684
|
+
function formatAgentStats(event: EvalStatusEvent, theme: Theme): string {
|
|
685
|
+
let line = "";
|
|
686
|
+
const toolCount = eventNumber(event.toolCount);
|
|
687
|
+
if (toolCount > 0) {
|
|
688
|
+
line += `${theme.sep.dot}${theme.fg("dim", `${formatNumber(toolCount)} ${theme.icon.extensionTool}`)}`;
|
|
689
|
+
}
|
|
690
|
+
const contextTokens = eventNumber(event.contextTokens);
|
|
691
|
+
if (contextTokens > 0) {
|
|
692
|
+
const contextWindow = eventNumber(event.contextWindow);
|
|
693
|
+
const ctx =
|
|
694
|
+
contextWindow > 0
|
|
695
|
+
? formatContextUsage((contextTokens / contextWindow) * 100, contextWindow)
|
|
696
|
+
: formatNumber(contextTokens);
|
|
697
|
+
line += `${theme.sep.dot}${theme.fg("dim", ctx)}`;
|
|
698
|
+
}
|
|
699
|
+
const cost = eventNumber(event.cost);
|
|
700
|
+
if (cost > 0) {
|
|
701
|
+
line += `${theme.sep.dot}${theme.fg("statusLineCost", `$${cost.toFixed(2)}`)}`;
|
|
702
|
+
}
|
|
703
|
+
const model = eventString(event.model);
|
|
704
|
+
if (model && settings.get("task.showResolvedModelBadge")) {
|
|
705
|
+
line += `${theme.sep.dot}${theme.fg("dim", truncateToWidth(replaceTabs(model), 30))}`;
|
|
706
|
+
}
|
|
707
|
+
return line;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* Render coalesced `agent()` progress as a Task-tool-style tree, one entry per
|
|
712
|
+
* subagent: a status line (icon · id · stats) plus, while running, the current
|
|
713
|
+
* tool/intent. Drawn below the cell box so progress streams live.
|
|
714
|
+
*/
|
|
715
|
+
function renderAgentProgressEvents(events: EvalStatusEvent[], theme: Theme, spinnerFrame?: number): string[] {
|
|
716
|
+
const lines: string[] = [];
|
|
717
|
+
for (let i = 0; i < events.length; i++) {
|
|
718
|
+
const event = events[i];
|
|
719
|
+
const isLast = i === events.length - 1;
|
|
720
|
+
const prefix = theme.fg("dim", isLast ? theme.tree.last : theme.tree.branch);
|
|
721
|
+
const cont = isLast ? " " : `${theme.fg("dim", theme.tree.vertical)} `;
|
|
722
|
+
|
|
723
|
+
const status = agentEventStatus(event.status);
|
|
724
|
+
const iconStatus =
|
|
725
|
+
status === "completed"
|
|
726
|
+
? "success"
|
|
727
|
+
: status === "failed"
|
|
728
|
+
? "error"
|
|
729
|
+
: status === "aborted"
|
|
730
|
+
? "aborted"
|
|
731
|
+
: status === "pending"
|
|
732
|
+
? "pending"
|
|
733
|
+
: "running";
|
|
734
|
+
const iconColor =
|
|
735
|
+
status === "completed" ? "success" : status === "failed" || status === "aborted" ? "error" : "accent";
|
|
736
|
+
const icon = formatStatusIcon(iconStatus, theme, status === "running" ? spinnerFrame : undefined);
|
|
737
|
+
|
|
738
|
+
const id = eventString(event.id) ?? "agent";
|
|
739
|
+
let line = `${prefix} ${theme.fg(iconColor, icon)} ${theme.fg("accent", theme.bold(id))}`;
|
|
740
|
+
|
|
741
|
+
if (status === "failed" || status === "aborted") {
|
|
742
|
+
line += ` ${formatBadge(status, iconColor, theme)}`;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
const currentTool = eventString(event.currentTool);
|
|
746
|
+
const lastIntent = eventString(event.lastIntent);
|
|
747
|
+
if (status === "running" && !currentTool && !lastIntent) {
|
|
748
|
+
const preview = eventString(event.taskPreview);
|
|
749
|
+
if (preview) line += ` ${theme.fg("muted", truncateToWidth(replaceTabs(preview), 48))}`;
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
line += formatAgentStats(event, theme);
|
|
753
|
+
if (status === "completed" || status === "failed" || status === "aborted") {
|
|
754
|
+
const durationMs = eventNumber(event.durationMs);
|
|
755
|
+
if (durationMs > 0) line += `${theme.sep.dot}${theme.fg("dim", formatDuration(durationMs))}`;
|
|
756
|
+
}
|
|
757
|
+
lines.push(line);
|
|
758
|
+
|
|
759
|
+
if (status === "running") {
|
|
760
|
+
if (currentTool) {
|
|
761
|
+
let toolLine = `${cont}${theme.tree.hook} ${theme.fg("muted", currentTool)}`;
|
|
762
|
+
const detail = lastIntent ?? eventString(event.currentToolArgs);
|
|
763
|
+
if (detail) toolLine += `: ${theme.fg("dim", truncateToWidth(replaceTabs(detail), 48))}`;
|
|
764
|
+
lines.push(toolLine);
|
|
765
|
+
} else if (lastIntent) {
|
|
766
|
+
lines.push(`${cont}${theme.tree.hook} ${theme.fg("dim", truncateToWidth(replaceTabs(lastIntent), 48))}`);
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return lines;
|
|
771
|
+
}
|
|
772
|
+
|
|
611
773
|
/** Format a status event as a single line for display. */
|
|
612
774
|
function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
613
775
|
const { op, ...data } = event;
|
|
@@ -636,6 +798,8 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
|
636
798
|
env: "icon.package",
|
|
637
799
|
batch: "icon.package",
|
|
638
800
|
llm: "icon.package",
|
|
801
|
+
log: "icon.package",
|
|
802
|
+
phase: "icon.package",
|
|
639
803
|
};
|
|
640
804
|
|
|
641
805
|
const iconKey = opIcons[op] ?? "icon.file";
|
|
@@ -716,6 +880,12 @@ function formatStatusEvent(event: EvalStatusEvent, theme: Theme): string {
|
|
|
716
880
|
case "touch":
|
|
717
881
|
if (data.path) parts.push(shortenPath(String(data.path)));
|
|
718
882
|
break;
|
|
883
|
+
case "log":
|
|
884
|
+
parts.push(String(data.message ?? ""));
|
|
885
|
+
break;
|
|
886
|
+
case "phase":
|
|
887
|
+
parts.push(String(data.title ?? ""));
|
|
888
|
+
break;
|
|
719
889
|
default:
|
|
720
890
|
if (data.count !== undefined) {
|
|
721
891
|
parts.push(String(data.count));
|
|
@@ -963,7 +1133,10 @@ export const evalToolRenderer = {
|
|
|
963
1133
|
const lines: string[] = [];
|
|
964
1134
|
for (let i = 0; i < cellResults.length; i++) {
|
|
965
1135
|
const cell = cellResults[i];
|
|
966
|
-
const
|
|
1136
|
+
const allEvents = cell.statusEvents ?? [];
|
|
1137
|
+
const agentEvents = allEvents.filter(e => e.op === "agent");
|
|
1138
|
+
const otherEvents = agentEvents.length > 0 ? allEvents.filter(e => e.op !== "agent") : allEvents;
|
|
1139
|
+
const statusLines = renderStatusEvents(otherEvents, uiTheme, expanded);
|
|
967
1140
|
const outputContent = formatCellOutputLines(cell, expanded, previewLines, uiTheme, width);
|
|
968
1141
|
const outputLines = [...outputContent.lines];
|
|
969
1142
|
if (!expanded && outputContent.hiddenCount > 0) {
|
|
@@ -997,6 +1170,9 @@ export const evalToolRenderer = {
|
|
|
997
1170
|
uiTheme,
|
|
998
1171
|
);
|
|
999
1172
|
lines.push(...cellLines);
|
|
1173
|
+
if (agentEvents.length > 0) {
|
|
1174
|
+
lines.push(...renderAgentProgressEvents(agentEvents, uiTheme, options.spinnerFrame));
|
|
1175
|
+
}
|
|
1000
1176
|
if (i < cellResults.length - 1) {
|
|
1001
1177
|
lines.push("");
|
|
1002
1178
|
}
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
|
|
3
|
+
const URL_LIKE_PATH_RE = /^[a-z][a-z0-9+.-]*:\/\//i;
|
|
4
|
+
|
|
5
|
+
function isUrlLikePath(filePath: string): boolean {
|
|
6
|
+
return URL_LIKE_PATH_RE.test(filePath);
|
|
7
|
+
}
|
|
8
|
+
|
|
3
9
|
/**
|
|
4
10
|
* One file's contribution to a grouped file output. The header itself is generated
|
|
5
11
|
* by `formatGroupedFiles` (single `#` for root files, `##` for files inside a dir);
|
|
@@ -43,7 +49,7 @@ export function formatGroupedFiles(
|
|
|
43
49
|
): GroupedFilesOutput {
|
|
44
50
|
const filesByDirectory = new Map<string, string[]>();
|
|
45
51
|
for (const filePath of files) {
|
|
46
|
-
const directory = path.dirname(filePath).replace(/\\/g, "/");
|
|
52
|
+
const directory = isUrlLikePath(filePath) ? "." : path.dirname(filePath).replace(/\\/g, "/");
|
|
47
53
|
if (!filesByDirectory.has(directory)) {
|
|
48
54
|
filesByDirectory.set(directory, []);
|
|
49
55
|
}
|
|
@@ -66,7 +72,8 @@ export function formatGroupedFiles(
|
|
|
66
72
|
const section = renderFile(filePath);
|
|
67
73
|
if (section.skip) continue;
|
|
68
74
|
pushSeparatorIfNeeded();
|
|
69
|
-
const
|
|
75
|
+
const headerName = isUrlLikePath(filePath) ? filePath : path.basename(filePath);
|
|
76
|
+
const header = `# ${headerName}${section.headerSuffix ?? ""}`;
|
|
70
77
|
model.push(header, ...section.modelLines);
|
|
71
78
|
display.push(header, ...(section.displayLines ?? section.modelLines));
|
|
72
79
|
}
|
package/src/tools/index.ts
CHANGED
|
@@ -10,8 +10,10 @@ import type { Skill } from "../extensibility/skills";
|
|
|
10
10
|
import type { GoalModeState, GoalRuntime } from "../goals";
|
|
11
11
|
import { GoalTool } from "../goals/tools/goal-tool";
|
|
12
12
|
import type { HindsightSessionState } from "../hindsight/state";
|
|
13
|
+
import type { LocalProtocolOptions } from "../internal-urls";
|
|
13
14
|
import { LspTool } from "../lsp";
|
|
14
|
-
import type {
|
|
15
|
+
import type { MCPManager } from "../mcp";
|
|
16
|
+
import type { MnemopiSessionState } from "../mnemopi/state";
|
|
15
17
|
import type { PlanModeState } from "../plan-mode/state";
|
|
16
18
|
import { type AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
|
|
17
19
|
import type { ArtifactManager } from "../session/artifacts";
|
|
@@ -153,8 +155,8 @@ export interface ToolSession {
|
|
|
153
155
|
getSessionId?: () => string | null;
|
|
154
156
|
/** Get Hindsight runtime state for this agent session. */
|
|
155
157
|
getHindsightSessionState?: () => HindsightSessionState | undefined;
|
|
156
|
-
/** Get
|
|
157
|
-
|
|
158
|
+
/** Get Mnemopi runtime state for this agent session. */
|
|
159
|
+
getMnemopiSessionState?: () => MnemopiSessionState | undefined;
|
|
158
160
|
/** Agent identity used for IRC routing. Returns the registry id (e.g. "0-Main", "0-AuthLoader"). */
|
|
159
161
|
getAgentId?: () => string | null;
|
|
160
162
|
/** Look up a registered tool by name (used by the eval js backend's tool bridge). */
|
|
@@ -179,6 +181,10 @@ export interface ToolSession {
|
|
|
179
181
|
modelRegistry?: import("../config/model-registry").ModelRegistry;
|
|
180
182
|
/** Agent output manager for unique agent:// IDs across task invocations */
|
|
181
183
|
agentOutputManager?: AgentOutputManager;
|
|
184
|
+
/** MCP manager visible to subagents without relying on the process-global singleton. */
|
|
185
|
+
mcpManager?: MCPManager;
|
|
186
|
+
/** Local protocol root to propagate to nested subagents and eval-created agents. */
|
|
187
|
+
localProtocolOptions?: LocalProtocolOptions;
|
|
182
188
|
/** Settings instance for passing to subagents */
|
|
183
189
|
settings: Settings;
|
|
184
190
|
/** Plan mode state (if active) */
|
|
@@ -187,6 +193,12 @@ export interface ToolSession {
|
|
|
187
193
|
getGoalModeState?: () => GoalModeState | undefined;
|
|
188
194
|
/** Goal runtime for the active agent session. */
|
|
189
195
|
getGoalRuntime?: () => GoalRuntime | undefined;
|
|
196
|
+
/** Get cumulative session usage statistics (input/output tokens, cost). */
|
|
197
|
+
getUsageStatistics?: () => import("../session/session-manager").UsageStatistics;
|
|
198
|
+
/** Current per-turn token budget {total, spent, hard} for the eval `budget` helper. */
|
|
199
|
+
getTurnBudget?: () => { total: number | null; spent: number; hard: boolean };
|
|
200
|
+
/** Record output tokens consumed by an eval-spawned subagent toward the current turn budget. */
|
|
201
|
+
recordEvalSubagentUsage?: (output: number) => void;
|
|
190
202
|
/** Bridge to the connected client (e.g. ACP editor host). Tools should route fs/terminal/permission requests through this when available. */
|
|
191
203
|
getClientBridge?: () => ClientBridge | undefined;
|
|
192
204
|
/** Get compact conversation context for subagents (excludes tool results, system prompts) */
|
|
@@ -413,7 +425,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
413
425
|
) {
|
|
414
426
|
requestedTools.push("ast_edit");
|
|
415
427
|
}
|
|
416
|
-
if (["hindsight", "
|
|
428
|
+
if (["hindsight", "mnemopi"].includes(session.settings.get("memory.backend") ?? "")) {
|
|
417
429
|
for (const name of ["recall", "retain", "reflect"]) {
|
|
418
430
|
if (!requestedTools.includes(name)) requestedTools.push(name);
|
|
419
431
|
}
|
|
@@ -458,7 +470,7 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
458
470
|
return true;
|
|
459
471
|
}
|
|
460
472
|
if (name === "retain" || name === "recall" || name === "reflect") {
|
|
461
|
-
return ["hindsight", "
|
|
473
|
+
return ["hindsight", "mnemopi"].includes(session.settings.get("memory.backend") ?? "");
|
|
462
474
|
}
|
|
463
475
|
if (name === "task") {
|
|
464
476
|
const maxDepth = session.settings.get("task.maxRecursionDepth") ?? 2;
|
package/src/tools/memory-edit.ts
CHANGED
|
@@ -21,20 +21,20 @@ export class MemoryEditTool implements AgentTool<typeof memoryEditSchema> {
|
|
|
21
21
|
readonly parameters = memoryEditSchema;
|
|
22
22
|
readonly strict = true;
|
|
23
23
|
readonly loadMode = "discoverable";
|
|
24
|
-
readonly summary = "Update, forget, or invalidate
|
|
24
|
+
readonly summary = "Update, forget, or invalidate Mnemopi memories";
|
|
25
25
|
|
|
26
26
|
constructor(private readonly session: ToolSession) {}
|
|
27
27
|
|
|
28
28
|
static createIf(session: ToolSession): MemoryEditTool | null {
|
|
29
29
|
const backend = session.settings.get("memory.backend");
|
|
30
|
-
if (backend !== "
|
|
30
|
+
if (backend !== "mnemopi") return null;
|
|
31
31
|
return new MemoryEditTool(session);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
async execute(_id: string, params: MemoryEditParams): Promise<AgentToolResult> {
|
|
35
|
-
const state = this.session.
|
|
35
|
+
const state = this.session.getMnemopiSessionState?.();
|
|
36
36
|
if (!state) {
|
|
37
|
-
throw new Error("
|
|
37
|
+
throw new Error("Mnemopi backend is not initialised for this session.");
|
|
38
38
|
}
|
|
39
39
|
if (params.op === "update" && params.content === undefined && params.importance === undefined) {
|
|
40
40
|
throw new Error("memory_edit update requires content or importance.");
|
|
@@ -25,17 +25,17 @@ export class MemoryRecallTool implements AgentTool<typeof memoryRecallSchema> {
|
|
|
25
25
|
|
|
26
26
|
static createIf(session: ToolSession): MemoryRecallTool | null {
|
|
27
27
|
const backend = session.settings.get("memory.backend");
|
|
28
|
-
if (backend !== "hindsight" && backend !== "
|
|
28
|
+
if (backend !== "hindsight" && backend !== "mnemopi") return null;
|
|
29
29
|
return new MemoryRecallTool(session);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
async execute(_id: string, params: MemoryRecallParams, signal?: AbortSignal): Promise<AgentToolResult> {
|
|
33
33
|
return untilAborted(signal, async () => {
|
|
34
34
|
const backend = this.session.settings.get("memory.backend");
|
|
35
|
-
if (backend === "
|
|
36
|
-
const state = this.session.
|
|
35
|
+
if (backend === "mnemopi") {
|
|
36
|
+
const state = this.session.getMnemopiSessionState?.();
|
|
37
37
|
if (!state) {
|
|
38
|
-
throw new Error("
|
|
38
|
+
throw new Error("Mnemopi backend is not initialised for this session.");
|
|
39
39
|
}
|
|
40
40
|
try {
|
|
41
41
|
const results = state.recallResultsScoped(params.query);
|
|
@@ -56,7 +56,7 @@ export class MemoryRecallTool implements AgentTool<typeof memoryRecallSchema> {
|
|
|
56
56
|
details: {},
|
|
57
57
|
};
|
|
58
58
|
} catch (err) {
|
|
59
|
-
logger.warn("recall failed", { backend: "
|
|
59
|
+
logger.warn("recall failed", { backend: "mnemopi", bank: state.config.bank, error: String(err) });
|
|
60
60
|
throw err instanceof Error ? err : new Error(String(err));
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -26,17 +26,17 @@ export class MemoryReflectTool implements AgentTool<typeof memoryReflectSchema>
|
|
|
26
26
|
|
|
27
27
|
static createIf(session: ToolSession): MemoryReflectTool | null {
|
|
28
28
|
const backend = session.settings.get("memory.backend");
|
|
29
|
-
if (backend !== "hindsight" && backend !== "
|
|
29
|
+
if (backend !== "hindsight" && backend !== "mnemopi") return null;
|
|
30
30
|
return new MemoryReflectTool(session);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
async execute(_id: string, params: MemoryReflectParams, signal?: AbortSignal): Promise<AgentToolResult> {
|
|
34
34
|
return untilAborted(signal, async () => {
|
|
35
35
|
const backend = this.session.settings.get("memory.backend");
|
|
36
|
-
if (backend === "
|
|
37
|
-
const state = this.session.
|
|
36
|
+
if (backend === "mnemopi") {
|
|
37
|
+
const state = this.session.getMnemopiSessionState?.();
|
|
38
38
|
if (!state) {
|
|
39
|
-
throw new Error("
|
|
39
|
+
throw new Error("Mnemopi backend is not initialised for this session.");
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
try {
|
|
@@ -56,7 +56,7 @@ export class MemoryReflectTool implements AgentTool<typeof memoryReflectSchema>
|
|
|
56
56
|
details: {},
|
|
57
57
|
};
|
|
58
58
|
} catch (err) {
|
|
59
|
-
logger.warn("reflect failed", { backend: "
|
|
59
|
+
logger.warn("reflect failed", { backend: "mnemopi", bank: state.config.bank, error: String(err) });
|
|
60
60
|
throw err instanceof Error ? err : new Error(String(err));
|
|
61
61
|
}
|
|
62
62
|
}
|
|
@@ -30,16 +30,16 @@ export class MemoryRetainTool implements AgentTool<typeof memoryRetainSchema> {
|
|
|
30
30
|
|
|
31
31
|
static createIf(session: ToolSession): MemoryRetainTool | null {
|
|
32
32
|
const backend = session.settings.get("memory.backend");
|
|
33
|
-
if (backend !== "hindsight" && backend !== "
|
|
33
|
+
if (backend !== "hindsight" && backend !== "mnemopi") return null;
|
|
34
34
|
return new MemoryRetainTool(session);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
async execute(_id: string, params: MemoryRetainParams): Promise<AgentToolResult> {
|
|
38
38
|
const backend = this.session.settings.get("memory.backend");
|
|
39
|
-
if (backend === "
|
|
40
|
-
const state = this.session.
|
|
39
|
+
if (backend === "mnemopi") {
|
|
40
|
+
const state = this.session.getMnemopiSessionState?.();
|
|
41
41
|
if (!state) {
|
|
42
|
-
throw new Error("
|
|
42
|
+
throw new Error("Mnemopi backend is not initialised for this session.");
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
for (const item of params.items) {
|
|
@@ -177,7 +177,8 @@ export function formatMeta(meta: string[], theme: Theme): string {
|
|
|
177
177
|
|
|
178
178
|
export function formatErrorMessage(message: string | undefined, theme: Theme): string {
|
|
179
179
|
const clean = (message ?? "").replace(/^Error:\s*/, "").trim();
|
|
180
|
-
|
|
180
|
+
const safe = clean ? replaceTabs(truncateToWidth(clean, TRUNCATE_LENGTHS.LINE)) : "Unknown error";
|
|
181
|
+
return `${theme.styledSymbol("status.error", "error")} ${theme.fg("error", `Error: ${safe}`)}`;
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
export function formatEmptyMessage(message: string, theme: Theme): string {
|