@oh-my-pi/pi-coding-agent 14.6.1 → 14.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +82 -1
- package/README.md +21 -0
- package/package.json +23 -7
- package/src/cli/grievances-cli.ts +89 -4
- package/src/commands/grievances.ts +33 -7
- package/src/config/prompt-templates.ts +14 -7
- package/src/config/settings-schema.ts +595 -100
- package/src/config/settings.ts +46 -0
- package/src/discovery/helpers.ts +13 -6
- package/src/edit/index.ts +3 -3
- package/src/edit/line-hash.ts +73 -25
- package/src/edit/modes/hashline.lark +10 -3
- package/src/edit/modes/hashline.ts +104 -38
- package/src/edit/renderer.ts +3 -3
- package/src/hindsight/backend.ts +444 -0
- package/src/hindsight/bank.ts +131 -0
- package/src/hindsight/client.ts +445 -0
- package/src/hindsight/config.ts +165 -0
- package/src/hindsight/content.ts +205 -0
- package/src/hindsight/index.ts +6 -0
- package/src/hindsight/retain-queue.ts +166 -0
- package/src/hindsight/transcript.ts +71 -0
- package/src/main.ts +7 -10
- package/src/memories/index.ts +1 -1
- package/src/memory-backend/index.ts +4 -0
- package/src/memory-backend/local-backend.ts +30 -0
- package/src/memory-backend/off-backend.ts +16 -0
- package/src/memory-backend/resolve.ts +24 -0
- package/src/memory-backend/types.ts +69 -0
- package/src/modes/components/settings-defs.ts +50 -451
- package/src/modes/components/settings-selector.ts +4 -2
- package/src/modes/components/status-line/presets.ts +1 -1
- package/src/modes/components/status-line.ts +4 -1
- package/src/modes/controllers/command-controller.ts +6 -5
- package/src/modes/controllers/event-controller.ts +12 -0
- package/src/modes/controllers/mcp-command-controller.ts +23 -0
- package/src/modes/controllers/selector-controller.ts +10 -12
- package/src/modes/interactive-mode.ts +3 -2
- package/src/modes/theme/theme.ts +4 -0
- package/src/prompts/tools/github.md +3 -0
- package/src/prompts/tools/hashline.md +20 -16
- package/src/prompts/tools/read.md +10 -6
- package/src/prompts/tools/recall.md +5 -0
- package/src/prompts/tools/reflect.md +5 -0
- package/src/prompts/tools/retain.md +5 -0
- package/src/prompts/tools/search.md +1 -1
- package/src/sdk.ts +12 -9
- package/src/session/agent-session.ts +75 -3
- package/src/slash-commands/builtin-registry.ts +2 -12
- package/src/ssh/connection-manager.ts +1 -1
- package/src/tools/ast-edit.ts +14 -5
- package/src/tools/ast-grep.ts +12 -3
- package/src/tools/find.ts +47 -7
- package/src/tools/gh-renderer.ts +10 -1
- package/src/tools/gh.ts +233 -5
- package/src/tools/hindsight-recall.ts +70 -0
- package/src/tools/hindsight-reflect.ts +57 -0
- package/src/tools/hindsight-retain.ts +63 -0
- package/src/tools/index.ts +17 -0
- package/src/tools/output-meta.ts +1 -0
- package/src/tools/path-utils.ts +55 -0
- package/src/tools/read.ts +1 -1
- package/src/tools/search.ts +45 -8
|
@@ -17,7 +17,7 @@ import { clearClaudePluginRootsCache } from "../../discovery/helpers";
|
|
|
17
17
|
import { getGatewayStatus } from "../../eval/py/gateway-coordinator";
|
|
18
18
|
import { loadCustomShare } from "../../export/custom-share";
|
|
19
19
|
import type { CompactOptions } from "../../extensibility/extensions/types";
|
|
20
|
-
import {
|
|
20
|
+
import { resolveMemoryBackend } from "../../memory-backend";
|
|
21
21
|
import { BashExecutionComponent } from "../../modes/components/bash-execution";
|
|
22
22
|
import { BorderedLoader } from "../../modes/components/bordered-loader";
|
|
23
23
|
import { DynamicBorder } from "../../modes/components/dynamic-border";
|
|
@@ -570,11 +570,12 @@ export class CommandController {
|
|
|
570
570
|
const argumentText = text.slice(7).trim();
|
|
571
571
|
const action = argumentText.split(/\s+/, 1)[0]?.toLowerCase() || "view";
|
|
572
572
|
const agentDir = this.ctx.settings.getAgentDir();
|
|
573
|
+
const backend = resolveMemoryBackend(this.ctx.settings);
|
|
573
574
|
|
|
574
575
|
if (action === "view") {
|
|
575
|
-
const payload = await
|
|
576
|
+
const payload = await backend.buildDeveloperInstructions(agentDir, this.ctx.settings);
|
|
576
577
|
if (!payload) {
|
|
577
|
-
this.ctx.showWarning("Memory payload is empty (
|
|
578
|
+
this.ctx.showWarning("Memory payload is empty (memory backend off, disabled, or no memory available).");
|
|
578
579
|
return;
|
|
579
580
|
}
|
|
580
581
|
this.ctx.chatContainer.addChild(new Spacer(1));
|
|
@@ -589,7 +590,7 @@ export class CommandController {
|
|
|
589
590
|
|
|
590
591
|
if (action === "reset" || action === "clear") {
|
|
591
592
|
try {
|
|
592
|
-
await
|
|
593
|
+
await backend.clear(agentDir, this.ctx.sessionManager.getCwd());
|
|
593
594
|
await this.ctx.session.refreshBaseSystemPrompt();
|
|
594
595
|
this.ctx.showStatus("Memory data cleared and system prompt refreshed.");
|
|
595
596
|
} catch (error) {
|
|
@@ -600,7 +601,7 @@ export class CommandController {
|
|
|
600
601
|
|
|
601
602
|
if (action === "enqueue" || action === "rebuild") {
|
|
602
603
|
try {
|
|
603
|
-
|
|
604
|
+
await backend.enqueue(agentDir, this.ctx.sessionManager.getCwd());
|
|
604
605
|
this.ctx.showStatus("Memory consolidation enqueued.");
|
|
605
606
|
} catch (error) {
|
|
606
607
|
this.ctx.showError(`Memory enqueue failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -53,6 +53,7 @@ export class EventController {
|
|
|
53
53
|
todo_reminder: e => this.#handleTodoReminder(e),
|
|
54
54
|
todo_auto_clear: e => this.#handleTodoAutoClear(e),
|
|
55
55
|
irc_message: e => this.#handleIrcMessage(e),
|
|
56
|
+
notice: e => this.#handleNotice(e),
|
|
56
57
|
} satisfies AgentSessionEventHandlers;
|
|
57
58
|
}
|
|
58
59
|
|
|
@@ -223,6 +224,17 @@ export class EventController {
|
|
|
223
224
|
this.ctx.ui.requestRender();
|
|
224
225
|
}
|
|
225
226
|
|
|
227
|
+
async #handleNotice(event: Extract<AgentSessionEvent, { type: "notice" }>): Promise<void> {
|
|
228
|
+
const message = event.source ? `${event.source}: ${event.message}` : event.message;
|
|
229
|
+
if (event.level === "error") {
|
|
230
|
+
this.ctx.showError(message);
|
|
231
|
+
} else if (event.level === "warning") {
|
|
232
|
+
this.ctx.showWarning(message);
|
|
233
|
+
} else {
|
|
234
|
+
this.ctx.showStatus(message);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
226
238
|
async #handleMessageUpdate(event: Extract<AgentSessionEvent, { type: "message_update" }>): Promise<void> {
|
|
227
239
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
228
240
|
this.ctx.streamingMessage = event.message;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Handles /mcp subcommands for managing MCP servers.
|
|
5
5
|
*/
|
|
6
|
+
import * as path from "node:path";
|
|
6
7
|
import { Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
7
8
|
import { getMCPConfigPath, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
8
9
|
import type { SourceMeta } from "../../capability/types";
|
|
@@ -656,6 +657,28 @@ export class MCPCommandController {
|
|
|
656
657
|
if (projectConfig.mcpServers?.[name]) {
|
|
657
658
|
return { filePath: projectPath, scope: "project", config: projectConfig.mcpServers[name] };
|
|
658
659
|
}
|
|
660
|
+
|
|
661
|
+
// Check standalone fallback files (mcp.json, .mcp.json) in the project root —
|
|
662
|
+
// these match the discovery paths used by the mcp-json provider. Reads run in
|
|
663
|
+
// parallel (mirroring user/project above) but precedence is preserved by the
|
|
664
|
+
// for-loop's iteration order: mcp.json wins over .mcp.json on a same-name hit.
|
|
665
|
+
const standalonePaths = [path.join(cwd, "mcp.json"), path.join(cwd, ".mcp.json")];
|
|
666
|
+
const fallbackConfigs = await Promise.all(
|
|
667
|
+
standalonePaths.map(async fallbackPath => {
|
|
668
|
+
try {
|
|
669
|
+
return await readMCPConfigFile(fallbackPath);
|
|
670
|
+
} catch {
|
|
671
|
+
// Malformed JSON in a standalone file — skip and continue lookup.
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
}),
|
|
675
|
+
);
|
|
676
|
+
for (const [index, fallbackConfig] of fallbackConfigs.entries()) {
|
|
677
|
+
const config = fallbackConfig?.mcpServers?.[name];
|
|
678
|
+
if (config) {
|
|
679
|
+
return { filePath: standalonePaths[index]!, scope: "project", config };
|
|
680
|
+
}
|
|
681
|
+
}
|
|
659
682
|
return null;
|
|
660
683
|
}
|
|
661
684
|
|
|
@@ -1,18 +1,15 @@
|
|
|
1
|
-
import * as os from "node:os";
|
|
2
|
-
import * as path from "node:path";
|
|
3
1
|
import { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
4
2
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
|
|
5
3
|
import type { OAuthProvider } from "@oh-my-pi/pi-ai/utils/oauth/types";
|
|
6
4
|
import type { Component, OverlayHandle } from "@oh-my-pi/pi-tui";
|
|
7
5
|
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
8
|
-
import { getAgentDbPath,
|
|
9
|
-
import { invalidate as invalidateFsCache } from "../../capability/fs";
|
|
6
|
+
import { getAgentDbPath, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
10
7
|
import { getRoleInfo } from "../../config/model-registry";
|
|
11
8
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
12
9
|
import { settings } from "../../config/settings";
|
|
13
10
|
import { DebugSelectorComponent } from "../../debug";
|
|
14
11
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
15
|
-
import {
|
|
12
|
+
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
16
13
|
import {
|
|
17
14
|
getInstalledPluginsRegistryPath,
|
|
18
15
|
getMarketplacesCacheDir,
|
|
@@ -118,6 +115,7 @@ export class SelectorController {
|
|
|
118
115
|
rightSegments: settings.get("statusLine.rightSegments"),
|
|
119
116
|
separator: settings.get("statusLine.separator"),
|
|
120
117
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
118
|
+
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
121
119
|
...previewSettings,
|
|
122
120
|
});
|
|
123
121
|
this.ctx.updateEditorTopBorder();
|
|
@@ -140,6 +138,7 @@ export class SelectorController {
|
|
|
140
138
|
rightSegments: settings.get("statusLine.rightSegments"),
|
|
141
139
|
separator: settings.get("statusLine.separator"),
|
|
142
140
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
141
|
+
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
143
142
|
});
|
|
144
143
|
this.ctx.updateEditorTopBorder();
|
|
145
144
|
this.ctx.ui.requestRender();
|
|
@@ -332,8 +331,12 @@ export class SelectorController {
|
|
|
332
331
|
break;
|
|
333
332
|
}
|
|
334
333
|
case "statusLinePreset":
|
|
334
|
+
case "statusLine.preset":
|
|
335
335
|
case "statusLineSeparator":
|
|
336
|
+
case "statusLine.separator":
|
|
336
337
|
case "statusLineShowHooks":
|
|
338
|
+
case "statusLine.showHookStatus":
|
|
339
|
+
case "statusLine.sessionAccent":
|
|
337
340
|
case "statusLineSegments":
|
|
338
341
|
case "statusLineModelThinking":
|
|
339
342
|
case "statusLinePathAbbreviate":
|
|
@@ -351,6 +354,7 @@ export class SelectorController {
|
|
|
351
354
|
rightSegments: settings.get("statusLine.rightSegments"),
|
|
352
355
|
separator: settings.get("statusLine.separator"),
|
|
353
356
|
showHookStatus: settings.get("statusLine.showHookStatus"),
|
|
357
|
+
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
354
358
|
segmentOptions: settings.get("statusLine.segmentOptions"),
|
|
355
359
|
};
|
|
356
360
|
this.ctx.statusLine.updateSettings(statusLineSettings);
|
|
@@ -444,13 +448,7 @@ export class SelectorController {
|
|
|
444
448
|
projectInstalledRegistryPath: (await resolveActiveProjectRegistryPath(getProjectDir())) ?? undefined,
|
|
445
449
|
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
446
450
|
pluginsCacheDir: getPluginsCacheDir(),
|
|
447
|
-
clearPluginRootsCache:
|
|
448
|
-
const home = os.homedir();
|
|
449
|
-
invalidateFsCache(path.join(home, ".claude", "plugins", "installed_plugins.json"));
|
|
450
|
-
invalidateFsCache(path.join(home, getConfigDirName(), "plugins", "installed_plugins.json"));
|
|
451
|
-
for (const p of extraPaths ?? []) invalidateFsCache(p);
|
|
452
|
-
clearClaudePluginRootsCache();
|
|
453
|
-
},
|
|
451
|
+
clearPluginRootsCache: clearPluginRootsAndCaches,
|
|
454
452
|
});
|
|
455
453
|
|
|
456
454
|
const [marketplaces, installed] = await Promise.all([mgr.listMarketplaces(), mgr.listInstalledPlugins()]);
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
import { APP_NAME, getProjectDir, hsvToRgb, isEnoent, logger, postmortem, prompt } from "@oh-my-pi/pi-utils";
|
|
29
29
|
import chalk from "chalk";
|
|
30
30
|
import { KeybindingsManager } from "../config/keybindings";
|
|
31
|
-
import { type Settings, settings } from "../config/settings";
|
|
31
|
+
import { isSettingsInitialized, type Settings, settings } from "../config/settings";
|
|
32
32
|
import type {
|
|
33
33
|
ExtensionUIContext,
|
|
34
34
|
ExtensionUIDialogOptions,
|
|
@@ -644,7 +644,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
644
644
|
} else if (this.isPythonMode) {
|
|
645
645
|
this.editor.borderColor = theme.getPythonModeBorderColor();
|
|
646
646
|
} else {
|
|
647
|
-
const
|
|
647
|
+
const accentEnabled = !isSettingsInitialized() || settings.get("statusLine.sessionAccent") !== false;
|
|
648
|
+
const sessionName = accentEnabled ? this.sessionManager.getSessionName() : undefined;
|
|
648
649
|
const hex = sessionName ? getSessionAccentHex(sessionName) : undefined;
|
|
649
650
|
const ansi = getSessionAccentAnsi(hex);
|
|
650
651
|
if (ansi) {
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -186,6 +186,7 @@ export type SymbolKey =
|
|
|
186
186
|
| "tab.context"
|
|
187
187
|
| "tab.editing"
|
|
188
188
|
| "tab.tools"
|
|
189
|
+
| "tab.memory"
|
|
189
190
|
| "tab.tasks"
|
|
190
191
|
| "tab.providers";
|
|
191
192
|
|
|
@@ -346,6 +347,7 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
346
347
|
"tab.context": "📋",
|
|
347
348
|
"tab.editing": "💻",
|
|
348
349
|
"tab.tools": "🔧",
|
|
350
|
+
"tab.memory": "🧠",
|
|
349
351
|
"tab.tasks": "📦",
|
|
350
352
|
"tab.providers": "🌐",
|
|
351
353
|
};
|
|
@@ -599,6 +601,7 @@ const NERD_SYMBOLS: SymbolMap = {
|
|
|
599
601
|
"tab.context": "",
|
|
600
602
|
"tab.editing": "",
|
|
601
603
|
"tab.tools": "",
|
|
604
|
+
"tab.memory": "",
|
|
602
605
|
"tab.tasks": "",
|
|
603
606
|
"tab.providers": "",
|
|
604
607
|
};
|
|
@@ -757,6 +760,7 @@ const ASCII_SYMBOLS: SymbolMap = {
|
|
|
757
760
|
"tab.context": "[X]",
|
|
758
761
|
"tab.editing": "[E]",
|
|
759
762
|
"tab.tools": "[T]",
|
|
763
|
+
"tab.memory": "[Y]",
|
|
760
764
|
"tab.tasks": "[K]",
|
|
761
765
|
"tab.providers": "[P]",
|
|
762
766
|
};
|
|
@@ -10,6 +10,9 @@ Pick the operation via `op`. Each op uses a subset of the parameters:
|
|
|
10
10
|
- `pr_push` — Push a checked-out PR branch back to its source branch. Requires the branch to have been checked out via `op: pr_checkout` (carries push metadata). Optional `branch`; defaults to the current checked-out git branch. Optional `forceWithLease`.
|
|
11
11
|
- `search_issues` — Search issues using normal GitHub issue search syntax. Required `query`. Optional `repo`, `limit`.
|
|
12
12
|
- `search_prs` — Search pull requests using normal GitHub PR search syntax. Required `query`. Optional `repo`, `limit`.
|
|
13
|
+
- `search_code` — Search code with GitHub code search syntax. Required `query`. Optional `repo`, `limit`. Returns matching paths with surrounding fragments.
|
|
14
|
+
- `search_commits` — Search commits across GitHub. Required `query`. Optional `repo`, `limit`. Returns short SHA, author, and the first line of each commit message.
|
|
15
|
+
- `search_repos` — Search repositories across GitHub. Required `query`. Optional `limit` (use query qualifiers like `org:`, `language:` instead of `repo`).
|
|
13
16
|
- `run_watch` — Watch a GitHub Actions workflow run. Optional `run` (id or URL). Omitting `run` watches all workflow runs for the current HEAD commit; `branch` falls back to the current branch. Optional `tail` (log lines per failed job). Streams snapshots, fast-fails on the first detected job failure (with a brief grace period to capture concurrent failures), then fetches tailed logs for the failed jobs. The full failed-job logs are saved as a session artifact for on-demand reads.
|
|
14
17
|
</instruction>
|
|
15
18
|
|
|
@@ -8,15 +8,15 @@ This format is purely textual. The tool has NO awareness of language, indentatio
|
|
|
8
8
|
|
|
9
9
|
<ops>
|
|
10
10
|
@PATH header: subsequent ops apply to PATH
|
|
11
|
-
< ANCHOR insert lines BEFORE the anchored line (or BOF); payload follows as
|
|
12
|
-
+ ANCHOR insert lines AFTER the anchored line (or EOF); payload follows as
|
|
11
|
+
< ANCHOR insert lines BEFORE the anchored line (or BOF); payload follows as `{{hsep}}TEXT` lines
|
|
12
|
+
+ ANCHOR insert lines AFTER the anchored line (or EOF); payload follows as `{{hsep}}TEXT` lines
|
|
13
13
|
- A..B delete the line range (inclusive); `- A` for one line
|
|
14
|
-
= A..B replace the range with payload
|
|
14
|
+
= A..B replace the range with payload `{{hsep}}TEXT` lines, or with one blank line if no payload follows
|
|
15
15
|
</ops>
|
|
16
16
|
|
|
17
17
|
<rules>
|
|
18
|
-
- Every line of inserted/replacement content **MUST** be emitted as a payload line starting with
|
|
19
|
-
-
|
|
18
|
+
- Every line of inserted/replacement content **MUST** be emitted as a payload line starting with `{{hsep}}`.
|
|
19
|
+
- `{{hsep}}` is syntax, not content. The inserted text begins after the first `{{hsep}}`; use a bare `{{hsep}}` to insert a blank line.
|
|
20
20
|
- `< A` inserts before line A; `+ A` inserts after line A. `< BOF` / `+ BOF` both prepend; `< EOF` / `+ EOF` both append.
|
|
21
21
|
- `= A..B` replaces the inclusive range with the following payload lines. `= A` (or `= A..B`) with no payload blanks the range to a single empty line.
|
|
22
22
|
- `- A..B` deletes the inclusive range; omit `..B` for one line.
|
|
@@ -35,30 +35,34 @@ This format is purely textual. The tool has NO awareness of language, indentatio
|
|
|
35
35
|
# Replace one line (preserve the leading tab from the original)
|
|
36
36
|
@a.ts
|
|
37
37
|
= {{hrefr 5}}
|
|
38
|
-
|
|
38
|
+
{{hsep}} return clean.trim().toUpperCase();
|
|
39
39
|
|
|
40
40
|
# Replace a contiguous range with multiple lines
|
|
41
41
|
@a.ts
|
|
42
42
|
= {{hrefr 3}}..{{hrefr 6}}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
{{hsep}}export function label(name: string): string {
|
|
44
|
+
{{hsep}} const clean = (name || DEF).trim();
|
|
45
|
+
{{hsep}} return clean.length === 0 ? DEF : clean.toUpperCase();
|
|
46
|
+
{{hsep}}}
|
|
47
47
|
|
|
48
48
|
# Insert BEFORE a line
|
|
49
49
|
@a.ts
|
|
50
50
|
< {{hrefr 5}}
|
|
51
|
-
|
|
51
|
+
{{hsep}} const debug = false;
|
|
52
52
|
|
|
53
53
|
# Insert AFTER a line
|
|
54
54
|
@a.ts
|
|
55
55
|
+ {{hrefr 4}}
|
|
56
|
-
|
|
56
|
+
{{hsep}} if (clean.length === 0) return DEF;
|
|
57
|
+
|
|
58
|
+
# Append WITHIN a line
|
|
59
|
+
@a.ts
|
|
60
|
+
+ {{hrefr 4}}{{hsep}} // first run
|
|
57
61
|
|
|
58
62
|
# Append to end of file
|
|
59
63
|
@a.ts
|
|
60
64
|
+ EOF
|
|
61
|
-
|
|
65
|
+
{{hsep}}export const done = true;
|
|
62
66
|
|
|
63
67
|
# Delete a single line
|
|
64
68
|
@a.ts
|
|
@@ -70,9 +74,9 @@ This format is purely textual. The tool has NO awareness of language, indentatio
|
|
|
70
74
|
</examples>
|
|
71
75
|
|
|
72
76
|
<critical>
|
|
73
|
-
- Always copy anchors exactly from tool output, but **NEVER** include line content after the
|
|
77
|
+
- Always copy anchors exactly from tool output, but **NEVER** include line content after the `{{hsep}}` separator in the op line.
|
|
74
78
|
- Only emit changed lines. Do not restate unchanged context as payload.
|
|
75
|
-
- Every inserted/replacement content line **MUST** start with
|
|
79
|
+
- Every inserted/replacement content line **MUST** start with `{{hsep}}`; raw content lines are invalid.
|
|
76
80
|
- Do not write unified diff syntax (`@@`, `-OLD`, `+NEW`).
|
|
77
|
-
- To replace a block, use one `= A..B` op followed by all replacement
|
|
81
|
+
- To replace a block, use one `= A..B` op followed by all replacement `{{hsep}}TEXT` payload lines.
|
|
78
82
|
</critical>
|
|
@@ -14,7 +14,7 @@ The `read` tool is multi-purpose and more capable than it looks — inspects fil
|
|
|
14
14
|
|
|
15
15
|
|`sel` value|Behavior|
|
|
16
16
|
|---|---|
|
|
17
|
-
|
|
17
|
+
|_(omitted)_|Read full file (up to {{DEFAULT_LIMIT}} lines)|
|
|
18
18
|
|`50`|Read from line 50 onward|
|
|
19
19
|
|`50-200`|Read lines 50-200|
|
|
20
20
|
|`50+150`|Read 150 lines starting at line 50|
|
|
@@ -22,21 +22,24 @@ The `read` tool is multi-purpose and more capable than it looks — inspects fil
|
|
|
22
22
|
|
|
23
23
|
# Filesystem
|
|
24
24
|
- Reading a directory path returns a list of dirents.
|
|
25
|
-
{{#if
|
|
25
|
+
{{#if IS_HL_MODE}}
|
|
26
26
|
- Reading a file returns lines prefixed with anchors (line+hash): `41th|def alpha():`
|
|
27
|
-
{{else}}
|
|
28
|
-
{{#if IS_LINE_NUMBER_MODE}}
|
|
27
|
+
{{else}}
|
|
28
|
+
{{#if IS_LINE_NUMBER_MODE}}
|
|
29
29
|
- Reading a file returns lines prefixed with line numbers: `41|def alpha():`
|
|
30
|
-
{{/if}}
|
|
31
|
-
{{/if}}
|
|
30
|
+
{{/if}}
|
|
31
|
+
{{/if}}
|
|
32
32
|
|
|
33
33
|
# Inspection
|
|
34
|
+
|
|
34
35
|
Extracts text from PDF, Word, PowerPoint, Excel, RTF, EPUB, and Jupyter notebook files. Can inspect images.
|
|
35
36
|
|
|
36
37
|
# Directories & Archives
|
|
38
|
+
|
|
37
39
|
Directories and archive roots return a list of entries. Supports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read contents.
|
|
38
40
|
|
|
39
41
|
# SQLite Databases
|
|
42
|
+
|
|
40
43
|
For `.sqlite`, `.sqlite3`, `.db`, `.db3`:
|
|
41
44
|
- `file.db` — list tables with row counts
|
|
42
45
|
- `file.db:table` — schema + sample rows
|
|
@@ -46,6 +49,7 @@ For `.sqlite`, `.sqlite3`, `.db`, `.db3`:
|
|
|
46
49
|
- `file.db?q=SELECT …` — read-only SELECT query
|
|
47
50
|
|
|
48
51
|
# URLs
|
|
52
|
+
|
|
49
53
|
Extracts content from web pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom feeds, JSON endpoints, PDFs at URLs, and similar text-based resources. Returns clean reader-mode text/markdown — no browser required. Use `sel="raw"` for untouched HTML; `timeout` to override the default request timeout.
|
|
50
54
|
</instruction>
|
|
51
55
|
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Search long-term memory for relevant information. Returns raw matching entries ranked by relevance.
|
|
2
|
+
|
|
3
|
+
Use proactively — before answering questions about past conversations, user preferences, project decisions, or any topic where prior context would help accuracy. When in doubt, recall first.
|
|
4
|
+
|
|
5
|
+
Prefer `recall` when you need specific facts or entries. Use `reflect` instead when you need a synthesised answer across many memories.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Generate a synthesised answer by reasoning over long-term memory. Unlike `recall` (which returns raw entries), `reflect` blends relevant memories into a single coherent response.
|
|
2
|
+
|
|
3
|
+
Use for open-ended questions that span many stored facts: "What do you know about this user?", "Summarize project decisions.", "What are my preferences for X?"
|
|
4
|
+
|
|
5
|
+
Provide an optional `context` to focus the synthesis on a specific angle or sub-topic.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Store one or more facts in long-term memory for future sessions.
|
|
2
|
+
|
|
3
|
+
Use for durable, reusable knowledge: user preferences, project decisions, architectural choices, and anything that would improve future responses if recalled. Ephemeral task state does not belong here.
|
|
4
|
+
|
|
5
|
+
Each item must be specific and self-contained — include who, what, when, and why. Batch related facts in a single call; they are deduplicated and consolidated together.
|
|
@@ -7,7 +7,7 @@ Searches files using powerful regex matching.
|
|
|
7
7
|
</instruction>
|
|
8
8
|
|
|
9
9
|
<output>
|
|
10
|
-
{{#if
|
|
10
|
+
{{#if IS_HL_MODE}}
|
|
11
11
|
- Text output is anchor-prefixed: `*5th|content` (match) or ` 9x}|content` (context, leading space). The 2-char suffix is a content fingerprint.
|
|
12
12
|
{{else}}
|
|
13
13
|
{{#if IS_LINE_NUMBER_MODE}}
|
package/src/sdk.ts
CHANGED
|
@@ -82,7 +82,8 @@ import {
|
|
|
82
82
|
selectDiscoverableMCPToolNamesByServer,
|
|
83
83
|
summarizeDiscoverableMCPTools,
|
|
84
84
|
} from "./mcp/discoverable-tool-metadata";
|
|
85
|
-
import {
|
|
85
|
+
import { getMemoryRoot } from "./memories";
|
|
86
|
+
import { resolveMemoryBackend } from "./memory-backend";
|
|
86
87
|
import asyncResultTemplate from "./prompts/tools/async-result.md" with { type: "text" };
|
|
87
88
|
import { AgentRegistry, MAIN_AGENT_ID } from "./registry/agent-registry";
|
|
88
89
|
import {
|
|
@@ -1334,7 +1335,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1334
1335
|
const promptTools = buildSystemPromptToolMetadata(tools, {
|
|
1335
1336
|
search_tool_bm25: { description: renderSearchToolBm25Description(discoverableMCPTools) },
|
|
1336
1337
|
});
|
|
1337
|
-
const memoryInstructions = await
|
|
1338
|
+
const memoryInstructions = await resolveMemoryBackend(settings).buildDeveloperInstructions(agentDir, settings);
|
|
1338
1339
|
|
|
1339
1340
|
// Build combined append prompt: memory instructions + MCP server instructions
|
|
1340
1341
|
const serverInstructions = mcpManager?.getServerInstructions();
|
|
@@ -1747,13 +1748,15 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1747
1748
|
}
|
|
1748
1749
|
|
|
1749
1750
|
logger.time("startMemoryStartupTask", () =>
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1751
|
+
Promise.resolve(
|
|
1752
|
+
resolveMemoryBackend(settings).start({
|
|
1753
|
+
session,
|
|
1754
|
+
settings,
|
|
1755
|
+
modelRegistry,
|
|
1756
|
+
agentDir,
|
|
1757
|
+
taskDepth,
|
|
1758
|
+
}),
|
|
1759
|
+
),
|
|
1757
1760
|
);
|
|
1758
1761
|
|
|
1759
1762
|
// Wire MCP manager callbacks to session for reactive tool updates.
|
|
@@ -111,6 +111,7 @@ import {
|
|
|
111
111
|
isMCPToolName,
|
|
112
112
|
selectDiscoverableMCPToolNamesByServer,
|
|
113
113
|
} from "../mcp/discoverable-tool-metadata";
|
|
114
|
+
import { resolveMemoryBackend } from "../memory-backend";
|
|
114
115
|
import { getCurrentThemeName, theme } from "../modes/theme/theme";
|
|
115
116
|
import type { PlanModeState } from "../plan-mode/state";
|
|
116
117
|
import autoContinuePrompt from "../prompts/system/auto-continue.md" with { type: "text" };
|
|
@@ -192,7 +193,8 @@ export type AgentSessionEvent =
|
|
|
192
193
|
| { type: "ttsr_triggered"; rules: Rule[] }
|
|
193
194
|
| { type: "todo_reminder"; todos: TodoItem[]; attempt: number; maxAttempts: number }
|
|
194
195
|
| { type: "todo_auto_clear" }
|
|
195
|
-
| { type: "irc_message"; message: CustomMessage }
|
|
196
|
+
| { type: "irc_message"; message: CustomMessage }
|
|
197
|
+
| { type: "notice"; level: "info" | "warning" | "error"; message: string; source?: string };
|
|
196
198
|
|
|
197
199
|
/** Listener function for agent session events */
|
|
198
200
|
export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
|
|
@@ -742,6 +744,19 @@ export class AgentSession {
|
|
|
742
744
|
}
|
|
743
745
|
}
|
|
744
746
|
|
|
747
|
+
/**
|
|
748
|
+
* Emit a UI-only notice to the session. Surfaces in interactive mode as a
|
|
749
|
+
* `showWarning` / `showError` / `showStatus` line; non-interactive modes
|
|
750
|
+
* receive the event through the normal subscribe stream.
|
|
751
|
+
*
|
|
752
|
+
* Notices are NOT added to agent state and never reach the LLM — use this
|
|
753
|
+
* for out-of-band conditions the user should see but the model shouldn't
|
|
754
|
+
* react to (e.g. background queue flush failures).
|
|
755
|
+
*/
|
|
756
|
+
emitNotice(level: "info" | "warning" | "error", message: string, source?: string): void {
|
|
757
|
+
this.#emit({ type: "notice", level, message, source });
|
|
758
|
+
}
|
|
759
|
+
|
|
745
760
|
#queuedExtensionEvents: Promise<void> = Promise.resolve();
|
|
746
761
|
|
|
747
762
|
#queueExtensionEvent(event: AgentSessionEvent): Promise<void> {
|
|
@@ -2289,6 +2304,23 @@ export class AgentSession {
|
|
|
2289
2304
|
this.#lastAppliedToolSignature = this.#computeAppliedToolSignature(activeToolNames, activeTools);
|
|
2290
2305
|
}
|
|
2291
2306
|
|
|
2307
|
+
async #buildSystemPromptForAgentStart(promptText: string): Promise<string> {
|
|
2308
|
+
const backend = resolveMemoryBackend(this.settings);
|
|
2309
|
+
if (!backend.beforeAgentStartPrompt) return this.#baseSystemPrompt;
|
|
2310
|
+
|
|
2311
|
+
try {
|
|
2312
|
+
const injected = await backend.beforeAgentStartPrompt(this, promptText);
|
|
2313
|
+
if (!injected) return this.#baseSystemPrompt;
|
|
2314
|
+
return `${this.#baseSystemPrompt}\n\n${injected}`;
|
|
2315
|
+
} catch (err) {
|
|
2316
|
+
logger.debug("Memory backend beforeAgentStartPrompt failed", {
|
|
2317
|
+
backend: backend.id,
|
|
2318
|
+
error: String(err),
|
|
2319
|
+
});
|
|
2320
|
+
return this.#baseSystemPrompt;
|
|
2321
|
+
}
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2292
2324
|
/**
|
|
2293
2325
|
* Compose a stable signature for the inputs that `rebuildSystemPrompt` reads.
|
|
2294
2326
|
* Two calls producing identical signatures are guaranteed to produce identical
|
|
@@ -2908,12 +2940,14 @@ export class AgentSession {
|
|
|
2908
2940
|
messages.push(...fileMentionMessages);
|
|
2909
2941
|
}
|
|
2910
2942
|
|
|
2943
|
+
const beforeAgentStartSystemPrompt = await this.#buildSystemPromptForAgentStart(expandedText);
|
|
2944
|
+
|
|
2911
2945
|
// Emit before_agent_start extension event
|
|
2912
2946
|
if (this.#extensionRunner) {
|
|
2913
2947
|
const result = await this.#extensionRunner.emitBeforeAgentStart(
|
|
2914
2948
|
expandedText,
|
|
2915
2949
|
options?.images,
|
|
2916
|
-
|
|
2950
|
+
beforeAgentStartSystemPrompt,
|
|
2917
2951
|
);
|
|
2918
2952
|
if (result?.messages) {
|
|
2919
2953
|
const promptAttribution: "user" | "agent" | undefined =
|
|
@@ -2934,8 +2968,10 @@ export class AgentSession {
|
|
|
2934
2968
|
if (result?.systemPrompt !== undefined) {
|
|
2935
2969
|
this.agent.setSystemPrompt(result.systemPrompt);
|
|
2936
2970
|
} else {
|
|
2937
|
-
this.agent.setSystemPrompt(
|
|
2971
|
+
this.agent.setSystemPrompt(beforeAgentStartSystemPrompt);
|
|
2938
2972
|
}
|
|
2973
|
+
} else {
|
|
2974
|
+
this.agent.setSystemPrompt(beforeAgentStartSystemPrompt);
|
|
2939
2975
|
}
|
|
2940
2976
|
|
|
2941
2977
|
// Bail out if a newer abort/prompt cycle has started since we began setup
|
|
@@ -4118,6 +4154,11 @@ export class AgentSession {
|
|
|
4118
4154
|
preserveData = result?.preserveData;
|
|
4119
4155
|
}
|
|
4120
4156
|
|
|
4157
|
+
const memoryBackendContext = await this.#collectMemoryBackendContext(preparation);
|
|
4158
|
+
if (memoryBackendContext) {
|
|
4159
|
+
hookContext = hookContext ? [...hookContext, memoryBackendContext] : [memoryBackendContext];
|
|
4160
|
+
}
|
|
4161
|
+
|
|
4121
4162
|
let summary: string;
|
|
4122
4163
|
let shortSummary: string | undefined;
|
|
4123
4164
|
let firstKeptEntryId: string;
|
|
@@ -4204,6 +4245,32 @@ export class AgentSession {
|
|
|
4204
4245
|
}
|
|
4205
4246
|
}
|
|
4206
4247
|
|
|
4248
|
+
/**
|
|
4249
|
+
* Ask the active memory backend for an extra-context block to splice into
|
|
4250
|
+
* the compaction summary prompt. Both the manual and auto compaction paths
|
|
4251
|
+
* funnel through this helper so the behaviour stays identical.
|
|
4252
|
+
*
|
|
4253
|
+
* Failures are swallowed: a memory backend going sideways MUST NOT block
|
|
4254
|
+
* compaction (which is itself the recovery path for context overflow).
|
|
4255
|
+
*/
|
|
4256
|
+
async #collectMemoryBackendContext(preparation: {
|
|
4257
|
+
messagesToSummarize: AgentMessage[];
|
|
4258
|
+
turnPrefixMessages: AgentMessage[];
|
|
4259
|
+
}): Promise<string | undefined> {
|
|
4260
|
+
const backend = resolveMemoryBackend(this.settings);
|
|
4261
|
+
if (!backend.preCompactionContext) return undefined;
|
|
4262
|
+
const messages = preparation.messagesToSummarize.concat(preparation.turnPrefixMessages);
|
|
4263
|
+
try {
|
|
4264
|
+
return await backend.preCompactionContext(messages, this.settings);
|
|
4265
|
+
} catch (err) {
|
|
4266
|
+
logger.debug("Memory backend preCompactionContext failed", {
|
|
4267
|
+
backend: backend.id,
|
|
4268
|
+
error: String(err),
|
|
4269
|
+
});
|
|
4270
|
+
return undefined;
|
|
4271
|
+
}
|
|
4272
|
+
}
|
|
4273
|
+
|
|
4207
4274
|
/**
|
|
4208
4275
|
* Cancel in-progress context maintenance (manual compaction, auto-compaction, or auto-handoff).
|
|
4209
4276
|
*/
|
|
@@ -5190,6 +5257,11 @@ export class AgentSession {
|
|
|
5190
5257
|
preserveData = result?.preserveData;
|
|
5191
5258
|
}
|
|
5192
5259
|
|
|
5260
|
+
const memoryBackendContext = await this.#collectMemoryBackendContext(preparation);
|
|
5261
|
+
if (memoryBackendContext) {
|
|
5262
|
+
hookContext = hookContext ? [...hookContext, memoryBackendContext] : [memoryBackendContext];
|
|
5263
|
+
}
|
|
5264
|
+
|
|
5193
5265
|
let summary: string;
|
|
5194
5266
|
let shortSummary: string | undefined;
|
|
5195
5267
|
let firstKeptEntryId: string;
|
|
@@ -1,13 +1,7 @@
|
|
|
1
|
-
import * as os from "node:os";
|
|
2
|
-
import * as path from "node:path";
|
|
3
|
-
|
|
4
1
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai/utils/oauth";
|
|
5
|
-
import { getConfigDirName } from "@oh-my-pi/pi-utils";
|
|
6
|
-
import { invalidate as invalidateFsCache } from "../capability/fs";
|
|
7
2
|
import type { SettingPath, SettingValue } from "../config/settings";
|
|
8
3
|
import { settings } from "../config/settings";
|
|
9
4
|
import {
|
|
10
|
-
clearClaudePluginRootsCache,
|
|
11
5
|
clearPluginRootsAndCaches,
|
|
12
6
|
resolveActiveProjectRegistryPath,
|
|
13
7
|
resolveOrDefaultProjectRegistryPath,
|
|
@@ -942,14 +936,10 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
942
936
|
name: "reload-plugins",
|
|
943
937
|
description: "Reload all plugins (skills, commands, hooks, tools, agents, MCP)",
|
|
944
938
|
handle: async (_command, runtime) => {
|
|
945
|
-
// Invalidate
|
|
939
|
+
// Invalidate registry fs caches and the plugin roots cache so
|
|
946
940
|
// listClaudePluginRoots re-reads from disk on next access.
|
|
947
|
-
const home = os.homedir();
|
|
948
|
-
invalidateFsCache(path.join(home, ".claude", "plugins", "installed_plugins.json"));
|
|
949
|
-
invalidateFsCache(path.join(home, getConfigDirName(), "plugins", "installed_plugins.json"));
|
|
950
941
|
const projectPath = await resolveActiveProjectRegistryPath(runtime.ctx.sessionManager.getCwd());
|
|
951
|
-
|
|
952
|
-
clearClaudePluginRootsCache();
|
|
942
|
+
clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
|
|
953
943
|
await runtime.ctx.refreshSlashCommandState();
|
|
954
944
|
runtime.ctx.showStatus("Plugins reloaded.");
|
|
955
945
|
runtime.ctx.editor.setText("");
|
|
@@ -25,7 +25,7 @@ export interface SSHHostInfo {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
const CONTROL_DIR = getSshControlDir();
|
|
28
|
-
const CONTROL_PATH = path.join(CONTROL_DIR, "%
|
|
28
|
+
const CONTROL_PATH = path.join(CONTROL_DIR, "%C.sock");
|
|
29
29
|
const HOST_INFO_DIR = getRemoteHostDir();
|
|
30
30
|
const HOST_INFO_VERSION = 2;
|
|
31
31
|
|