@oh-my-pi/pi-coding-agent 15.12.4 → 15.13.0
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 +304 -6
- package/dist/cli.js +1015 -881
- package/dist/types/async/job-manager.d.ts +15 -0
- package/dist/types/autolearn/controller.d.ts +25 -0
- package/dist/types/autolearn/managed-skills.d.ts +45 -0
- package/dist/types/autoresearch/state.d.ts +1 -1
- package/dist/types/autoresearch/types.d.ts +1 -1
- package/dist/types/cli/args.d.ts +19 -1
- package/dist/types/cli/session-picker.d.ts +1 -1
- package/dist/types/cli/setup-cli.d.ts +1 -1
- package/dist/types/cli/setup-model-picker.d.ts +14 -0
- package/dist/types/collab/protocol.d.ts +1 -1
- package/dist/types/commands/say.d.ts +24 -0
- package/dist/types/config/keybindings.d.ts +3 -3
- package/dist/types/config/model-registry.d.ts +10 -0
- package/dist/types/config/models-config-schema.d.ts +12 -0
- package/dist/types/config/models-config.d.ts +8 -2
- package/dist/types/config/settings-schema.d.ts +261 -58
- package/dist/types/export/html/index.d.ts +2 -1
- package/dist/types/extensibility/extensions/model-api.d.ts +17 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -1
- package/dist/types/extensibility/extensions/types.d.ts +47 -1
- package/dist/types/extensibility/hooks/index.d.ts +2 -1
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +9 -0
- package/dist/types/extensibility/plugins/loader.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/extensibility/skills.d.ts +10 -0
- package/dist/types/goals/guided-setup.d.ts +18 -0
- package/dist/types/goals/state.d.ts +1 -1
- package/dist/types/hindsight/transcript.d.ts +1 -1
- package/dist/types/index.d.ts +5 -0
- package/dist/types/internal-urls/local-protocol.d.ts +4 -2
- package/dist/types/main.d.ts +4 -3
- package/dist/types/mcp/startup-events.d.ts +11 -0
- package/dist/types/memories/index.d.ts +7 -0
- package/dist/types/memory-backend/local-backend.d.ts +4 -3
- package/dist/types/mnemopi/config.d.ts +4 -4
- package/dist/types/modes/components/agent-hub.d.ts +6 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -2
- package/dist/types/modes/components/compaction-summary-message.d.ts +15 -1
- package/dist/types/modes/components/custom-editor.d.ts +39 -1
- package/dist/types/modes/components/custom-editor.test.d.ts +1 -0
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/tool-execution.d.ts +26 -16
- package/dist/types/modes/components/transcript-container.d.ts +23 -2
- package/dist/types/modes/components/tree-selector.d.ts +1 -1
- package/dist/types/modes/components/usage-row.d.ts +3 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -2
- package/dist/types/modes/controllers/input-controller.d.ts +14 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +3 -1
- package/dist/types/modes/gradient-highlight.d.ts +9 -4
- package/dist/types/modes/image-references.d.ts +6 -0
- package/dist/types/modes/interactive-mode.d.ts +27 -3
- package/dist/types/modes/magic-keywords.d.ts +13 -1
- package/dist/types/modes/rpc/rpc-mode.d.ts +35 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +9 -1
- package/dist/types/modes/runtime-init.d.ts +4 -0
- package/dist/types/modes/theme/theme.d.ts +13 -2
- package/dist/types/modes/types.d.ts +8 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-registry.d.ts +17 -0
- package/dist/types/secrets/obfuscator.d.ts +1 -1
- package/dist/types/session/agent-session.d.ts +14 -2
- package/dist/types/session/indexed-session-storage.d.ts +3 -4
- package/dist/types/session/session-context.d.ts +39 -0
- package/dist/types/session/session-entries.d.ts +159 -0
- package/dist/types/session/session-listing.d.ts +69 -0
- package/dist/types/session/session-loader.d.ts +16 -0
- package/dist/types/session/session-manager.d.ts +82 -474
- package/dist/types/session/session-migrations.d.ts +12 -0
- package/dist/types/session/session-paths.d.ts +25 -0
- package/dist/types/session/session-persistence.d.ts +8 -0
- package/dist/types/session/session-storage.d.ts +11 -12
- package/dist/types/session/snapcompact-inline.d.ts +12 -1
- package/dist/types/session/snapcompact-savings-journal.d.ts +46 -0
- package/dist/types/session/tool-choice-queue.d.ts +6 -6
- package/dist/types/stt/asr-client.d.ts +90 -0
- package/dist/types/stt/asr-protocol.d.ts +97 -0
- package/dist/types/stt/asr-worker.d.ts +2 -0
- package/dist/types/stt/downloader.d.ts +38 -0
- package/dist/types/stt/endpointer.d.ts +59 -0
- package/dist/types/stt/index.d.ts +5 -1
- package/dist/types/stt/models.d.ts +120 -0
- package/dist/types/stt/recorder.d.ts +17 -0
- package/dist/types/stt/stt-controller.d.ts +6 -0
- package/dist/types/stt/transcriber.d.ts +5 -7
- package/dist/types/stt/wav.d.ts +29 -0
- package/dist/types/system-prompt.d.ts +4 -0
- package/dist/types/task/executor.d.ts +2 -0
- package/dist/types/task/index.d.ts +9 -1
- package/dist/types/task/types.d.ts +36 -0
- package/dist/types/tools/bash.d.ts +2 -2
- package/dist/types/tools/eval-render.d.ts +1 -1
- package/dist/types/tools/index.d.ts +11 -1
- package/dist/types/tools/irc.d.ts +1 -0
- package/dist/types/tools/learn.d.ts +51 -0
- package/dist/types/tools/manage-skill.d.ts +40 -0
- package/dist/types/tools/plan-mode-guard.d.ts +10 -0
- package/dist/types/tools/renderers.d.ts +7 -11
- package/dist/types/tools/ssh.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +1 -1
- package/dist/types/tools/tts.d.ts +25 -0
- package/dist/types/tools/write.d.ts +1 -1
- package/dist/types/tts/downloader.d.ts +20 -0
- package/dist/types/tts/index.d.ts +8 -0
- package/dist/types/tts/models.d.ts +82 -0
- package/dist/types/tts/player.d.ts +32 -0
- package/dist/types/tts/runtime.d.ts +6 -0
- package/dist/types/tts/streaming-player.d.ts +41 -0
- package/dist/types/tts/tts-client.d.ts +93 -0
- package/dist/types/tts/tts-protocol.d.ts +95 -0
- package/dist/types/tts/tts-worker.d.ts +2 -0
- package/dist/types/tts/vocalizer.d.ts +41 -0
- package/dist/types/tts/wav.d.ts +8 -0
- package/dist/types/utils/tool-choice.d.ts +8 -0
- package/dist/types/utils/tools-manager.d.ts +2 -1
- package/dist/types/utils/tools-manager.test.d.ts +1 -0
- package/dist/types/web/scrapers/github.d.ts +1 -1
- package/package.json +15 -14
- package/src/async/job-manager.ts +49 -0
- package/src/autolearn/controller.ts +139 -0
- package/src/autolearn/managed-skills.ts +257 -0
- package/src/autoresearch/state.ts +1 -1
- package/src/autoresearch/types.ts +1 -1
- package/src/cli/args.ts +56 -2
- package/src/cli/session-picker.ts +2 -1
- package/src/cli/setup-cli.ts +148 -47
- package/src/cli/setup-model-picker.ts +43 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +45 -13
- package/src/collab/host.ts +1 -1
- package/src/collab/protocol.ts +1 -1
- package/src/commands/say.ts +102 -0
- package/src/commands/setup.ts +1 -1
- package/src/commit/agentic/tools/analyze-file.ts +3 -0
- package/src/config/keybindings.ts +2 -2
- package/src/config/model-discovery.ts +11 -5
- package/src/config/model-registry.ts +64 -9
- package/src/config/models-config-schema.ts +4 -1
- package/src/config/models-config.ts +2 -1
- package/src/config/settings-schema.ts +248 -32
- package/src/config/settings.ts +10 -0
- package/src/discovery/builtin.ts +23 -1
- package/src/discovery/claude-plugins.ts +44 -5
- package/src/discovery/helpers.ts +41 -1
- package/src/eval/__tests__/budget-bridge.test.ts +1 -1
- package/src/eval/js/shared/prelude.txt +69 -17
- package/src/export/html/index.ts +3 -6
- package/src/extensibility/extensions/model-api.ts +41 -0
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +52 -1
- package/src/extensibility/extensions/wrapper.ts +41 -5
- package/src/extensibility/hooks/index.ts +2 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +43 -13
- package/src/extensibility/plugins/loader.ts +30 -19
- package/src/extensibility/plugins/manager.ts +221 -90
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +96 -15
- package/src/goals/guided-setup.ts +133 -0
- package/src/goals/state.ts +1 -1
- package/src/hindsight/transcript.ts +1 -1
- package/src/index.ts +5 -0
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/internal-urls/history-protocol.ts +1 -1
- package/src/internal-urls/local-protocol.ts +29 -7
- package/src/main.ts +27 -7
- package/src/mcp/startup-events.ts +21 -0
- package/src/mcp/transports/stdio.ts +2 -1
- package/src/memories/index.ts +146 -11
- package/src/memory-backend/local-backend.ts +11 -5
- package/src/mnemopi/backend.ts +1 -0
- package/src/mnemopi/config.ts +26 -10
- package/src/modes/acp/acp-agent.ts +3 -5
- package/src/modes/components/agent-hub.ts +49 -4
- package/src/modes/components/assistant-message.ts +4 -37
- package/src/modes/components/compaction-summary-message.ts +125 -26
- package/src/modes/components/custom-editor.test.ts +96 -0
- package/src/modes/components/custom-editor.ts +164 -8
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-defs.ts +7 -0
- package/src/modes/components/tool-execution.ts +82 -43
- package/src/modes/components/transcript-container.ts +70 -1
- package/src/modes/components/tree-selector.ts +1 -1
- package/src/modes/components/usage-row.ts +18 -0
- package/src/modes/components/user-message.ts +4 -2
- package/src/modes/controllers/command-controller.ts +14 -4
- package/src/modes/controllers/event-controller.ts +78 -11
- package/src/modes/controllers/extension-ui-controller.ts +6 -0
- package/src/modes/controllers/input-controller.ts +258 -27
- package/src/modes/controllers/selector-controller.ts +12 -2
- package/src/modes/gradient-highlight.ts +21 -9
- package/src/modes/image-references.ts +20 -0
- package/src/modes/interactive-mode.ts +286 -40
- package/src/modes/magic-keywords.ts +27 -5
- package/src/modes/rpc/rpc-mode.ts +146 -14
- package/src/modes/rpc/rpc-subagents.ts +2 -2
- package/src/modes/rpc/rpc-types.ts +8 -2
- package/src/modes/runtime-init.ts +28 -3
- package/src/modes/theme/theme.ts +98 -50
- package/src/modes/types.ts +6 -2
- package/src/modes/utils/hotkeys-markdown.ts +1 -1
- package/src/modes/utils/ui-helpers.ts +34 -6
- package/src/priority.json +5 -1
- package/src/prompts/agents/task.md +1 -0
- package/src/prompts/goals/guided-goal-interview.md +8 -0
- package/src/prompts/goals/guided-goal-system.md +12 -0
- package/src/prompts/memories/read-path.md +6 -0
- package/src/prompts/system/autolearn-guidance-learn.md +1 -0
- package/src/prompts/system/autolearn-guidance.md +7 -0
- package/src/prompts/system/autolearn-nudge.md +3 -0
- package/src/prompts/system/eager-task.md +7 -0
- package/src/prompts/system/eager-todo.md +11 -6
- package/src/prompts/system/subagent-system-prompt.md +4 -0
- package/src/prompts/system/system-prompt.md +10 -5
- package/src/prompts/system/title-marker-instruction.md +1 -0
- package/src/prompts/system/title-system-marker.md +16 -0
- package/src/prompts/tools/job.md +1 -0
- package/src/prompts/tools/learn.md +7 -0
- package/src/prompts/tools/manage-skill.md +9 -0
- package/src/prompts/tools/task.md +3 -0
- package/src/registry/agent-registry.ts +30 -0
- package/src/sdk.ts +88 -24
- package/src/secrets/obfuscator.ts +1 -1
- package/src/session/agent-session.ts +209 -87
- package/src/session/history-storage.ts +2 -2
- package/src/session/indexed-session-storage.ts +7 -17
- package/src/session/session-context.ts +352 -0
- package/src/session/session-entries.ts +194 -0
- package/src/session/session-listing.ts +588 -0
- package/src/session/session-loader.ts +106 -0
- package/src/session/session-manager.ts +933 -3145
- package/src/session/session-migrations.ts +78 -0
- package/src/session/session-paths.ts +193 -0
- package/src/session/session-persistence.ts +131 -0
- package/src/session/session-storage.ts +91 -50
- package/src/session/snapcompact-inline.ts +21 -1
- package/src/session/snapcompact-savings-journal.ts +113 -0
- package/src/session/tool-choice-queue.ts +23 -11
- package/src/slash-commands/builtin-registry.ts +25 -3
- package/src/stt/asr-client.ts +520 -0
- package/src/stt/asr-protocol.ts +65 -0
- package/src/stt/asr-worker.ts +790 -0
- package/src/stt/downloader.ts +107 -47
- package/src/stt/endpointer.ts +259 -0
- package/src/stt/index.ts +5 -1
- package/src/stt/models.ts +150 -0
- package/src/stt/recorder.ts +247 -60
- package/src/stt/stt-controller.ts +201 -22
- package/src/stt/transcriber.ts +37 -68
- package/src/stt/wav.ts +173 -0
- package/src/system-prompt.ts +8 -0
- package/src/task/agents.ts +1 -2
- package/src/task/executor.ts +49 -15
- package/src/task/index.ts +60 -6
- package/src/task/render.ts +83 -8
- package/src/task/types.ts +53 -0
- package/src/tools/ask.ts +8 -0
- package/src/tools/bash.ts +4 -3
- package/src/tools/eval-render.ts +4 -3
- package/src/tools/index.ts +40 -4
- package/src/tools/irc.ts +10 -2
- package/src/tools/job.ts +14 -2
- package/src/tools/learn.ts +144 -0
- package/src/tools/manage-skill.ts +104 -0
- package/src/tools/plan-mode-guard.ts +53 -19
- package/src/tools/renderers.ts +7 -11
- package/src/tools/ssh.ts +4 -3
- package/src/tools/todo.ts +1 -1
- package/src/tools/tts.ts +203 -92
- package/src/tools/write.ts +18 -2
- package/src/tts/downloader.ts +64 -0
- package/src/tts/index.ts +8 -0
- package/src/tts/models.ts +137 -0
- package/src/tts/player.ts +137 -0
- package/src/tts/runtime.ts +21 -0
- package/src/tts/streaming-player.ts +266 -0
- package/src/tts/tts-client.ts +647 -0
- package/src/tts/tts-protocol.ts +60 -0
- package/src/tts/tts-worker.ts +497 -0
- package/src/tts/vocalizer.ts +162 -0
- package/src/tts/wav.ts +58 -0
- package/src/utils/title-generator.ts +48 -5
- package/src/utils/tool-choice.ts +16 -0
- package/src/utils/tools-manager.test.ts +25 -0
- package/src/utils/tools-manager.ts +19 -1
- package/src/web/scrapers/github.ts +96 -0
- package/src/web/search/index.ts +13 -0
- package/src/web/search/providers/searxng.ts +13 -1
- package/dist/types/stt/setup.d.ts +0 -18
- package/src/stt/setup.ts +0 -52
- package/src/stt/transcribe.py +0 -70
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import type { AgentRef } from "../registry/agent-registry";
|
|
13
13
|
import { AgentRegistry } from "../registry/agent-registry";
|
|
14
14
|
import { formatSessionHistoryMarkdown } from "../session/session-history-format";
|
|
15
|
-
import { loadSessionMessagesReadOnly } from "../session/session-
|
|
15
|
+
import { loadSessionMessagesReadOnly } from "../session/session-loader";
|
|
16
16
|
import type { InternalResource, InternalUrl, ProtocolHandler, UrlCompletion } from "./types";
|
|
17
17
|
|
|
18
18
|
/** Humanize a last-activity timestamp as `Ns/Nm/Nh/Nd ago`. */
|
|
@@ -26,6 +26,20 @@ function toLocalValidationError(error: unknown): Error {
|
|
|
26
26
|
const message = error instanceof Error ? error.message : String(error);
|
|
27
27
|
return new Error(message.replace("skill://", "local://"));
|
|
28
28
|
}
|
|
29
|
+
const WINDOWS_LOCAL_ROOT_MAX_CHARS = 180;
|
|
30
|
+
|
|
31
|
+
function safeSessionId(options: LocalProtocolOptions): string {
|
|
32
|
+
const raw = options.getSessionId?.() ?? "session";
|
|
33
|
+
const safe = raw.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
34
|
+
return safe.length > 0 ? safe : "session";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function shortLocalRoot(options: LocalProtocolOptions): string {
|
|
38
|
+
// Derive the short root from the stable session id, never the artifact path,
|
|
39
|
+
// so `SessionManager.moveTo()` and the resume-after-move flow keep finding
|
|
40
|
+
// the same `local://` directory the session wrote pre-move.
|
|
41
|
+
return path.join(os.tmpdir(), "omp-local", safeSessionId(options));
|
|
42
|
+
}
|
|
29
43
|
|
|
30
44
|
function getContentType(filePath: string): InternalResource["contentType"] {
|
|
31
45
|
const ext = path.extname(filePath).toLowerCase();
|
|
@@ -108,20 +122,28 @@ function extractRelativePath(url: InternalUrl): string {
|
|
|
108
122
|
return decoded;
|
|
109
123
|
}
|
|
110
124
|
|
|
111
|
-
|
|
125
|
+
/** Resolve the session-scoped local:// root, shortening long Windows artifact paths before writes hit MAX_PATH. */
|
|
126
|
+
export function resolveLocalRoot(options: LocalProtocolOptions, platform: NodeJS.Platform = process.platform): string {
|
|
112
127
|
const artifactsDir = options.getArtifactsDir?.();
|
|
113
128
|
if (artifactsDir) {
|
|
114
|
-
|
|
129
|
+
const candidate = path.resolve(artifactsDir, "local");
|
|
130
|
+
if (platform === "win32" && candidate.length >= WINDOWS_LOCAL_ROOT_MAX_CHARS) {
|
|
131
|
+
return shortLocalRoot(options);
|
|
132
|
+
}
|
|
133
|
+
return candidate;
|
|
115
134
|
}
|
|
116
135
|
|
|
117
|
-
|
|
118
|
-
const safeSessionId = sessionId.replace(/[^a-zA-Z0-9_.-]/g, "_");
|
|
119
|
-
return path.join(os.tmpdir(), "omp-local", safeSessionId);
|
|
136
|
+
return path.join(os.tmpdir(), "omp-local", safeSessionId(options));
|
|
120
137
|
}
|
|
121
138
|
|
|
122
|
-
|
|
139
|
+
/** Resolve a local:// URL to an on-disk path under the active session's local root. */
|
|
140
|
+
export function resolveLocalUrlToPath(
|
|
141
|
+
input: string | InternalUrl,
|
|
142
|
+
options: LocalProtocolOptions,
|
|
143
|
+
platform: NodeJS.Platform = process.platform,
|
|
144
|
+
): string {
|
|
123
145
|
const url = typeof input === "string" ? parseLocalUrl(input) : input;
|
|
124
|
-
const localRoot = path.resolve(resolveLocalRoot(options));
|
|
146
|
+
const localRoot = path.resolve(resolveLocalRoot(options, platform));
|
|
125
147
|
const relativePath = extractRelativePath(url);
|
|
126
148
|
|
|
127
149
|
if (!relativePath) {
|
package/src/main.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from "@oh-my-pi/pi-utils";
|
|
22
22
|
import chalk from "chalk";
|
|
23
23
|
import { reset as resetCapabilities } from "./capability";
|
|
24
|
-
import type
|
|
24
|
+
import { type Args, reportUnrecognizedFlags } from "./cli/args";
|
|
25
25
|
import { applyExtensionFlags, type ExtensionFlagSink } from "./cli/extension-flags";
|
|
26
26
|
import { processFileArguments } from "./cli/file-processor";
|
|
27
27
|
import { buildInitialMessage } from "./cli/initial-message";
|
|
@@ -64,7 +64,8 @@ import {
|
|
|
64
64
|
} from "./sdk";
|
|
65
65
|
import type { AgentSession } from "./session/agent-session";
|
|
66
66
|
import type { AuthStorage } from "./session/auth-storage";
|
|
67
|
-
import { resolveResumableSession, type SessionInfo
|
|
67
|
+
import { resolveResumableSession, type SessionInfo } from "./session/session-listing";
|
|
68
|
+
import { SessionManager } from "./session/session-manager";
|
|
68
69
|
import { executeBuiltinSlashCommand } from "./slash-commands/builtin-registry";
|
|
69
70
|
import { discoverTitleSystemPromptFile, resolvePromptInput } from "./system-prompt";
|
|
70
71
|
import { initTelemetryExport, isTelemetryExportEnabled } from "./telemetry-export";
|
|
@@ -264,7 +265,7 @@ export async function submitInteractiveInput(
|
|
|
264
265
|
InteractiveMode,
|
|
265
266
|
"markPendingSubmissionStarted" | "finishPendingSubmission" | "showError" | "checkShutdownRequested"
|
|
266
267
|
>,
|
|
267
|
-
session: Pick<AgentSession, "prompt" | "promptCustomMessage">,
|
|
268
|
+
session: Pick<AgentSession, "prompt" | "promptCustomMessage" | "isStreaming">,
|
|
268
269
|
input: SubmittedUserInput,
|
|
269
270
|
): Promise<void> {
|
|
270
271
|
if (input.cancelled) {
|
|
@@ -273,22 +274,32 @@ export async function submitInteractiveInput(
|
|
|
273
274
|
|
|
274
275
|
try {
|
|
275
276
|
using _keepalive = new EventLoopKeepalive();
|
|
277
|
+
const streamingBehavior = session.isStreaming ? ("followUp" as const) : undefined;
|
|
276
278
|
// Continue shortcuts submit an already-started synthetic developer prompt with
|
|
277
279
|
// no optimistic user message.
|
|
278
280
|
if (!input.started && !mode.markPendingSubmissionStarted(input)) {
|
|
279
281
|
return;
|
|
280
282
|
}
|
|
281
283
|
if (input.customType) {
|
|
282
|
-
|
|
284
|
+
const message = {
|
|
283
285
|
customType: input.customType,
|
|
284
286
|
content: input.text,
|
|
285
287
|
display: input.display ?? false,
|
|
286
|
-
attribution: "agent",
|
|
287
|
-
}
|
|
288
|
+
attribution: "agent" as const,
|
|
289
|
+
};
|
|
290
|
+
await (streamingBehavior
|
|
291
|
+
? session.promptCustomMessage(message, { streamingBehavior })
|
|
292
|
+
: session.promptCustomMessage(message));
|
|
288
293
|
} else if (input.synthetic) {
|
|
294
|
+
// Synthetic continue shortcuts are hidden developer prompts. The streaming
|
|
295
|
+
// queue (#queueUserMessage) only carries user-attributed messages, so we do
|
|
296
|
+
// NOT pass streamingBehavior here: queueing would silently demote the
|
|
297
|
+
// developer directive to a visible user message. A synthetic submit while
|
|
298
|
+
// streaming keeps its prior behavior (rejected as busy) rather than changing
|
|
299
|
+
// its role.
|
|
289
300
|
await session.prompt(input.text, { synthetic: true, expandPromptTemplates: false });
|
|
290
301
|
} else {
|
|
291
|
-
await session.prompt(input.text, { images: input.images });
|
|
302
|
+
await session.prompt(input.text, { images: input.images, ...(streamingBehavior && { streamingBehavior }) });
|
|
292
303
|
}
|
|
293
304
|
} catch (error: unknown) {
|
|
294
305
|
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
|
|
@@ -1197,6 +1208,15 @@ export async function runRootCommand(
|
|
|
1197
1208
|
},
|
|
1198
1209
|
};
|
|
1199
1210
|
const initialArgs = applyExtensionFlags(extensionFlagSink, rawArgs) ?? parsedArgs;
|
|
1211
|
+
// Fail fast on stale/typo flags (e.g. `omp --list-models`) now that we
|
|
1212
|
+
// know the real extension flag set. Without this check the unrecognized
|
|
1213
|
+
// token gets silently consumed and any following positional leaks as the
|
|
1214
|
+
// initial prompt — kicking off a real LLM session, MCP connection, and
|
|
1215
|
+
// tool calls (issue #2459). Exit code 2 matches the conventional
|
|
1216
|
+
// "command line usage error" convention.
|
|
1217
|
+
if (reportUnrecognizedFlags(initialArgs)) {
|
|
1218
|
+
process.exit(2);
|
|
1219
|
+
}
|
|
1200
1220
|
const processedFiles =
|
|
1201
1221
|
initialArgs.fileArgs.length > 0
|
|
1202
1222
|
? await logger.time("processFileArguments", () =>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export const MCP_CONNECTING_EVENT_CHANNEL = "mcp:connecting";
|
|
2
|
+
|
|
3
|
+
export type McpConnectingEvent = { serverNames: string[] };
|
|
4
|
+
|
|
5
|
+
export function formatMCPConnectingMessage(serverNames: string[]): string {
|
|
6
|
+
return `Connecting to MCP servers: ${serverNames.join(", ")}…`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Runtime validator for the cross-module event payload. The event bus is
|
|
11
|
+
* untyped at runtime, so the subscriber verifies the shape before formatting
|
|
12
|
+
* rather than trusting a cast — a malformed emit is ignored instead of throwing.
|
|
13
|
+
*/
|
|
14
|
+
export function isMcpConnectingEvent(data: unknown): data is McpConnectingEvent {
|
|
15
|
+
return (
|
|
16
|
+
typeof data === "object" &&
|
|
17
|
+
data !== null &&
|
|
18
|
+
Array.isArray((data as { serverNames?: unknown }).serverNames) &&
|
|
19
|
+
(data as { serverNames: unknown[] }).serverNames.every(name => typeof name === "string")
|
|
20
|
+
);
|
|
21
|
+
}
|
|
@@ -589,7 +589,8 @@ export class StdioTransport implements MCPTransport {
|
|
|
589
589
|
}
|
|
590
590
|
|
|
591
591
|
if (this.#readLoop) {
|
|
592
|
-
await
|
|
592
|
+
// Do not block/await the read loop as it can hang indefinitely in some environments
|
|
593
|
+
this.#readLoop.catch(() => {});
|
|
593
594
|
this.#readLoop = null;
|
|
594
595
|
}
|
|
595
596
|
}
|
package/src/memories/index.ts
CHANGED
|
@@ -5,11 +5,12 @@ import * as path from "node:path";
|
|
|
5
5
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
6
6
|
import { type ApiKey, completeSimple, Effort, type Model } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import { clampThinkingLevelForModel } from "@oh-my-pi/pi-catalog/model-thinking";
|
|
8
|
-
import { getAgentDbPath, getMemoriesDir, logger, parseJsonlLenient, prompt } from "@oh-my-pi/pi-utils";
|
|
8
|
+
import { getAgentDbPath, getMemoriesDir, isEnoent, logger, parseJsonlLenient, prompt } from "@oh-my-pi/pi-utils";
|
|
9
9
|
|
|
10
10
|
import type { ModelRegistry } from "../config/model-registry";
|
|
11
11
|
import { getModelMatchPreferences, resolveModelRoleValue } from "../config/model-resolver";
|
|
12
12
|
import type { Settings } from "../config/settings";
|
|
13
|
+
import type { MemoryBackendSaveInput, MemoryBackendSaveResult } from "../memory-backend/types";
|
|
13
14
|
import consolidationTemplate from "../prompts/memories/consolidation.md" with { type: "text" };
|
|
14
15
|
import consolidationSystemTemplate from "../prompts/memories/consolidation_system.md" with { type: "text" };
|
|
15
16
|
import readPathTemplate from "../prompts/memories/read-path.md" with { type: "text" };
|
|
@@ -156,22 +157,31 @@ export async function buildMemoryToolDeveloperInstructions(
|
|
|
156
157
|
const cfg = loadMemoryConfig(settings);
|
|
157
158
|
if (!cfg.enabled) return undefined;
|
|
158
159
|
const memoryRoot = getMemoryRoot(agentDir, settings.getCwd());
|
|
159
|
-
const summaryPath = path.join(memoryRoot, "memory_summary.md");
|
|
160
160
|
|
|
161
|
-
let
|
|
161
|
+
let summary = "";
|
|
162
162
|
try {
|
|
163
|
-
|
|
163
|
+
summary = (await Bun.file(path.join(memoryRoot, "memory_summary.md")).text()).trim();
|
|
164
164
|
} catch {
|
|
165
|
-
|
|
165
|
+
// Missing or unreadable summary — injection is best-effort; fall through
|
|
166
|
+
// so any captured lessons still surface on their own.
|
|
166
167
|
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const
|
|
171
|
-
|
|
168
|
+
const learned = await readLearnedLessons(memoryRoot);
|
|
169
|
+
if (!summary && !learned) return undefined;
|
|
170
|
+
|
|
171
|
+
const summaryOut = summary ? truncateByApproxTokens(summary, cfg.summaryInjectionTokenLimit).trim() : "";
|
|
172
|
+
// Lessons share ONE injection budget with the summary so the combined block
|
|
173
|
+
// stays within `summaryInjectionTokenLimit` (~4 chars/token, matching
|
|
174
|
+
// truncateByApproxTokens). With no summary, lessons get the whole budget.
|
|
175
|
+
// Clamp to 0: truncateByApproxTokens appends a marker, so a truncated summary
|
|
176
|
+
// can exceed `limit * 4` chars and drive the remainder negative — when the
|
|
177
|
+
// summary already fills the budget, lessons are simply dropped.
|
|
178
|
+
const learnedBudget = Math.max(0, cfg.summaryInjectionTokenLimit - Math.ceil(summaryOut.length / 4));
|
|
179
|
+
const learnedOut = learned && learnedBudget > 0 ? truncateByApproxTokens(learned, learnedBudget).trim() : "";
|
|
180
|
+
if (!summaryOut && !learnedOut) return undefined;
|
|
172
181
|
|
|
173
182
|
return prompt.render(readPathTemplate, {
|
|
174
|
-
memory_summary:
|
|
183
|
+
memory_summary: summaryOut,
|
|
184
|
+
learned: learnedOut,
|
|
175
185
|
});
|
|
176
186
|
}
|
|
177
187
|
|
|
@@ -982,6 +992,12 @@ function redactSecrets(input: string): string {
|
|
|
982
992
|
/(?:sk|pk|rk|tok|key|secret|token|password)[-_A-Za-z0-9]{12,}/g,
|
|
983
993
|
/[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}\.[A-Za-z0-9_-]{16,}/g,
|
|
984
994
|
/(?:AKIA|ASIA)[A-Z0-9]{16}/g,
|
|
995
|
+
// Common provider token prefixes (GitHub, npm, Slack, Google).
|
|
996
|
+
/(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}/g,
|
|
997
|
+
/github_pat_[A-Za-z0-9_]{20,}/g,
|
|
998
|
+
/npm_[A-Za-z0-9]{30,}/g,
|
|
999
|
+
/xox[baprs]-[A-Za-z0-9-]{10,}/g,
|
|
1000
|
+
/AIza[A-Za-z0-9_-]{30,}/g,
|
|
985
1001
|
];
|
|
986
1002
|
for (const pattern of patterns) {
|
|
987
1003
|
out = out.replace(pattern, "[REDACTED]");
|
|
@@ -1121,6 +1137,125 @@ export function getMemoryRoot(agentDir: string, cwd: string): string {
|
|
|
1121
1137
|
return path.join(getMemoriesDir(agentDir), encodeProjectPath(cwd));
|
|
1122
1138
|
}
|
|
1123
1139
|
|
|
1140
|
+
/**
|
|
1141
|
+
* Filename of the captured-lessons file under a project's memory root.
|
|
1142
|
+
*
|
|
1143
|
+
* Written by the `learn` tool via {@link saveLearnedLesson} and read back by
|
|
1144
|
+
* {@link buildMemoryToolDeveloperInstructions}. Deliberately distinct from the
|
|
1145
|
+
* consolidation artifacts (`MEMORY.md`, `memory_summary.md`, `skills/`) so a
|
|
1146
|
+
* consolidation pass never clobbers manually captured lessons.
|
|
1147
|
+
*/
|
|
1148
|
+
const LEARNED_LESSONS_FILE = "learned.md";
|
|
1149
|
+
/** Newest-first cap on retained lessons, bounding file growth by entry count. */
|
|
1150
|
+
const MAX_LEARNED_LESSONS = 100;
|
|
1151
|
+
/** Per-field char caps so a single huge capture can't bloat learned.md. */
|
|
1152
|
+
const MAX_LEARNED_CONTENT_CHARS = 2000;
|
|
1153
|
+
const MAX_LEARNED_CONTEXT_CHARS = 400;
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* Strip prompt-injection vectors from a single line of lesson text: control/
|
|
1157
|
+
* format chars, angle brackets (`</skills>`), backticks, and `~~~` fences, then
|
|
1158
|
+
* collapse whitespace. Applied on BOTH write and read (the block renders
|
|
1159
|
+
* unescaped into the system prompt), mirroring managed-skill descriptions.
|
|
1160
|
+
*/
|
|
1161
|
+
function neutralizeInjection(text: string): string {
|
|
1162
|
+
return text
|
|
1163
|
+
.replace(/[\p{Cc}\p{Cf}]/gu, " ")
|
|
1164
|
+
.replace(/[<>`]/g, "")
|
|
1165
|
+
.replace(/~{2,}/g, "~")
|
|
1166
|
+
.replace(/\s+/g, " ")
|
|
1167
|
+
.trim();
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/** Slice to `maxChars`, dropping a trailing unpaired high surrogate. */
|
|
1171
|
+
function boundChars(text: string, maxChars: number): string {
|
|
1172
|
+
if (text.length <= maxChars) return text;
|
|
1173
|
+
const sliced = text.slice(0, maxChars);
|
|
1174
|
+
return /[\uD800-\uDBFF]$/.test(sliced) ? sliced.slice(0, -1) : sliced;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
/**
|
|
1178
|
+
* Normalize one lesson field for storage: neutralize injection delimiters
|
|
1179
|
+
* FIRST, then redact secrets (so delimiter stripping can't reassemble a token
|
|
1180
|
+
* the redactor would have caught), then bound the length.
|
|
1181
|
+
*/
|
|
1182
|
+
function normalizeLearnedText(text: string, maxChars: number): string {
|
|
1183
|
+
return boundChars(redactSecrets(neutralizeInjection(text)).trim(), maxChars);
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
/** Per-path write chains serializing `learned.md` read-modify-write. */
|
|
1187
|
+
const learnedWriteChains = new Map<string, Promise<unknown>>();
|
|
1188
|
+
|
|
1189
|
+
/**
|
|
1190
|
+
* Append one lesson to the project's `learned.md` (newest-first, deduped,
|
|
1191
|
+
* capped, secret-redacted, injection-neutralized). The file backs the `learn`
|
|
1192
|
+
* tool when `memory.backend` is `local`.
|
|
1193
|
+
*/
|
|
1194
|
+
export async function saveLearnedLesson(
|
|
1195
|
+
agentDir: string,
|
|
1196
|
+
cwd: string,
|
|
1197
|
+
input: MemoryBackendSaveInput,
|
|
1198
|
+
): Promise<MemoryBackendSaveResult> {
|
|
1199
|
+
const content = normalizeLearnedText(input.content, MAX_LEARNED_CONTENT_CHARS);
|
|
1200
|
+
if (!content) {
|
|
1201
|
+
return { backend: "local", stored: 0, message: "Empty lesson; nothing stored." };
|
|
1202
|
+
}
|
|
1203
|
+
const context = input.context ? normalizeLearnedText(input.context, MAX_LEARNED_CONTEXT_CHARS) : "";
|
|
1204
|
+
const line = context ? `- ${content} _(context: ${context})_` : `- ${content}`;
|
|
1205
|
+
const filePath = path.join(getMemoryRoot(agentDir, cwd), LEARNED_LESSONS_FILE);
|
|
1206
|
+
|
|
1207
|
+
// Serialize the read-modify-write per file: parallel `learn` calls (sibling
|
|
1208
|
+
// subagents, or two shared tool calls in one turn) share the project memory
|
|
1209
|
+
// root, so an unguarded RMW would let the last writer drop the other's lesson.
|
|
1210
|
+
const run = (learnedWriteChains.get(filePath) ?? Promise.resolve()).then(() => appendLearnedLine(filePath, line));
|
|
1211
|
+
const guarded = run.catch(() => {});
|
|
1212
|
+
learnedWriteChains.set(filePath, guarded);
|
|
1213
|
+
try {
|
|
1214
|
+
await run;
|
|
1215
|
+
} finally {
|
|
1216
|
+
// Drop the entry once this write is the chain tail, so the map does not
|
|
1217
|
+
// retain one promise per distinct memory root for the process lifetime.
|
|
1218
|
+
if (learnedWriteChains.get(filePath) === guarded) learnedWriteChains.delete(filePath);
|
|
1219
|
+
}
|
|
1220
|
+
return { backend: "local", stored: 1, message: `Lesson saved to ${LEARNED_LESSONS_FILE}.` };
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
async function appendLearnedLine(filePath: string, line: string): Promise<void> {
|
|
1224
|
+
let existing = "";
|
|
1225
|
+
try {
|
|
1226
|
+
existing = await Bun.file(filePath).text();
|
|
1227
|
+
} catch (err) {
|
|
1228
|
+
if (!isEnoent(err)) throw err;
|
|
1229
|
+
}
|
|
1230
|
+
const prior = existing
|
|
1231
|
+
.split("\n")
|
|
1232
|
+
.map(l => l.trim())
|
|
1233
|
+
.filter(l => l.startsWith("- ") && l !== line);
|
|
1234
|
+
const lessons = [line, ...prior].slice(0, MAX_LEARNED_LESSONS);
|
|
1235
|
+
await Bun.write(filePath, `${lessons.join("\n")}\n`);
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Read `learned.md`, neutralizing each line on read too — a hand-edited or
|
|
1240
|
+
* pre-existing file bypasses write-time normalization and the block renders
|
|
1241
|
+
* unescaped into the system prompt. Returns "" when absent/unreadable.
|
|
1242
|
+
*/
|
|
1243
|
+
async function readLearnedLessons(memoryRoot: string): Promise<string> {
|
|
1244
|
+
let raw = "";
|
|
1245
|
+
try {
|
|
1246
|
+
raw = (await Bun.file(path.join(memoryRoot, LEARNED_LESSONS_FILE)).text()).trim();
|
|
1247
|
+
} catch {
|
|
1248
|
+
return "";
|
|
1249
|
+
}
|
|
1250
|
+
if (!raw) return "";
|
|
1251
|
+
// Neutralize delimiters THEN redact per line — mirrors the write path so a
|
|
1252
|
+
// hand-edited line cannot reassemble a token after delimiter stripping.
|
|
1253
|
+
return raw
|
|
1254
|
+
.split("\n")
|
|
1255
|
+
.map(line => redactSecrets(neutralizeInjection(line)))
|
|
1256
|
+
.join("\n");
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1124
1259
|
function encodeProjectPath(cwd: string): string {
|
|
1125
1260
|
return `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
1126
1261
|
}
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
buildMemoryToolDeveloperInstructions,
|
|
3
3
|
clearMemoryData,
|
|
4
4
|
enqueueMemoryConsolidation,
|
|
5
|
+
saveLearnedLesson,
|
|
5
6
|
startMemoryStartupTask,
|
|
6
7
|
} from "../memories";
|
|
7
8
|
import type { MemoryBackend } from "./types";
|
|
@@ -9,9 +10,10 @@ import type { MemoryBackend } from "./types";
|
|
|
9
10
|
/**
|
|
10
11
|
* Wraps the existing `memories/` module as a `MemoryBackend`.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* The rollout-summarisation pipeline (rollouts → SQLite → memory_summary.md) is
|
|
14
|
+
* delegated unchanged. On top of it, `save()` persists `learn`-tool lessons to
|
|
15
|
+
* `learned.md` (so `status()` reports `writable: true`); structured search is
|
|
16
|
+
* still unavailable.
|
|
15
17
|
*/
|
|
16
18
|
export const localBackend: MemoryBackend = {
|
|
17
19
|
id: "local",
|
|
@@ -27,13 +29,17 @@ export const localBackend: MemoryBackend = {
|
|
|
27
29
|
async enqueue(agentDir, cwd) {
|
|
28
30
|
enqueueMemoryConsolidation(agentDir, cwd);
|
|
29
31
|
},
|
|
32
|
+
async save(context, input) {
|
|
33
|
+
return saveLearnedLesson(context.agentDir, context.cwd, input);
|
|
34
|
+
},
|
|
30
35
|
async status() {
|
|
31
36
|
return {
|
|
32
37
|
backend: "local" as const,
|
|
33
38
|
active: true,
|
|
34
|
-
writable:
|
|
39
|
+
writable: true,
|
|
35
40
|
searchable: false,
|
|
36
|
-
message:
|
|
41
|
+
message:
|
|
42
|
+
"Local rollout-summary memory is active; lessons from the `learn` tool are saved to learned.md. Structured search is not available.",
|
|
37
43
|
};
|
|
38
44
|
},
|
|
39
45
|
};
|
package/src/mnemopi/backend.ts
CHANGED
package/src/mnemopi/config.ts
CHANGED
|
@@ -48,6 +48,17 @@ export function loadMnemopiConfig(settings: Settings, agentDir: string): Mnemopi
|
|
|
48
48
|
const recallBanks =
|
|
49
49
|
scoping === "global" ? scope.recallBanks : extendRecallWithLegacyBanks(scope.recallBanks, dbPath, cwd);
|
|
50
50
|
const llmMode = settings.get("mnemopi.llmMode");
|
|
51
|
+
const embeddingOverride = settings.get("mnemopi.embeddingModel");
|
|
52
|
+
const embeddingVariant = settings.get("mnemopi.embeddingVariant");
|
|
53
|
+
// Map the variant explicitly rather than indexing an object with the raw config
|
|
54
|
+
// value (which could resolve an inherited property like `__proto__`); any value
|
|
55
|
+
// other than the multilingual variant falls back to the English default.
|
|
56
|
+
const variantModel =
|
|
57
|
+
embeddingVariant === "multilingual" ? "intfloat/multilingual-e5-large" : "BAAI/bge-base-en-v1.5";
|
|
58
|
+
// Precedence: explicit `mnemopi.embeddingModel` setting > `MNEMOPI_EMBEDDING_MODEL`
|
|
59
|
+
// env (documented model-level override) > variant-derived default. Without the env
|
|
60
|
+
// term a variant default would silently shadow a user's configured env model.
|
|
61
|
+
const embeddingModel = embeddingOverride?.trim() || Bun.env.MNEMOPI_EMBEDDING_MODEL?.trim() || variantModel;
|
|
51
62
|
return {
|
|
52
63
|
dbPath,
|
|
53
64
|
baseBank: scope.baseBank,
|
|
@@ -69,7 +80,7 @@ export function loadMnemopiConfig(settings: Settings, agentDir: string): Mnemopi
|
|
|
69
80
|
providerOptions: {
|
|
70
81
|
noEmbeddings: settings.get("mnemopi.noEmbeddings"),
|
|
71
82
|
debug: settings.get("mnemopi.debug"),
|
|
72
|
-
embeddingModel
|
|
83
|
+
embeddingModel,
|
|
73
84
|
embeddingApiUrl: settings.get("mnemopi.embeddingApiUrl"),
|
|
74
85
|
embeddingApiKey: settings.get("mnemopi.embeddingApiKey"),
|
|
75
86
|
llm:
|
|
@@ -172,10 +183,10 @@ function projectBankSegment(projectRoot: string): string {
|
|
|
172
183
|
|
|
173
184
|
/**
|
|
174
185
|
* Discover sibling banks under `<dbDir>/banks/` whose `working_memory` rows
|
|
175
|
-
*
|
|
176
|
-
* the recall set. This rescues memories stranded by a
|
|
177
|
-
* bank derivation (#2412) without
|
|
178
|
-
*
|
|
186
|
+
* all carry the active `cwd` in `metadata_json.$.cwd`, and add those safe
|
|
187
|
+
* single-cwd banks to the recall set. This rescues memories stranded by a
|
|
188
|
+
* previous, less-stable bank derivation (#2412) without recalling mixed-cwd
|
|
189
|
+
* legacy banks wholesale under per-project isolation.
|
|
179
190
|
*
|
|
180
191
|
* Robust by design: a missing banks directory, unreadable bank dir, or
|
|
181
192
|
* corrupt SQLite file is silently skipped. Scanning is capped at
|
|
@@ -202,19 +213,24 @@ export function extendRecallWithLegacyBanks(
|
|
|
202
213
|
if (scanned >= LEGACY_BANK_SCAN_LIMIT) break;
|
|
203
214
|
scanned++;
|
|
204
215
|
const candidate = path.join(banksDir, entry.name, "mnemopi.db");
|
|
205
|
-
if (
|
|
216
|
+
if (bankOnlyHasCwd(candidate, cwdAbs)) extras.push(entry.name);
|
|
206
217
|
}
|
|
207
218
|
return extras.length === 0 ? resolved : [...resolved, ...extras];
|
|
208
219
|
}
|
|
209
220
|
|
|
210
|
-
function
|
|
221
|
+
function bankOnlyHasCwd(dbPath: string, cwd: string): boolean {
|
|
211
222
|
let db: Database | undefined;
|
|
212
223
|
try {
|
|
213
224
|
db = new Database(dbPath, { readonly: true });
|
|
214
225
|
const row = db
|
|
215
|
-
.
|
|
216
|
-
|
|
217
|
-
|
|
226
|
+
.prepare<{ matching: number; unsafe: number }, [string, string]>(`
|
|
227
|
+
SELECT
|
|
228
|
+
SUM(CASE WHEN json_extract(metadata_json, '$.cwd') = ? THEN 1 ELSE 0 END) AS matching,
|
|
229
|
+
SUM(CASE WHEN json_extract(metadata_json, '$.cwd') IS NULL OR json_extract(metadata_json, '$.cwd') <> ? THEN 1 ELSE 0 END) AS unsafe
|
|
230
|
+
FROM working_memory
|
|
231
|
+
`)
|
|
232
|
+
.get(cwd, cwd);
|
|
233
|
+
return (row?.matching ?? 0) > 0 && (row?.unsafe ?? 0) === 0;
|
|
218
234
|
} catch (error) {
|
|
219
235
|
logger.debug("Mnemopi: legacy bank probe failed", { dbPath, error: String(error) });
|
|
220
236
|
return false;
|
|
@@ -63,11 +63,9 @@ import { theme } from "../../modes/theme/theme";
|
|
|
63
63
|
import { type PlanApprovalDetails, resolveApprovedPlan } from "../../plan-mode/approved-plan";
|
|
64
64
|
import type { AgentSession, AgentSessionEvent } from "../../session/agent-session";
|
|
65
65
|
import { isSilentAbort, SKILL_PROMPT_MESSAGE_TYPE } from "../../session/messages";
|
|
66
|
-
import {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
type UsageStatistics,
|
|
70
|
-
} from "../../session/session-manager";
|
|
66
|
+
import type { UsageStatistics } from "../../session/session-entries";
|
|
67
|
+
import type { SessionInfo as StoredSessionInfo } from "../../session/session-listing";
|
|
68
|
+
import { SessionManager } from "../../session/session-manager";
|
|
71
69
|
import { executeAcpBuiltinSlashCommand } from "../../slash-commands/acp-builtins";
|
|
72
70
|
import { buildAvailableSlashCommands, toAcpAvailableCommands } from "../../slash-commands/available-commands";
|
|
73
71
|
import { AUTO_THINKING, parseConfiguredThinkingLevel } from "../../thinking";
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
import * as fs from "node:fs";
|
|
17
17
|
import * as path from "node:path";
|
|
18
18
|
import type { AgentMessage, AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
19
|
+
import type { Usage } from "@oh-my-pi/pi-ai";
|
|
19
20
|
import { Container, Editor, matchesKey, ScrollView, Text, type TUI } from "@oh-my-pi/pi-tui";
|
|
20
21
|
import { formatAge, formatBytes, formatDuration, formatNumber, getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
21
22
|
import { COLLAB_PROMPT_MESSAGE_TYPE, type CollabPromptDetails } from "../../collab/protocol";
|
|
@@ -35,8 +36,8 @@ import {
|
|
|
35
36
|
type SkillPromptDetails,
|
|
36
37
|
USER_INTERRUPT_LABEL,
|
|
37
38
|
} from "../../session/messages";
|
|
38
|
-
import type { SessionMessageEntry } from "../../session/session-
|
|
39
|
-
import { parseSessionEntries } from "../../session/session-
|
|
39
|
+
import type { SessionMessageEntry } from "../../session/session-entries";
|
|
40
|
+
import { parseSessionEntries } from "../../session/session-loader";
|
|
40
41
|
import { createIrcMessageCard } from "../../tools/irc";
|
|
41
42
|
import { replaceTabs, TRUNCATE_LENGTHS, truncateToWidth } from "../../tools/render-utils";
|
|
42
43
|
import { hasVisibleThinking } from "../../utils/thinking-display";
|
|
@@ -47,7 +48,7 @@ import { AssistantMessageComponent } from "./assistant-message";
|
|
|
47
48
|
import { BashExecutionComponent } from "./bash-execution";
|
|
48
49
|
import { BranchSummaryMessageComponent } from "./branch-summary-message";
|
|
49
50
|
import { CollabPromptMessageComponent } from "./collab-prompt-message";
|
|
50
|
-
import { CompactionSummaryMessageComponent } from "./compaction-summary-message";
|
|
51
|
+
import { CompactionSummaryMessageComponent, createHandoffSummaryMessageComponent } from "./compaction-summary-message";
|
|
51
52
|
import { CustomMessageComponent } from "./custom-message";
|
|
52
53
|
import { DynamicBorder } from "./dynamic-border";
|
|
53
54
|
import { EvalExecutionComponent } from "./eval-execution";
|
|
@@ -57,6 +58,7 @@ import { SkillMessageComponent } from "./skill-message";
|
|
|
57
58
|
import { formatContextUsage } from "./status-line/context-thresholds";
|
|
58
59
|
import { ToolExecutionComponent } from "./tool-execution";
|
|
59
60
|
import { TranscriptBlock, TranscriptContainer } from "./transcript-container";
|
|
61
|
+
import { createUsageRowBlock } from "./usage-row";
|
|
60
62
|
import { UserMessageComponent } from "./user-message";
|
|
61
63
|
|
|
62
64
|
/** Lines per page for PageUp/PageDown */
|
|
@@ -213,6 +215,7 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
213
215
|
#chatPendingTools = new Map<string, ToolExecutionComponent | ReadToolGroupComponent>();
|
|
214
216
|
#chatReadArgs = new Map<string, Record<string, unknown>>();
|
|
215
217
|
#chatReadGroup: ReadToolGroupComponent | null = null;
|
|
218
|
+
#pendingUsage: Usage | undefined;
|
|
216
219
|
#chatWaitingPoll: ToolExecutionComponent | null = null;
|
|
217
220
|
#chatExpandables: Array<{ setExpanded(expanded: boolean): void }> = [];
|
|
218
221
|
#chatExpanded = false;
|
|
@@ -264,6 +267,15 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
264
267
|
this.#refreshRows();
|
|
265
268
|
}
|
|
266
269
|
|
|
270
|
+
/**
|
|
271
|
+
* Whether the table view has no agents to show (every registered agent except
|
|
272
|
+
* Main, after the persisted-subagent scan in the constructor). The double-←
|
|
273
|
+
* gesture reads this to stay inert when there is nothing to open.
|
|
274
|
+
*/
|
|
275
|
+
get isEmpty(): boolean {
|
|
276
|
+
return this.#rows.length === 0;
|
|
277
|
+
}
|
|
278
|
+
|
|
267
279
|
/** Tear down every subscription and timer. Called by the overlay owner on close. */
|
|
268
280
|
dispose(): void {
|
|
269
281
|
for (const unsubscribe of this.#unsubscribers.splice(0)) unsubscribe();
|
|
@@ -438,6 +450,7 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
438
450
|
#renderRow(ref: AgentRef, selected: boolean, width: number): string {
|
|
439
451
|
const cursor = selected ? theme.fg("accent", theme.nav.cursor) : " ";
|
|
440
452
|
const parts: string[] = [statusBadge(ref.status), theme.bold(replaceTabs(ref.id))];
|
|
453
|
+
parts.push(theme.fg("dim", replaceTabs(ref.displayName)));
|
|
441
454
|
parts.push(theme.fg("dim", ref.parentId ? `${ref.kind} · of ${ref.parentId}` : ref.kind));
|
|
442
455
|
const observed = this.#observableFor(ref.id);
|
|
443
456
|
const task = observed?.description ?? observed?.progress?.task;
|
|
@@ -850,6 +863,7 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
850
863
|
this.#chatPendingTools.clear();
|
|
851
864
|
this.#chatReadArgs.clear();
|
|
852
865
|
this.#chatReadGroup = null;
|
|
866
|
+
this.#pendingUsage = undefined;
|
|
853
867
|
this.#chatWaitingPoll = null;
|
|
854
868
|
this.#chatExpandables = [];
|
|
855
869
|
this.#chatLog.dispose();
|
|
@@ -869,6 +883,13 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
869
883
|
this.#appendChatMessage(entries[i].message);
|
|
870
884
|
}
|
|
871
885
|
this.#chatBuiltCount = entries.length;
|
|
886
|
+
// Flush the trailing turn's usage row only once its tools are materialized.
|
|
887
|
+
// A read (or any tool) whose toolResult lands in a later debounced sync stays
|
|
888
|
+
// pending in #chatReadArgs / #chatPendingTools; flushing now would emit the
|
|
889
|
+
// row above it. The sync that drains the maps flushes it below the tools.
|
|
890
|
+
if (this.#chatReadArgs.size === 0 && this.#chatPendingTools.size === 0) {
|
|
891
|
+
this.#flushPendingUsage();
|
|
892
|
+
}
|
|
872
893
|
}
|
|
873
894
|
|
|
874
895
|
#trackExpandable(component: { setExpanded(expanded: boolean): void }): void {
|
|
@@ -898,7 +919,21 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
898
919
|
return this.#chatReadGroup;
|
|
899
920
|
}
|
|
900
921
|
|
|
922
|
+
// The per-turn token-usage row must land below the turn's tool blocks, but
|
|
923
|
+
// normal `read` calls only materialize their group in #appendToolResult. Defer
|
|
924
|
+
// the row: stash it on the assistant message and flush once the turn's tools
|
|
925
|
+
// are placed — before the next non-toolResult message and at the end of each
|
|
926
|
+
// sync pass — sealing the read run so the row sits under it.
|
|
927
|
+
#flushPendingUsage(): void {
|
|
928
|
+
if (!this.#pendingUsage) return;
|
|
929
|
+
this.#chatReadGroup?.seal();
|
|
930
|
+
this.#chatReadGroup = null;
|
|
931
|
+
this.#chatLog.addChild(createUsageRowBlock(this.#pendingUsage));
|
|
932
|
+
this.#pendingUsage = undefined;
|
|
933
|
+
}
|
|
934
|
+
|
|
901
935
|
#appendChatMessage(message: AgentMessage): void {
|
|
936
|
+
if (message.role !== "toolResult") this.#flushPendingUsage();
|
|
902
937
|
switch (message.role) {
|
|
903
938
|
case "assistant":
|
|
904
939
|
this.#appendAssistantMessage(message);
|
|
@@ -987,7 +1022,6 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
987
1022
|
const assistantComponent = new AssistantMessageComponent(message, this.#hideThinkingBlock?.() ?? false, () =>
|
|
988
1023
|
this.#requestRender(),
|
|
989
1024
|
);
|
|
990
|
-
assistantComponent.setUsageInfo(message.usage);
|
|
991
1025
|
this.#chatLog.addChild(assistantComponent);
|
|
992
1026
|
|
|
993
1027
|
const hasVisibleAssistantContent = message.content.some(
|
|
@@ -1066,6 +1100,8 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
1066
1100
|
this.#chatPendingTools.set(content.id, component);
|
|
1067
1101
|
}
|
|
1068
1102
|
}
|
|
1103
|
+
|
|
1104
|
+
this.#pendingUsage = settings.get("display.showTokenUsage") ? message.usage : undefined;
|
|
1069
1105
|
}
|
|
1070
1106
|
|
|
1071
1107
|
#appendToolResult(message: Extract<AgentMessage, { role: "toolResult" }>): void {
|
|
@@ -1179,6 +1215,15 @@ export class AgentHubOverlayComponent extends Container {
|
|
|
1179
1215
|
this.#chatLog.addChild(card);
|
|
1180
1216
|
return;
|
|
1181
1217
|
}
|
|
1218
|
+
const handoffComponent = createHandoffSummaryMessageComponent(
|
|
1219
|
+
message as CustomMessage<unknown>,
|
|
1220
|
+
this.#chatExpanded,
|
|
1221
|
+
);
|
|
1222
|
+
if (handoffComponent) {
|
|
1223
|
+
this.#trackExpandable(handoffComponent);
|
|
1224
|
+
this.#chatLog.addChild(handoffComponent);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
1182
1227
|
const component = new CustomMessageComponent(
|
|
1183
1228
|
message as CustomMessage<unknown>,
|
|
1184
1229
|
this.#getMessageRenderer?.(message.customType),
|