@oh-my-pi/pi-coding-agent 15.10.11 → 15.10.12
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 +44 -0
- package/dist/cli.js +5349 -5328
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli-commands.d.ts +12 -0
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/api-key-resolver.d.ts +3 -0
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +18 -0
- package/dist/types/config/settings-schema.d.ts +29 -1
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
- package/dist/types/eval/py/executor.d.ts +5 -0
- package/dist/types/eval/py/kernel.d.ts +6 -1
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/extensibility/extensions/runner.d.ts +3 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/runtime.d.ts +4 -0
- package/dist/types/memory-backend/types.d.ts +66 -1
- package/dist/types/modes/index.d.ts +3 -3
- package/dist/types/modes/interactive-mode.d.ts +7 -2
- package/dist/types/modes/oauth-manual-input.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
- package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
- package/dist/types/modes/setup-wizard/index.d.ts +5 -1
- package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/secrets/index.d.ts +1 -1
- package/dist/types/secrets/obfuscator.d.ts +8 -2
- package/dist/types/session/agent-session.d.ts +14 -2
- package/dist/types/session/streaming-output.d.ts +23 -0
- package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
- package/dist/types/slash-commands/types.d.ts +1 -1
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/index.d.ts +2 -2
- package/dist/types/task/types.d.ts +8 -0
- package/dist/types/thinking.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +11 -0
- package/dist/types/tiny/title-protocol.d.ts +1 -0
- package/dist/types/tools/index.d.ts +6 -0
- package/dist/types/utils/git.d.ts +15 -2
- package/dist/types/utils/title-generator.d.ts +3 -2
- package/package.json +10 -10
- package/src/auto-thinking/classifier.ts +1 -0
- package/src/cli/args.ts +3 -0
- package/src/cli-commands.ts +29 -0
- package/src/cli.ts +8 -9
- package/src/commands/launch.ts +4 -0
- package/src/commit/model-selection.ts +3 -2
- package/src/config/api-key-resolver.ts +8 -6
- package/src/config/model-registry.ts +97 -30
- package/src/config/model-resolver.ts +60 -0
- package/src/config/settings-schema.ts +43 -15
- package/src/config/settings.ts +61 -3
- package/src/edit/hashline/execute.ts +39 -2
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/eval/completion-bridge.ts +1 -0
- package/src/eval/py/executor.ts +29 -7
- package/src/eval/py/index.ts +6 -1
- package/src/eval/py/kernel.ts +31 -11
- package/src/eval/py/runtime.ts +37 -0
- package/src/exec/bash-executor.ts +82 -3
- package/src/extensibility/extensions/get-commands-handler.ts +2 -1
- package/src/extensibility/extensions/runner.ts +6 -1
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/hindsight/bank.ts +17 -2
- package/src/internal-urls/docs-index.generated.ts +3 -3
- package/src/main.ts +18 -6
- package/src/memories/index.ts +2 -0
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/local-backend.ts +9 -0
- package/src/memory-backend/off-backend.ts +9 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/types.ts +81 -1
- package/src/mnemopi/backend.ts +151 -4
- package/src/modes/acp/acp-agent.ts +119 -11
- package/src/modes/components/assistant-message.ts +19 -21
- package/src/modes/components/footer.ts +3 -1
- package/src/modes/components/status-line/component.ts +118 -34
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +1 -0
- package/src/modes/controllers/mcp-command-controller.ts +38 -3
- package/src/modes/index.ts +3 -21
- package/src/modes/interactive-mode.ts +39 -9
- package/src/modes/oauth-manual-input.ts +30 -3
- package/src/modes/rpc/rpc-client.ts +154 -3
- package/src/modes/rpc/rpc-mode.ts +97 -12
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +81 -1
- package/src/modes/setup-wizard/index.ts +12 -2
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/types.ts +2 -0
- package/src/sdk.ts +8 -1
- package/src/secrets/index.ts +8 -1
- package/src/secrets/obfuscator.ts +39 -18
- package/src/session/agent-session.ts +179 -54
- package/src/session/streaming-output.ts +166 -10
- package/src/slash-commands/acp-builtins.ts +24 -0
- package/src/slash-commands/builtin-registry.ts +20 -0
- package/src/slash-commands/types.ts +1 -1
- package/src/system-prompt.ts +14 -0
- package/src/task/executor.ts +13 -12
- package/src/task/index.ts +9 -8
- package/src/task/render.ts +18 -3
- package/src/task/types.ts +9 -0
- package/src/thinking.ts +7 -0
- package/src/tiny/title-client.ts +34 -5
- package/src/tiny/title-protocol.ts +1 -1
- package/src/tiny/worker.ts +6 -4
- package/src/tools/bash.ts +46 -5
- package/src/tools/image-gen.ts +11 -4
- package/src/tools/index.ts +13 -1
- package/src/tools/inspect-image.ts +1 -0
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/git.ts +267 -13
- package/src/utils/title-generator.ts +24 -5
package/src/main.ts
CHANGED
|
@@ -67,7 +67,7 @@ import {
|
|
|
67
67
|
import type { AgentSession } from "./session/agent-session";
|
|
68
68
|
import type { AuthStorage } from "./session/auth-storage";
|
|
69
69
|
import { resolveResumableSession, type SessionInfo, SessionManager } from "./session/session-manager";
|
|
70
|
-
import { resolvePromptInput } from "./system-prompt";
|
|
70
|
+
import { discoverTitleSystemPromptFile, resolvePromptInput } from "./system-prompt";
|
|
71
71
|
import { initTelemetryExport, isTelemetryExportEnabled } from "./telemetry-export";
|
|
72
72
|
import { AUTO_THINKING } from "./thinking";
|
|
73
73
|
import { discoverStartupLspServers, type LspStartupServerInfo } from "./tools";
|
|
@@ -85,6 +85,7 @@ type RunPrintMode = (session: AgentSession, options: PrintModeOptions) => Promis
|
|
|
85
85
|
type RunRpcMode = (
|
|
86
86
|
session: AgentSession,
|
|
87
87
|
setToolUIContext?: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
|
|
88
|
+
eventBus?: EventBus,
|
|
88
89
|
) => Promise<never>;
|
|
89
90
|
|
|
90
91
|
function maybeShowStartupSplash(options: {
|
|
@@ -370,6 +371,7 @@ async function runInteractiveMode(
|
|
|
370
371
|
eventBus?: EventBus,
|
|
371
372
|
initialMessage?: string,
|
|
372
373
|
initialImages?: ImageContent[],
|
|
374
|
+
titleSystemPrompt?: string,
|
|
373
375
|
): Promise<void> {
|
|
374
376
|
const mode = new InteractiveMode(
|
|
375
377
|
session,
|
|
@@ -379,6 +381,7 @@ async function runInteractiveMode(
|
|
|
379
381
|
lspServers,
|
|
380
382
|
mcpManager,
|
|
381
383
|
eventBus,
|
|
384
|
+
titleSystemPrompt,
|
|
382
385
|
);
|
|
383
386
|
|
|
384
387
|
// Cold-launch gate: the full setup wizard (every scene + the overlay and
|
|
@@ -724,7 +727,7 @@ async function buildSessionOptions(
|
|
|
724
727
|
sessionManager: SessionManager | undefined,
|
|
725
728
|
modelRegistry: ModelRegistry,
|
|
726
729
|
activeSettings: Settings,
|
|
727
|
-
): Promise<{ options: CreateAgentSessionOptions }> {
|
|
730
|
+
): Promise<{ options: CreateAgentSessionOptions; titleSystemPrompt?: string }> {
|
|
728
731
|
const options: CreateAgentSessionOptions = {
|
|
729
732
|
cwd: parsed.cwd ?? getProjectDir(),
|
|
730
733
|
autoApprove: parsed.autoApprove ?? false,
|
|
@@ -735,6 +738,8 @@ async function buildSessionOptions(
|
|
|
735
738
|
const resolvedSystemPrompt = await resolvePromptInput(systemPromptSource, "system prompt");
|
|
736
739
|
const appendPromptSource = parsed.appendSystemPrompt ?? discoverAppendSystemPromptFile();
|
|
737
740
|
const resolvedAppendPrompt = await resolvePromptInput(appendPromptSource, "append system prompt");
|
|
741
|
+
const titleSystemPromptSource = discoverTitleSystemPromptFile();
|
|
742
|
+
const titleSystemPrompt = await resolvePromptInput(titleSystemPromptSource, "title system prompt");
|
|
738
743
|
|
|
739
744
|
if (sessionManager) {
|
|
740
745
|
options.sessionManager = sessionManager;
|
|
@@ -880,7 +885,7 @@ async function buildSessionOptions(
|
|
|
880
885
|
options.additionalExtensionPaths = [];
|
|
881
886
|
}
|
|
882
887
|
|
|
883
|
-
return { options };
|
|
888
|
+
return { options, titleSystemPrompt };
|
|
884
889
|
}
|
|
885
890
|
|
|
886
891
|
interface RunRootCommandDependencies {
|
|
@@ -920,6 +925,7 @@ export async function runRootCommand(
|
|
|
920
925
|
if (parsedArgs.listModels !== undefined) {
|
|
921
926
|
const settingsInstance = await logger.time("settings:init:list-models", Settings.init, {
|
|
922
927
|
cwd: getProjectDir(),
|
|
928
|
+
configFiles: parsedArgs.config,
|
|
923
929
|
});
|
|
924
930
|
await modelRegistry.refresh("online");
|
|
925
931
|
const cliExtensionPaths = parsedArgs.noExtensions
|
|
@@ -983,11 +989,16 @@ export async function runRootCommand(
|
|
|
983
989
|
}
|
|
984
990
|
|
|
985
991
|
let cwd = getProjectDir();
|
|
986
|
-
const settingsInstance =
|
|
992
|
+
const settingsInstance =
|
|
993
|
+
deps.settings ?? (await logger.time("settings:init", Settings.init, { cwd, configFiles: parsedArgs.config }));
|
|
987
994
|
if (parsedArgs.approvalMode) {
|
|
988
995
|
// Runtime override (not persisted): every settings.get("tools.approvalMode") downstream
|
|
989
996
|
// sees this value. The wrapper still honours --auto-approve / --yolo on top of it.
|
|
990
997
|
settingsInstance.override("tools.approvalMode", parsedArgs.approvalMode);
|
|
998
|
+
} else if (parsedArgs.autoApprove) {
|
|
999
|
+
// --auto-approve / --yolo without an explicit --approval-mode: reflect in settings so
|
|
1000
|
+
// setup-time checks (e.g. #wrapToolForAcpPermission) also see the yolo intent.
|
|
1001
|
+
settingsInstance.override("tools.approvalMode", "yolo");
|
|
991
1002
|
}
|
|
992
1003
|
if (parsedArgs.mode === "rpc" || parsedArgs.mode === "rpc-ui") {
|
|
993
1004
|
applyRpcDefaultSettingOverrides(settingsInstance);
|
|
@@ -1133,7 +1144,7 @@ export async function runRootCommand(
|
|
|
1133
1144
|
clearPluginRootsCache: clearPluginRootsAndCaches,
|
|
1134
1145
|
});
|
|
1135
1146
|
|
|
1136
|
-
const { options: sessionOptions } = await logger.time(
|
|
1147
|
+
const { options: sessionOptions, titleSystemPrompt } = await logger.time(
|
|
1137
1148
|
"buildSessionOptions",
|
|
1138
1149
|
buildSessionOptions,
|
|
1139
1150
|
parsedArgs,
|
|
@@ -1298,7 +1309,7 @@ export async function runRootCommand(
|
|
|
1298
1309
|
// Branch-only protocol runner: keep RPC host code out of normal interactive startup.
|
|
1299
1310
|
const runRpcMode: RunRpcMode = (await import("./modes/rpc/rpc-mode")).runRpcMode;
|
|
1300
1311
|
stopStartupWatchdog();
|
|
1301
|
-
await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined);
|
|
1312
|
+
await runRpcMode(session, mode === "rpc-ui" ? setToolUIContext : undefined, eventBus);
|
|
1302
1313
|
} else if (isInteractive) {
|
|
1303
1314
|
const versionCheckPromise = checkForNewVersion(VERSION).catch(() => undefined);
|
|
1304
1315
|
const changelogMarkdown = await logger.time("main:getChangelogForDisplay", getChangelogForDisplay, parsedArgs);
|
|
@@ -1338,6 +1349,7 @@ export async function runRootCommand(
|
|
|
1338
1349
|
eventBus,
|
|
1339
1350
|
initialMessage,
|
|
1340
1351
|
initialImages,
|
|
1352
|
+
titleSystemPrompt,
|
|
1341
1353
|
);
|
|
1342
1354
|
} else {
|
|
1343
1355
|
// Branch-only single-shot runner: keep print-mode code out of normal interactive startup.
|
package/src/memories/index.ts
CHANGED
|
@@ -276,6 +276,7 @@ async function runPhase1(options: {
|
|
|
276
276
|
apiKey: modelRegistry.resolver(phase1Model.provider, {
|
|
277
277
|
sessionId: session.sessionId,
|
|
278
278
|
baseUrl: phase1Model.baseUrl,
|
|
279
|
+
modelId: phase1Model.id,
|
|
279
280
|
}),
|
|
280
281
|
modelMaxTokens: computeModelTokenBudget(phase1Model, config),
|
|
281
282
|
config,
|
|
@@ -436,6 +437,7 @@ async function runPhase2(options: {
|
|
|
436
437
|
apiKey: modelRegistry.resolver(phase2Model.provider, {
|
|
437
438
|
sessionId: session.sessionId,
|
|
438
439
|
baseUrl: phase2Model.baseUrl,
|
|
440
|
+
modelId: phase2Model.id,
|
|
439
441
|
}),
|
|
440
442
|
metadata: session.agent?.metadataForProvider(phase2Model.provider),
|
|
441
443
|
});
|
|
@@ -27,4 +27,13 @@ export const localBackend: MemoryBackend = {
|
|
|
27
27
|
async enqueue(agentDir, cwd) {
|
|
28
28
|
enqueueMemoryConsolidation(agentDir, cwd);
|
|
29
29
|
},
|
|
30
|
+
async status() {
|
|
31
|
+
return {
|
|
32
|
+
backend: "local" as const,
|
|
33
|
+
active: true,
|
|
34
|
+
writable: false,
|
|
35
|
+
searchable: false,
|
|
36
|
+
message: "Local rollout-summary memory is active; structured search/save is not available.",
|
|
37
|
+
};
|
|
38
|
+
},
|
|
30
39
|
};
|
|
@@ -13,4 +13,13 @@ export const offBackend: MemoryBackend = {
|
|
|
13
13
|
},
|
|
14
14
|
async clear() {},
|
|
15
15
|
async enqueue() {},
|
|
16
|
+
async status() {
|
|
17
|
+
return {
|
|
18
|
+
backend: "off" as const,
|
|
19
|
+
active: false,
|
|
20
|
+
writable: false,
|
|
21
|
+
searchable: false,
|
|
22
|
+
message: "Memory backend is off.",
|
|
23
|
+
};
|
|
24
|
+
},
|
|
16
25
|
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { AgentSession } from "../session/agent-session";
|
|
2
|
+
import { resolveMemoryBackend } from "./resolve";
|
|
3
|
+
import type {
|
|
4
|
+
MemoryBackendId,
|
|
5
|
+
MemoryBackendOperationContext,
|
|
6
|
+
MemoryBackendSaveInput,
|
|
7
|
+
MemoryBackendSearchOptions,
|
|
8
|
+
MemoryRuntimeContext,
|
|
9
|
+
} from "./types";
|
|
10
|
+
export function createMemoryRuntimeContext(context: MemoryBackendOperationContext): MemoryRuntimeContext {
|
|
11
|
+
const settings = context.session?.settings;
|
|
12
|
+
return {
|
|
13
|
+
async status() {
|
|
14
|
+
if (!settings) {
|
|
15
|
+
return {
|
|
16
|
+
backend: "off" as const,
|
|
17
|
+
active: false,
|
|
18
|
+
writable: false,
|
|
19
|
+
searchable: false,
|
|
20
|
+
message: "No active agent session.",
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const backend = await resolveMemoryBackend(settings);
|
|
24
|
+
return backend.status
|
|
25
|
+
? await backend.status(context)
|
|
26
|
+
: {
|
|
27
|
+
backend: backend.id,
|
|
28
|
+
active: backend.id !== "off",
|
|
29
|
+
writable: false,
|
|
30
|
+
searchable: false,
|
|
31
|
+
message: "This memory backend does not expose structured status.",
|
|
32
|
+
};
|
|
33
|
+
},
|
|
34
|
+
async search(query: string, options?: MemoryBackendSearchOptions) {
|
|
35
|
+
if (!settings) return unavailableSearch("off", query, "No active agent session.");
|
|
36
|
+
const backend = await resolveMemoryBackend(settings);
|
|
37
|
+
return backend.search
|
|
38
|
+
? await backend.search(context, query, options)
|
|
39
|
+
: unavailableSearch(backend.id, query, `Memory search is not available for the ${backend.id} backend.`);
|
|
40
|
+
},
|
|
41
|
+
async save(input: string | MemoryBackendSaveInput) {
|
|
42
|
+
if (!settings) return unavailableSave("off", "No active agent session.");
|
|
43
|
+
const backend = await resolveMemoryBackend(settings);
|
|
44
|
+
const normalized = typeof input === "string" ? { content: input } : input;
|
|
45
|
+
return backend.save
|
|
46
|
+
? await backend.save(context, normalized)
|
|
47
|
+
: unavailableSave(backend.id, `Memory save is not available for the ${backend.id} backend.`);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createSessionMemoryRuntimeContext(
|
|
53
|
+
session: AgentSession,
|
|
54
|
+
agentDir: string,
|
|
55
|
+
cwd: string,
|
|
56
|
+
): MemoryRuntimeContext {
|
|
57
|
+
return createMemoryRuntimeContext({ agentDir, cwd, session });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function unavailableSearch(backend: MemoryBackendId, query: string, message: string) {
|
|
61
|
+
return { backend, query, count: 0, items: [], message };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function unavailableSave(backend: MemoryBackendId, message: string) {
|
|
65
|
+
return { backend, stored: 0, message };
|
|
66
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Memory backend abstraction.
|
|
3
3
|
*
|
|
4
|
-
* Backends are mutually exclusive — `await resolveMemoryBackend(settings)`
|
|
4
|
+
* Backends are mutually exclusive — `await resolveMemoryBackend(settings)` returns
|
|
5
5
|
* exactly one. Implementations MUST be self-contained: they own the per-session
|
|
6
6
|
* state they create in `start()` and tear it down on `clear()`.
|
|
7
7
|
*/
|
|
@@ -15,6 +15,73 @@ import type { AgentSession } from "../session/agent-session";
|
|
|
15
15
|
|
|
16
16
|
export type MemoryBackendId = "off" | "local" | "hindsight" | "mnemopi";
|
|
17
17
|
|
|
18
|
+
export interface MemoryBackendStatus {
|
|
19
|
+
backend: MemoryBackendId;
|
|
20
|
+
active: boolean;
|
|
21
|
+
writable: boolean;
|
|
22
|
+
searchable: boolean;
|
|
23
|
+
scope?: string;
|
|
24
|
+
retainBank?: string;
|
|
25
|
+
recallBanks?: string[];
|
|
26
|
+
workingCount?: number;
|
|
27
|
+
episodicCount?: number;
|
|
28
|
+
tripleCount?: number;
|
|
29
|
+
lastMemory?: string;
|
|
30
|
+
lastRecall?: boolean;
|
|
31
|
+
database?: string;
|
|
32
|
+
message?: string;
|
|
33
|
+
error?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface MemoryBackendSearchOptions {
|
|
37
|
+
limit?: number;
|
|
38
|
+
/** Best-effort abort signal. Backends may only observe it before/after an underlying recall call. */
|
|
39
|
+
signal?: AbortSignal;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface MemoryBackendSearchItem {
|
|
43
|
+
id?: string;
|
|
44
|
+
content: string;
|
|
45
|
+
source?: string;
|
|
46
|
+
timestamp?: string;
|
|
47
|
+
score?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface MemoryBackendSearchResult {
|
|
51
|
+
backend: MemoryBackendId;
|
|
52
|
+
query: string;
|
|
53
|
+
count: number;
|
|
54
|
+
items: MemoryBackendSearchItem[];
|
|
55
|
+
message?: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface MemoryBackendSaveInput {
|
|
59
|
+
content: string;
|
|
60
|
+
context?: string;
|
|
61
|
+
source?: string;
|
|
62
|
+
importance?: number;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface MemoryBackendSaveResult {
|
|
66
|
+
backend: MemoryBackendId;
|
|
67
|
+
stored: number;
|
|
68
|
+
ids?: string[];
|
|
69
|
+
queued?: boolean;
|
|
70
|
+
message?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface MemoryBackendOperationContext {
|
|
74
|
+
agentDir: string;
|
|
75
|
+
cwd: string;
|
|
76
|
+
session?: AgentSession;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface MemoryRuntimeContext {
|
|
80
|
+
status(): Promise<MemoryBackendStatus>;
|
|
81
|
+
search(query: string, options?: MemoryBackendSearchOptions): Promise<MemoryBackendSearchResult>;
|
|
82
|
+
save(input: string | MemoryBackendSaveInput): Promise<MemoryBackendSaveResult>;
|
|
83
|
+
}
|
|
84
|
+
|
|
18
85
|
export interface MemoryBackendStartOptions {
|
|
19
86
|
session: AgentSession;
|
|
20
87
|
settings: Settings;
|
|
@@ -53,6 +120,19 @@ export interface MemoryBackend {
|
|
|
53
120
|
/** Force consolidation/retain to happen now (slash `/memory enqueue`). */
|
|
54
121
|
enqueue(agentDir: string, cwd: string, session?: AgentSession): Promise<void>;
|
|
55
122
|
|
|
123
|
+
/** Structured state for UI, slash commands, and extensions. */
|
|
124
|
+
status?(context: MemoryBackendOperationContext): Promise<MemoryBackendStatus>;
|
|
125
|
+
|
|
126
|
+
/** Explicit user-facing semantic/lexical search. */
|
|
127
|
+
search?(
|
|
128
|
+
context: MemoryBackendOperationContext,
|
|
129
|
+
query: string,
|
|
130
|
+
options?: MemoryBackendSearchOptions,
|
|
131
|
+
): Promise<MemoryBackendSearchResult>;
|
|
132
|
+
|
|
133
|
+
/** Explicit user-facing save operation. */
|
|
134
|
+
save?(context: MemoryBackendOperationContext, input: MemoryBackendSaveInput): Promise<MemoryBackendSaveResult>;
|
|
135
|
+
|
|
56
136
|
/** Render backend-specific memory statistics as markdown (`/memory stats`). */
|
|
57
137
|
stats?(agentDir: string, cwd: string, session?: AgentSession): Promise<string | undefined>;
|
|
58
138
|
|
package/src/mnemopi/backend.ts
CHANGED
|
@@ -5,10 +5,15 @@ import type { Mnemopi } from "@oh-my-pi/pi-mnemopi";
|
|
|
5
5
|
import type * as MnemopiDiagnoseNs from "@oh-my-pi/pi-mnemopi/diagnose";
|
|
6
6
|
import type { DiagnosticSummary } from "@oh-my-pi/pi-mnemopi/diagnose";
|
|
7
7
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
8
|
-
|
|
9
8
|
import type { ModelRegistry } from "../config/model-registry";
|
|
10
9
|
import { resolveRoleSelection } from "../config/model-resolver";
|
|
11
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
MemoryBackend,
|
|
12
|
+
MemoryBackendSaveInput,
|
|
13
|
+
MemoryBackendSearchItem,
|
|
14
|
+
MemoryBackendStartOptions,
|
|
15
|
+
MemoryBackendStatus,
|
|
16
|
+
} from "../memory-backend/types";
|
|
12
17
|
import memoryConsolidationPrompt from "../prompts/system/memory-consolidation-system.md" with { type: "text" };
|
|
13
18
|
import memoryExtractionPrompt from "../prompts/system/memory-extraction-system.md" with { type: "text" };
|
|
14
19
|
import type { AgentSession } from "../session/agent-session";
|
|
@@ -166,6 +171,101 @@ export const mnemopiBackend: MemoryBackend = {
|
|
|
166
171
|
return renderMnemopiDiagnostics(summaries);
|
|
167
172
|
},
|
|
168
173
|
|
|
174
|
+
async status({ agentDir, session }): Promise<MemoryBackendStatus> {
|
|
175
|
+
const state = getMnemopiSessionState(session);
|
|
176
|
+
const primary = state?.aliasOf ?? state;
|
|
177
|
+
if (!primary) {
|
|
178
|
+
return {
|
|
179
|
+
backend: "mnemopi",
|
|
180
|
+
active: false,
|
|
181
|
+
writable: false,
|
|
182
|
+
searchable: false,
|
|
183
|
+
message: "Mnemopi backend is not initialised for this session.",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const { targets, owned } = createStatsTargets(agentDir, session);
|
|
188
|
+
try {
|
|
189
|
+
if (targets.length === 0) {
|
|
190
|
+
return {
|
|
191
|
+
backend: "mnemopi",
|
|
192
|
+
active: false,
|
|
193
|
+
writable: false,
|
|
194
|
+
searchable: false,
|
|
195
|
+
message: "Mnemopi backend is configured but not initialised for this session.",
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
return summarizeMnemopiStatus(targets, session);
|
|
199
|
+
} finally {
|
|
200
|
+
for (const memory of owned) memory.close();
|
|
201
|
+
}
|
|
202
|
+
},
|
|
203
|
+
|
|
204
|
+
async search({ session }, query, options) {
|
|
205
|
+
const state = getMnemopiSessionState(session);
|
|
206
|
+
const primary = state?.aliasOf ?? state;
|
|
207
|
+
if (!primary) {
|
|
208
|
+
return {
|
|
209
|
+
backend: "mnemopi",
|
|
210
|
+
query,
|
|
211
|
+
count: 0,
|
|
212
|
+
items: [],
|
|
213
|
+
message: "Mnemopi backend is not initialised for this session.",
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (options?.signal?.aborted) {
|
|
217
|
+
return { backend: "mnemopi", query, count: 0, items: [], message: "Search aborted." };
|
|
218
|
+
}
|
|
219
|
+
const limit = clampLimit(options?.limit);
|
|
220
|
+
const results = (await primary.recallResultsScoped(query)).slice(0, limit);
|
|
221
|
+
if (options?.signal?.aborted) {
|
|
222
|
+
return { backend: "mnemopi", query, count: 0, items: [], message: "Search aborted." };
|
|
223
|
+
}
|
|
224
|
+
const items: MemoryBackendSearchItem[] = results.map(result => ({
|
|
225
|
+
id: result.id,
|
|
226
|
+
content: result.content,
|
|
227
|
+
source: result.source ?? undefined,
|
|
228
|
+
timestamp: result.timestamp ?? undefined,
|
|
229
|
+
score: result.score,
|
|
230
|
+
}));
|
|
231
|
+
return { backend: "mnemopi", query, count: items.length, items };
|
|
232
|
+
},
|
|
233
|
+
|
|
234
|
+
async save({ cwd, session }, input: MemoryBackendSaveInput) {
|
|
235
|
+
const state = getMnemopiSessionState(session);
|
|
236
|
+
const primary = state?.aliasOf ?? state;
|
|
237
|
+
if (!primary) {
|
|
238
|
+
return {
|
|
239
|
+
backend: "mnemopi",
|
|
240
|
+
stored: 0,
|
|
241
|
+
message: "Mnemopi backend is not initialised for this session.",
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
const content = input.content.trim();
|
|
245
|
+
if (!content) return { backend: "mnemopi", stored: 0, message: "Memory content is empty." };
|
|
246
|
+
const id = primary.rememberScoped(content, {
|
|
247
|
+
source: input.source || "coding-agent-memory-command",
|
|
248
|
+
importance: normalizeImportance(input.importance),
|
|
249
|
+
metadata: {
|
|
250
|
+
session_id: primary.sessionId,
|
|
251
|
+
cwd,
|
|
252
|
+
context: input.context ?? null,
|
|
253
|
+
operation: "memory.save",
|
|
254
|
+
},
|
|
255
|
+
scope: "bank",
|
|
256
|
+
extract: true,
|
|
257
|
+
extractEntities: true,
|
|
258
|
+
veracity: "user",
|
|
259
|
+
memoryType: "fact",
|
|
260
|
+
});
|
|
261
|
+
return {
|
|
262
|
+
backend: "mnemopi",
|
|
263
|
+
stored: id ? 1 : 0,
|
|
264
|
+
ids: id ? [id] : [],
|
|
265
|
+
message: id ? undefined : "Mnemopi did not return a stored memory id.",
|
|
266
|
+
};
|
|
267
|
+
},
|
|
268
|
+
|
|
169
269
|
async preCompactionContext(messages, _settings, session): Promise<string | undefined> {
|
|
170
270
|
const state = getMnemopiSessionState(session);
|
|
171
271
|
return await state?.recallForCompaction(messages);
|
|
@@ -247,6 +347,52 @@ function renderMnemopiStats(targets: readonly MnemopiStatsTarget[]): string {
|
|
|
247
347
|
return lines.join("\n");
|
|
248
348
|
}
|
|
249
349
|
|
|
350
|
+
function summarizeMnemopiStatus(
|
|
351
|
+
targets: readonly MnemopiStatsTarget[],
|
|
352
|
+
session: AgentSession | undefined,
|
|
353
|
+
): MemoryBackendStatus {
|
|
354
|
+
let workingCount = 0;
|
|
355
|
+
let episodicCount = 0;
|
|
356
|
+
let tripleCount = 0;
|
|
357
|
+
let lastMemory: string | undefined;
|
|
358
|
+
let database: string | undefined;
|
|
359
|
+
for (const target of targets) {
|
|
360
|
+
const stats = target.memory.getStats();
|
|
361
|
+
workingCount += statCount(stats.beam.working_memory);
|
|
362
|
+
episodicCount += statCount(stats.beam.episodic_memory);
|
|
363
|
+
tripleCount += stats.beam.triples.total;
|
|
364
|
+
lastMemory ??= stats.last_memory ?? undefined;
|
|
365
|
+
database ??= stats.database ? shortenPath(stats.database) : undefined;
|
|
366
|
+
}
|
|
367
|
+
const state = getMnemopiSessionState(session);
|
|
368
|
+
const primary = state?.aliasOf ?? state;
|
|
369
|
+
return {
|
|
370
|
+
backend: "mnemopi",
|
|
371
|
+
active: true,
|
|
372
|
+
writable: true,
|
|
373
|
+
searchable: true,
|
|
374
|
+
scope: primary?.config.scoping,
|
|
375
|
+
retainBank: primary?.getScopedRetainTarget().bank ?? targets[0]?.bank,
|
|
376
|
+
recallBanks: primary?.getScopedRecallTargets().map(target => target.bank) ?? targets.map(target => target.bank),
|
|
377
|
+
workingCount,
|
|
378
|
+
episodicCount,
|
|
379
|
+
tripleCount,
|
|
380
|
+
lastMemory,
|
|
381
|
+
lastRecall: Boolean(primary?.lastRecallSnippet),
|
|
382
|
+
database,
|
|
383
|
+
};
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function clampLimit(limit: number | undefined): number {
|
|
387
|
+
if (!Number.isFinite(limit)) return 10;
|
|
388
|
+
return Math.max(1, Math.min(50, Math.trunc(limit ?? 10)));
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function normalizeImportance(value: number | undefined): number {
|
|
392
|
+
if (!Number.isFinite(value)) return 0.75;
|
|
393
|
+
return Math.max(0, Math.min(1, value ?? 0.75));
|
|
394
|
+
}
|
|
395
|
+
|
|
250
396
|
function renderMnemopiDiagnostics(entries: readonly { bank: string; summary: DiagnosticSummary }[]): string {
|
|
251
397
|
const lines = [
|
|
252
398
|
"# Mnemopi Memory Diagnostics",
|
|
@@ -343,8 +489,8 @@ async function resolveMnemopiProviderOptions(
|
|
|
343
489
|
return {
|
|
344
490
|
...base,
|
|
345
491
|
llm: async (prompt, opts) => {
|
|
346
|
-
const
|
|
347
|
-
if (!
|
|
492
|
+
const hasApiKey = await modelRegistry.getApiKey(model, sessionId);
|
|
493
|
+
if (!hasApiKey) {
|
|
348
494
|
logger.warn("Mnemopi: smol completion requested but no current API key is available.", {
|
|
349
495
|
provider: model.provider,
|
|
350
496
|
model: model.id,
|
|
@@ -360,6 +506,7 @@ async function resolveMnemopiProviderOptions(
|
|
|
360
506
|
apiKey: modelRegistry.resolver(model.provider, {
|
|
361
507
|
sessionId,
|
|
362
508
|
baseUrl: model.baseUrl,
|
|
509
|
+
modelId: model.id,
|
|
363
510
|
}),
|
|
364
511
|
maxTokens: opts?.maxTokens,
|
|
365
512
|
temperature: opts?.temperature,
|