@oh-my-pi/pi-coding-agent 15.9.67 → 15.10.1
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 +136 -0
- package/dist/types/cli/args.d.ts +1 -1
- package/dist/types/cli/dry-balance-cli.d.ts +15 -1
- package/dist/types/cli/gallery-cli.d.ts +43 -0
- package/dist/types/cli/gallery-fixtures/agentic.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/codeintel.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/edit.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/fs.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/index.d.ts +4 -0
- package/dist/types/cli/gallery-fixtures/interaction.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/memory.d.ts +2 -0
- package/dist/types/cli/gallery-fixtures/misc.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/search.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/shell.d.ts +3 -0
- package/dist/types/cli/gallery-fixtures/types.d.ts +44 -0
- package/dist/types/cli/gallery-fixtures/web.d.ts +2 -0
- package/dist/types/cli/gallery-screenshot.d.ts +35 -0
- package/dist/types/commands/gallery.d.ts +47 -0
- package/dist/types/commit/analysis/conventional.d.ts +2 -2
- package/dist/types/commit/analysis/summary.d.ts +2 -2
- package/dist/types/commit/changelog/generate.d.ts +2 -2
- package/dist/types/commit/changelog/index.d.ts +2 -2
- package/dist/types/commit/map-reduce/index.d.ts +3 -3
- package/dist/types/commit/map-reduce/map-phase.d.ts +2 -2
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +2 -2
- package/dist/types/commit/model-selection.d.ts +10 -4
- package/dist/types/config/api-key-resolver.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +6 -1
- package/dist/types/config/model-id-affixes.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +25 -2
- package/dist/types/config/settings-schema.d.ts +41 -6
- package/dist/types/dap/config.d.ts +14 -1
- package/dist/types/dap/types.d.ts +10 -0
- package/dist/types/extensibility/plugins/marketplace-auto-update.d.ts +8 -0
- package/dist/types/lsp/types.d.ts +10 -0
- package/dist/types/lsp/utils.d.ts +3 -2
- package/dist/types/main.d.ts +3 -2
- package/dist/types/memory-backend/index.d.ts +2 -1
- package/dist/types/memory-backend/resolve.d.ts +1 -1
- package/dist/types/memory-backend/types.d.ts +1 -1
- package/dist/types/modes/components/chat-block.d.ts +64 -0
- package/dist/types/modes/components/custom-editor.d.ts +5 -1
- package/dist/types/modes/components/overlay-box.d.ts +17 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +59 -0
- package/dist/types/modes/components/plan-toc.d.ts +41 -0
- package/dist/types/modes/components/read-tool-group.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +18 -0
- package/dist/types/modes/components/transcript-container.d.ts +11 -0
- package/dist/types/modes/controllers/command-controller.d.ts +1 -0
- package/dist/types/modes/controllers/event-controller.d.ts +0 -1
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +0 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +22 -0
- package/dist/types/modes/controllers/tan-command-controller.d.ts +6 -0
- package/dist/types/modes/index.d.ts +5 -4
- package/dist/types/modes/interactive-mode.d.ts +16 -6
- package/dist/types/modes/setup-version.d.ts +11 -0
- package/dist/types/modes/setup-wizard/index.d.ts +2 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +2 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +19 -6
- package/dist/types/modes/utils/copy-targets.d.ts +21 -1
- package/dist/types/plan-mode/approved-plan.d.ts +27 -8
- package/dist/types/plan-mode/plan-protection.d.ts +4 -4
- package/dist/types/sdk.d.ts +3 -1
- package/dist/types/session/agent-session.d.ts +21 -0
- package/dist/types/session/messages.d.ts +12 -0
- package/dist/types/session/session-manager.d.ts +3 -1
- package/dist/types/slash-commands/types.d.ts +4 -6
- package/dist/types/task/executor.d.ts +14 -0
- package/dist/types/task/index.d.ts +1 -0
- package/dist/types/task/render.d.ts +3 -2
- package/dist/types/telemetry-export.d.ts +1 -1
- package/dist/types/tools/archive-reader.d.ts +5 -0
- package/dist/types/tools/ast-edit.d.ts +3 -0
- package/dist/types/tools/ast-grep.d.ts +3 -0
- package/dist/types/tools/bash.d.ts +1 -0
- package/dist/types/tools/eval-render.d.ts +1 -8
- package/dist/types/tools/fetch.d.ts +15 -7
- package/dist/types/tools/find.d.ts +8 -4
- package/dist/types/tools/grouped-file-output.d.ts +95 -12
- package/dist/types/tools/memory-render.d.ts +4 -1
- package/dist/types/tools/plan-mode-guard.d.ts +8 -9
- package/dist/types/tools/render-utils.d.ts +13 -9
- package/dist/types/tools/renderers.d.ts +16 -2
- package/dist/types/tools/search.d.ts +5 -1
- package/dist/types/tools/sqlite-reader.d.ts +1 -0
- package/dist/types/tools/todo.d.ts +3 -2
- package/dist/types/tools/write.d.ts +5 -0
- package/dist/types/tui/output-block.d.ts +16 -4
- package/dist/types/tui/status-line.d.ts +3 -0
- package/dist/types/utils/enhanced-paste.d.ts +20 -0
- package/dist/types/web/scrapers/github.d.ts +22 -0
- package/dist/types/web/search/providers/kimi.d.ts +1 -1
- package/dist/types/web/search/providers/perplexity.d.ts +8 -1
- package/dist/types/web/search/types.d.ts +1 -1
- package/package.json +9 -9
- package/scripts/dev-launch +42 -0
- package/scripts/dev-launch-preload.ts +19 -0
- package/src/auto-thinking/classifier.ts +5 -1
- package/src/cli/args.ts +2 -2
- package/src/cli/dry-balance-cli.ts +52 -17
- package/src/cli/gallery-cli.ts +226 -0
- package/src/cli/gallery-fixtures/agentic.ts +292 -0
- package/src/cli/gallery-fixtures/codeintel.ts +188 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +153 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +250 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +41 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli-commands.ts +1 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/launch.ts +1 -1
- package/src/commit/analysis/conventional.ts +2 -2
- package/src/commit/analysis/summary.ts +2 -2
- package/src/commit/changelog/generate.ts +2 -2
- package/src/commit/changelog/index.ts +2 -2
- package/src/commit/map-reduce/index.ts +3 -3
- package/src/commit/map-reduce/map-phase.ts +2 -2
- package/src/commit/map-reduce/reduce-phase.ts +2 -2
- package/src/commit/model-selection.ts +33 -9
- package/src/commit/pipeline.ts +4 -4
- package/src/config/api-key-resolver.ts +58 -0
- package/src/config/keybindings.ts +15 -6
- package/src/config/model-equivalence.ts +35 -12
- package/src/config/model-id-affixes.ts +39 -22
- package/src/config/model-registry.ts +41 -18
- package/src/config/settings-schema.ts +28 -5
- package/src/config/settings.ts +31 -2
- package/src/dap/client.ts +14 -16
- package/src/dap/config.ts +41 -2
- package/src/dap/defaults.json +1 -0
- package/src/dap/session.ts +1 -0
- package/src/dap/types.ts +10 -0
- package/src/debug/index.ts +40 -54
- package/src/edit/renderer.ts +111 -119
- package/src/eval/__tests__/agent-bridge.test.ts +75 -32
- package/src/eval/__tests__/llm-bridge.test.ts +90 -31
- package/src/eval/agent-bridge.ts +34 -7
- package/src/eval/llm-bridge.ts +8 -3
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/plugins/doctor.ts +0 -1
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/goals/tools/goal-tool.ts +37 -27
- package/src/internal-urls/docs-index.generated.ts +10 -10
- package/src/lsp/client.ts +104 -55
- package/src/lsp/types.ts +10 -0
- package/src/lsp/utils.ts +3 -2
- package/src/main.ts +53 -56
- package/src/memories/index.ts +12 -5
- package/src/memory-backend/index.ts +13 -1
- package/src/memory-backend/resolve.ts +3 -5
- package/src/memory-backend/types.ts +1 -1
- package/src/mnemopi/backend.ts +5 -1
- package/src/modes/acp/acp-agent.ts +33 -26
- package/src/modes/components/assistant-message.ts +2 -9
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/copy-selector.ts +1 -44
- package/src/modes/components/custom-editor.ts +33 -1
- package/src/modes/components/custom-message.ts +1 -3
- package/src/modes/components/execution-shared.ts +1 -2
- package/src/modes/components/hook-message.ts +1 -3
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +799 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/read-tool-group.ts +20 -4
- package/src/modes/components/skill-message.ts +0 -1
- package/src/modes/components/status-line.ts +3 -5
- package/src/modes/components/tips.txt +1 -0
- package/src/modes/components/todo-reminder.ts +0 -2
- package/src/modes/components/tool-execution.ts +115 -90
- package/src/modes/components/transcript-container.ts +84 -24
- package/src/modes/components/user-message.ts +1 -2
- package/src/modes/controllers/command-controller-shared.ts +7 -6
- package/src/modes/controllers/command-controller.ts +70 -57
- package/src/modes/controllers/event-controller.ts +41 -40
- package/src/modes/controllers/extension-ui-controller.ts +10 -73
- package/src/modes/controllers/input-controller.ts +135 -122
- package/src/modes/controllers/mcp-command-controller.ts +69 -60
- package/src/modes/controllers/selector-controller.ts +25 -27
- package/src/modes/controllers/streaming-reveal.ts +212 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/index.ts +5 -4
- package/src/modes/interactive-mode.ts +171 -82
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +3 -2
- package/src/modes/setup-wizard/scenes/web-search.ts +3 -2
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme-schema.json +1 -1
- package/src/modes/theme/theme.ts +8 -4
- package/src/modes/types.ts +19 -8
- package/src/modes/utils/context-usage.ts +10 -6
- package/src/modes/utils/copy-targets.ts +133 -27
- package/src/modes/utils/hotkeys-markdown.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +44 -46
- package/src/plan-mode/approved-plan.ts +66 -43
- package/src/plan-mode/plan-protection.ts +4 -4
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/plan-mode-active.md +67 -58
- package/src/prompts/system/plan-mode-approved.md +1 -1
- package/src/sdk.ts +32 -60
- package/src/session/agent-session.ts +89 -13
- package/src/session/messages.ts +26 -0
- package/src/session/session-manager.ts +13 -5
- package/src/slash-commands/builtin-registry.ts +37 -10
- package/src/slash-commands/helpers/usage-report.ts +2 -0
- package/src/slash-commands/types.ts +4 -6
- package/src/task/executor.ts +25 -4
- package/src/task/index.ts +4 -0
- package/src/task/render.ts +212 -148
- package/src/telemetry-export.ts +25 -7
- package/src/tools/archive-reader.ts +64 -0
- package/src/tools/ask.ts +119 -164
- package/src/tools/ast-edit.ts +98 -71
- package/src/tools/ast-grep.ts +37 -43
- package/src/tools/bash.ts +50 -6
- package/src/tools/debug.ts +20 -8
- package/src/tools/eval-backends.ts +6 -17
- package/src/tools/eval-render.ts +21 -18
- package/src/tools/eval.ts +5 -4
- package/src/tools/fetch.ts +391 -91
- package/src/tools/find.ts +44 -30
- package/src/tools/gh-renderer.ts +81 -42
- package/src/tools/grouped-file-output.ts +272 -48
- package/src/tools/image-gen.ts +150 -103
- package/src/tools/inspect-image-renderer.ts +63 -41
- package/src/tools/inspect-image.ts +8 -1
- package/src/tools/job.ts +3 -4
- package/src/tools/memory-render.ts +4 -1
- package/src/tools/plan-mode-guard.ts +21 -39
- package/src/tools/read.ts +23 -16
- package/src/tools/render-utils.ts +38 -40
- package/src/tools/renderers.ts +16 -1
- package/src/tools/report-tool-issue.ts +1 -1
- package/src/tools/resolve.ts +14 -0
- package/src/tools/search-tool-bm25.ts +36 -23
- package/src/tools/search.ts +189 -95
- package/src/tools/sqlite-reader.ts +9 -12
- package/src/tools/todo.ts +138 -59
- package/src/tools/write.ts +100 -60
- package/src/tui/output-block.ts +60 -13
- package/src/tui/status-line.ts +5 -1
- package/src/utils/commit-message-generator.ts +9 -1
- package/src/utils/enhanced-paste.ts +202 -0
- package/src/utils/title-generator.ts +2 -1
- package/src/web/scrapers/github.ts +255 -3
- package/src/web/scrapers/youtube.ts +3 -2
- package/src/web/search/providers/anthropic.ts +25 -19
- package/src/web/search/providers/exa.ts +11 -3
- package/src/web/search/providers/kimi.ts +28 -17
- package/src/web/search/providers/parallel.ts +35 -24
- package/src/web/search/providers/perplexity.ts +199 -51
- package/src/web/search/providers/synthetic.ts +8 -6
- package/src/web/search/providers/tavily.ts +9 -8
- package/src/web/search/providers/zai.ts +8 -6
- package/src/web/search/render.ts +39 -54
- package/src/web/search/types.ts +5 -1
- package/dist/types/eval/__tests__/shared-executors.test.d.ts +0 -1
- package/src/eval/__tests__/shared-executors.test.ts +0 -609
|
@@ -7,7 +7,6 @@ import { getAgentDbPath, getProjectDir, normalizePathForComparison } from "@oh-m
|
|
|
7
7
|
import { getRoleInfo } from "../../config/model-registry";
|
|
8
8
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
9
9
|
import { settings } from "../../config/settings";
|
|
10
|
-
import { DebugSelectorComponent } from "../../debug";
|
|
11
10
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
12
11
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
13
12
|
import {
|
|
@@ -51,6 +50,7 @@ import { SessionObserverOverlayComponent } from "../components/session-observer-
|
|
|
51
50
|
import { SessionSelectorComponent } from "../components/session-selector";
|
|
52
51
|
import { SettingsSelectorComponent } from "../components/settings-selector";
|
|
53
52
|
import { ToolExecutionComponent } from "../components/tool-execution";
|
|
53
|
+
import { TranscriptBlock } from "../components/transcript-container";
|
|
54
54
|
import { TreeSelectorComponent } from "../components/tree-selector";
|
|
55
55
|
import { UserMessageSelectorComponent } from "../components/user-message-selector";
|
|
56
56
|
import type { SessionObserverRegistry } from "../session-observer-registry";
|
|
@@ -932,28 +932,28 @@ export class SelectorController {
|
|
|
932
932
|
try {
|
|
933
933
|
await this.ctx.session.modelRegistry.authStorage.login(providerId as OAuthProvider, {
|
|
934
934
|
onAuth: (info: { url: string; instructions?: string }) => {
|
|
935
|
-
|
|
936
|
-
|
|
935
|
+
const block = new TranscriptBlock();
|
|
936
|
+
block.addChild(new Text(theme.fg("dim", info.url), 1, 0));
|
|
937
937
|
const hyperlink = `\x1b]8;;${info.url}\x07Click here to login\x1b]8;;\x07`;
|
|
938
|
-
|
|
938
|
+
block.addChild(new Text(theme.fg("accent", hyperlink), 1, 0));
|
|
939
939
|
if (info.instructions) {
|
|
940
|
-
|
|
941
|
-
|
|
940
|
+
block.addChild(new Spacer(1));
|
|
941
|
+
block.addChild(new Text(theme.fg("warning", info.instructions), 1, 0));
|
|
942
942
|
}
|
|
943
943
|
if (useManualInput) {
|
|
944
|
-
|
|
945
|
-
|
|
944
|
+
block.addChild(new Spacer(1));
|
|
945
|
+
block.addChild(new Text(theme.fg("dim", MANUAL_LOGIN_TIP), 1, 0));
|
|
946
946
|
}
|
|
947
|
-
this.ctx.
|
|
947
|
+
this.ctx.present(block);
|
|
948
948
|
this.ctx.openInBrowser(info.url);
|
|
949
949
|
},
|
|
950
950
|
onPrompt: async (prompt: { message: string; placeholder?: string }) => {
|
|
951
|
-
|
|
952
|
-
|
|
951
|
+
const promptBlock = new TranscriptBlock();
|
|
952
|
+
promptBlock.addChild(new Text(theme.fg("warning", prompt.message), 1, 0));
|
|
953
953
|
if (prompt.placeholder) {
|
|
954
|
-
|
|
954
|
+
promptBlock.addChild(new Text(theme.fg("dim", prompt.placeholder), 1, 0));
|
|
955
955
|
}
|
|
956
|
-
this.ctx.
|
|
956
|
+
this.ctx.present(promptBlock);
|
|
957
957
|
const { promise, resolve } = Promise.withResolvers<string>();
|
|
958
958
|
const codeInput = new Input();
|
|
959
959
|
codeInput.onSubmit = () => {
|
|
@@ -970,18 +970,17 @@ export class SelectorController {
|
|
|
970
970
|
return promise;
|
|
971
971
|
},
|
|
972
972
|
onProgress: (message: string) => {
|
|
973
|
-
this.ctx.
|
|
974
|
-
this.ctx.ui.requestRender();
|
|
973
|
+
this.ctx.present(new Text(theme.fg("dim", message), 1, 0));
|
|
975
974
|
},
|
|
976
975
|
onManualCodeInput: useManualInput ? () => manualInput.waitForInput(providerId) : undefined,
|
|
977
976
|
});
|
|
978
977
|
await this.ctx.session.modelRegistry.refresh();
|
|
979
|
-
|
|
980
|
-
|
|
978
|
+
const block = new TranscriptBlock();
|
|
979
|
+
block.addChild(
|
|
981
980
|
new Text(theme.fg("success", `${theme.status.success} Successfully logged in to ${providerId}`), 1, 0),
|
|
982
981
|
);
|
|
983
|
-
|
|
984
|
-
this.ctx.
|
|
982
|
+
block.addChild(new Text(theme.fg("dim", `Credentials saved to ${getAgentDbPath()}`), 1, 0));
|
|
983
|
+
this.ctx.present(block);
|
|
985
984
|
} catch (error: unknown) {
|
|
986
985
|
this.ctx.showError(`Login failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
987
986
|
} finally {
|
|
@@ -1003,20 +1002,18 @@ export class SelectorController {
|
|
|
1003
1002
|
|
|
1004
1003
|
await authStorage.logout(providerId);
|
|
1005
1004
|
await this.ctx.session.modelRegistry.refresh();
|
|
1006
|
-
|
|
1007
|
-
|
|
1005
|
+
const block = new TranscriptBlock();
|
|
1006
|
+
block.addChild(
|
|
1008
1007
|
new Text(theme.fg("success", `${theme.status.success} Successfully logged out of ${providerId}`), 1, 0),
|
|
1009
1008
|
);
|
|
1010
|
-
|
|
1011
|
-
new Text(theme.fg("dim", `Credentials removed from ${getAgentDbPath()}`), 1, 0),
|
|
1012
|
-
);
|
|
1009
|
+
block.addChild(new Text(theme.fg("dim", `Credentials removed from ${getAgentDbPath()}`), 1, 0));
|
|
1013
1010
|
const remainingSource = authStorage.describeCredentialSource(providerId, this.ctx.session.sessionId);
|
|
1014
1011
|
if (remainingSource) {
|
|
1015
|
-
|
|
1012
|
+
block.addChild(
|
|
1016
1013
|
new Text(theme.fg("warning", `${providerId} is still authenticated via ${remainingSource}`), 1, 0),
|
|
1017
1014
|
);
|
|
1018
1015
|
}
|
|
1019
|
-
this.ctx.
|
|
1016
|
+
this.ctx.present(block);
|
|
1020
1017
|
} catch (error: unknown) {
|
|
1021
1018
|
this.ctx.showError(`Logout failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1022
1019
|
}
|
|
@@ -1080,7 +1077,8 @@ export class SelectorController {
|
|
|
1080
1077
|
});
|
|
1081
1078
|
}
|
|
1082
1079
|
|
|
1083
|
-
showDebugSelector(): void {
|
|
1080
|
+
async showDebugSelector(): Promise<void> {
|
|
1081
|
+
const { DebugSelectorComponent } = await import("../../debug");
|
|
1084
1082
|
this.showSelector(done => {
|
|
1085
1083
|
const selector = new DebugSelectorComponent(this.ctx, done);
|
|
1086
1084
|
return { component: selector, focus: selector };
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
2
|
+
import { getSegmenter } from "@oh-my-pi/pi-tui";
|
|
3
|
+
import type { AssistantMessageComponent } from "../components/assistant-message";
|
|
4
|
+
|
|
5
|
+
export const STREAMING_REVEAL_FRAME_MS = 1000 / 30;
|
|
6
|
+
export const MIN_STEP = 3;
|
|
7
|
+
export const CATCHUP_FRAMES = 8;
|
|
8
|
+
|
|
9
|
+
type AssistantContentBlock = AssistantMessage["content"][number];
|
|
10
|
+
type StreamingRevealComponent = Pick<AssistantMessageComponent, "updateContent">;
|
|
11
|
+
|
|
12
|
+
type StreamingRevealControllerOptions = {
|
|
13
|
+
getSmoothStreaming(): boolean;
|
|
14
|
+
getHideThinkingBlock(): boolean;
|
|
15
|
+
requestRender(): void;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function countGraphemes(text: string): number {
|
|
19
|
+
let count = 0;
|
|
20
|
+
for (const _segment of getSegmenter().segment(text)) {
|
|
21
|
+
count += 1;
|
|
22
|
+
}
|
|
23
|
+
return count;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function sliceGraphemes(text: string, units: number): string {
|
|
27
|
+
if (units <= 0 || text.length === 0) return "";
|
|
28
|
+
let count = 0;
|
|
29
|
+
for (const { index, segment } of getSegmenter().segment(text)) {
|
|
30
|
+
count += 1;
|
|
31
|
+
if (count >= units) {
|
|
32
|
+
const end = index + segment.length;
|
|
33
|
+
return end >= text.length ? text : text.slice(0, end);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return text;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function visibleUnits(message: AssistantMessage, hideThinking: boolean): number {
|
|
40
|
+
let total = 0;
|
|
41
|
+
for (const block of message.content) {
|
|
42
|
+
if (block.type === "text") {
|
|
43
|
+
total += countGraphemes(block.text);
|
|
44
|
+
} else if (block.type === "thinking" && !hideThinking) {
|
|
45
|
+
total += countGraphemes(block.thinking);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return total;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function revealTextBlock(
|
|
52
|
+
block: Extract<AssistantContentBlock, { type: "text" }>,
|
|
53
|
+
remaining: number,
|
|
54
|
+
): AssistantContentBlock {
|
|
55
|
+
if (remaining <= 0) return block.text.length === 0 ? block : { ...block, text: "" };
|
|
56
|
+
const units = countGraphemes(block.text);
|
|
57
|
+
if (remaining >= units) return block;
|
|
58
|
+
return { ...block, text: sliceGraphemes(block.text, remaining) };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function revealThinkingBlock(
|
|
62
|
+
block: Extract<AssistantContentBlock, { type: "thinking" }>,
|
|
63
|
+
remaining: number,
|
|
64
|
+
): AssistantContentBlock {
|
|
65
|
+
if (remaining <= 0) return block.thinking.length === 0 ? block : { ...block, thinking: "" };
|
|
66
|
+
const units = countGraphemes(block.thinking);
|
|
67
|
+
if (remaining >= units) return block;
|
|
68
|
+
return { ...block, thinking: sliceGraphemes(block.thinking, remaining) };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function buildDisplayMessage(
|
|
72
|
+
target: AssistantMessage,
|
|
73
|
+
revealed: number,
|
|
74
|
+
hideThinking: boolean,
|
|
75
|
+
): AssistantMessage {
|
|
76
|
+
let remaining = Math.max(0, Math.floor(revealed));
|
|
77
|
+
const content: AssistantContentBlock[] = [];
|
|
78
|
+
for (const block of target.content) {
|
|
79
|
+
if (block.type === "text") {
|
|
80
|
+
content.push(revealTextBlock(block, remaining));
|
|
81
|
+
remaining = Math.max(0, remaining - countGraphemes(block.text));
|
|
82
|
+
} else if (block.type === "thinking" && !hideThinking) {
|
|
83
|
+
content.push(revealThinkingBlock(block, remaining));
|
|
84
|
+
remaining = Math.max(0, remaining - countGraphemes(block.thinking));
|
|
85
|
+
} else {
|
|
86
|
+
content.push(block);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return { ...target, content };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function nextStep(backlog: number): number {
|
|
93
|
+
return Math.max(MIN_STEP, Math.ceil(Math.max(0, backlog) / CATCHUP_FRAMES));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export class StreamingRevealController {
|
|
97
|
+
readonly #getSmoothStreaming: () => boolean;
|
|
98
|
+
readonly #getHideThinkingBlock: () => boolean;
|
|
99
|
+
readonly #requestRender: () => void;
|
|
100
|
+
#target: AssistantMessage | undefined;
|
|
101
|
+
#component: StreamingRevealComponent | undefined;
|
|
102
|
+
#timer: NodeJS.Timeout | undefined;
|
|
103
|
+
#revealed = 0;
|
|
104
|
+
#hideThinkingBlock = false;
|
|
105
|
+
#smoothStreaming = true;
|
|
106
|
+
|
|
107
|
+
constructor(options: StreamingRevealControllerOptions) {
|
|
108
|
+
this.#getSmoothStreaming = options.getSmoothStreaming;
|
|
109
|
+
this.#getHideThinkingBlock = options.getHideThinkingBlock;
|
|
110
|
+
this.#requestRender = options.requestRender;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
begin(component: StreamingRevealComponent, message: AssistantMessage): void {
|
|
114
|
+
this.stop();
|
|
115
|
+
this.#component = component;
|
|
116
|
+
this.#target = message;
|
|
117
|
+
this.#revealed = 0;
|
|
118
|
+
this.#hideThinkingBlock = this.#getHideThinkingBlock();
|
|
119
|
+
this.#smoothStreaming = this.#getSmoothStreaming();
|
|
120
|
+
if (!this.#smoothStreaming) {
|
|
121
|
+
component.updateContent(message);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const total = visibleUnits(message, this.#hideThinkingBlock);
|
|
125
|
+
if (message.content.some(block => block.type === "toolCall")) {
|
|
126
|
+
// A tool call is a transcript-order boundary: finish any leading
|
|
127
|
+
// assistant text before EventController renders the separate tool card.
|
|
128
|
+
this.#revealed = total;
|
|
129
|
+
component.updateContent(buildDisplayMessage(message, this.#revealed, this.#hideThinkingBlock));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
this.#renderCurrent();
|
|
133
|
+
this.#syncTimer(total);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
setTarget(message: AssistantMessage): void {
|
|
137
|
+
this.#target = message;
|
|
138
|
+
if (!this.#component) return;
|
|
139
|
+
if (!this.#smoothStreaming) {
|
|
140
|
+
this.#component.updateContent(message);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
const total = visibleUnits(message, this.#hideThinkingBlock);
|
|
144
|
+
if (message.content.some(block => block.type === "toolCall")) {
|
|
145
|
+
// A tool call is a transcript-order boundary: finish any leading
|
|
146
|
+
// assistant text before EventController renders the separate tool card.
|
|
147
|
+
this.#revealed = total;
|
|
148
|
+
this.#stopTimer();
|
|
149
|
+
this.#component.updateContent(buildDisplayMessage(message, this.#revealed, this.#hideThinkingBlock));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
if (this.#revealed > total) {
|
|
153
|
+
this.#revealed = total;
|
|
154
|
+
}
|
|
155
|
+
this.#renderCurrent();
|
|
156
|
+
this.#syncTimer(total);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
stop(): void {
|
|
160
|
+
this.#stopTimer();
|
|
161
|
+
this.#target = undefined;
|
|
162
|
+
this.#component = undefined;
|
|
163
|
+
this.#revealed = 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#renderCurrent(): void {
|
|
167
|
+
if (!this.#target || !this.#component) return;
|
|
168
|
+
this.#component.updateContent(buildDisplayMessage(this.#target, this.#revealed, this.#hideThinkingBlock));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#syncTimer(total = this.#target ? visibleUnits(this.#target, this.#hideThinkingBlock) : 0): void {
|
|
172
|
+
if (!this.#target || !this.#component || this.#revealed >= total) {
|
|
173
|
+
this.#stopTimer();
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
this.#startTimer();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
#startTimer(): void {
|
|
180
|
+
if (this.#timer) return;
|
|
181
|
+
this.#timer = setInterval(() => {
|
|
182
|
+
this.#tick();
|
|
183
|
+
}, STREAMING_REVEAL_FRAME_MS);
|
|
184
|
+
this.#timer.unref?.();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#stopTimer(): void {
|
|
188
|
+
if (!this.#timer) return;
|
|
189
|
+
clearInterval(this.#timer);
|
|
190
|
+
this.#timer = undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
#tick(): void {
|
|
194
|
+
const target = this.#target;
|
|
195
|
+
const component = this.#component;
|
|
196
|
+
if (!target || !component) {
|
|
197
|
+
this.stop();
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
const total = visibleUnits(target, this.#hideThinkingBlock);
|
|
201
|
+
if (this.#revealed >= total) {
|
|
202
|
+
this.#stopTimer();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
this.#revealed = Math.min(total, this.#revealed + nextStep(total - this.#revealed));
|
|
206
|
+
component.updateContent(buildDisplayMessage(target, this.#revealed, this.#hideThinkingBlock));
|
|
207
|
+
this.#requestRender();
|
|
208
|
+
if (this.#revealed >= total) {
|
|
209
|
+
this.#stopTimer();
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { prompt, Snowflake } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import backgroundTanDispatchPrompt from "../../prompts/system/background-tan-dispatch.md" with { type: "text" };
|
|
5
|
+
import { AgentRegistry, MAIN_AGENT_ID } from "../../registry/agent-registry";
|
|
6
|
+
import * as sdk from "../../sdk";
|
|
7
|
+
import type { AgentSession } from "../../session/agent-session";
|
|
8
|
+
import { SessionManager } from "../../session/session-manager";
|
|
9
|
+
import { createMCPProxyTools, createSubagentSettings } from "../../task/executor";
|
|
10
|
+
import type { InteractiveModeContext } from "../types";
|
|
11
|
+
|
|
12
|
+
const TAN_LABEL_PREVIEW_LENGTH = 80;
|
|
13
|
+
|
|
14
|
+
function previewWork(work: string): string {
|
|
15
|
+
const singleLine = work.trim().replace(/\s+/g, " ");
|
|
16
|
+
if (singleLine.length <= TAN_LABEL_PREVIEW_LENGTH) return singleLine;
|
|
17
|
+
return `${singleLine.slice(0, TAN_LABEL_PREVIEW_LENGTH - 1)}…`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function extractAssistantText(message: AssistantMessage | undefined): string {
|
|
21
|
+
if (!message) return "";
|
|
22
|
+
return message.content
|
|
23
|
+
.filter(content => content.type === "text")
|
|
24
|
+
.map(content => content.text)
|
|
25
|
+
.join("")
|
|
26
|
+
.trim();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function removeCloneSession(cloneFile: string): Promise<void> {
|
|
30
|
+
await Promise.allSettled([
|
|
31
|
+
fs.rm(cloneFile, { force: true }),
|
|
32
|
+
fs.rm(cloneFile.slice(0, -6), { recursive: true, force: true }),
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class TanCommandController {
|
|
37
|
+
constructor(private readonly ctx: InteractiveModeContext) {}
|
|
38
|
+
|
|
39
|
+
async start(work: string): Promise<void> {
|
|
40
|
+
const trimmedWork = work.trim();
|
|
41
|
+
if (!trimmedWork) {
|
|
42
|
+
this.ctx.showStatus("Usage: /tan <work>");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const session = this.ctx.session;
|
|
47
|
+
if (session.isStreaming) {
|
|
48
|
+
this.ctx.showWarning("Wait for the current response to finish or abort it before using /tan.");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const model = session.model;
|
|
53
|
+
if (!model) {
|
|
54
|
+
this.ctx.showError("No active model available for /tan.");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const manager = session.asyncJobManager;
|
|
59
|
+
if (!manager) {
|
|
60
|
+
this.ctx.showError("Background jobs are disabled; enable async jobs to use /tan.");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const parentFile = this.ctx.sessionManager.getSessionFile();
|
|
65
|
+
if (!parentFile) {
|
|
66
|
+
this.ctx.showError("/tan requires a persisted session.");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const parentSessionId = session.sessionId;
|
|
71
|
+
const thinkingLevel = session.configuredThinkingLevel();
|
|
72
|
+
const systemPrompt = [...session.systemPrompt];
|
|
73
|
+
const toolNames = session.getActiveToolNames();
|
|
74
|
+
const modelRegistry = session.modelRegistry;
|
|
75
|
+
const ownerId = session.getAgentId() ?? MAIN_AGENT_ID;
|
|
76
|
+
const mcpManager = this.ctx.mcpManager;
|
|
77
|
+
const cwd = this.ctx.sessionManager.getCwd();
|
|
78
|
+
// Nest the clone inside the parent's artifact directory (like a subagent
|
|
79
|
+
// session) rather than as a top-level sibling, so it shares the parent's
|
|
80
|
+
// artifacts in place — no copy needed.
|
|
81
|
+
const sessionDir = parentFile.slice(0, -6);
|
|
82
|
+
const settings = createSubagentSettings(this.ctx.settings);
|
|
83
|
+
const customTools = mcpManager ? createMCPProxyTools(mcpManager) : undefined;
|
|
84
|
+
const enableLsp = this.ctx.settings.get("task.enableLsp") !== false;
|
|
85
|
+
const agentRegistry = AgentRegistry.global();
|
|
86
|
+
const cloneId = `Tan-${Snowflake.next()}`;
|
|
87
|
+
const label = `/tan ${previewWork(trimmedWork)}`;
|
|
88
|
+
|
|
89
|
+
await this.ctx.sessionManager.ensureOnDisk();
|
|
90
|
+
await this.ctx.sessionManager.flush();
|
|
91
|
+
|
|
92
|
+
let cloneFile = "";
|
|
93
|
+
let jobId = "";
|
|
94
|
+
try {
|
|
95
|
+
const cloneManager = await SessionManager.forkFrom(parentFile, cwd, sessionDir, undefined, {
|
|
96
|
+
suppressBreadcrumb: true,
|
|
97
|
+
});
|
|
98
|
+
cloneFile = cloneManager.getSessionFile() ?? "";
|
|
99
|
+
if (!cloneFile) throw new Error("Forked session did not create a session file.");
|
|
100
|
+
|
|
101
|
+
jobId = manager.register(
|
|
102
|
+
"task",
|
|
103
|
+
label,
|
|
104
|
+
async ({ signal }) => {
|
|
105
|
+
if (signal.aborted) throw new Error("Aborted before execution");
|
|
106
|
+
|
|
107
|
+
let clone: AgentSession | undefined;
|
|
108
|
+
try {
|
|
109
|
+
const created = await sdk.createAgentSession({
|
|
110
|
+
cwd,
|
|
111
|
+
sessionManager: cloneManager,
|
|
112
|
+
model,
|
|
113
|
+
thinkingLevel,
|
|
114
|
+
systemPrompt,
|
|
115
|
+
toolNames,
|
|
116
|
+
providerSessionId: `${parentSessionId}:tan:${Snowflake.next()}`,
|
|
117
|
+
providerPromptCacheKey: parentSessionId,
|
|
118
|
+
modelRegistry,
|
|
119
|
+
authStorage: modelRegistry.authStorage,
|
|
120
|
+
settings,
|
|
121
|
+
hasUI: false,
|
|
122
|
+
enableMCP: false,
|
|
123
|
+
customTools,
|
|
124
|
+
enableLsp,
|
|
125
|
+
agentId: cloneId,
|
|
126
|
+
agentDisplayName: "tan",
|
|
127
|
+
parentTaskPrefix: cloneId,
|
|
128
|
+
agentRegistry,
|
|
129
|
+
disableExtensionDiscovery: true,
|
|
130
|
+
});
|
|
131
|
+
clone = created.session;
|
|
132
|
+
const abortClone = () => {
|
|
133
|
+
void clone?.abort();
|
|
134
|
+
};
|
|
135
|
+
signal.addEventListener("abort", abortClone, { once: true });
|
|
136
|
+
try {
|
|
137
|
+
if (signal.aborted) {
|
|
138
|
+
abortClone();
|
|
139
|
+
throw new Error("Aborted before execution");
|
|
140
|
+
}
|
|
141
|
+
await clone.prompt(trimmedWork, { attribution: "user" });
|
|
142
|
+
await clone.waitForIdle();
|
|
143
|
+
return extractAssistantText(clone.getLastAssistantMessage()) || "(no output)";
|
|
144
|
+
} finally {
|
|
145
|
+
signal.removeEventListener("abort", abortClone);
|
|
146
|
+
}
|
|
147
|
+
} finally {
|
|
148
|
+
await clone?.dispose();
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
{ ownerId },
|
|
152
|
+
);
|
|
153
|
+
} catch (error) {
|
|
154
|
+
if (cloneFile) await removeCloneSession(cloneFile);
|
|
155
|
+
this.ctx.showError(error instanceof Error ? error.message : String(error));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const content = prompt.render(backgroundTanDispatchPrompt, { jobId, work: trimmedWork });
|
|
160
|
+
await session.sendCustomMessage(
|
|
161
|
+
{
|
|
162
|
+
customType: "background-tan-dispatch",
|
|
163
|
+
content,
|
|
164
|
+
display: true,
|
|
165
|
+
attribution: "user",
|
|
166
|
+
details: { jobId, work: trimmedWork, sessionFile: cloneFile },
|
|
167
|
+
},
|
|
168
|
+
{ triggerTurn: false },
|
|
169
|
+
);
|
|
170
|
+
this.ctx.rebuildChatFromMessages();
|
|
171
|
+
this.ctx.showStatus(`Dispatched background tan ${jobId}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
package/src/modes/index.ts
CHANGED
|
@@ -2,11 +2,13 @@ import { emergencyTerminalRestore } from "@oh-my-pi/pi-tui";
|
|
|
2
2
|
import { postmortem } from "@oh-my-pi/pi-utils";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* Interactive mode and embeddable RPC client exports for the coding agent.
|
|
6
|
+
*
|
|
7
|
+
* Branch-specific runners live in their concrete modules so importing this
|
|
8
|
+
* barrel does not pull print, RPC server, or ACP server mode into the normal
|
|
9
|
+
* TUI graph.
|
|
6
10
|
*/
|
|
7
|
-
export { runAcpMode } from "./acp";
|
|
8
11
|
export { InteractiveMode, type InteractiveModeOptions } from "./interactive-mode";
|
|
9
|
-
export { type PrintModeOptions, runPrintMode } from "./print-mode";
|
|
10
12
|
export {
|
|
11
13
|
defineRpcClientTool,
|
|
12
14
|
type ModelInfo,
|
|
@@ -17,7 +19,6 @@ export {
|
|
|
17
19
|
type RpcClientToolResult,
|
|
18
20
|
type RpcEventListener,
|
|
19
21
|
} from "./rpc/rpc-client";
|
|
20
|
-
export { runRpcMode } from "./rpc/rpc-mode";
|
|
21
22
|
export type {
|
|
22
23
|
RpcCommand,
|
|
23
24
|
RpcHostToolCallRequest,
|