@oh-my-pi/pi-coding-agent 15.10.11 → 15.11.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 +103 -2
- package/dist/cli.js +5790 -5731
- package/dist/types/async/index.d.ts +0 -1
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +5 -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/keybindings.d.ts +6 -1
- 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 +85 -34
- 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/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +3 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/internal-urls/history-protocol.d.ts +14 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/irc/bus.d.ts +66 -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/components/agent-hub.d.ts +30 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +10 -4
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
- package/dist/types/modes/components/welcome.d.ts +3 -9
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/index.d.ts +3 -3
- package/dist/types/modes/interactive-mode.d.ts +10 -4
- 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/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +5 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-lifecycle.d.ts +51 -0
- package/dist/types/registry/agent-registry.d.ts +16 -5
- 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 +49 -32
- package/dist/types/session/messages.d.ts +2 -4
- package/dist/types/session/session-history-format.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +21 -3
- package/dist/types/session/streaming-output.d.ts +46 -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 +12 -2
- package/dist/types/task/index.d.ts +13 -6
- package/dist/types/task/output-manager.d.ts +0 -7
- package/dist/types/task/repair-args.d.ts +8 -7
- package/dist/types/task/types.d.ts +63 -51
- 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/browser/tab-worker.d.ts +3 -1
- package/dist/types/tools/find.d.ts +0 -11
- package/dist/types/tools/grouped-file-output.d.ts +0 -49
- package/dist/types/tools/index.d.ts +7 -3
- package/dist/types/tools/irc.d.ts +76 -38
- package/dist/types/tools/job.d.ts +7 -1
- package/dist/types/utils/git.d.ts +15 -2
- package/dist/types/utils/title-generator.d.ts +3 -2
- package/examples/extensions/with-deps/package.json +1 -0
- package/package.json +11 -10
- package/scripts/bundle-dist.ts +28 -19
- package/src/async/index.ts +0 -1
- package/src/auto-thinking/classifier.ts +1 -0
- package/src/cli/args.ts +3 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli/gallery-fixtures/agentic.ts +230 -115
- package/src/cli/gallery-fixtures/types.ts +5 -0
- package/src/cli-commands.ts +29 -0
- package/src/cli.ts +28 -15
- package/src/commands/launch.ts +4 -0
- package/src/commit/agentic/tools/analyze-file.ts +38 -19
- package/src/commit/model-selection.ts +3 -2
- package/src/config/api-key-resolver.ts +8 -6
- package/src/config/keybindings.ts +6 -1
- package/src/config/model-registry.ts +97 -30
- package/src/config/model-resolver.ts +60 -0
- package/src/config/settings-schema.ts +99 -55
- package/src/config/settings.ts +68 -3
- package/src/edit/hashline/execute.ts +39 -2
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/eval/__tests__/agent-bridge.test.ts +5 -3
- package/src/eval/agent-bridge.ts +3 -16
- package/src/eval/completion-bridge.ts +1 -0
- package/src/eval/js/shared/prelude.txt +1 -1
- 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/prelude.py +5 -6
- package/src/eval/py/runtime.ts +37 -0
- package/src/exec/bash-executor.ts +82 -3
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +38 -13
- package/src/extensibility/custom-tools/types.ts +2 -2
- 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/extensibility/shared-events.ts +2 -2
- package/src/hindsight/bank.ts +17 -2
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/internal-urls/history-protocol.ts +113 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/router.ts +3 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/irc/bus.ts +292 -0
- package/src/main.ts +26 -66
- 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/{session-observer-overlay.ts → agent-hub.ts} +586 -367
- package/src/modes/components/assistant-message.ts +19 -21
- package/src/modes/components/compaction-summary-message.ts +68 -32
- package/src/modes/components/custom-editor.ts +10 -0
- package/src/modes/components/footer.ts +3 -1
- package/src/modes/components/status-line/component.ts +118 -34
- package/src/modes/components/tool-execution.ts +31 -1
- package/src/modes/components/ttsr-notification.ts +72 -30
- package/src/modes/components/welcome.ts +9 -33
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +65 -0
- package/src/modes/controllers/extension-ui-controller.ts +8 -8
- package/src/modes/controllers/input-controller.ts +19 -2
- package/src/modes/controllers/mcp-command-controller.ts +38 -3
- package/src/modes/controllers/selector-controller.ts +21 -17
- package/src/modes/index.ts +3 -21
- package/src/modes/interactive-mode.ts +47 -22
- 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/theme/theme.ts +18 -5
- package/src/modes/types.ts +5 -5
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +51 -49
- package/src/prompts/system/irc-incoming.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +0 -5
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +2 -2
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/irc.md +29 -19
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/task-summary.md +5 -16
- package/src/prompts/tools/task.md +38 -29
- package/src/registry/agent-lifecycle.ts +218 -0
- package/src/registry/agent-registry.ts +16 -5
- package/src/sdk.ts +37 -10
- package/src/secrets/index.ts +8 -1
- package/src/secrets/obfuscator.ts +39 -18
- package/src/session/agent-session.ts +422 -291
- package/src/session/messages.ts +11 -78
- package/src/session/session-history-format.ts +246 -0
- package/src/session/session-manager.ts +59 -5
- package/src/session/streaming-output.ts +226 -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 +851 -461
- package/src/task/index.ts +721 -796
- package/src/task/output-manager.ts +0 -11
- package/src/task/render.ts +148 -63
- package/src/task/repair-args.ts +21 -9
- package/src/task/types.ts +82 -66
- 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/ask.ts +4 -2
- package/src/tools/bash.ts +61 -10
- package/src/tools/browser/tab-worker.ts +26 -7
- package/src/tools/browser.ts +28 -1
- package/src/tools/find.ts +2 -27
- package/src/tools/grouped-file-output.ts +1 -118
- package/src/tools/image-gen.ts +11 -4
- package/src/tools/index.ts +17 -13
- package/src/tools/inspect-image.ts +1 -0
- package/src/tools/irc.ts +596 -171
- package/src/tools/job.ts +41 -7
- package/src/tools/read.ts +57 -1
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +4 -1
- 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/dist/types/async/support.d.ts +0 -2
- package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
- package/dist/types/task/simple-mode.d.ts +0 -8
- package/src/async/support.ts +0 -5
- package/src/task/simple-mode.ts +0 -27
|
@@ -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,
|
|
@@ -71,7 +71,12 @@ import {
|
|
|
71
71
|
type SessionInfo as StoredSessionInfo,
|
|
72
72
|
type UsageStatistics,
|
|
73
73
|
} from "../../session/session-manager";
|
|
74
|
-
import {
|
|
74
|
+
import {
|
|
75
|
+
ACP_BUILTIN_RESERVED_NAMES,
|
|
76
|
+
ACP_BUILTIN_SLASH_COMMANDS,
|
|
77
|
+
executeAcpBuiltinSlashCommand,
|
|
78
|
+
isAcpBuiltinShadowedName,
|
|
79
|
+
} from "../../slash-commands/acp-builtins";
|
|
75
80
|
import { AUTO_THINKING, parseConfiguredThinkingLevel } from "../../thinking";
|
|
76
81
|
import { normalizeLocalScheme } from "../../tools/path-utils";
|
|
77
82
|
import { runResolveInvocation } from "../../tools/resolve";
|
|
@@ -117,6 +122,7 @@ type PromptQueueState = {
|
|
|
117
122
|
promise: Promise<void>;
|
|
118
123
|
release: (() => void) | undefined;
|
|
119
124
|
};
|
|
125
|
+
type PromptLifecycleError = Error & { readonly code: "ACP_SESSION_CLOSED" };
|
|
120
126
|
|
|
121
127
|
type PromptTurnState = {
|
|
122
128
|
userMessageId: string;
|
|
@@ -158,6 +164,9 @@ type ManagedSessionRecord = {
|
|
|
158
164
|
// Installed inside `#scheduleBootstrapUpdates` (post-race-guard); released
|
|
159
165
|
// in `#disposeSessionRecord`. Lives independent of any prompt turn.
|
|
160
166
|
lifetimeUnsubscribe: (() => void) | undefined;
|
|
167
|
+
closedError: PromptLifecycleError | undefined;
|
|
168
|
+
promptEventHandlers: Set<Promise<void>>;
|
|
169
|
+
extensionUserMessageTasks: Set<Promise<void>>;
|
|
161
170
|
};
|
|
162
171
|
|
|
163
172
|
type ReplayableMessage = {
|
|
@@ -594,7 +603,23 @@ export class AcpAgent implements Agent {
|
|
|
594
603
|
const record = this.#getSessionRecord(params.sessionId);
|
|
595
604
|
const activeTurn = record.promptTurn;
|
|
596
605
|
if (activeTurn && !activeTurn.settled && record.session.isStreaming) {
|
|
597
|
-
|
|
606
|
+
// New prompt arrived while the previous turn is still in-flight (e.g. the
|
|
607
|
+
// client sent a message immediately after pressing stop, before or without
|
|
608
|
+
// a preceding session/cancel notification). Implicitly cancel the running
|
|
609
|
+
// turn so the new prompt can queue behind the abort cleanup — identical to
|
|
610
|
+
// what cancel() does when called explicitly. #beginCancelCleanup is
|
|
611
|
+
// idempotent, so a concurrent session/cancel notification is harmless.
|
|
612
|
+
// Mirror cancel()'s timeout handling: if abort() hangs past the cleanup
|
|
613
|
+
// timeout, close the managed session instead of leaving it registered
|
|
614
|
+
// with a still-streaming AgentSession. The queued prompt below observes
|
|
615
|
+
// the same cleanup rejection and fails accordingly.
|
|
616
|
+
this.#beginCancelCleanup(record, activeTurn).catch(async (error: unknown) => {
|
|
617
|
+
logger.warn("ACP cancel cleanup timed out; closing session", {
|
|
618
|
+
sessionId: record.session.sessionId,
|
|
619
|
+
error,
|
|
620
|
+
});
|
|
621
|
+
await this.#closeManagedSession(params.sessionId, record);
|
|
622
|
+
});
|
|
598
623
|
}
|
|
599
624
|
return await this.#queuePrompt(record, async () => {
|
|
600
625
|
const previousTurn = record.promptTurn;
|
|
@@ -607,6 +632,7 @@ export class AcpAgent implements Agent {
|
|
|
607
632
|
await previousTurn.promise.catch(() => undefined);
|
|
608
633
|
await previousTurn.cleanup;
|
|
609
634
|
}
|
|
635
|
+
this.#throwIfRecordClosed(record);
|
|
610
636
|
|
|
611
637
|
const converted = this.#convertPromptBlocks(params.prompt);
|
|
612
638
|
const pendingPrompt = Promise.withResolvers<PromptResponse>();
|
|
@@ -623,7 +649,7 @@ export class AcpAgent implements Agent {
|
|
|
623
649
|
};
|
|
624
650
|
|
|
625
651
|
record.promptTurn.unsubscribe = record.session.subscribe(event => {
|
|
626
|
-
|
|
652
|
+
this.#trackPromptEvent(record, event);
|
|
627
653
|
});
|
|
628
654
|
|
|
629
655
|
this.#runPromptOrCommand(record, converted.text, converted.images).catch((error: unknown) => {
|
|
@@ -643,6 +669,7 @@ export class AcpAgent implements Agent {
|
|
|
643
669
|
release: releaseQueue,
|
|
644
670
|
};
|
|
645
671
|
await previousQueue.promise;
|
|
672
|
+
this.#throwIfRecordClosed(record);
|
|
646
673
|
try {
|
|
647
674
|
return await run();
|
|
648
675
|
} finally {
|
|
@@ -653,6 +680,55 @@ export class AcpAgent implements Agent {
|
|
|
653
680
|
}
|
|
654
681
|
}
|
|
655
682
|
|
|
683
|
+
#throwIfRecordClosed(record: ManagedSessionRecord): void {
|
|
684
|
+
if (record.closedError) {
|
|
685
|
+
throw record.closedError;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
#createPromptLifecycleError(message: string): PromptLifecycleError {
|
|
690
|
+
return Object.assign(new Error(message), { code: "ACP_SESSION_CLOSED" as const });
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
#trackPromptEvent(record: ManagedSessionRecord, event: AgentSessionEvent): void {
|
|
694
|
+
const handling = this.#handlePromptEvent(record, event).catch((error: unknown) => {
|
|
695
|
+
logger.warn("ACP prompt event handler failed", { error });
|
|
696
|
+
});
|
|
697
|
+
record.promptEventHandlers.add(handling);
|
|
698
|
+
void handling.finally(() => {
|
|
699
|
+
record.promptEventHandlers.delete(handling);
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
async #waitForPromptEventHandlers(record: ManagedSessionRecord): Promise<void> {
|
|
704
|
+
while (record.promptEventHandlers.size > 0) {
|
|
705
|
+
await Promise.allSettled(Array.from(record.promptEventHandlers));
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
#trackExtensionUserMessage(record: ManagedSessionRecord, task: Promise<void>): void {
|
|
710
|
+
const tracked = task.catch((error: unknown) => {
|
|
711
|
+
logger.warn("ACP extension sendUserMessage failed", { error });
|
|
712
|
+
});
|
|
713
|
+
record.extensionUserMessageTasks.add(tracked);
|
|
714
|
+
void tracked.finally(() => {
|
|
715
|
+
record.extensionUserMessageTasks.delete(tracked);
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
async #waitForExtensionUserMessages(
|
|
720
|
+
record: ManagedSessionRecord,
|
|
721
|
+
baseline: ReadonlySet<Promise<void>>,
|
|
722
|
+
): Promise<void> {
|
|
723
|
+
while (true) {
|
|
724
|
+
const pending = Array.from(record.extensionUserMessageTasks).filter(task => !baseline.has(task));
|
|
725
|
+
if (pending.length === 0) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
await Promise.allSettled(pending);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
|
|
656
732
|
async #runPromptOrCommand(record: ManagedSessionRecord, text: string, images: AgentImageContent[]): Promise<void> {
|
|
657
733
|
const skillResult = await this.#tryRunSkillCommand(record, text);
|
|
658
734
|
if (skillResult) {
|
|
@@ -699,7 +775,18 @@ export class AcpAgent implements Agent {
|
|
|
699
775
|
return;
|
|
700
776
|
}
|
|
701
777
|
|
|
702
|
-
|
|
778
|
+
const extensionPromptBaseline = new Set(record.extensionUserMessageTasks);
|
|
779
|
+
const agentInvoked = await record.session.prompt(text, { images });
|
|
780
|
+
// Extension and custom-TS commands are handled locally inside session.prompt().
|
|
781
|
+
// An ACP extension command can still call pi.sendUserMessage(), which starts
|
|
782
|
+
// an async nested prompt through the extension runtime. Keep the ACP turn
|
|
783
|
+
// subscribed until those scheduled prompts and their event handlers drain;
|
|
784
|
+
// only then is `false` proof that the slash command was purely local.
|
|
785
|
+
if (!agentInvoked) {
|
|
786
|
+
await this.#waitForExtensionUserMessages(record, extensionPromptBaseline);
|
|
787
|
+
await this.#waitForPromptEventHandlers(record);
|
|
788
|
+
this.#finishPrompt(record, { stopReason: "end_turn" });
|
|
789
|
+
}
|
|
703
790
|
}
|
|
704
791
|
|
|
705
792
|
async #tryRunSkillCommand(record: ManagedSessionRecord, text: string): Promise<boolean> {
|
|
@@ -991,6 +1078,9 @@ export class AcpAgent implements Agent {
|
|
|
991
1078
|
liveMessageProgress: undefined,
|
|
992
1079
|
toolArgsById: new Map(),
|
|
993
1080
|
extensionsConfigured: false,
|
|
1081
|
+
closedError: undefined,
|
|
1082
|
+
promptEventHandlers: new Set(),
|
|
1083
|
+
extensionUserMessageTasks: new Set(),
|
|
994
1084
|
lifetimeUnsubscribe: undefined,
|
|
995
1085
|
};
|
|
996
1086
|
}
|
|
@@ -1582,10 +1672,12 @@ export class AcpAgent implements Agent {
|
|
|
1582
1672
|
commands.push(command);
|
|
1583
1673
|
};
|
|
1584
1674
|
|
|
1585
|
-
// Advertise in the order dispatch resolves them
|
|
1586
|
-
//
|
|
1587
|
-
//
|
|
1588
|
-
// commands
|
|
1675
|
+
// Advertise in the order dispatch resolves them (mirrors AgentSession
|
|
1676
|
+
// dispatch: builtins → skills → extensions → custom TS → file-based).
|
|
1677
|
+
// `appendCommand` dedupes by name so earlier entries win; extension
|
|
1678
|
+
// commands therefore correctly shadow custom TS commands of the same
|
|
1679
|
+
// name, matching the runtime behaviour of #tryExecuteExtensionCommand
|
|
1680
|
+
// running before #tryExecuteCustomCommand.
|
|
1589
1681
|
for (const command of ACP_BUILTIN_SLASH_COMMANDS) {
|
|
1590
1682
|
appendCommand(command);
|
|
1591
1683
|
}
|
|
@@ -1600,6 +1692,20 @@ export class AcpAgent implements Agent {
|
|
|
1600
1692
|
}
|
|
1601
1693
|
}
|
|
1602
1694
|
|
|
1695
|
+
for (const command of session.extensionRunner?.getRegisteredCommands(ACP_BUILTIN_RESERVED_NAMES) ?? []) {
|
|
1696
|
+
// Reserved-set filtering in getRegisteredCommands only covers exact
|
|
1697
|
+
// names; colon-namespaced names whose prefix is a builtin (e.g.
|
|
1698
|
+
// `model:foo`) would still dispatch to the builtin in ACP.
|
|
1699
|
+
if (isAcpBuiltinShadowedName(command.name)) {
|
|
1700
|
+
continue;
|
|
1701
|
+
}
|
|
1702
|
+
appendCommand({
|
|
1703
|
+
name: command.name,
|
|
1704
|
+
description: command.description ?? "(extension command)",
|
|
1705
|
+
input: { hint: "arguments" },
|
|
1706
|
+
});
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1603
1709
|
for (const command of session.customCommands) {
|
|
1604
1710
|
appendCommand({
|
|
1605
1711
|
name: command.command.name,
|
|
@@ -2069,9 +2175,7 @@ export class AcpAgent implements Agent {
|
|
|
2069
2175
|
});
|
|
2070
2176
|
},
|
|
2071
2177
|
sendUserMessage: (content, options) => {
|
|
2072
|
-
record.session.sendUserMessage(content, options)
|
|
2073
|
-
logger.warn("ACP extension sendUserMessage failed", { error });
|
|
2074
|
-
});
|
|
2178
|
+
this.#trackExtensionUserMessage(record, record.session.sendUserMessage(content, options));
|
|
2075
2179
|
},
|
|
2076
2180
|
appendEntry: (customType, data) => {
|
|
2077
2181
|
record.session.sessionManager.appendCustomEntry(customType, data);
|
|
@@ -2224,6 +2328,7 @@ export class AcpAgent implements Agent {
|
|
|
2224
2328
|
}
|
|
2225
2329
|
|
|
2226
2330
|
async #closeManagedSession(sessionId: string, record: ManagedSessionRecord): Promise<void> {
|
|
2331
|
+
record.closedError ??= this.#createPromptLifecycleError("ACP session closed before queued prompt could run");
|
|
2227
2332
|
this.#sessions.delete(sessionId);
|
|
2228
2333
|
await this.#cancelPromptForClose(record);
|
|
2229
2334
|
await this.#disposeSessionRecord(record);
|
|
@@ -2279,6 +2384,9 @@ export class AcpAgent implements Agent {
|
|
|
2279
2384
|
await Promise.all(
|
|
2280
2385
|
records.map(async ([sessionId, record]) => {
|
|
2281
2386
|
try {
|
|
2387
|
+
record.closedError ??= this.#createPromptLifecycleError(
|
|
2388
|
+
"ACP agent disposed before queued prompt could run",
|
|
2389
|
+
);
|
|
2282
2390
|
await this.#cancelPromptForClose(record);
|
|
2283
2391
|
await this.#disposeSessionRecord(record);
|
|
2284
2392
|
} catch (error) {
|