@oh-my-pi/pi-coding-agent 13.11.1 → 13.12.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 +58 -0
- package/package.json +7 -7
- package/src/capability/rule.ts +4 -0
- package/src/cli/commands/init-xdg.ts +27 -0
- package/src/cli/config-cli.ts +8 -3
- package/src/cli/shell-cli.ts +1 -1
- package/src/commands/config.ts +1 -1
- package/src/config/model-registry.ts +63 -10
- package/src/config/model-resolver.ts +84 -21
- package/src/config/settings-schema.ts +803 -637
- package/src/discovery/helpers.ts +8 -2
- package/src/exec/bash-executor.ts +62 -25
- package/src/extensibility/custom-tools/types.ts +2 -3
- package/src/extensibility/extensions/types.ts +2 -0
- package/src/extensibility/hooks/types.ts +2 -0
- package/src/index.ts +6 -6
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/memories/index.ts +20 -7
- package/src/memories/storage.ts +46 -32
- package/src/modes/components/agent-dashboard.ts +23 -35
- package/src/modes/components/assistant-message.ts +25 -2
- package/src/modes/components/btw-panel.ts +104 -0
- package/src/modes/components/settings-defs.ts +1 -1
- package/src/modes/components/settings-selector.ts +6 -6
- package/src/modes/controllers/btw-controller.ts +193 -0
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +4 -0
- package/src/modes/controllers/input-controller.ts +10 -1
- package/src/modes/interactive-mode.ts +22 -0
- package/src/modes/prompt-action-autocomplete.ts +17 -3
- package/src/modes/rpc/rpc-client.ts +30 -19
- package/src/modes/theme/theme.ts +28 -36
- package/src/modes/types.ts +4 -0
- package/src/modes/utils/ui-helpers.ts +3 -0
- package/src/prompts/system/btw-user.md +8 -0
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/sdk.ts +17 -25
- package/src/session/agent-session.ts +65 -37
- package/src/session/blob-store.ts +32 -0
- package/src/session/compaction/compaction.ts +27 -6
- package/src/session/history-storage.ts +2 -2
- package/src/session/session-manager.ts +116 -44
- package/src/slash-commands/builtin-registry.ts +11 -0
- package/src/system-prompt.ts +4 -17
- package/src/task/agents.ts +1 -1
- package/src/task/index.ts +9 -8
- package/src/tools/browser.ts +11 -0
- package/src/tools/output-meta.ts +96 -3
- package/src/utils/title-generator.ts +70 -92
- package/src/utils/tools-manager.ts +1 -1
- package/src/web/scrapers/index.ts +7 -7
- package/src/web/scrapers/utils.ts +1 -0
|
@@ -297,6 +297,7 @@ When a tool call fails, read the full error before doing anything else. When a f
|
|
|
297
297
|
- You **MUST** exhaust tools/context/files first — explore.
|
|
298
298
|
## 7. Verification
|
|
299
299
|
- Test everything rigorously → Future contributor cannot break behavior without failure. Prefer unit/e2e.
|
|
300
|
+
- You **MUST NOT** rely on mocks — they invent behaviors that never happen in production and hide real bugs.
|
|
300
301
|
- You **SHOULD** run only tests you added/modified unless asked otherwise.
|
|
301
302
|
- You **MUST NOT** yield without proof when non-trivial work, self-assessment is deceptive: tests, linters, type checks, repro steps… exhaust all external verification.
|
|
302
303
|
|
package/src/sdk.ts
CHANGED
|
@@ -227,17 +227,17 @@ export {
|
|
|
227
227
|
BashTool,
|
|
228
228
|
// Tool classes and factories
|
|
229
229
|
BUILTIN_TOOLS,
|
|
230
|
-
HIDDEN_TOOLS,
|
|
231
230
|
createTools,
|
|
232
231
|
EditTool,
|
|
233
232
|
FindTool,
|
|
234
233
|
GrepTool,
|
|
234
|
+
HIDDEN_TOOLS,
|
|
235
235
|
loadSshTool,
|
|
236
236
|
PythonTool,
|
|
237
237
|
ReadTool,
|
|
238
238
|
ResolveTool,
|
|
239
|
-
WriteTool,
|
|
240
239
|
type ToolSession,
|
|
240
|
+
WriteTool,
|
|
241
241
|
};
|
|
242
242
|
|
|
243
243
|
// Helper Functions
|
|
@@ -702,19 +702,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
702
702
|
}
|
|
703
703
|
}
|
|
704
704
|
|
|
705
|
-
// For subagent sessions using GitHub Copilot, add X-Initiator header
|
|
706
|
-
// to ensure proper billing (agent-initiated vs user-initiated)
|
|
707
705
|
const taskDepth = options.taskDepth ?? 0;
|
|
708
|
-
const forceCopilotAgentInitiator = taskDepth > 0;
|
|
709
|
-
if (forceCopilotAgentInitiator && model?.provider === "github-copilot") {
|
|
710
|
-
model = {
|
|
711
|
-
...model,
|
|
712
|
-
headers: {
|
|
713
|
-
...model.headers,
|
|
714
|
-
"X-Initiator": "agent",
|
|
715
|
-
},
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
706
|
|
|
719
707
|
let thinkingLevel = options.thinkingLevel;
|
|
720
708
|
|
|
@@ -1340,6 +1328,16 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1340
1328
|
if (!obfuscator?.hasSecrets()) return converted;
|
|
1341
1329
|
return obfuscateMessages(obfuscator, converted);
|
|
1342
1330
|
};
|
|
1331
|
+
const transformContext = extensionRunner
|
|
1332
|
+
? async (messages: AgentMessage[], _signal?: AbortSignal) => {
|
|
1333
|
+
return await extensionRunner.emitContext(messages);
|
|
1334
|
+
}
|
|
1335
|
+
: undefined;
|
|
1336
|
+
const onPayload = extensionRunner
|
|
1337
|
+
? async (payload: unknown, _model?: Model) => {
|
|
1338
|
+
return await extensionRunner.emitBeforeProviderRequest(payload);
|
|
1339
|
+
}
|
|
1340
|
+
: undefined;
|
|
1343
1341
|
|
|
1344
1342
|
const setToolUIContext = (uiContext: ExtensionUIContext, hasUI: boolean) => {
|
|
1345
1343
|
toolContextStore.setUIContext(uiContext, hasUI);
|
|
@@ -1362,17 +1360,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1362
1360
|
tools: initialTools,
|
|
1363
1361
|
},
|
|
1364
1362
|
convertToLlm: convertToLlmFinal,
|
|
1365
|
-
onPayload
|
|
1366
|
-
? async (payload, _model) => {
|
|
1367
|
-
return extensionRunner.emitBeforeProviderRequest(payload);
|
|
1368
|
-
}
|
|
1369
|
-
: undefined,
|
|
1363
|
+
onPayload,
|
|
1370
1364
|
sessionId: sessionManager.getSessionId(),
|
|
1371
|
-
transformContext
|
|
1372
|
-
? async messages => {
|
|
1373
|
-
return extensionRunner.emitContext(messages);
|
|
1374
|
-
}
|
|
1375
|
-
: undefined,
|
|
1365
|
+
transformContext,
|
|
1376
1366
|
steeringMode: settings.get("steeringMode") ?? "one-at-a-time",
|
|
1377
1367
|
followUpMode: settings.get("followUpMode") ?? "one-at-a-time",
|
|
1378
1368
|
interruptMode: settings.get("interruptMode") ?? "immediate",
|
|
@@ -1447,9 +1437,11 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1447
1437
|
skillsSettings: settings.getGroup("skills") as Required<SkillsSettings>,
|
|
1448
1438
|
modelRegistry,
|
|
1449
1439
|
toolRegistry,
|
|
1440
|
+
transformContext,
|
|
1441
|
+
onPayload,
|
|
1442
|
+
convertToLlm: convertToLlmFinal,
|
|
1450
1443
|
rebuildSystemPrompt,
|
|
1451
1444
|
ttsrManager,
|
|
1452
|
-
forceCopilotAgentInitiator,
|
|
1453
1445
|
obfuscator,
|
|
1454
1446
|
asyncJobManager,
|
|
1455
1447
|
pendingActionStore,
|
|
@@ -34,6 +34,7 @@ import type {
|
|
|
34
34
|
Model,
|
|
35
35
|
ProviderSessionState,
|
|
36
36
|
ServiceTier,
|
|
37
|
+
SimpleStreamOptions,
|
|
37
38
|
TextContent,
|
|
38
39
|
ToolCall,
|
|
39
40
|
ToolChoice,
|
|
@@ -104,6 +105,7 @@ import { outputMeta } from "../tools/output-meta";
|
|
|
104
105
|
import { resolveToCwd } from "../tools/path-utils";
|
|
105
106
|
import type { PendingActionStore } from "../tools/pending-action";
|
|
106
107
|
import { getLatestTodoPhasesFromEntries, type TodoItem, type TodoPhase } from "../tools/todo-write";
|
|
108
|
+
import { clampTimeout } from "../tools/tool-timeouts";
|
|
107
109
|
import { parseCommandArgs } from "../utils/command-args";
|
|
108
110
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
109
111
|
import { extractFileMentions, generateFileMentionMessages } from "../utils/file-mentions";
|
|
@@ -126,6 +128,7 @@ import {
|
|
|
126
128
|
bashExecutionToText,
|
|
127
129
|
type CompactionSummaryMessage,
|
|
128
130
|
type CustomMessage,
|
|
131
|
+
convertToLlm,
|
|
129
132
|
type FileMentionMessage,
|
|
130
133
|
type HookMessage,
|
|
131
134
|
type PythonExecutionMessage,
|
|
@@ -145,6 +148,8 @@ export type AgentSessionEvent =
|
|
|
145
148
|
aborted: boolean;
|
|
146
149
|
willRetry: boolean;
|
|
147
150
|
errorMessage?: string;
|
|
151
|
+
/** True when compaction was skipped for a benign reason (no model, no candidates, nothing to compact). */
|
|
152
|
+
skipped?: boolean;
|
|
148
153
|
}
|
|
149
154
|
| { type: "auto_retry_start"; attempt: number; maxAttempts: number; delayMs: number; errorMessage: string }
|
|
150
155
|
| { type: "auto_retry_end"; success: boolean; attempt: number; finalError?: string }
|
|
@@ -191,12 +196,16 @@ export interface AgentSessionConfig {
|
|
|
191
196
|
modelRegistry: ModelRegistry;
|
|
192
197
|
/** Tool registry for LSP and settings */
|
|
193
198
|
toolRegistry?: Map<string, AgentTool>;
|
|
199
|
+
/** Current session pre-LLM message transform pipeline */
|
|
200
|
+
transformContext?: (messages: AgentMessage[], signal?: AbortSignal) => AgentMessage[] | Promise<AgentMessage[]>;
|
|
201
|
+
/** Provider payload hook used by the active session request path */
|
|
202
|
+
onPayload?: SimpleStreamOptions["onPayload"];
|
|
203
|
+
/** Current session message-to-LLM conversion pipeline */
|
|
204
|
+
convertToLlm?: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
|
|
194
205
|
/** System prompt builder that can consider tool availability */
|
|
195
206
|
rebuildSystemPrompt?: (toolNames: string[], tools: Map<string, AgentTool>) => Promise<string>;
|
|
196
207
|
/** TTSR manager for time-traveling stream rules */
|
|
197
208
|
ttsrManager?: TtsrManager;
|
|
198
|
-
/** Force X-Initiator: agent for GitHub Copilot model selections in this session. */
|
|
199
|
-
forceCopilotAgentInitiator?: boolean;
|
|
200
209
|
/** Secret obfuscator for deobfuscating streaming edit content */
|
|
201
210
|
obfuscator?: SecretObfuscator;
|
|
202
211
|
/** Pending action store for preview/apply workflows */
|
|
@@ -381,9 +390,11 @@ export class AgentSession {
|
|
|
381
390
|
|
|
382
391
|
// Tool registry and prompt builder for extensions
|
|
383
392
|
#toolRegistry: Map<string, AgentTool>;
|
|
393
|
+
#transformContext: (messages: AgentMessage[], signal?: AbortSignal) => AgentMessage[] | Promise<AgentMessage[]>;
|
|
394
|
+
#onPayload: SimpleStreamOptions["onPayload"] | undefined;
|
|
395
|
+
#convertToLlm: (messages: AgentMessage[]) => Message[] | Promise<Message[]>;
|
|
384
396
|
#rebuildSystemPrompt: ((toolNames: string[], tools: Map<string, AgentTool>) => Promise<string>) | undefined;
|
|
385
397
|
#baseSystemPrompt: string;
|
|
386
|
-
#forceCopilotAgentInitiator = false;
|
|
387
398
|
|
|
388
399
|
// TTSR manager for time-traveling stream rules
|
|
389
400
|
#ttsrManager: TtsrManager | undefined = undefined;
|
|
@@ -425,10 +436,12 @@ export class AgentSession {
|
|
|
425
436
|
this.#skillsSettings = config.skillsSettings;
|
|
426
437
|
this.#modelRegistry = config.modelRegistry;
|
|
427
438
|
this.#toolRegistry = config.toolRegistry ?? new Map();
|
|
439
|
+
this.#transformContext = config.transformContext ?? (messages => messages);
|
|
440
|
+
this.#onPayload = config.onPayload;
|
|
441
|
+
this.#convertToLlm = config.convertToLlm ?? convertToLlm;
|
|
428
442
|
this.#rebuildSystemPrompt = config.rebuildSystemPrompt;
|
|
429
443
|
this.#baseSystemPrompt = this.agent.state.systemPrompt;
|
|
430
444
|
this.#ttsrManager = config.ttsrManager;
|
|
431
|
-
this.#forceCopilotAgentInitiator = config.forceCopilotAgentInitiator ?? false;
|
|
432
445
|
this.#obfuscator = config.obfuscator;
|
|
433
446
|
this.agent.providerSessionState = this.#providerSessionState;
|
|
434
447
|
this.#pendingActionStore = config.pendingActionStore;
|
|
@@ -580,7 +593,7 @@ export class AgentSession {
|
|
|
580
593
|
|
|
581
594
|
this.#addPendingTtsrInjections(matches);
|
|
582
595
|
|
|
583
|
-
if (this.#shouldInterruptForTtsrMatch(matchContext)) {
|
|
596
|
+
if (this.#shouldInterruptForTtsrMatch(matches, matchContext)) {
|
|
584
597
|
// Abort the stream immediately — do not gate on extension callbacks
|
|
585
598
|
this.#ttsrAbortPending = true;
|
|
586
599
|
this.#ensureTtsrResumePromise();
|
|
@@ -1014,18 +1027,17 @@ export class AgentSession {
|
|
|
1014
1027
|
return -1;
|
|
1015
1028
|
}
|
|
1016
1029
|
|
|
1017
|
-
#shouldInterruptForTtsrMatch(matchContext: TtsrMatchContext): boolean {
|
|
1018
|
-
const
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
return matchContext.source === "tool";
|
|
1030
|
+
#shouldInterruptForTtsrMatch(matches: Rule[], matchContext: TtsrMatchContext): boolean {
|
|
1031
|
+
const globalMode = this.#ttsrManager?.getSettings().interruptMode ?? "always";
|
|
1032
|
+
for (const rule of matches) {
|
|
1033
|
+
const mode = rule.interruptMode ?? globalMode;
|
|
1034
|
+
if (mode === "never") continue;
|
|
1035
|
+
if (mode === "prose-only" && (matchContext.source === "text" || matchContext.source === "thinking"))
|
|
1036
|
+
return true;
|
|
1037
|
+
if (mode === "tool-only" && matchContext.source === "tool") return true;
|
|
1038
|
+
if (mode === "always") return true;
|
|
1027
1039
|
}
|
|
1028
|
-
return
|
|
1040
|
+
return false;
|
|
1029
1041
|
}
|
|
1030
1042
|
|
|
1031
1043
|
#queueDeferredTtsrInjectionIfNeeded(assistantMsg: AssistantMessage): void {
|
|
@@ -1442,6 +1454,7 @@ export class AgentSession {
|
|
|
1442
1454
|
aborted: event.aborted,
|
|
1443
1455
|
willRetry: event.willRetry,
|
|
1444
1456
|
errorMessage: event.errorMessage,
|
|
1457
|
+
skipped: event.skipped,
|
|
1445
1458
|
});
|
|
1446
1459
|
} else if (event.type === "auto_retry_start") {
|
|
1447
1460
|
await this.#extensionRunner.emit({
|
|
@@ -1526,7 +1539,7 @@ export class AgentSession {
|
|
|
1526
1539
|
if (drained === false && deliveryState) {
|
|
1527
1540
|
logger.warn("Async job completion deliveries still pending during dispose", { ...deliveryState });
|
|
1528
1541
|
}
|
|
1529
|
-
await this.sessionManager.
|
|
1542
|
+
await this.sessionManager.close();
|
|
1530
1543
|
for (const state of this.#providerSessionState.values()) {
|
|
1531
1544
|
state.close();
|
|
1532
1545
|
}
|
|
@@ -1551,19 +1564,6 @@ export class AgentSession {
|
|
|
1551
1564
|
return this.agent.state.model;
|
|
1552
1565
|
}
|
|
1553
1566
|
|
|
1554
|
-
#applySessionModelOverrides(model: Model): Model {
|
|
1555
|
-
if (!this.#forceCopilotAgentInitiator || model.provider !== "github-copilot") {
|
|
1556
|
-
return model;
|
|
1557
|
-
}
|
|
1558
|
-
return {
|
|
1559
|
-
...model,
|
|
1560
|
-
headers: {
|
|
1561
|
-
...model.headers,
|
|
1562
|
-
"X-Initiator": "agent",
|
|
1563
|
-
},
|
|
1564
|
-
};
|
|
1565
|
-
}
|
|
1566
|
-
|
|
1567
1567
|
/** Current thinking level */
|
|
1568
1568
|
get thinkingLevel(): ThinkingLevel | undefined {
|
|
1569
1569
|
return this.#thinkingLevel;
|
|
@@ -1714,6 +1714,31 @@ export class AgentSession {
|
|
|
1714
1714
|
return this.agent.state.messages;
|
|
1715
1715
|
}
|
|
1716
1716
|
|
|
1717
|
+
/** Convert session messages using the same pre-LLM pipeline as the active session. */
|
|
1718
|
+
async convertMessagesToLlm(messages: AgentMessage[], signal?: AbortSignal): Promise<Message[]> {
|
|
1719
|
+
const transformedMessages = await this.#transformContext(messages, signal);
|
|
1720
|
+
return await this.#convertToLlm(transformedMessages);
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
/** Apply session-level stream hooks to a direct side request. */
|
|
1724
|
+
prepareSimpleStreamOptions(options: SimpleStreamOptions): SimpleStreamOptions {
|
|
1725
|
+
if (!this.#onPayload) return options;
|
|
1726
|
+
if (!options.onPayload) {
|
|
1727
|
+
return { ...options, onPayload: this.#onPayload };
|
|
1728
|
+
}
|
|
1729
|
+
const sessionOnPayload = this.#onPayload;
|
|
1730
|
+
const requestOnPayload = options.onPayload;
|
|
1731
|
+
return {
|
|
1732
|
+
...options,
|
|
1733
|
+
onPayload: async (payload, model) => {
|
|
1734
|
+
const sessionPayload = await sessionOnPayload(payload, model);
|
|
1735
|
+
const sessionResolvedPayload = sessionPayload ?? payload;
|
|
1736
|
+
const requestPayload = await requestOnPayload(sessionResolvedPayload, model);
|
|
1737
|
+
return requestPayload ?? sessionResolvedPayload;
|
|
1738
|
+
},
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1717
1742
|
/** Current steering mode */
|
|
1718
1743
|
get steeringMode(): "all" | "one-at-a-time" {
|
|
1719
1744
|
return this.agent.getSteeringMode();
|
|
@@ -2905,14 +2930,12 @@ export class AgentSession {
|
|
|
2905
2930
|
* Cycle to next thinking level.
|
|
2906
2931
|
* @returns New level, or undefined if model doesn't support thinking
|
|
2907
2932
|
*/
|
|
2908
|
-
cycleThinkingLevel():
|
|
2933
|
+
cycleThinkingLevel(): ThinkingLevel | undefined {
|
|
2909
2934
|
if (!this.model?.reasoning) return undefined;
|
|
2910
2935
|
|
|
2911
|
-
const levels = this.getAvailableThinkingLevels();
|
|
2912
|
-
const
|
|
2913
|
-
|
|
2914
|
-
? levels.indexOf(this.thinkingLevel)
|
|
2915
|
-
: -1;
|
|
2936
|
+
const levels = [ThinkingLevel.Off, ...this.getAvailableThinkingLevels()];
|
|
2937
|
+
const currentLevel = this.thinkingLevel === ThinkingLevel.Inherit ? ThinkingLevel.Off : this.thinkingLevel;
|
|
2938
|
+
const currentIndex = currentLevel ? levels.indexOf(currentLevel) : -1;
|
|
2916
2939
|
const nextIndex = (currentIndex + 1) % levels.length;
|
|
2917
2940
|
const nextLevel = levels[nextIndex];
|
|
2918
2941
|
if (!nextLevel) return undefined;
|
|
@@ -3685,7 +3708,7 @@ export class AgentSession {
|
|
|
3685
3708
|
if (currentModel) {
|
|
3686
3709
|
this.#closeProviderSessionsForModelSwitch(currentModel, model);
|
|
3687
3710
|
}
|
|
3688
|
-
this.agent.setModel(
|
|
3711
|
+
this.agent.setModel(model);
|
|
3689
3712
|
}
|
|
3690
3713
|
|
|
3691
3714
|
#closeCodexProviderSessionsForHistoryRewrite(): void {
|
|
@@ -3853,6 +3876,7 @@ export class AgentSession {
|
|
|
3853
3876
|
result: undefined,
|
|
3854
3877
|
aborted: false,
|
|
3855
3878
|
willRetry: false,
|
|
3879
|
+
skipped: true,
|
|
3856
3880
|
});
|
|
3857
3881
|
return;
|
|
3858
3882
|
}
|
|
@@ -3865,6 +3889,7 @@ export class AgentSession {
|
|
|
3865
3889
|
result: undefined,
|
|
3866
3890
|
aborted: false,
|
|
3867
3891
|
willRetry: false,
|
|
3892
|
+
skipped: true,
|
|
3868
3893
|
});
|
|
3869
3894
|
return;
|
|
3870
3895
|
}
|
|
@@ -3879,6 +3904,7 @@ export class AgentSession {
|
|
|
3879
3904
|
result: undefined,
|
|
3880
3905
|
aborted: false,
|
|
3881
3906
|
willRetry: false,
|
|
3907
|
+
skipped: true,
|
|
3882
3908
|
});
|
|
3883
3909
|
if (!willRetry && this.agent.hasQueuedMessages()) {
|
|
3884
3910
|
this.#scheduleAgentContinue({
|
|
@@ -3966,6 +3992,7 @@ export class AgentSession {
|
|
|
3966
3992
|
promptOverride: hookPrompt,
|
|
3967
3993
|
extraContext: hookContext,
|
|
3968
3994
|
remoteInstructions: this.#baseSystemPrompt,
|
|
3995
|
+
initiatorOverride: "agent",
|
|
3969
3996
|
});
|
|
3970
3997
|
break;
|
|
3971
3998
|
} catch (error) {
|
|
@@ -4429,6 +4456,7 @@ export class AgentSession {
|
|
|
4429
4456
|
onChunk,
|
|
4430
4457
|
signal: this.#bashAbortController.signal,
|
|
4431
4458
|
sessionKey: this.sessionId,
|
|
4459
|
+
timeout: clampTimeout("bash") * 1000,
|
|
4432
4460
|
});
|
|
4433
4461
|
|
|
4434
4462
|
this.recordBashResult(command, result, options);
|
|
@@ -74,6 +74,21 @@ export function parseBlobRef(data: string): string | null {
|
|
|
74
74
|
return data.slice(BLOB_PREFIX.length);
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/** Identify provider transport image data URLs so persistence can externalize and restore them losslessly. */
|
|
78
|
+
export function isImageDataUrl(data: string): boolean {
|
|
79
|
+
return data.startsWith("data:image/") && data.includes(";base64,");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Externalize a provider image data URL to the blob store, returning a blob reference.
|
|
84
|
+
* The full data URL string is preserved so transport-native history can be reconstructed on resume.
|
|
85
|
+
*/
|
|
86
|
+
export async function externalizeImageDataUrl(blobStore: BlobStore, dataUrl: string): Promise<string> {
|
|
87
|
+
if (isBlobRef(dataUrl)) return dataUrl;
|
|
88
|
+
const { ref } = await blobStore.put(Buffer.from(dataUrl, "utf8"));
|
|
89
|
+
return ref;
|
|
90
|
+
}
|
|
91
|
+
|
|
77
92
|
/**
|
|
78
93
|
* Externalize an image's base64 data to the blob store, returning a blob reference.
|
|
79
94
|
* If the data is already a blob reference, returns it unchanged.
|
|
@@ -85,6 +100,23 @@ export async function externalizeImageData(blobStore: BlobStore, base64Data: str
|
|
|
85
100
|
return ref;
|
|
86
101
|
}
|
|
87
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Resolve an externalized provider image data URL back to its original string.
|
|
105
|
+
* If the data is not a blob reference, returns it unchanged.
|
|
106
|
+
* If the blob is missing, logs a warning and returns the reference as-is.
|
|
107
|
+
*/
|
|
108
|
+
export async function resolveImageDataUrl(blobStore: BlobStore, data: string): Promise<string> {
|
|
109
|
+
const hash = parseBlobRef(data);
|
|
110
|
+
if (!hash) return data;
|
|
111
|
+
|
|
112
|
+
const buffer = await blobStore.get(hash);
|
|
113
|
+
if (!buffer) {
|
|
114
|
+
logger.warn("Blob not found for persisted image data URL", { hash });
|
|
115
|
+
return data;
|
|
116
|
+
}
|
|
117
|
+
return buffer.toString("utf8");
|
|
118
|
+
}
|
|
119
|
+
|
|
88
120
|
/**
|
|
89
121
|
* Resolve a blob reference back to base64 data.
|
|
90
122
|
* If the data is not a blob reference, returns it unchanged.
|
|
@@ -5,7 +5,14 @@
|
|
|
5
5
|
* and after compaction the session is reloaded.
|
|
6
6
|
*/
|
|
7
7
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
type AssistantMessage,
|
|
10
|
+
completeSimple,
|
|
11
|
+
Effort,
|
|
12
|
+
type MessageAttribution,
|
|
13
|
+
type Model,
|
|
14
|
+
type Usage,
|
|
15
|
+
} from "@oh-my-pi/pi-ai";
|
|
9
16
|
import {
|
|
10
17
|
CODEX_BASE_URL,
|
|
11
18
|
getCodexAccountId,
|
|
@@ -935,6 +942,7 @@ export interface SummaryOptions {
|
|
|
935
942
|
extraContext?: string[];
|
|
936
943
|
remoteEndpoint?: string;
|
|
937
944
|
remoteInstructions?: string;
|
|
945
|
+
initiatorOverride?: MessageAttribution;
|
|
938
946
|
}
|
|
939
947
|
|
|
940
948
|
export async function generateSummary(
|
|
@@ -990,7 +998,7 @@ export async function generateSummary(
|
|
|
990
998
|
const response = await completeSimple(
|
|
991
999
|
model,
|
|
992
1000
|
{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },
|
|
993
|
-
{ maxTokens, signal, apiKey, reasoning: Effort.High },
|
|
1001
|
+
{ maxTokens, signal, apiKey, reasoning: Effort.High, initiatorOverride: options?.initiatorOverride },
|
|
994
1002
|
);
|
|
995
1003
|
|
|
996
1004
|
if (response.stopReason === "error") {
|
|
@@ -1039,7 +1047,7 @@ async function generateShortSummary(
|
|
|
1039
1047
|
systemPrompt: SUMMARIZATION_SYSTEM_PROMPT,
|
|
1040
1048
|
messages: [{ role: "user", content: [{ type: "text", text: promptText }], timestamp: Date.now() }],
|
|
1041
1049
|
},
|
|
1042
|
-
{ maxTokens, signal, apiKey, reasoning: Effort.High },
|
|
1050
|
+
{ maxTokens, signal, apiKey, reasoning: Effort.High, initiatorOverride: options?.initiatorOverride },
|
|
1043
1051
|
);
|
|
1044
1052
|
|
|
1045
1053
|
if (response.stopReason === "error") {
|
|
@@ -1218,6 +1226,7 @@ export async function compact(
|
|
|
1218
1226
|
extraContext: options?.extraContext,
|
|
1219
1227
|
remoteEndpoint: settings.remoteEnabled === false ? undefined : settings.remoteEndpoint,
|
|
1220
1228
|
remoteInstructions: options?.remoteInstructions,
|
|
1229
|
+
initiatorOverride: options?.initiatorOverride,
|
|
1221
1230
|
};
|
|
1222
1231
|
|
|
1223
1232
|
let preserveData = withOpenAiRemoteCompactionPreserveData(previousPreserveData, undefined);
|
|
@@ -1266,7 +1275,14 @@ export async function compact(
|
|
|
1266
1275
|
summaryOptions,
|
|
1267
1276
|
)
|
|
1268
1277
|
: Promise.resolve("No prior history."),
|
|
1269
|
-
generateTurnPrefixSummary(
|
|
1278
|
+
generateTurnPrefixSummary(
|
|
1279
|
+
turnPrefixMessages,
|
|
1280
|
+
model,
|
|
1281
|
+
settings.reserveTokens,
|
|
1282
|
+
apiKey,
|
|
1283
|
+
signal,
|
|
1284
|
+
summaryOptions.initiatorOverride,
|
|
1285
|
+
),
|
|
1270
1286
|
]);
|
|
1271
1287
|
// Merge into single summary
|
|
1272
1288
|
summary = `${historyResult}\n\n---\n\n**Turn Context (split turn):**\n\n${turnPrefixResult}`;
|
|
@@ -1297,7 +1313,11 @@ export async function compact(
|
|
|
1297
1313
|
settings.reserveTokens,
|
|
1298
1314
|
apiKey,
|
|
1299
1315
|
signal,
|
|
1300
|
-
{
|
|
1316
|
+
{
|
|
1317
|
+
extraContext: options?.extraContext,
|
|
1318
|
+
remoteEndpoint: summaryOptions.remoteEndpoint,
|
|
1319
|
+
initiatorOverride: summaryOptions.initiatorOverride,
|
|
1320
|
+
},
|
|
1301
1321
|
);
|
|
1302
1322
|
|
|
1303
1323
|
// Compute file lists and append to summary
|
|
@@ -1327,6 +1347,7 @@ async function generateTurnPrefixSummary(
|
|
|
1327
1347
|
reserveTokens: number,
|
|
1328
1348
|
apiKey: string,
|
|
1329
1349
|
signal?: AbortSignal,
|
|
1350
|
+
initiatorOverride?: MessageAttribution,
|
|
1330
1351
|
): Promise<string> {
|
|
1331
1352
|
const maxTokens = Math.floor(0.5 * reserveTokens); // Smaller budget for turn prefix
|
|
1332
1353
|
|
|
@@ -1344,7 +1365,7 @@ async function generateTurnPrefixSummary(
|
|
|
1344
1365
|
const response = await completeSimple(
|
|
1345
1366
|
model,
|
|
1346
1367
|
{ systemPrompt: SUMMARIZATION_SYSTEM_PROMPT, messages: summarizationMessages },
|
|
1347
|
-
{ maxTokens, signal, apiKey, reasoning: Effort.High },
|
|
1368
|
+
{ maxTokens, signal, apiKey, reasoning: Effort.High, initiatorOverride },
|
|
1348
1369
|
);
|
|
1349
1370
|
|
|
1350
1371
|
if (response.stopReason === "error") {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Database, type Statement } from "bun:sqlite";
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import {
|
|
4
|
+
import { getHistoryDbPath, logger } from "@oh-my-pi/pi-utils";
|
|
5
5
|
|
|
6
6
|
export interface HistoryEntry {
|
|
7
7
|
id: number;
|
|
@@ -78,7 +78,7 @@ END;
|
|
|
78
78
|
this.#lastPromptCache = last?.prompt ?? null;
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
static open(dbPath: string =
|
|
81
|
+
static open(dbPath: string = getHistoryDbPath()): HistoryStorage {
|
|
82
82
|
if (!HistoryStorage.#instance) {
|
|
83
83
|
HistoryStorage.#instance = new HistoryStorage(dbPath);
|
|
84
84
|
}
|