@oh-my-pi/pi-coding-agent 15.10.11 → 15.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +103 -2
- package/dist/cli.js +5790 -5731
- package/dist/types/async/index.d.ts +0 -1
- package/dist/types/cli/args.d.ts +1 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +5 -0
- package/dist/types/cli-commands.d.ts +12 -0
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/api-key-resolver.d.ts +3 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/model-registry.d.ts +1 -0
- package/dist/types/config/model-resolver.d.ts +18 -0
- package/dist/types/config/settings-schema.d.ts +85 -34
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
- package/dist/types/eval/py/executor.d.ts +5 -0
- package/dist/types/eval/py/kernel.d.ts +6 -1
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +2 -2
- package/dist/types/extensibility/extensions/runner.d.ts +3 -2
- package/dist/types/extensibility/extensions/types.d.ts +3 -0
- package/dist/types/extensibility/shared-events.d.ts +2 -2
- package/dist/types/internal-urls/history-protocol.d.ts +14 -0
- package/dist/types/internal-urls/index.d.ts +1 -0
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/irc/bus.d.ts +66 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/runtime.d.ts +4 -0
- package/dist/types/memory-backend/types.d.ts +66 -1
- package/dist/types/modes/components/agent-hub.d.ts +30 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +10 -4
- package/dist/types/modes/components/custom-editor.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/ttsr-notification.d.ts +5 -1
- package/dist/types/modes/components/welcome.d.ts +3 -9
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/index.d.ts +3 -3
- package/dist/types/modes/interactive-mode.d.ts +10 -4
- package/dist/types/modes/oauth-manual-input.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
- package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
- package/dist/types/modes/setup-wizard/index.d.ts +5 -1
- package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
- package/dist/types/modes/theme/theme.d.ts +2 -1
- package/dist/types/modes/types.d.ts +5 -2
- package/dist/types/modes/utils/ui-helpers.d.ts +1 -1
- package/dist/types/registry/agent-lifecycle.d.ts +51 -0
- package/dist/types/registry/agent-registry.d.ts +16 -5
- package/dist/types/secrets/index.d.ts +1 -1
- package/dist/types/secrets/obfuscator.d.ts +8 -2
- package/dist/types/session/agent-session.d.ts +49 -32
- package/dist/types/session/messages.d.ts +2 -4
- package/dist/types/session/session-history-format.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +21 -3
- package/dist/types/session/streaming-output.d.ts +46 -0
- package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
- package/dist/types/slash-commands/types.d.ts +1 -1
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +12 -2
- package/dist/types/task/index.d.ts +13 -6
- package/dist/types/task/output-manager.d.ts +0 -7
- package/dist/types/task/repair-args.d.ts +8 -7
- package/dist/types/task/types.d.ts +63 -51
- package/dist/types/thinking.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +11 -0
- package/dist/types/tiny/title-protocol.d.ts +1 -0
- package/dist/types/tools/browser/tab-worker.d.ts +3 -1
- package/dist/types/tools/find.d.ts +0 -11
- package/dist/types/tools/grouped-file-output.d.ts +0 -49
- package/dist/types/tools/index.d.ts +7 -3
- package/dist/types/tools/irc.d.ts +76 -38
- package/dist/types/tools/job.d.ts +7 -1
- package/dist/types/utils/git.d.ts +15 -2
- package/dist/types/utils/title-generator.d.ts +3 -2
- package/examples/extensions/with-deps/package.json +1 -0
- package/package.json +11 -10
- package/scripts/bundle-dist.ts +28 -19
- package/src/async/index.ts +0 -1
- package/src/auto-thinking/classifier.ts +1 -0
- package/src/cli/args.ts +3 -0
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli/gallery-fixtures/agentic.ts +230 -115
- package/src/cli/gallery-fixtures/types.ts +5 -0
- package/src/cli-commands.ts +29 -0
- package/src/cli.ts +28 -15
- package/src/commands/launch.ts +4 -0
- package/src/commit/agentic/tools/analyze-file.ts +38 -19
- package/src/commit/model-selection.ts +3 -2
- package/src/config/api-key-resolver.ts +8 -6
- package/src/config/keybindings.ts +6 -1
- package/src/config/model-registry.ts +97 -30
- package/src/config/model-resolver.ts +60 -0
- package/src/config/settings-schema.ts +99 -55
- package/src/config/settings.ts +68 -3
- package/src/edit/hashline/execute.ts +39 -2
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/eval/__tests__/agent-bridge.test.ts +5 -3
- package/src/eval/agent-bridge.ts +3 -16
- package/src/eval/completion-bridge.ts +1 -0
- package/src/eval/js/shared/prelude.txt +1 -1
- package/src/eval/py/executor.ts +29 -7
- package/src/eval/py/index.ts +6 -1
- package/src/eval/py/kernel.ts +31 -11
- package/src/eval/py/prelude.py +5 -6
- package/src/eval/py/runtime.ts +37 -0
- package/src/exec/bash-executor.ts +82 -3
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +38 -13
- package/src/extensibility/custom-tools/types.ts +2 -2
- package/src/extensibility/extensions/get-commands-handler.ts +2 -1
- package/src/extensibility/extensions/runner.ts +6 -1
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/shared-events.ts +2 -2
- package/src/hindsight/bank.ts +17 -2
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/internal-urls/history-protocol.ts +113 -0
- package/src/internal-urls/index.ts +1 -0
- package/src/internal-urls/router.ts +3 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/irc/bus.ts +292 -0
- package/src/main.ts +26 -66
- package/src/memories/index.ts +2 -0
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/local-backend.ts +9 -0
- package/src/memory-backend/off-backend.ts +9 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/types.ts +81 -1
- package/src/mnemopi/backend.ts +151 -4
- package/src/modes/acp/acp-agent.ts +119 -11
- package/src/modes/components/{session-observer-overlay.ts → agent-hub.ts} +586 -367
- package/src/modes/components/assistant-message.ts +19 -21
- package/src/modes/components/compaction-summary-message.ts +68 -32
- package/src/modes/components/custom-editor.ts +10 -0
- package/src/modes/components/footer.ts +3 -1
- package/src/modes/components/status-line/component.ts +118 -34
- package/src/modes/components/tool-execution.ts +31 -1
- package/src/modes/components/ttsr-notification.ts +72 -30
- package/src/modes/components/welcome.ts +9 -33
- package/src/modes/controllers/command-controller.ts +1 -1
- package/src/modes/controllers/event-controller.ts +65 -0
- package/src/modes/controllers/extension-ui-controller.ts +8 -8
- package/src/modes/controllers/input-controller.ts +19 -2
- package/src/modes/controllers/mcp-command-controller.ts +38 -3
- package/src/modes/controllers/selector-controller.ts +21 -17
- package/src/modes/index.ts +3 -21
- package/src/modes/interactive-mode.ts +47 -22
- package/src/modes/oauth-manual-input.ts +30 -3
- package/src/modes/rpc/rpc-client.ts +154 -3
- package/src/modes/rpc/rpc-mode.ts +97 -12
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +81 -1
- package/src/modes/setup-wizard/index.ts +12 -2
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/theme/theme.ts +18 -5
- package/src/modes/types.ts +5 -5
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +51 -49
- package/src/prompts/system/irc-incoming.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +0 -5
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/system/workflow-notice.md +2 -2
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/irc.md +29 -19
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/task-summary.md +5 -16
- package/src/prompts/tools/task.md +38 -29
- package/src/registry/agent-lifecycle.ts +218 -0
- package/src/registry/agent-registry.ts +16 -5
- package/src/sdk.ts +37 -10
- package/src/secrets/index.ts +8 -1
- package/src/secrets/obfuscator.ts +39 -18
- package/src/session/agent-session.ts +422 -291
- package/src/session/messages.ts +11 -78
- package/src/session/session-history-format.ts +246 -0
- package/src/session/session-manager.ts +59 -5
- package/src/session/streaming-output.ts +226 -10
- package/src/slash-commands/acp-builtins.ts +24 -0
- package/src/slash-commands/builtin-registry.ts +20 -0
- package/src/slash-commands/types.ts +1 -1
- package/src/system-prompt.ts +14 -0
- package/src/task/executor.ts +851 -461
- package/src/task/index.ts +721 -796
- package/src/task/output-manager.ts +0 -11
- package/src/task/render.ts +148 -63
- package/src/task/repair-args.ts +21 -9
- package/src/task/types.ts +82 -66
- package/src/thinking.ts +7 -0
- package/src/tiny/title-client.ts +34 -5
- package/src/tiny/title-protocol.ts +1 -1
- package/src/tiny/worker.ts +6 -4
- package/src/tools/ask.ts +4 -2
- package/src/tools/bash.ts +61 -10
- package/src/tools/browser/tab-worker.ts +26 -7
- package/src/tools/browser.ts +28 -1
- package/src/tools/find.ts +2 -27
- package/src/tools/grouped-file-output.ts +1 -118
- package/src/tools/image-gen.ts +11 -4
- package/src/tools/index.ts +17 -13
- package/src/tools/inspect-image.ts +1 -0
- package/src/tools/irc.ts +596 -171
- package/src/tools/job.ts +41 -7
- package/src/tools/read.ts +57 -1
- package/src/tools/renderers.ts +2 -0
- package/src/tools/resolve.ts +4 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/git.ts +267 -13
- package/src/utils/title-generator.ts +24 -5
- package/dist/types/async/support.d.ts +0 -2
- package/dist/types/modes/components/session-observer-overlay.d.ts +0 -11
- package/dist/types/task/simple-mode.d.ts +0 -8
- package/src/async/support.ts +0 -5
- package/src/task/simple-mode.ts +0 -27
package/src/session/messages.ts
CHANGED
|
@@ -8,8 +8,7 @@ import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
|
8
8
|
import {
|
|
9
9
|
type BranchSummaryMessage,
|
|
10
10
|
type CompactionSummaryMessage,
|
|
11
|
-
|
|
12
|
-
renderCompactionSummaryContext,
|
|
11
|
+
convertMessageToLlm,
|
|
13
12
|
} from "@oh-my-pi/pi-agent-core/compaction/messages";
|
|
14
13
|
import type {
|
|
15
14
|
AssistantMessage,
|
|
@@ -17,7 +16,6 @@ import type {
|
|
|
17
16
|
Message,
|
|
18
17
|
MessageAttribution,
|
|
19
18
|
TextContent,
|
|
20
|
-
ToolResultMessage,
|
|
21
19
|
UserMessage,
|
|
22
20
|
} from "@oh-my-pi/pi-ai";
|
|
23
21
|
import { prompt } from "@oh-my-pi/pi-utils";
|
|
@@ -28,6 +26,7 @@ export {
|
|
|
28
26
|
type CompactionSummaryMessage,
|
|
29
27
|
createBranchSummaryMessage,
|
|
30
28
|
createCompactionSummaryMessage,
|
|
29
|
+
createCustomMessage,
|
|
31
30
|
} from "@oh-my-pi/pi-agent-core/compaction/messages";
|
|
32
31
|
|
|
33
32
|
import type { OutputMeta } from "../tools/output-meta";
|
|
@@ -59,7 +58,7 @@ export interface SkillPromptDetails {
|
|
|
59
58
|
*
|
|
60
59
|
* Consumers: `AgentSession.#handleAgentEvent` (stamper) writes this value;
|
|
61
60
|
* `EventController.#handleMessageEnd`, `AssistantMessageComponent`,
|
|
62
|
-
* `ui-helpers.addMessageToChat` (renderers), `
|
|
61
|
+
* `ui-helpers.addMessageToChat` (renderers), `AgentHubOverlayComponent
|
|
63
62
|
* #buildTranscriptLines`, `runPrintMode`, and `AcpAgent#replayAssistantMessage`
|
|
64
63
|
* (fallback error emission) read it via `isSilentAbort`. */
|
|
65
64
|
export const SILENT_ABORT_MARKER = "__omp.silent_abort__";
|
|
@@ -220,15 +219,6 @@ export function wrapSteeringForModel(messages: AgentMessage[]): AgentMessage[] {
|
|
|
220
219
|
return wrappedMessages ?? messages;
|
|
221
220
|
}
|
|
222
221
|
|
|
223
|
-
function getPrunedToolResultContent(message: ToolResultMessage): (TextContent | ImageContent)[] {
|
|
224
|
-
if (message.prunedAt === undefined) {
|
|
225
|
-
return message.content;
|
|
226
|
-
}
|
|
227
|
-
const textBlocks = message.content.filter((content): content is TextContent => content.type === "text");
|
|
228
|
-
const text = textBlocks.map(block => block.text).join("") || "[Output truncated]";
|
|
229
|
-
return [{ type: "text", text }];
|
|
230
|
-
}
|
|
231
|
-
|
|
232
222
|
/** Result of filtering image blocks out of a `(TextContent | ImageContent)[]` array. */
|
|
233
223
|
interface StripContentResult {
|
|
234
224
|
content: (TextContent | ImageContent)[];
|
|
@@ -478,26 +468,6 @@ export function sanitizeRehydratedOpenAIResponsesAssistantMessage(message: Assis
|
|
|
478
468
|
};
|
|
479
469
|
}
|
|
480
470
|
|
|
481
|
-
/** Convert CustomMessageEntry to AgentMessage format */
|
|
482
|
-
export function createCustomMessage(
|
|
483
|
-
customType: string,
|
|
484
|
-
content: string | (TextContent | ImageContent)[],
|
|
485
|
-
display: boolean,
|
|
486
|
-
details: unknown | undefined,
|
|
487
|
-
timestamp: string,
|
|
488
|
-
attribution?: MessageAttribution,
|
|
489
|
-
): CustomMessage {
|
|
490
|
-
return {
|
|
491
|
-
role: "custom",
|
|
492
|
-
customType,
|
|
493
|
-
content,
|
|
494
|
-
display,
|
|
495
|
-
details,
|
|
496
|
-
attribution,
|
|
497
|
-
timestamp: new Date(timestamp).getTime(),
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
|
|
501
471
|
/**
|
|
502
472
|
* Transform AgentMessages (including custom types) to LLM-compatible Messages.
|
|
503
473
|
*
|
|
@@ -530,43 +500,6 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
|
|
|
530
500
|
attribution: "user",
|
|
531
501
|
timestamp: m.timestamp,
|
|
532
502
|
};
|
|
533
|
-
case "custom":
|
|
534
|
-
case "hookMessage": {
|
|
535
|
-
const content = typeof m.content === "string" ? [{ type: "text" as const, text: m.content }] : m.content;
|
|
536
|
-
const role = "developer";
|
|
537
|
-
const attribution = m.attribution;
|
|
538
|
-
return {
|
|
539
|
-
role,
|
|
540
|
-
content,
|
|
541
|
-
attribution,
|
|
542
|
-
timestamp: m.timestamp,
|
|
543
|
-
};
|
|
544
|
-
}
|
|
545
|
-
case "branchSummary":
|
|
546
|
-
return {
|
|
547
|
-
role: "user",
|
|
548
|
-
content: [
|
|
549
|
-
{
|
|
550
|
-
type: "text" as const,
|
|
551
|
-
text: renderBranchSummaryContext(m.summary),
|
|
552
|
-
},
|
|
553
|
-
],
|
|
554
|
-
attribution: "agent",
|
|
555
|
-
timestamp: m.timestamp,
|
|
556
|
-
};
|
|
557
|
-
case "compactionSummary":
|
|
558
|
-
return {
|
|
559
|
-
role: "user",
|
|
560
|
-
content: [
|
|
561
|
-
{
|
|
562
|
-
type: "text" as const,
|
|
563
|
-
text: renderCompactionSummaryContext(m.summary),
|
|
564
|
-
},
|
|
565
|
-
],
|
|
566
|
-
attribution: "agent",
|
|
567
|
-
providerPayload: m.providerPayload,
|
|
568
|
-
timestamp: m.timestamp,
|
|
569
|
-
};
|
|
570
503
|
case "fileMention": {
|
|
571
504
|
const fileContents = m.files
|
|
572
505
|
.map(file => {
|
|
@@ -587,18 +520,18 @@ export function convertToLlm(messages: AgentMessage[]): Message[] {
|
|
|
587
520
|
timestamp: m.timestamp,
|
|
588
521
|
};
|
|
589
522
|
}
|
|
523
|
+
case "custom":
|
|
524
|
+
case "hookMessage":
|
|
525
|
+
case "branchSummary":
|
|
526
|
+
case "compactionSummary":
|
|
590
527
|
case "user":
|
|
591
|
-
return { ...m, attribution: m.attribution ?? "user" };
|
|
592
528
|
case "developer":
|
|
593
|
-
return { ...m, attribution: m.attribution ?? "agent" };
|
|
594
529
|
case "assistant":
|
|
595
|
-
return m;
|
|
596
530
|
case "toolResult":
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
};
|
|
531
|
+
// Core roles share one transformer with agent-core —
|
|
532
|
+
// duplicating them here is how snapcompact frames once
|
|
533
|
+
// silently fell off the provider request.
|
|
534
|
+
return convertMessageToLlm(m);
|
|
602
535
|
default:
|
|
603
536
|
m satisfies never;
|
|
604
537
|
return undefined;
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Concise markdown transcript serializer for `history://` URLs.
|
|
3
|
+
*
|
|
4
|
+
* Unlike `session-dump-format.ts` (verbose `/dump` export), this emits a
|
|
5
|
+
* compressed transcript: full user/assistant/developer text, tool call +
|
|
6
|
+
* result pairs collapsed to single lines, thinking elided, custom messages
|
|
7
|
+
* as one-liners. No system prompt, no tool catalog, no config sections.
|
|
8
|
+
*/
|
|
9
|
+
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
10
|
+
import { INTENT_FIELD } from "@oh-my-pi/pi-agent-core";
|
|
11
|
+
import type { AssistantMessage, ImageContent, TextContent, ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
12
|
+
import type {
|
|
13
|
+
BashExecutionMessage,
|
|
14
|
+
BranchSummaryMessage,
|
|
15
|
+
CompactionSummaryMessage,
|
|
16
|
+
CustomMessage,
|
|
17
|
+
FileMentionMessage,
|
|
18
|
+
HookMessage,
|
|
19
|
+
PythonExecutionMessage,
|
|
20
|
+
} from "./messages";
|
|
21
|
+
|
|
22
|
+
export interface HistoryFormatOptions {
|
|
23
|
+
/** Optional H1 prepended to the transcript. */
|
|
24
|
+
title?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Max length of the primary-arg summary inside `→ tool(...)` lines. */
|
|
28
|
+
const PRIMARY_ARG_MAX = 120;
|
|
29
|
+
|
|
30
|
+
/** Per-tool preference order for the most informative scalar argument. */
|
|
31
|
+
const PRIMARY_ARG_KEYS = [
|
|
32
|
+
"path",
|
|
33
|
+
"file_path",
|
|
34
|
+
"filePath",
|
|
35
|
+
"command",
|
|
36
|
+
"cmd",
|
|
37
|
+
"pattern",
|
|
38
|
+
"url",
|
|
39
|
+
"query",
|
|
40
|
+
"prompt",
|
|
41
|
+
"assignment",
|
|
42
|
+
"message",
|
|
43
|
+
"op",
|
|
44
|
+
"name",
|
|
45
|
+
"id",
|
|
46
|
+
] as const;
|
|
47
|
+
|
|
48
|
+
/** Collapse whitespace runs and truncate to `max` chars with an ellipsis. */
|
|
49
|
+
function oneLine(text: string, max = PRIMARY_ARG_MAX): string {
|
|
50
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
51
|
+
return flat.length > max ? `${flat.slice(0, max - 1)}…` : flat;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Join the text blocks of a string-or-blocks content field. Images become `[image]`. */
|
|
55
|
+
function contentToText(content: string | readonly (TextContent | ImageContent)[]): string {
|
|
56
|
+
if (typeof content === "string") return content;
|
|
57
|
+
const parts: string[] = [];
|
|
58
|
+
for (const block of content) {
|
|
59
|
+
if (block.type === "text") parts.push(block.text);
|
|
60
|
+
else parts.push("[image]");
|
|
61
|
+
}
|
|
62
|
+
return parts.join("\n");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function lineCount(text: string): number {
|
|
66
|
+
if (!text) return 0;
|
|
67
|
+
return text.split("\n").length;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Pick the most informative scalar argument of a tool call. */
|
|
71
|
+
function primaryArg(args: Record<string, unknown> | undefined): string {
|
|
72
|
+
if (!args || typeof args !== "object") return "";
|
|
73
|
+
for (const key of PRIMARY_ARG_KEYS) {
|
|
74
|
+
const value = args[key];
|
|
75
|
+
if (typeof value === "string" && value.length > 0) return oneLine(value);
|
|
76
|
+
if (Array.isArray(value) && value.length > 0 && value.every(v => typeof v === "string")) {
|
|
77
|
+
return oneLine(value.join(", "));
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Fallback: first non-intent string arg, then a compact JSON of the args.
|
|
81
|
+
const rest: Record<string, unknown> = {};
|
|
82
|
+
let restCount = 0;
|
|
83
|
+
for (const key in args) {
|
|
84
|
+
if (key === INTENT_FIELD) continue;
|
|
85
|
+
const value = args[key];
|
|
86
|
+
if (typeof value === "string" && value.length > 0) return oneLine(value);
|
|
87
|
+
rest[key] = value;
|
|
88
|
+
restCount++;
|
|
89
|
+
}
|
|
90
|
+
if (restCount === 0) return "";
|
|
91
|
+
try {
|
|
92
|
+
return oneLine(JSON.stringify(rest));
|
|
93
|
+
} catch {
|
|
94
|
+
return "";
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** One line per tool call: `→ read(src/foo.ts:50-80) ⇒ ok · 31 lines`. */
|
|
99
|
+
function toolCallLine(
|
|
100
|
+
name: string,
|
|
101
|
+
args: Record<string, unknown> | undefined,
|
|
102
|
+
result: ToolResultMessage | undefined,
|
|
103
|
+
): string {
|
|
104
|
+
const head = `→ ${name}(${primaryArg(args)})`;
|
|
105
|
+
if (!result) return `${head} ⇒ pending`;
|
|
106
|
+
const text = contentToText(result.content);
|
|
107
|
+
const lines = lineCount(text);
|
|
108
|
+
const count = `${lines} ${lines === 1 ? "line" : "lines"}`;
|
|
109
|
+
if (result.isError) {
|
|
110
|
+
const firstLine = oneLine(text.split("\n", 1)[0] ?? "");
|
|
111
|
+
return firstLine ? `${head} ⇒ error · ${count} — ${firstLine}` : `${head} ⇒ error · ${count}`;
|
|
112
|
+
}
|
|
113
|
+
return `${head} ⇒ ok · ${count}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** One line for a user-initiated `!`/`$` execution. */
|
|
117
|
+
function executionLine(
|
|
118
|
+
kind: "bash" | "python",
|
|
119
|
+
source: string,
|
|
120
|
+
msg: BashExecutionMessage | PythonExecutionMessage,
|
|
121
|
+
): string {
|
|
122
|
+
const status = msg.cancelled
|
|
123
|
+
? "cancelled"
|
|
124
|
+
: msg.exitCode !== undefined && msg.exitCode !== 0
|
|
125
|
+
? `error · exit ${msg.exitCode}`
|
|
126
|
+
: "ok";
|
|
127
|
+
const lines = lineCount(msg.output);
|
|
128
|
+
return `→ ${kind}! ${oneLine(source)} ⇒ ${status} · ${lines} ${lines === 1 ? "line" : "lines"}`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** One-liner for custom/hook messages: `[irc] A → B: body…`. */
|
|
132
|
+
function customOneLiner(msg: CustomMessage | HookMessage): string {
|
|
133
|
+
const details = (msg.details ?? {}) as Record<string, unknown>;
|
|
134
|
+
const str = (key: string): string => (typeof details[key] === "string" ? (details[key] as string) : "");
|
|
135
|
+
switch (msg.customType) {
|
|
136
|
+
case "irc:incoming":
|
|
137
|
+
return `[irc] ${str("from") || "?"} → me: ${oneLine(str("message"))}`;
|
|
138
|
+
case "irc:relay":
|
|
139
|
+
return `[irc] ${str("from") || "?"} → ${str("to") || "?"}: ${oneLine(str("body"))}`;
|
|
140
|
+
case "async-result": {
|
|
141
|
+
const jobs = Array.isArray(details.jobs) && details.jobs.length > 0 ? details.jobs : [details];
|
|
142
|
+
const labels = jobs
|
|
143
|
+
.map(job => {
|
|
144
|
+
const j = (job ?? {}) as Record<string, unknown>;
|
|
145
|
+
return typeof j.label === "string" && j.label ? j.label : typeof j.jobId === "string" ? j.jobId : "job";
|
|
146
|
+
})
|
|
147
|
+
.join(", ");
|
|
148
|
+
return `[async-result] ${oneLine(labels)}`;
|
|
149
|
+
}
|
|
150
|
+
default:
|
|
151
|
+
return `[${msg.customType}] ${oneLine(contentToText(msg.content))}`;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Format a session's message array as a concise markdown transcript.
|
|
157
|
+
*
|
|
158
|
+
* `messages` is the session's in-memory message array (or the read-only
|
|
159
|
+
* equivalent loaded from a session file) — the same shapes
|
|
160
|
+
* `session-dump-format.ts` consumes.
|
|
161
|
+
*/
|
|
162
|
+
export function formatSessionHistoryMarkdown(messages: unknown[], opts?: HistoryFormatOptions): string {
|
|
163
|
+
const typed = messages as AgentMessage[];
|
|
164
|
+
const lines: string[] = [];
|
|
165
|
+
if (opts?.title) {
|
|
166
|
+
lines.push(`# ${opts.title}`, "");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Index tool results by call id so each toolCall collapses to one line.
|
|
170
|
+
const resultsByCallId = new Map<string, ToolResultMessage>();
|
|
171
|
+
for (const msg of typed) {
|
|
172
|
+
if (msg.role === "toolResult") {
|
|
173
|
+
resultsByCallId.set(msg.toolCallId, msg);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const consumed = new Set<string>();
|
|
177
|
+
|
|
178
|
+
for (const msg of typed) {
|
|
179
|
+
switch (msg.role) {
|
|
180
|
+
case "user":
|
|
181
|
+
case "developer": {
|
|
182
|
+
const text = contentToText(msg.content);
|
|
183
|
+
if (!text.trim()) break;
|
|
184
|
+
lines.push(`## ${msg.role}`, "", text, "");
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
case "assistant": {
|
|
188
|
+
const assistantMsg = msg as AssistantMessage;
|
|
189
|
+
const body: string[] = [];
|
|
190
|
+
for (const block of assistantMsg.content) {
|
|
191
|
+
if (block.type === "text") {
|
|
192
|
+
if (block.text.trim()) body.push(block.text);
|
|
193
|
+
} else if (block.type === "toolCall") {
|
|
194
|
+
const result = resultsByCallId.get(block.id);
|
|
195
|
+
if (result) consumed.add(block.id);
|
|
196
|
+
body.push(toolCallLine(block.name, block.arguments, result));
|
|
197
|
+
}
|
|
198
|
+
// thinking / redactedThinking elided entirely
|
|
199
|
+
}
|
|
200
|
+
if (body.length === 0) break;
|
|
201
|
+
lines.push("## assistant", "", ...body, "");
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
case "toolResult": {
|
|
205
|
+
// Normally consumed by its toolCall; orphans (e.g. truncated history) get their own line.
|
|
206
|
+
if (consumed.has(msg.toolCallId)) break;
|
|
207
|
+
lines.push(toolCallLine(msg.toolName, undefined, msg), "");
|
|
208
|
+
break;
|
|
209
|
+
}
|
|
210
|
+
case "bashExecution": {
|
|
211
|
+
const bashMsg = msg as BashExecutionMessage;
|
|
212
|
+
if (bashMsg.excludeFromContext) break;
|
|
213
|
+
lines.push(executionLine("bash", bashMsg.command, bashMsg), "");
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
case "pythonExecution": {
|
|
217
|
+
const pythonMsg = msg as PythonExecutionMessage;
|
|
218
|
+
if (pythonMsg.excludeFromContext) break;
|
|
219
|
+
lines.push(executionLine("python", pythonMsg.code, pythonMsg), "");
|
|
220
|
+
break;
|
|
221
|
+
}
|
|
222
|
+
case "custom":
|
|
223
|
+
case "hookMessage": {
|
|
224
|
+
lines.push(customOneLiner(msg as CustomMessage | HookMessage), "");
|
|
225
|
+
break;
|
|
226
|
+
}
|
|
227
|
+
case "branchSummary": {
|
|
228
|
+
const branchMsg = msg as BranchSummaryMessage;
|
|
229
|
+
lines.push(`[branch] from ${branchMsg.fromId}: ${oneLine(branchMsg.summary)}`, "");
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
case "compactionSummary": {
|
|
233
|
+
const compactMsg = msg as CompactionSummaryMessage;
|
|
234
|
+
lines.push(`[compaction] ${oneLine(compactMsg.summary)}`, "");
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
case "fileMention": {
|
|
238
|
+
const fileMsg = msg as FileMentionMessage;
|
|
239
|
+
lines.push(`[file-mention] ${oneLine(fileMsg.files.map(f => f.path).join(", "))}`, "");
|
|
240
|
+
break;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return `${lines.join("\n").trim()}\n`;
|
|
246
|
+
}
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
Snowflake,
|
|
28
28
|
toError,
|
|
29
29
|
} from "@oh-my-pi/pi-utils";
|
|
30
|
+
import { getPreservedSnapcompactArchive, snapcompactImages } from "@oh-my-pi/snapcompact";
|
|
30
31
|
import { ArtifactManager } from "./artifacts";
|
|
31
32
|
import {
|
|
32
33
|
type BlobPutOptions,
|
|
@@ -544,6 +545,17 @@ export function getLatestCompactionEntry(entries: SessionEntry[]): CompactionEnt
|
|
|
544
545
|
return null;
|
|
545
546
|
}
|
|
546
547
|
|
|
548
|
+
export interface BuildSessionContextOptions {
|
|
549
|
+
/**
|
|
550
|
+
* Build the full-history display transcript instead of the LLM context:
|
|
551
|
+
* every path entry in chronological order, with each compaction emitted
|
|
552
|
+
* inline as a `compactionSummary` message at the position it fired rather
|
|
553
|
+
* than replacing the history before it. Display-only — never send the
|
|
554
|
+
* result to a provider.
|
|
555
|
+
*/
|
|
556
|
+
transcript?: boolean;
|
|
557
|
+
}
|
|
558
|
+
|
|
547
559
|
/**
|
|
548
560
|
* Build the session context from entries using tree traversal.
|
|
549
561
|
* If leafId is provided, walks from that entry to root.
|
|
@@ -553,6 +565,7 @@ export function buildSessionContext(
|
|
|
553
565
|
entries: SessionEntry[],
|
|
554
566
|
leafId?: string | null,
|
|
555
567
|
byId?: Map<string, SessionEntry>,
|
|
568
|
+
options?: BuildSessionContextOptions,
|
|
556
569
|
): SessionContext {
|
|
557
570
|
// Build uuid index if not available
|
|
558
571
|
if (!byId) {
|
|
@@ -692,7 +705,29 @@ export function buildSessionContext(
|
|
|
692
705
|
}
|
|
693
706
|
};
|
|
694
707
|
|
|
695
|
-
if (
|
|
708
|
+
if (options?.transcript) {
|
|
709
|
+
// Display transcript: every entry in chronological order. Compactions do
|
|
710
|
+
// not erase prior history here — each renders inline (as a divider in the
|
|
711
|
+
// TUI) at the point it fired, with any snapcompact frames re-attached so
|
|
712
|
+
// the component can report them.
|
|
713
|
+
for (const entry of path) {
|
|
714
|
+
if (entry.type === "compaction") {
|
|
715
|
+
const snapcompactArchive = getPreservedSnapcompactArchive(entry.preserveData);
|
|
716
|
+
messages.push(
|
|
717
|
+
createCompactionSummaryMessage(
|
|
718
|
+
entry.summary,
|
|
719
|
+
entry.tokensBefore,
|
|
720
|
+
entry.timestamp,
|
|
721
|
+
entry.shortSummary,
|
|
722
|
+
undefined,
|
|
723
|
+
snapcompactArchive ? snapcompactImages(snapcompactArchive) : undefined,
|
|
724
|
+
),
|
|
725
|
+
);
|
|
726
|
+
} else {
|
|
727
|
+
appendMessage(entry);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
} else if (compaction) {
|
|
696
731
|
const providerPayload: ProviderPayload | undefined = (() => {
|
|
697
732
|
const candidate = compaction.preserveData?.openaiRemoteCompaction;
|
|
698
733
|
if (!candidate || typeof candidate !== "object") return undefined;
|
|
@@ -707,7 +742,9 @@ export function buildSessionContext(
|
|
|
707
742
|
})();
|
|
708
743
|
const remoteReplacementHistory = providerPayload?.items;
|
|
709
744
|
|
|
710
|
-
// Emit summary first
|
|
745
|
+
// Emit summary first; re-attach any archived snapcompact frames so the
|
|
746
|
+
// model can keep reading the archived history after every context rebuild.
|
|
747
|
+
const snapcompactArchive = getPreservedSnapcompactArchive(compaction.preserveData);
|
|
711
748
|
messages.push(
|
|
712
749
|
createCompactionSummaryMessage(
|
|
713
750
|
compaction.summary,
|
|
@@ -715,6 +752,7 @@ export function buildSessionContext(
|
|
|
715
752
|
compaction.timestamp,
|
|
716
753
|
compaction.shortSummary,
|
|
717
754
|
providerPayload,
|
|
755
|
+
snapcompactArchive ? snapcompactImages(snapcompactArchive) : undefined,
|
|
718
756
|
),
|
|
719
757
|
);
|
|
720
758
|
|
|
@@ -957,6 +995,21 @@ async function resolveBlobRefsInEntries(entries: FileEntry[], blobStore: BlobSto
|
|
|
957
995
|
await Promise.all(promises);
|
|
958
996
|
}
|
|
959
997
|
|
|
998
|
+
/**
|
|
999
|
+
* Read-only message view of a session file: load entries, migrate to the
|
|
1000
|
+
* current version, resolve blob refs, and build the context along the
|
|
1001
|
+
* persisted leaf path (last entry). Does NOT create a writer or take the
|
|
1002
|
+
* session lock — safe to call against a file another session is writing.
|
|
1003
|
+
*/
|
|
1004
|
+
export async function loadSessionMessagesReadOnly(filePath: string): Promise<AgentMessage[]> {
|
|
1005
|
+
const entries = await loadEntriesFromFile(filePath);
|
|
1006
|
+
if (entries.length === 0) return [];
|
|
1007
|
+
migrateToCurrentVersion(entries);
|
|
1008
|
+
await resolveBlobRefsInEntries(entries, new BlobStore(getBlobsDir()));
|
|
1009
|
+
const sessionEntries = entries.filter((e): e is SessionEntry => e.type !== "session");
|
|
1010
|
+
return buildSessionContext(sessionEntries).messages;
|
|
1011
|
+
}
|
|
1012
|
+
|
|
960
1013
|
/**
|
|
961
1014
|
* Lightweight metadata for a session file, used in session picker UI.
|
|
962
1015
|
* Uses lazy getters to defer string formatting until actually displayed.
|
|
@@ -3205,11 +3258,12 @@ export class SessionManager {
|
|
|
3205
3258
|
}
|
|
3206
3259
|
|
|
3207
3260
|
/**
|
|
3208
|
-
* Build the session context (what gets sent to the LLM)
|
|
3261
|
+
* Build the session context (what gets sent to the LLM), or — with
|
|
3262
|
+
* `{ transcript: true }` — the full-history display transcript.
|
|
3209
3263
|
* Uses tree traversal from current leaf.
|
|
3210
3264
|
*/
|
|
3211
|
-
buildSessionContext(): SessionContext {
|
|
3212
|
-
return buildSessionContext(this.getEntries(), this.#leafId, this.#byId);
|
|
3265
|
+
buildSessionContext(options?: BuildSessionContextOptions): SessionContext {
|
|
3266
|
+
return buildSessionContext(this.getEntries(), this.#leafId, this.#byId, options);
|
|
3213
3267
|
}
|
|
3214
3268
|
|
|
3215
3269
|
/** Strip stale OpenAI Responses assistant replay metadata from loaded in-memory entries. */
|