@oh-my-pi/pi-coding-agent 14.6.2 → 14.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +95 -2
- 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 +610 -100
- package/src/config/settings.ts +42 -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 +295 -40
- package/src/edit/renderer.ts +3 -3
- package/src/hindsight/backend.ts +205 -0
- package/src/hindsight/bank.ts +131 -0
- package/src/hindsight/client.ts +598 -0
- package/src/hindsight/config.ts +175 -0
- package/src/hindsight/content.ts +210 -0
- package/src/hindsight/index.ts +8 -0
- package/src/hindsight/mental-models.ts +382 -0
- package/src/hindsight/seeds.json +32 -0
- package/src/hindsight/state.ts +469 -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 +79 -0
- package/src/modes/components/settings-defs.ts +50 -451
- package/src/modes/components/settings-selector.ts +2 -2
- package/src/modes/components/status-line/presets.ts +1 -1
- package/src/modes/controllers/command-controller.ts +266 -6
- package/src/modes/controllers/event-controller.ts +12 -0
- package/src/modes/controllers/selector-controller.ts +3 -12
- package/src/modes/theme/theme.ts +4 -0
- package/src/prompts/tools/github.md +3 -0
- package/src/prompts/tools/hashline.md +21 -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 +21 -9
- package/src/session/agent-session.ts +118 -3
- package/src/slash-commands/builtin-registry.ts +12 -12
- package/src/task/executor.ts +3 -0
- package/src/task/index.ts +2 -0
- 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 +68 -0
- package/src/tools/hindsight-reflect.ts +55 -0
- package/src/tools/hindsight-retain.ts +60 -0
- package/src/tools/index.ts +20 -0
- package/src/tools/path-utils.ts +55 -0
- package/src/tools/read.ts +1 -1
- package/src/tools/search.ts +45 -8
|
@@ -102,6 +102,7 @@ import { ExtensionToolWrapper } from "../extensibility/extensions/wrapper";
|
|
|
102
102
|
import type { HookCommandContext } from "../extensibility/hooks/types";
|
|
103
103
|
import type { Skill, SkillWarning } from "../extensibility/skills";
|
|
104
104
|
import { expandSlashCommand, type FileSlashCommand } from "../extensibility/slash-commands";
|
|
105
|
+
import type { HindsightSessionState } from "../hindsight/state";
|
|
105
106
|
import { type LocalProtocolOptions, resolveLocalUrlToPath } from "../internal-urls";
|
|
106
107
|
import {
|
|
107
108
|
buildDiscoverableMCPSearchIndex,
|
|
@@ -111,6 +112,7 @@ import {
|
|
|
111
112
|
isMCPToolName,
|
|
112
113
|
selectDiscoverableMCPToolNamesByServer,
|
|
113
114
|
} from "../mcp/discoverable-tool-metadata";
|
|
115
|
+
import { resolveMemoryBackend } from "../memory-backend";
|
|
114
116
|
import { getCurrentThemeName, theme } from "../modes/theme/theme";
|
|
115
117
|
import type { PlanModeState } from "../plan-mode/state";
|
|
116
118
|
import autoContinuePrompt from "../prompts/system/auto-continue.md" with { type: "text" };
|
|
@@ -192,7 +194,8 @@ export type AgentSessionEvent =
|
|
|
192
194
|
| { type: "ttsr_triggered"; rules: Rule[] }
|
|
193
195
|
| { type: "todo_reminder"; todos: TodoItem[]; attempt: number; maxAttempts: number }
|
|
194
196
|
| { type: "todo_auto_clear" }
|
|
195
|
-
| { type: "irc_message"; message: CustomMessage }
|
|
197
|
+
| { type: "irc_message"; message: CustomMessage }
|
|
198
|
+
| { type: "notice"; level: "info" | "warning" | "error"; message: string; source?: string };
|
|
196
199
|
|
|
197
200
|
/** Listener function for agent session events */
|
|
198
201
|
export type AgentSessionEventListener = (event: AgentSessionEvent) => void;
|
|
@@ -561,6 +564,7 @@ export class AgentSession {
|
|
|
561
564
|
#lastSuccessfulYieldToolCallId: string | undefined = undefined;
|
|
562
565
|
#promptGeneration = 0;
|
|
563
566
|
#providerSessionState = new Map<string, ProviderSessionState>();
|
|
567
|
+
#hindsightSessionState: HindsightSessionState | undefined = undefined;
|
|
564
568
|
|
|
565
569
|
#startPowerAssertion(): void {
|
|
566
570
|
if (process.platform !== "darwin") {
|
|
@@ -700,6 +704,16 @@ export class AgentSession {
|
|
|
700
704
|
return this.#providerSessionState;
|
|
701
705
|
}
|
|
702
706
|
|
|
707
|
+
getHindsightSessionState(): HindsightSessionState | undefined {
|
|
708
|
+
return this.#hindsightSessionState;
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
setHindsightSessionState(state: HindsightSessionState | undefined): HindsightSessionState | undefined {
|
|
712
|
+
const previous = this.#hindsightSessionState;
|
|
713
|
+
this.#hindsightSessionState = state;
|
|
714
|
+
return previous;
|
|
715
|
+
}
|
|
716
|
+
|
|
703
717
|
/** TTSR manager for time-traveling stream rules */
|
|
704
718
|
get ttsrManager(): TtsrManager | undefined {
|
|
705
719
|
return this.#ttsrManager;
|
|
@@ -742,6 +756,19 @@ export class AgentSession {
|
|
|
742
756
|
}
|
|
743
757
|
}
|
|
744
758
|
|
|
759
|
+
/**
|
|
760
|
+
* Emit a UI-only notice to the session. Surfaces in interactive mode as a
|
|
761
|
+
* `showWarning` / `showError` / `showStatus` line; non-interactive modes
|
|
762
|
+
* receive the event through the normal subscribe stream.
|
|
763
|
+
*
|
|
764
|
+
* Notices are NOT added to agent state and never reach the LLM — use this
|
|
765
|
+
* for out-of-band conditions the user should see but the model shouldn't
|
|
766
|
+
* react to (e.g. background queue flush failures).
|
|
767
|
+
*/
|
|
768
|
+
emitNotice(level: "info" | "warning" | "error", message: string, source?: string): void {
|
|
769
|
+
this.#emit({ type: "notice", level, message, source });
|
|
770
|
+
}
|
|
771
|
+
|
|
745
772
|
#queuedExtensionEvents: Promise<void> = Promise.resolve();
|
|
746
773
|
|
|
747
774
|
#queueExtensionEvent(event: AgentSessionEvent): Promise<void> {
|
|
@@ -1949,6 +1976,22 @@ export class AgentSession {
|
|
|
1949
1976
|
this.#unsubscribeAgent = this.agent.subscribe(this.#handleAgentEvent);
|
|
1950
1977
|
}
|
|
1951
1978
|
|
|
1979
|
+
/** Keep Hindsight metadata aligned when the underlying agent session id changes. */
|
|
1980
|
+
#rekeyHindsightMemoryForCurrentSessionId(): void {
|
|
1981
|
+
if (resolveMemoryBackend(this.settings).id !== "hindsight") return;
|
|
1982
|
+
const sid = this.agent.sessionId;
|
|
1983
|
+
if (!sid) return;
|
|
1984
|
+
this.getHindsightSessionState()?.setSessionId(sid);
|
|
1985
|
+
}
|
|
1986
|
+
|
|
1987
|
+
/** New session file: reset auto-recall / retain-threshold counters for the new transcript. */
|
|
1988
|
+
#resetHindsightConversationTrackingIfHindsight(): void {
|
|
1989
|
+
if (resolveMemoryBackend(this.settings).id !== "hindsight") return;
|
|
1990
|
+
const state = this.getHindsightSessionState();
|
|
1991
|
+
if (!state || state.aliasOf) return;
|
|
1992
|
+
state.resetConversationTracking();
|
|
1993
|
+
}
|
|
1994
|
+
|
|
1952
1995
|
/**
|
|
1953
1996
|
* Remove all listeners, flush pending writes, and disconnect from agent.
|
|
1954
1997
|
* Call this when completely done with the session.
|
|
@@ -1979,6 +2022,9 @@ export class AgentSession {
|
|
|
1979
2022
|
this.#stopPowerAssertion();
|
|
1980
2023
|
await this.sessionManager.close();
|
|
1981
2024
|
this.#closeAllProviderSessions("dispose");
|
|
2025
|
+
const hindsightState = this.setHindsightSessionState(undefined);
|
|
2026
|
+
await hindsightState?.flushRetainQueue();
|
|
2027
|
+
hindsightState?.dispose();
|
|
1982
2028
|
this.#disconnectFromAgent();
|
|
1983
2029
|
this.#eventListeners = [];
|
|
1984
2030
|
}
|
|
@@ -2289,6 +2335,23 @@ export class AgentSession {
|
|
|
2289
2335
|
this.#lastAppliedToolSignature = this.#computeAppliedToolSignature(activeToolNames, activeTools);
|
|
2290
2336
|
}
|
|
2291
2337
|
|
|
2338
|
+
async #buildSystemPromptForAgentStart(promptText: string): Promise<string> {
|
|
2339
|
+
const backend = resolveMemoryBackend(this.settings);
|
|
2340
|
+
if (!backend.beforeAgentStartPrompt) return this.#baseSystemPrompt;
|
|
2341
|
+
|
|
2342
|
+
try {
|
|
2343
|
+
const injected = await backend.beforeAgentStartPrompt(this, promptText);
|
|
2344
|
+
if (!injected) return this.#baseSystemPrompt;
|
|
2345
|
+
return `${this.#baseSystemPrompt}\n\n${injected}`;
|
|
2346
|
+
} catch (err) {
|
|
2347
|
+
logger.debug("Memory backend beforeAgentStartPrompt failed", {
|
|
2348
|
+
backend: backend.id,
|
|
2349
|
+
error: String(err),
|
|
2350
|
+
});
|
|
2351
|
+
return this.#baseSystemPrompt;
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
|
|
2292
2355
|
/**
|
|
2293
2356
|
* Compose a stable signature for the inputs that `rebuildSystemPrompt` reads.
|
|
2294
2357
|
* Two calls producing identical signatures are guaranteed to produce identical
|
|
@@ -2908,12 +2971,14 @@ export class AgentSession {
|
|
|
2908
2971
|
messages.push(...fileMentionMessages);
|
|
2909
2972
|
}
|
|
2910
2973
|
|
|
2974
|
+
const beforeAgentStartSystemPrompt = await this.#buildSystemPromptForAgentStart(expandedText);
|
|
2975
|
+
|
|
2911
2976
|
// Emit before_agent_start extension event
|
|
2912
2977
|
if (this.#extensionRunner) {
|
|
2913
2978
|
const result = await this.#extensionRunner.emitBeforeAgentStart(
|
|
2914
2979
|
expandedText,
|
|
2915
2980
|
options?.images,
|
|
2916
|
-
|
|
2981
|
+
beforeAgentStartSystemPrompt,
|
|
2917
2982
|
);
|
|
2918
2983
|
if (result?.messages) {
|
|
2919
2984
|
const promptAttribution: "user" | "agent" | undefined =
|
|
@@ -2934,8 +2999,10 @@ export class AgentSession {
|
|
|
2934
2999
|
if (result?.systemPrompt !== undefined) {
|
|
2935
3000
|
this.agent.setSystemPrompt(result.systemPrompt);
|
|
2936
3001
|
} else {
|
|
2937
|
-
this.agent.setSystemPrompt(
|
|
3002
|
+
this.agent.setSystemPrompt(beforeAgentStartSystemPrompt);
|
|
2938
3003
|
}
|
|
3004
|
+
} else {
|
|
3005
|
+
this.agent.setSystemPrompt(beforeAgentStartSystemPrompt);
|
|
2939
3006
|
}
|
|
2940
3007
|
|
|
2941
3008
|
// Bail out if a newer abort/prompt cycle has started since we began setup
|
|
@@ -3590,6 +3657,8 @@ export class AgentSession {
|
|
|
3590
3657
|
await this.sessionManager.newSession(options);
|
|
3591
3658
|
this.setTodoPhases([]);
|
|
3592
3659
|
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
3660
|
+
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
3661
|
+
this.#resetHindsightConversationTrackingIfHindsight();
|
|
3593
3662
|
this.#steeringMessages = [];
|
|
3594
3663
|
this.#followUpMessages = [];
|
|
3595
3664
|
this.#pendingNextTurnMessages = [];
|
|
@@ -3683,6 +3752,7 @@ export class AgentSession {
|
|
|
3683
3752
|
|
|
3684
3753
|
// Update agent session ID
|
|
3685
3754
|
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
3755
|
+
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
3686
3756
|
|
|
3687
3757
|
// Emit session_switch event with reason "fork" to hooks
|
|
3688
3758
|
if (this.#extensionRunner) {
|
|
@@ -4118,6 +4188,11 @@ export class AgentSession {
|
|
|
4118
4188
|
preserveData = result?.preserveData;
|
|
4119
4189
|
}
|
|
4120
4190
|
|
|
4191
|
+
const memoryBackendContext = await this.#collectMemoryBackendContext(preparation);
|
|
4192
|
+
if (memoryBackendContext) {
|
|
4193
|
+
hookContext = hookContext ? [...hookContext, memoryBackendContext] : [memoryBackendContext];
|
|
4194
|
+
}
|
|
4195
|
+
|
|
4121
4196
|
let summary: string;
|
|
4122
4197
|
let shortSummary: string | undefined;
|
|
4123
4198
|
let firstKeptEntryId: string;
|
|
@@ -4204,6 +4279,32 @@ export class AgentSession {
|
|
|
4204
4279
|
}
|
|
4205
4280
|
}
|
|
4206
4281
|
|
|
4282
|
+
/**
|
|
4283
|
+
* Ask the active memory backend for an extra-context block to splice into
|
|
4284
|
+
* the compaction summary prompt. Both the manual and auto compaction paths
|
|
4285
|
+
* funnel through this helper so the behaviour stays identical.
|
|
4286
|
+
*
|
|
4287
|
+
* Failures are swallowed: a memory backend going sideways MUST NOT block
|
|
4288
|
+
* compaction (which is itself the recovery path for context overflow).
|
|
4289
|
+
*/
|
|
4290
|
+
async #collectMemoryBackendContext(preparation: {
|
|
4291
|
+
messagesToSummarize: AgentMessage[];
|
|
4292
|
+
turnPrefixMessages: AgentMessage[];
|
|
4293
|
+
}): Promise<string | undefined> {
|
|
4294
|
+
const backend = resolveMemoryBackend(this.settings);
|
|
4295
|
+
if (!backend.preCompactionContext) return undefined;
|
|
4296
|
+
const messages = preparation.messagesToSummarize.concat(preparation.turnPrefixMessages);
|
|
4297
|
+
try {
|
|
4298
|
+
return await backend.preCompactionContext(messages, this.settings, this);
|
|
4299
|
+
} catch (err) {
|
|
4300
|
+
logger.debug("Memory backend preCompactionContext failed", {
|
|
4301
|
+
backend: backend.id,
|
|
4302
|
+
error: String(err),
|
|
4303
|
+
});
|
|
4304
|
+
return undefined;
|
|
4305
|
+
}
|
|
4306
|
+
}
|
|
4307
|
+
|
|
4207
4308
|
/**
|
|
4208
4309
|
* Cancel in-progress context maintenance (manual compaction, auto-compaction, or auto-handoff).
|
|
4209
4310
|
*/
|
|
@@ -4358,6 +4459,8 @@ export class AgentSession {
|
|
|
4358
4459
|
await this.sessionManager.newSession(previousSessionFile ? { parentSession: previousSessionFile } : undefined);
|
|
4359
4460
|
this.agent.reset();
|
|
4360
4461
|
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
4462
|
+
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
4463
|
+
this.#resetHindsightConversationTrackingIfHindsight();
|
|
4361
4464
|
this.#steeringMessages = [];
|
|
4362
4465
|
this.#followUpMessages = [];
|
|
4363
4466
|
this.#pendingNextTurnMessages = [];
|
|
@@ -5190,6 +5293,11 @@ export class AgentSession {
|
|
|
5190
5293
|
preserveData = result?.preserveData;
|
|
5191
5294
|
}
|
|
5192
5295
|
|
|
5296
|
+
const memoryBackendContext = await this.#collectMemoryBackendContext(preparation);
|
|
5297
|
+
if (memoryBackendContext) {
|
|
5298
|
+
hookContext = hookContext ? [...hookContext, memoryBackendContext] : [memoryBackendContext];
|
|
5299
|
+
}
|
|
5300
|
+
|
|
5193
5301
|
let summary: string;
|
|
5194
5302
|
let shortSummary: string | undefined;
|
|
5195
5303
|
let firstKeptEntryId: string;
|
|
@@ -6499,6 +6607,7 @@ export class AgentSession {
|
|
|
6499
6607
|
try {
|
|
6500
6608
|
await this.sessionManager.setSessionFile(sessionPath);
|
|
6501
6609
|
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
6610
|
+
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
6502
6611
|
|
|
6503
6612
|
const sessionContext = this.buildDisplaySessionContext();
|
|
6504
6613
|
const didReloadConversationChange =
|
|
@@ -6568,11 +6677,15 @@ export class AgentSession {
|
|
|
6568
6677
|
? undefined
|
|
6569
6678
|
: configuredServiceTier;
|
|
6570
6679
|
|
|
6680
|
+
if (switchingToDifferentSession) {
|
|
6681
|
+
this.#resetHindsightConversationTrackingIfHindsight();
|
|
6682
|
+
}
|
|
6571
6683
|
this.#reconnectToAgent();
|
|
6572
6684
|
return true;
|
|
6573
6685
|
} catch (error) {
|
|
6574
6686
|
this.sessionManager.restoreState(previousSessionState);
|
|
6575
6687
|
this.agent.sessionId = previousSessionState.sessionId;
|
|
6688
|
+
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
6576
6689
|
let restoreMcpError: unknown;
|
|
6577
6690
|
try {
|
|
6578
6691
|
await this.#restoreMCPSelectionsForSessionContext(previousSessionContext, {
|
|
@@ -6664,6 +6777,8 @@ export class AgentSession {
|
|
|
6664
6777
|
}
|
|
6665
6778
|
this.#syncTodoPhasesFromBranch();
|
|
6666
6779
|
this.agent.sessionId = this.sessionManager.getSessionId();
|
|
6780
|
+
this.#rekeyHindsightMemoryForCurrentSessionId();
|
|
6781
|
+
this.#resetHindsightConversationTrackingIfHindsight();
|
|
6667
6782
|
|
|
6668
6783
|
// Reload messages from entries (works for both file and in-memory mode)
|
|
6669
6784
|
const sessionContext = this.buildDisplaySessionContext();
|
|
@@ -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,
|
|
@@ -604,6 +598,16 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
604
598
|
{ name: "reset", description: "Alias for clear" },
|
|
605
599
|
{ name: "enqueue", description: "Enqueue memory consolidation maintenance" },
|
|
606
600
|
{ name: "rebuild", description: "Alias for enqueue" },
|
|
601
|
+
{ name: "mm list", description: "List mental models on the active bank" },
|
|
602
|
+
{ name: "mm show", description: "Show one mental model (id required)" },
|
|
603
|
+
{
|
|
604
|
+
name: "mm refresh",
|
|
605
|
+
description: "Refresh auto-refresh models bank-wide, or one model by id",
|
|
606
|
+
},
|
|
607
|
+
{ name: "mm history", description: "Diff the change history of a mental model" },
|
|
608
|
+
{ name: "mm seed", description: "Create any built-in mental models that are missing" },
|
|
609
|
+
{ name: "mm delete", description: "Delete a mental model from the bank (id required)" },
|
|
610
|
+
{ name: "mm reload", description: "Re-pull the cached <mental_models> block" },
|
|
607
611
|
],
|
|
608
612
|
allowArgs: true,
|
|
609
613
|
handle: async (command, runtime) => {
|
|
@@ -942,14 +946,10 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
|
|
|
942
946
|
name: "reload-plugins",
|
|
943
947
|
description: "Reload all plugins (skills, commands, hooks, tools, agents, MCP)",
|
|
944
948
|
handle: async (_command, runtime) => {
|
|
945
|
-
// Invalidate
|
|
949
|
+
// Invalidate registry fs caches and the plugin roots cache so
|
|
946
950
|
// 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
951
|
const projectPath = await resolveActiveProjectRegistryPath(runtime.ctx.sessionManager.getCwd());
|
|
951
|
-
|
|
952
|
-
clearClaudePluginRootsCache();
|
|
952
|
+
clearPluginRootsAndCaches(projectPath ? [projectPath] : undefined);
|
|
953
953
|
await runtime.ctx.refreshSlashCommandState();
|
|
954
954
|
runtime.ctx.showStatus("Plugins reloaded.");
|
|
955
955
|
runtime.ctx.editor.setText("");
|
package/src/task/executor.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { SETTINGS_SCHEMA, type SettingPath } from "../config/settings-schema";
|
|
|
17
17
|
import type { CustomTool } from "../extensibility/custom-tools/types";
|
|
18
18
|
import { runExtensionCompact, runExtensionSetModel } from "../extensibility/extensions/compact-handler";
|
|
19
19
|
import type { Skill } from "../extensibility/skills";
|
|
20
|
+
import type { HindsightSessionState } from "../hindsight/state";
|
|
20
21
|
import type { LocalProtocolOptions } from "../internal-urls";
|
|
21
22
|
import { callTool } from "../mcp/client";
|
|
22
23
|
import type { MCPManager } from "../mcp/manager";
|
|
@@ -163,6 +164,7 @@ export interface ExecutorOptions {
|
|
|
163
164
|
settings?: Settings;
|
|
164
165
|
/** Override local:// protocol options so subagent shares parent's local:// root */
|
|
165
166
|
localProtocolOptions?: LocalProtocolOptions;
|
|
167
|
+
parentHindsightSessionState?: HindsightSessionState;
|
|
166
168
|
}
|
|
167
169
|
|
|
168
170
|
function parseStringifiedJson(value: unknown): unknown {
|
|
@@ -979,6 +981,7 @@ export async function runSubprocess(options: ExecutorOptions): Promise<SingleRes
|
|
|
979
981
|
hasUI: false,
|
|
980
982
|
spawns: spawnsEnv,
|
|
981
983
|
taskDepth: childDepth,
|
|
984
|
+
parentHindsightSessionState: options.parentHindsightSessionState,
|
|
982
985
|
parentTaskPrefix: id,
|
|
983
986
|
agentId: id,
|
|
984
987
|
agentDisplayName: agent.name,
|
package/src/task/index.ts
CHANGED
|
@@ -864,6 +864,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
864
864
|
skills: availableSkills,
|
|
865
865
|
promptTemplates,
|
|
866
866
|
localProtocolOptions,
|
|
867
|
+
parentHindsightSessionState: this.session.getHindsightSessionState?.(),
|
|
867
868
|
});
|
|
868
869
|
}
|
|
869
870
|
|
|
@@ -918,6 +919,7 @@ export class TaskTool implements AgentTool<TSchema, TaskToolDetails, Theme> {
|
|
|
918
919
|
skills: availableSkills,
|
|
919
920
|
promptTemplates,
|
|
920
921
|
localProtocolOptions,
|
|
922
|
+
parentHindsightSessionState: this.session.getHindsightSessionState?.(),
|
|
921
923
|
});
|
|
922
924
|
if (mergeMode === "branch" && result.exitCode === 0) {
|
|
923
925
|
try {
|
package/src/tools/ast-edit.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { Component } from "@oh-my-pi/pi-tui";
|
|
|
5
5
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { $envpos, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
7
7
|
import { type Static, Type } from "@sinclair/typebox";
|
|
8
|
-
import { computeLineHash,
|
|
8
|
+
import { computeLineHash, HL_BODY_SEP } from "../edit/line-hash";
|
|
9
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
10
10
|
import type { Theme } from "../modes/theme/theme";
|
|
11
11
|
import astEditDescription from "../prompts/tools/ast-edit.md" with { type: "text" };
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
hasGlobPathChars,
|
|
21
21
|
normalizePathLikeInput,
|
|
22
22
|
parseSearchPath,
|
|
23
|
+
partitionExistingPaths,
|
|
23
24
|
resolveExplicitSearchPaths,
|
|
24
25
|
resolveToCwd,
|
|
25
26
|
} from "./path-utils";
|
|
@@ -226,13 +227,21 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
226
227
|
}
|
|
227
228
|
resolvedPathInputs.push(resource.sourcePath);
|
|
228
229
|
}
|
|
229
|
-
|
|
230
|
-
|
|
230
|
+
let effectivePathInputs = resolvedPathInputs;
|
|
231
|
+
if (resolvedPathInputs.length > 1) {
|
|
232
|
+
const partition = await partitionExistingPaths(resolvedPathInputs, this.session.cwd, parseSearchPath);
|
|
233
|
+
if (partition.valid.length === 0) {
|
|
234
|
+
throw new ToolError(`Path not found: ${partition.missing.join(", ")}`);
|
|
235
|
+
}
|
|
236
|
+
effectivePathInputs = partition.valid;
|
|
237
|
+
}
|
|
238
|
+
if (effectivePathInputs.length === 1) {
|
|
239
|
+
const parsedPath = parseSearchPath(effectivePathInputs[0] ?? ".");
|
|
231
240
|
searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
|
|
232
241
|
globFilter = parsedPath.glob;
|
|
233
242
|
scopePath = formatScopePath(searchPath);
|
|
234
243
|
} else {
|
|
235
|
-
const multiSearchPath = await resolveExplicitSearchPaths(
|
|
244
|
+
const multiSearchPath = await resolveExplicitSearchPaths(effectivePathInputs, this.session.cwd, globFilter);
|
|
236
245
|
if (!multiSearchPath) {
|
|
237
246
|
throw new ToolError("`paths` must contain at least one path or glob");
|
|
238
247
|
}
|
|
@@ -321,7 +330,7 @@ export class AstEditTool implements AgentTool<typeof astEditSchema, AstEditToolD
|
|
|
321
330
|
const afterRef = useHashLines
|
|
322
331
|
? `${change.startLine}${computeLineHash(change.startLine, afterFirstLine)}`
|
|
323
332
|
: `${change.startLine}:${change.startColumn}`;
|
|
324
|
-
const lineSeparator = useHashLines ?
|
|
333
|
+
const lineSeparator = useHashLines ? HL_BODY_SEP : " ";
|
|
325
334
|
modelOut.push(`-${beforeRef}${lineSeparator}${beforeLine}`);
|
|
326
335
|
modelOut.push(`+${afterRef}${lineSeparator}${afterLine}`);
|
|
327
336
|
displayOut.push(formatCodeFrameLine("-", change.startLine, beforeLine, lineNumberWidth));
|
package/src/tools/ast-grep.ts
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
hasGlobPathChars,
|
|
21
21
|
normalizePathLikeInput,
|
|
22
22
|
parseSearchPath,
|
|
23
|
+
partitionExistingPaths,
|
|
23
24
|
resolveExplicitSearchPaths,
|
|
24
25
|
resolveToCwd,
|
|
25
26
|
} from "./path-utils";
|
|
@@ -171,13 +172,21 @@ export class AstGrepTool implements AgentTool<typeof astGrepSchema, AstGrepToolD
|
|
|
171
172
|
}
|
|
172
173
|
resolvedPathInputs.push(resource.sourcePath);
|
|
173
174
|
}
|
|
174
|
-
|
|
175
|
-
|
|
175
|
+
let effectivePathInputs = resolvedPathInputs;
|
|
176
|
+
if (resolvedPathInputs.length > 1) {
|
|
177
|
+
const partition = await partitionExistingPaths(resolvedPathInputs, this.session.cwd, parseSearchPath);
|
|
178
|
+
if (partition.valid.length === 0) {
|
|
179
|
+
throw new ToolError(`Path not found: ${partition.missing.join(", ")}`);
|
|
180
|
+
}
|
|
181
|
+
effectivePathInputs = partition.valid;
|
|
182
|
+
}
|
|
183
|
+
if (effectivePathInputs.length === 1) {
|
|
184
|
+
const parsedPath = parseSearchPath(effectivePathInputs[0] ?? ".");
|
|
176
185
|
searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
|
|
177
186
|
globFilter = parsedPath.glob;
|
|
178
187
|
scopePath = formatScopePath(searchPath);
|
|
179
188
|
} else {
|
|
180
|
-
const multiSearchPath = await resolveExplicitSearchPaths(
|
|
189
|
+
const multiSearchPath = await resolveExplicitSearchPaths(effectivePathInputs, this.session.cwd, globFilter);
|
|
181
190
|
if (!multiSearchPath) {
|
|
182
191
|
throw new ToolError("`paths` must contain at least one path or glob");
|
|
183
192
|
}
|
package/src/tools/find.ts
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
formatPathRelativeToCwd,
|
|
28
28
|
normalizePathLikeInput,
|
|
29
29
|
parseFindPattern,
|
|
30
|
+
partitionExistingPaths,
|
|
30
31
|
resolveExplicitFindPatterns,
|
|
31
32
|
resolveToCwd,
|
|
32
33
|
} from "./path-utils";
|
|
@@ -59,6 +60,10 @@ export interface FindToolDetails {
|
|
|
59
60
|
files?: string[];
|
|
60
61
|
truncated?: boolean;
|
|
61
62
|
error?: string;
|
|
63
|
+
/** User-supplied paths whose base directory was missing on disk. The tool
|
|
64
|
+
* skipped these and continued with the surviving entries; surfaced as a
|
|
65
|
+
* non-fatal warning in the renderer and in the model-facing text. */
|
|
66
|
+
missingPaths?: string[];
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
/**
|
|
@@ -114,8 +119,23 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
114
119
|
throw new ToolError("`paths` must contain non-empty globs or paths");
|
|
115
120
|
}
|
|
116
121
|
|
|
117
|
-
|
|
118
|
-
|
|
122
|
+
// Tolerate missing entries in a multi-path call: skip ones whose base
|
|
123
|
+
// directory is gone, and only error if every entry is missing. Single
|
|
124
|
+
// missing path keeps the original ENOENT semantics — the user explicitly
|
|
125
|
+
// asked about that one path, so silent empty results would be misleading.
|
|
126
|
+
let missingPaths: string[] = [];
|
|
127
|
+
let effectivePatterns = normalizedPatterns;
|
|
128
|
+
if (normalizedPatterns.length > 1 && !this.#customOps) {
|
|
129
|
+
const partition = await partitionExistingPaths(normalizedPatterns, this.session.cwd, parseFindPattern);
|
|
130
|
+
if (partition.valid.length === 0) {
|
|
131
|
+
throw new ToolError(`Path not found: ${partition.missing.join(", ")}`);
|
|
132
|
+
}
|
|
133
|
+
effectivePatterns = partition.valid;
|
|
134
|
+
missingPaths = partition.missing;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const multiPattern = await resolveExplicitFindPatterns(effectivePatterns, this.session.cwd);
|
|
138
|
+
const parsedPattern = multiPattern ? null : parseFindPattern(effectivePatterns[0] ?? ".");
|
|
119
139
|
const hasGlob = multiPattern ? true : (parsedPattern?.hasGlob ?? false);
|
|
120
140
|
const globPattern = multiPattern?.globPattern ?? parsedPattern?.globPattern ?? "**/*";
|
|
121
141
|
const searchPath = resolveToCwd(multiPattern?.basePath ?? parsedPattern?.basePath ?? ".", this.session.cwd);
|
|
@@ -124,7 +144,6 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
124
144
|
if (searchPath === "/") {
|
|
125
145
|
throw new ToolError("Searching from root directory '/' is not allowed");
|
|
126
146
|
}
|
|
127
|
-
|
|
128
147
|
const rawLimit = limit ?? DEFAULT_LIMIT;
|
|
129
148
|
const effectiveLimit = Number.isFinite(rawLimit) ? Math.floor(rawLimit) : Number.NaN;
|
|
130
149
|
if (!Number.isFinite(effectiveLimit) || effectiveLimit <= 0) {
|
|
@@ -141,16 +160,29 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
141
160
|
});
|
|
142
161
|
};
|
|
143
162
|
|
|
163
|
+
const missingPathsNote =
|
|
164
|
+
missingPaths.length > 0 ? `Skipped missing paths: ${missingPaths.join(", ")}` : undefined;
|
|
165
|
+
|
|
144
166
|
const buildResult = (files: string[]): AgentToolResult<FindToolDetails> => {
|
|
145
167
|
if (files.length === 0) {
|
|
146
|
-
const details: FindToolDetails = {
|
|
147
|
-
|
|
168
|
+
const details: FindToolDetails = {
|
|
169
|
+
scopePath,
|
|
170
|
+
fileCount: 0,
|
|
171
|
+
files: [],
|
|
172
|
+
truncated: false,
|
|
173
|
+
missingPaths: missingPaths.length > 0 ? missingPaths : undefined,
|
|
174
|
+
};
|
|
175
|
+
const text = missingPathsNote
|
|
176
|
+
? `No files found matching pattern\n${missingPathsNote}`
|
|
177
|
+
: "No files found matching pattern";
|
|
178
|
+
return toolResult(details).text(text).done();
|
|
148
179
|
}
|
|
149
180
|
|
|
150
181
|
const listLimit = applyListLimit(files, { limit: effectiveLimit });
|
|
151
182
|
const limited = listLimit.items;
|
|
152
183
|
const limitMeta = listLimit.meta;
|
|
153
|
-
const
|
|
184
|
+
const baseOutput = limited.join("\n");
|
|
185
|
+
const rawOutput = missingPathsNote ? `${baseOutput}\n\n${missingPathsNote}` : baseOutput;
|
|
154
186
|
const truncation = truncateHead(rawOutput, { maxLines: Number.MAX_SAFE_INTEGER });
|
|
155
187
|
|
|
156
188
|
const details: FindToolDetails = {
|
|
@@ -160,6 +192,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
|
|
|
160
192
|
truncated: Boolean(limitMeta.resultLimit || truncation.truncated),
|
|
161
193
|
resultLimitReached: limitMeta.resultLimit?.reached,
|
|
162
194
|
truncation: truncation.truncated ? truncation : undefined,
|
|
195
|
+
missingPaths: missingPaths.length > 0 ? missingPaths : undefined,
|
|
163
196
|
};
|
|
164
197
|
|
|
165
198
|
const resultBuilder = toolResult(details)
|
|
@@ -380,12 +413,18 @@ export const findToolRenderer = {
|
|
|
380
413
|
const truncated = Boolean(details?.truncated || truncation || details?.resultLimitReached || limits?.resultLimit);
|
|
381
414
|
const files = details?.files ?? [];
|
|
382
415
|
|
|
416
|
+
const missingPaths = details?.missingPaths ?? [];
|
|
417
|
+
const missingNote =
|
|
418
|
+
missingPaths.length > 0 ? uiTheme.fg("warning", `skipped missing: ${missingPaths.join(", ")}`) : undefined;
|
|
419
|
+
|
|
383
420
|
if (fileCount === 0) {
|
|
384
421
|
const header = renderStatusLine(
|
|
385
422
|
{ icon: "warning", title: "Find", description: args?.paths?.join(", "), meta: ["0 files"] },
|
|
386
423
|
uiTheme,
|
|
387
424
|
);
|
|
388
|
-
|
|
425
|
+
const lines = [header, formatEmptyMessage("No files found", uiTheme)];
|
|
426
|
+
if (missingNote) lines.push(missingNote);
|
|
427
|
+
return new Text(lines.join("\n"), 0, 0);
|
|
389
428
|
}
|
|
390
429
|
const meta: string[] = [formatCount("file", fileCount)];
|
|
391
430
|
if (details?.scopePath) meta.push(`in ${details.scopePath}`);
|
|
@@ -406,6 +445,7 @@ export const findToolRenderer = {
|
|
|
406
445
|
if (truncationReasons.length > 0) {
|
|
407
446
|
extraLines.push(uiTheme.fg("warning", `truncated: ${truncationReasons.join(", ")}`));
|
|
408
447
|
}
|
|
448
|
+
if (missingNote) extraLines.push(missingNote);
|
|
409
449
|
|
|
410
450
|
let cached: RenderCache | undefined;
|
|
411
451
|
return {
|
package/src/tools/gh-renderer.ts
CHANGED
|
@@ -47,6 +47,9 @@ const OP_TITLES: Record<string, string> = {
|
|
|
47
47
|
pr_push: "GitHub PR Push",
|
|
48
48
|
search_issues: "GitHub Search Issues",
|
|
49
49
|
search_prs: "GitHub Search PRs",
|
|
50
|
+
search_code: "GitHub Search Code",
|
|
51
|
+
search_commits: "GitHub Search Commits",
|
|
52
|
+
search_repos: "GitHub Search Repos",
|
|
50
53
|
run_watch: "GitHub Run Watch",
|
|
51
54
|
};
|
|
52
55
|
|
|
@@ -99,11 +102,17 @@ function buildOpMeta(args: GithubToolRenderArgs): string[] {
|
|
|
99
102
|
break;
|
|
100
103
|
}
|
|
101
104
|
case "search_issues":
|
|
102
|
-
case "search_prs":
|
|
105
|
+
case "search_prs":
|
|
106
|
+
case "search_code":
|
|
107
|
+
case "search_commits": {
|
|
103
108
|
if (args.query) meta.push(truncateVisualWidth(args.query, TRUNCATE_LENGTHS.CONTENT));
|
|
104
109
|
if (args.repo) meta.push(args.repo);
|
|
105
110
|
break;
|
|
106
111
|
}
|
|
112
|
+
case "search_repos": {
|
|
113
|
+
if (args.query) meta.push(truncateVisualWidth(args.query, TRUNCATE_LENGTHS.CONTENT));
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
107
116
|
case "repo_view": {
|
|
108
117
|
if (args.repo) meta.push(args.repo);
|
|
109
118
|
if (args.branch) meta.push(args.branch);
|