@oh-my-pi/pi-coding-agent 3.32.0 → 3.34.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 +49 -9
- package/README.md +12 -0
- package/docs/custom-tools.md +1 -1
- package/docs/extensions.md +4 -4
- package/docs/hooks.md +2 -2
- package/docs/sdk.md +4 -8
- package/examples/custom-tools/README.md +2 -2
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/todo.ts +1 -1
- package/examples/hooks/custom-compaction.ts +4 -2
- package/examples/hooks/handoff.ts +1 -1
- package/examples/hooks/qna.ts +1 -1
- package/examples/sdk/02-custom-model.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/package.json +5 -5
- package/src/capability/ssh.ts +42 -0
- package/src/cli/file-processor.ts +1 -1
- package/src/cli/list-models.ts +1 -1
- package/src/core/agent-session.ts +21 -6
- package/src/core/auth-storage.ts +1 -1
- package/src/core/compaction/branch-summarization.ts +2 -2
- package/src/core/compaction/compaction.ts +2 -2
- package/src/core/compaction/utils.ts +1 -1
- package/src/core/custom-tools/types.ts +1 -1
- package/src/core/extensions/runner.ts +1 -1
- package/src/core/extensions/types.ts +1 -1
- package/src/core/extensions/wrapper.ts +1 -1
- package/src/core/file-mentions.ts +147 -5
- package/src/core/hooks/runner.ts +2 -2
- package/src/core/hooks/types.ts +1 -1
- package/src/core/index.ts +11 -0
- package/src/core/messages.ts +1 -1
- package/src/core/model-registry.ts +1 -1
- package/src/core/model-resolver.ts +9 -4
- package/src/core/sdk.ts +26 -2
- package/src/core/session-manager.ts +3 -2
- package/src/core/settings-manager.ts +70 -0
- package/src/core/ssh/connection-manager.ts +466 -0
- package/src/core/ssh/ssh-executor.ts +190 -0
- package/src/core/ssh/sshfs-mount.ts +162 -0
- package/src/core/ssh-executor.ts +5 -0
- package/src/core/system-prompt.ts +424 -1
- package/src/core/title-generator.ts +109 -55
- package/src/core/tools/index.test.ts +1 -0
- package/src/core/tools/index.ts +3 -0
- package/src/core/tools/output.ts +37 -2
- package/src/core/tools/read.ts +24 -11
- package/src/core/tools/renderers.ts +2 -0
- package/src/core/tools/ssh.ts +302 -0
- package/src/core/tools/task/index.ts +1 -1
- package/src/core/tools/task/render.ts +10 -16
- package/src/core/tools/task/types.ts +1 -1
- package/src/core/tools/task/worker.ts +1 -1
- package/src/core/voice.ts +1 -1
- package/src/discovery/index.ts +3 -0
- package/src/discovery/ssh.ts +162 -0
- package/src/main.ts +2 -1
- package/src/modes/interactive/components/assistant-message.ts +1 -1
- package/src/modes/interactive/components/bash-execution.ts +9 -10
- package/src/modes/interactive/components/custom-message.ts +1 -1
- package/src/modes/interactive/components/footer.ts +1 -1
- package/src/modes/interactive/components/hook-message.ts +1 -1
- package/src/modes/interactive/components/model-selector.ts +1 -1
- package/src/modes/interactive/components/oauth-selector.ts +1 -1
- package/src/modes/interactive/components/status-line.ts +1 -1
- package/src/modes/interactive/components/tree-selector.ts +9 -12
- package/src/modes/interactive/interactive-mode.ts +5 -2
- package/src/modes/interactive/theme/theme.ts +2 -2
- package/src/modes/print-mode.ts +1 -1
- package/src/modes/rpc/rpc-client.ts +1 -1
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/prompts/system-prompt.md +4 -0
- package/src/prompts/tools/ssh.md +74 -0
- package/src/utils/image-resize.ts +1 -1
|
@@ -131,7 +131,7 @@ function renderJsonTreeLines(
|
|
|
131
131
|
pushLine(`${prefix}${iconArray} ${header}`);
|
|
132
132
|
if (val.length === 0) {
|
|
133
133
|
pushLine(
|
|
134
|
-
`${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.
|
|
134
|
+
`${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
135
135
|
"dim",
|
|
136
136
|
"[]",
|
|
137
137
|
)}`,
|
|
@@ -140,7 +140,7 @@ function renderJsonTreeLines(
|
|
|
140
140
|
}
|
|
141
141
|
if (depth >= maxDepth) {
|
|
142
142
|
pushLine(
|
|
143
|
-
`${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.
|
|
143
|
+
`${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
144
144
|
"dim",
|
|
145
145
|
theme.format.ellipsis,
|
|
146
146
|
)}`,
|
|
@@ -164,7 +164,7 @@ function renderJsonTreeLines(
|
|
|
164
164
|
const entries = Object.entries(val as Record<string, unknown>);
|
|
165
165
|
if (entries.length === 0) {
|
|
166
166
|
pushLine(
|
|
167
|
-
`${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.
|
|
167
|
+
`${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
168
168
|
"dim",
|
|
169
169
|
"{}",
|
|
170
170
|
)}`,
|
|
@@ -173,7 +173,7 @@ function renderJsonTreeLines(
|
|
|
173
173
|
}
|
|
174
174
|
if (depth >= maxDepth) {
|
|
175
175
|
pushLine(
|
|
176
|
-
`${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.
|
|
176
|
+
`${buildTreePrefix([...ancestors, !isLast], theme)}${theme.fg("dim", theme.tree.last)} ${theme.fg(
|
|
177
177
|
"dim",
|
|
178
178
|
theme.format.ellipsis,
|
|
179
179
|
)}`,
|
|
@@ -288,10 +288,8 @@ function renderAgentProgress(
|
|
|
288
288
|
spinnerFrame?: number,
|
|
289
289
|
): string[] {
|
|
290
290
|
const lines: string[] = [];
|
|
291
|
-
const prefix = isLast
|
|
292
|
-
|
|
293
|
-
: `${theme.boxSharp.teeRight}${theme.boxSharp.horizontal}`;
|
|
294
|
-
const continuePrefix = isLast ? " " : `${theme.boxSharp.vertical} `;
|
|
291
|
+
const prefix = isLast ? theme.tree.last : theme.tree.branch;
|
|
292
|
+
const continuePrefix = isLast ? " " : `${theme.tree.vertical} `;
|
|
295
293
|
|
|
296
294
|
const icon = getStatusIcon(progress.status, theme, spinnerFrame);
|
|
297
295
|
const iconColor =
|
|
@@ -460,10 +458,8 @@ function renderFindings(
|
|
|
460
458
|
for (let i = 0; i < displayCount; i++) {
|
|
461
459
|
const finding = findings[i];
|
|
462
460
|
const isLastFinding = i === displayCount - 1 && (expanded || findings.length <= 3);
|
|
463
|
-
const findingPrefix = isLastFinding
|
|
464
|
-
|
|
465
|
-
: `${theme.boxSharp.teeRight}${theme.boxSharp.horizontal}`;
|
|
466
|
-
const findingContinue = isLastFinding ? " " : `${theme.boxSharp.vertical} `;
|
|
461
|
+
const findingPrefix = isLastFinding ? theme.tree.last : theme.tree.branch;
|
|
462
|
+
const findingContinue = isLastFinding ? " " : `${theme.tree.vertical} `;
|
|
467
463
|
|
|
468
464
|
const priority = PRIORITY_LABELS[finding.priority] ?? "P?";
|
|
469
465
|
const color = finding.priority === 0 ? "error" : finding.priority === 1 ? "warning" : "muted";
|
|
@@ -496,10 +492,8 @@ function renderFindings(
|
|
|
496
492
|
*/
|
|
497
493
|
function renderAgentResult(result: SingleResult, isLast: boolean, expanded: boolean, theme: Theme): string[] {
|
|
498
494
|
const lines: string[] = [];
|
|
499
|
-
const prefix = isLast
|
|
500
|
-
|
|
501
|
-
: `${theme.boxSharp.teeRight}${theme.boxSharp.horizontal}`;
|
|
502
|
-
const continuePrefix = isLast ? " " : `${theme.boxSharp.vertical} `;
|
|
495
|
+
const prefix = isLast ? theme.tree.last : theme.tree.branch;
|
|
496
|
+
const continuePrefix = isLast ? " " : `${theme.tree.vertical} `;
|
|
503
497
|
|
|
504
498
|
const aborted = result.aborted ?? false;
|
|
505
499
|
const success = !aborted && result.exitCode === 0;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* 5. Parent can send { type: "abort" } to request cancellation
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import type { Api, Model } from "@mariozechner/pi-ai";
|
|
17
16
|
import type { AgentEvent, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
17
|
+
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
18
18
|
import type { AgentSessionEvent } from "../../agent-session";
|
|
19
19
|
import { parseModelPattern, parseModelString } from "../../model-resolver";
|
|
20
20
|
import { createAgentSession, discoverAuthStorage, discoverModels } from "../../sdk";
|
package/src/core/voice.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { unlinkSync } from "node:fs";
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { completeSimple, type Model } from "@
|
|
4
|
+
import { completeSimple, type Model } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { nanoid } from "nanoid";
|
|
6
6
|
import voiceSummaryPrompt from "../prompts/voice-summary.md" with { type: "text" };
|
|
7
7
|
import { logger } from "./logger";
|
package/src/discovery/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import "../capability/settings";
|
|
|
18
18
|
import "../capability/skill";
|
|
19
19
|
import "../capability/slash-command";
|
|
20
20
|
import "../capability/system-prompt";
|
|
21
|
+
import "../capability/ssh";
|
|
21
22
|
import "../capability/tool";
|
|
22
23
|
|
|
23
24
|
// Import providers (each registers itself on import)
|
|
@@ -32,6 +33,7 @@ import "./github";
|
|
|
32
33
|
import "./vscode";
|
|
33
34
|
import "./agents-md";
|
|
34
35
|
import "./mcp-json";
|
|
36
|
+
import "./ssh";
|
|
35
37
|
|
|
36
38
|
export type { ContextFile } from "../capability/context-file";
|
|
37
39
|
export type { Extension, ExtensionManifest } from "../capability/extension";
|
|
@@ -70,6 +72,7 @@ export type { Rule, RuleFrontmatter } from "../capability/rule";
|
|
|
70
72
|
export type { Settings } from "../capability/settings";
|
|
71
73
|
export type { Skill, SkillFrontmatter } from "../capability/skill";
|
|
72
74
|
export type { SlashCommand } from "../capability/slash-command";
|
|
75
|
+
export type { SSHHost } from "../capability/ssh";
|
|
73
76
|
export type { SystemPrompt } from "../capability/system-prompt";
|
|
74
77
|
export type { CustomTool } from "../capability/tool";
|
|
75
78
|
// Re-export types
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSH JSON Provider
|
|
3
|
+
*
|
|
4
|
+
* Discovers SSH hosts from ssh.json or .ssh.json in the project root.
|
|
5
|
+
* Priority: 5 (low, project-level only)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { join } from "node:path";
|
|
9
|
+
import { registerProvider } from "../capability/index";
|
|
10
|
+
import { type SSHHost, sshCapability } from "../capability/ssh";
|
|
11
|
+
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
|
12
|
+
import { createSourceMeta, expandEnvVarsDeep, parseJSON } from "./helpers";
|
|
13
|
+
|
|
14
|
+
const PROVIDER_ID = "ssh-json";
|
|
15
|
+
const DISPLAY_NAME = "SSH Config";
|
|
16
|
+
|
|
17
|
+
interface SSHConfigFile {
|
|
18
|
+
hosts?: Record<
|
|
19
|
+
string,
|
|
20
|
+
{
|
|
21
|
+
host?: string;
|
|
22
|
+
username?: string;
|
|
23
|
+
port?: number | string;
|
|
24
|
+
compat?: boolean | string;
|
|
25
|
+
key?: string;
|
|
26
|
+
keyPath?: string;
|
|
27
|
+
description?: string;
|
|
28
|
+
}
|
|
29
|
+
>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function expandTilde(value: string, home: string): string {
|
|
33
|
+
if (value === "~") return home;
|
|
34
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
35
|
+
return `${home}${value.slice(1)}`;
|
|
36
|
+
}
|
|
37
|
+
return value;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function parsePort(value: number | string | undefined): number | undefined {
|
|
41
|
+
if (value === undefined) return undefined;
|
|
42
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : undefined;
|
|
43
|
+
const parsed = Number.parseInt(value, 10);
|
|
44
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseCompat(value: boolean | string | undefined): boolean | undefined {
|
|
48
|
+
if (value === undefined) return undefined;
|
|
49
|
+
if (typeof value === "boolean") return value;
|
|
50
|
+
const normalized = value.trim().toLowerCase();
|
|
51
|
+
if (normalized === "true" || normalized === "1" || normalized === "yes") return true;
|
|
52
|
+
if (normalized === "false" || normalized === "0" || normalized === "no") return false;
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function normalizeHost(
|
|
57
|
+
name: string,
|
|
58
|
+
raw: NonNullable<SSHConfigFile["hosts"]>[string],
|
|
59
|
+
source: SourceMeta,
|
|
60
|
+
home: string,
|
|
61
|
+
warnings: string[],
|
|
62
|
+
): SSHHost | null {
|
|
63
|
+
if (!raw.host) {
|
|
64
|
+
warnings.push(`Missing host for SSH entry: ${name}`);
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const port = parsePort(raw.port);
|
|
69
|
+
if (raw.port !== undefined && port === undefined) {
|
|
70
|
+
warnings.push(`Invalid port for SSH entry ${name}: ${String(raw.port)}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const compat = parseCompat(raw.compat);
|
|
74
|
+
if (raw.compat !== undefined && compat === undefined) {
|
|
75
|
+
warnings.push(`Invalid compat flag for SSH entry ${name}: ${String(raw.compat)}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const keyValue = raw.keyPath ?? raw.key;
|
|
79
|
+
const keyPath = keyValue ? expandTilde(keyValue, home) : undefined;
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
name,
|
|
83
|
+
host: raw.host,
|
|
84
|
+
username: raw.username,
|
|
85
|
+
port,
|
|
86
|
+
keyPath,
|
|
87
|
+
description: raw.description,
|
|
88
|
+
compat,
|
|
89
|
+
_source: source,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function loadSshJsonFile(ctx: LoadContext, path: string): LoadResult<SSHHost> {
|
|
94
|
+
const items: SSHHost[] = [];
|
|
95
|
+
const warnings: string[] = [];
|
|
96
|
+
|
|
97
|
+
if (!ctx.fs.isFile(path)) {
|
|
98
|
+
return { items, warnings };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const content = ctx.fs.readFile(path);
|
|
102
|
+
if (content === null) {
|
|
103
|
+
warnings.push(`Failed to read ${path}`);
|
|
104
|
+
return { items, warnings };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const parsed = parseJSON<SSHConfigFile>(content);
|
|
108
|
+
if (!parsed) {
|
|
109
|
+
warnings.push(`Failed to parse JSON in ${path}`);
|
|
110
|
+
return { items, warnings };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const config = expandEnvVarsDeep(parsed);
|
|
114
|
+
if (!config.hosts || typeof config.hosts !== "object") {
|
|
115
|
+
warnings.push(`Missing hosts in ${path}`);
|
|
116
|
+
return { items, warnings };
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const source = createSourceMeta(PROVIDER_ID, path, "project");
|
|
120
|
+
for (const [name, rawHost] of Object.entries(config.hosts)) {
|
|
121
|
+
if (!name.trim()) {
|
|
122
|
+
warnings.push(`Invalid SSH host name in ${path}`);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (!rawHost || typeof rawHost !== "object") {
|
|
126
|
+
warnings.push(`Invalid host entry in ${path}: ${name}`);
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
const host = normalizeHost(name, rawHost, source, ctx.home, warnings);
|
|
130
|
+
if (host) items.push(host);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return {
|
|
134
|
+
items,
|
|
135
|
+
warnings: warnings.length > 0 ? warnings : undefined,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function load(ctx: LoadContext): LoadResult<SSHHost> {
|
|
140
|
+
const allItems: SSHHost[] = [];
|
|
141
|
+
const allWarnings: string[] = [];
|
|
142
|
+
|
|
143
|
+
for (const filename of ["ssh.json", ".ssh.json"]) {
|
|
144
|
+
const path = join(ctx.cwd, filename);
|
|
145
|
+
const result = loadSshJsonFile(ctx, path);
|
|
146
|
+
allItems.push(...result.items);
|
|
147
|
+
if (result.warnings) allWarnings.push(...result.warnings);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
items: allItems,
|
|
152
|
+
warnings: allWarnings.length > 0 ? allWarnings : undefined,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
registerProvider(sshCapability.id, {
|
|
157
|
+
id: PROVIDER_ID,
|
|
158
|
+
displayName: DISPLAY_NAME,
|
|
159
|
+
description: "Load SSH hosts from ssh.json or .ssh.json in the project root",
|
|
160
|
+
priority: 5,
|
|
161
|
+
load,
|
|
162
|
+
});
|
package/src/main.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
import { homedir, tmpdir } from "node:os";
|
|
9
9
|
import { join, resolve } from "node:path";
|
|
10
|
-
import { type ImageContent, supportsXhigh } from "@
|
|
10
|
+
import { type ImageContent, supportsXhigh } from "@oh-my-pi/pi-ai";
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
import { type Args, parseArgs, printHelp } from "./cli/args";
|
|
13
13
|
import { processFileArguments } from "./cli/file-processor";
|
|
@@ -421,6 +421,7 @@ export async function main(args: string[]) {
|
|
|
421
421
|
|
|
422
422
|
const cwd = process.cwd();
|
|
423
423
|
const settingsManager = SettingsManager.create(cwd);
|
|
424
|
+
settingsManager.applyEnvironmentVariables();
|
|
424
425
|
time("SettingsManager.create");
|
|
425
426
|
const { initialMessage, initialImages } = await prepareInitialMessage(parsed, settingsManager.getImageAutoResize());
|
|
426
427
|
time("prepareInitialMessage");
|
|
@@ -27,12 +27,10 @@ export class BashExecutionComponent extends Container {
|
|
|
27
27
|
private fullOutputPath?: string;
|
|
28
28
|
private expanded = false;
|
|
29
29
|
private contentContainer: Container;
|
|
30
|
-
private ui: TUI;
|
|
31
30
|
|
|
32
31
|
constructor(command: string, ui: TUI, excludeFromContext = false) {
|
|
33
32
|
super();
|
|
34
33
|
this.command = command;
|
|
35
|
-
this.ui = ui;
|
|
36
34
|
|
|
37
35
|
// Use dim border for excluded-from-context commands (!! prefix)
|
|
38
36
|
const colorKey = excludeFromContext ? "dim" : "bashMode";
|
|
@@ -142,15 +140,16 @@ export class BashExecutionComponent extends Container {
|
|
|
142
140
|
const displayText = availableLines.map((line) => theme.fg("muted", line)).join("\n");
|
|
143
141
|
this.contentContainer.addChild(new Text(`\n${displayText}`, 1, 0));
|
|
144
142
|
} else {
|
|
145
|
-
// Use shared visual truncation utility
|
|
143
|
+
// Use shared visual truncation utility, recomputed per render width
|
|
146
144
|
const styledOutput = previewLogicalLines.map((line) => theme.fg("muted", line)).join("\n");
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
145
|
+
const previewText = `\n${styledOutput}`;
|
|
146
|
+
this.contentContainer.addChild({
|
|
147
|
+
render: (width: number) => {
|
|
148
|
+
const { visualLines } = truncateToVisualLines(previewText, PREVIEW_LINES, width, 1);
|
|
149
|
+
return visualLines;
|
|
150
|
+
},
|
|
151
|
+
invalidate: () => {},
|
|
152
|
+
});
|
|
154
153
|
}
|
|
155
154
|
}
|
|
156
155
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TextContent } from "@
|
|
1
|
+
import type { TextContent } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { Box, Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import type { MessageRenderer } from "../../../core/extensions/types";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, type FSWatcher, readFileSync, watch } from "node:fs";
|
|
2
|
-
import type { AssistantMessage } from "@
|
|
2
|
+
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { dirname, join } from "path";
|
|
5
5
|
import type { AgentSession } from "../../../core/agent-session";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { TextContent } from "@
|
|
1
|
+
import type { TextContent } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { Box, Container, Markdown, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import type { HookMessageRenderer } from "../../../core/hooks/types";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getOAuthProviders, type OAuthProviderInfo } from "@
|
|
1
|
+
import { getOAuthProviders, type OAuthProviderInfo } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { Container, isArrowDown, isArrowUp, isCtrlC, isEnter, isEscape, Spacer, TruncatedText } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import type { AuthStorage } from "../../../core/auth-storage";
|
|
4
4
|
import { theme } from "../theme/theme";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AssistantMessage } from "@
|
|
1
|
+
import type { AssistantMessage } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { type Component, truncateToWidth, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { type FSWatcher, watch } from "fs";
|
|
4
4
|
import { dirname, join } from "path";
|
|
@@ -437,13 +437,10 @@ class TreeList implements Component {
|
|
|
437
437
|
|
|
438
438
|
// Build prefix with gutters at their correct positions
|
|
439
439
|
// Each gutter has a position (displayIndent where its connector was shown)
|
|
440
|
-
const
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
: `${theme.boxSharp.teeRight}${theme.boxSharp.horizontal} `
|
|
445
|
-
: "";
|
|
446
|
-
const connectorPosition = connector ? displayIndent - 1 : -1;
|
|
440
|
+
const hasConnector = flatNode.showConnector && !flatNode.isVirtualRootChild;
|
|
441
|
+
const connectorSymbol = hasConnector ? (flatNode.isLast ? theme.tree.last : theme.tree.branch) : "";
|
|
442
|
+
const connectorChars = hasConnector ? Array.from(connectorSymbol) : [];
|
|
443
|
+
const connectorPosition = hasConnector ? displayIndent - 1 : -1;
|
|
447
444
|
|
|
448
445
|
// Build prefix char by char, placing gutters and connector at their positions
|
|
449
446
|
const totalChars = displayIndent * 3;
|
|
@@ -456,18 +453,18 @@ class TreeList implements Component {
|
|
|
456
453
|
const gutter = flatNode.gutters.find((g) => g.position === level);
|
|
457
454
|
if (gutter) {
|
|
458
455
|
if (posInLevel === 0) {
|
|
459
|
-
prefixChars.push(gutter.show ? theme.
|
|
456
|
+
prefixChars.push(gutter.show ? theme.tree.vertical : " ");
|
|
460
457
|
} else {
|
|
461
458
|
prefixChars.push(" ");
|
|
462
459
|
}
|
|
463
|
-
} else if (
|
|
460
|
+
} else if (hasConnector && level === connectorPosition) {
|
|
464
461
|
// Connector at this level
|
|
465
462
|
if (posInLevel === 0) {
|
|
466
|
-
prefixChars.push(
|
|
463
|
+
prefixChars.push(connectorChars[0] ?? " ");
|
|
467
464
|
} else if (posInLevel === 1) {
|
|
468
|
-
prefixChars.push(theme.
|
|
465
|
+
prefixChars.push(connectorChars[1] ?? theme.tree.horizontal);
|
|
469
466
|
} else {
|
|
470
|
-
prefixChars.push(" ");
|
|
467
|
+
prefixChars.push(connectorChars[2] ?? " ");
|
|
471
468
|
}
|
|
472
469
|
} else {
|
|
473
470
|
prefixChars.push(" ");
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
import * as fs from "node:fs";
|
|
7
7
|
import * as os from "node:os";
|
|
8
8
|
import * as path from "node:path";
|
|
9
|
-
import type { AssistantMessage, ImageContent, Message, OAuthProvider } from "@mariozechner/pi-ai";
|
|
10
9
|
import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
10
|
+
import type { AssistantMessage, ImageContent, Message, OAuthProvider } from "@oh-my-pi/pi-ai";
|
|
11
11
|
import type { SlashCommand } from "@oh-my-pi/pi-tui";
|
|
12
12
|
import {
|
|
13
13
|
CombinedAutocompleteProvider,
|
|
@@ -1215,6 +1215,9 @@ export class InteractiveMode {
|
|
|
1215
1215
|
this.editor.setText("");
|
|
1216
1216
|
this.updatePendingMessagesDisplay();
|
|
1217
1217
|
this.ui.requestRender();
|
|
1218
|
+
} else if (event.message.role === "fileMention") {
|
|
1219
|
+
this.addMessageToChat(event.message);
|
|
1220
|
+
this.ui.requestRender();
|
|
1218
1221
|
} else if (event.message.role === "assistant") {
|
|
1219
1222
|
this.streamingComponent = new AssistantMessageComponent(undefined, this.hideThinkingBlock);
|
|
1220
1223
|
this.streamingMessage = event.message;
|
|
@@ -1566,7 +1569,7 @@ export class InteractiveMode {
|
|
|
1566
1569
|
case "fileMention": {
|
|
1567
1570
|
// Render compact file mention display
|
|
1568
1571
|
for (const file of message.files) {
|
|
1569
|
-
const text = `${theme.fg("dim", `${theme.tree.
|
|
1572
|
+
const text = `${theme.fg("dim", `${theme.tree.last} `)}${theme.fg("muted", "Read")} ${theme.fg(
|
|
1570
1573
|
"accent",
|
|
1571
1574
|
file.path,
|
|
1572
1575
|
)} ${theme.fg("dim", `(${file.lineCount} lines)`)}`;
|
|
@@ -209,8 +209,8 @@ const UNICODE_SYMBOLS: SymbolMap = {
|
|
|
209
209
|
"tree.vertical": "│",
|
|
210
210
|
// pick: ─ | alt: ━ ═ ╌ ┄
|
|
211
211
|
"tree.horizontal": "─",
|
|
212
|
-
// pick:
|
|
213
|
-
"tree.hook": "
|
|
212
|
+
// pick: └ | alt: ⎿ ╰ ↳
|
|
213
|
+
"tree.hook": "\u2514",
|
|
214
214
|
// Box Drawing - Rounded
|
|
215
215
|
// pick: ╭ | alt: ┌ ┏ ╔
|
|
216
216
|
"boxRound.topLeft": "╭",
|
package/src/modes/print-mode.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - `omp --mode json "prompt"` - JSON event stream
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import type { AssistantMessage, ImageContent } from "@
|
|
9
|
+
import type { AssistantMessage, ImageContent } from "@oh-my-pi/pi-ai";
|
|
10
10
|
import type { AgentSession } from "../core/agent-session";
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Spawns the agent in RPC mode and provides a typed API for all operations.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import type { ImageContent } from "@mariozechner/pi-ai";
|
|
8
7
|
import type { AgentEvent, AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
8
|
+
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
9
9
|
import type { Subprocess } from "bun";
|
|
10
10
|
import type { SessionStats } from "../../core/agent-session";
|
|
11
11
|
import type { BashResult } from "../../core/bash-executor";
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Responses and events are emitted as JSON lines on stdout.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import type { ImageContent, Model } from "@mariozechner/pi-ai";
|
|
9
8
|
import type { AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
9
|
+
import type { ImageContent, Model } from "@oh-my-pi/pi-ai";
|
|
10
10
|
import type { SessionStats } from "../../core/agent-session";
|
|
11
11
|
import type { BashResult } from "../../core/bash-executor";
|
|
12
12
|
import type { CompactionResult } from "../../core/compaction/index";
|
|
@@ -21,6 +21,10 @@ Core behavior:
|
|
|
21
21
|
- After each tool result, check relevance; iterate or clarify if results conflict or are insufficient.
|
|
22
22
|
- Use concise, scannable responses; include file paths in backticks; use short bullets for multi-item lists; avoid dumping large files.
|
|
23
23
|
|
|
24
|
+
<environment>
|
|
25
|
+
{{environmentInfo}}
|
|
26
|
+
</environment>
|
|
27
|
+
|
|
24
28
|
Documentation:
|
|
25
29
|
- Main documentation: {{readmePath}}
|
|
26
30
|
- Additional docs: {{docsPath}}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
Execute commands on remote SSH hosts.
|
|
2
|
+
|
|
3
|
+
## Critical: Match Commands to Host Shell
|
|
4
|
+
|
|
5
|
+
Each host runs a specific shell. **You MUST use commands native to that shell.**
|
|
6
|
+
|
|
7
|
+
### Command Reference
|
|
8
|
+
|
|
9
|
+
**linux/bash, linux/zsh, macos/bash, macos/zsh** — Unix-like systems:
|
|
10
|
+
- Files: `ls`, `cat`, `head`, `tail`, `grep`, `find`
|
|
11
|
+
- System: `ps`, `top`, `df`, `uname`, `free` (Linux), `df`, `uname`, `top` (macOS)
|
|
12
|
+
- Navigation: `cd`, `pwd`
|
|
13
|
+
|
|
14
|
+
**windows/bash, windows/sh** — Windows with Unix compatibility layer (WSL, Cygwin, Git Bash):
|
|
15
|
+
- Files: `ls`, `cat`, `head`, `tail`, `grep`, `find`
|
|
16
|
+
- System: `ps`, `top`, `df`, `uname`
|
|
17
|
+
- Navigation: `cd`, `pwd`
|
|
18
|
+
- Note: These are Windows hosts but use Unix commands
|
|
19
|
+
|
|
20
|
+
**windows/powershell** — Native Windows PowerShell:
|
|
21
|
+
- Files: `Get-ChildItem`, `Get-Content`, `Select-String`
|
|
22
|
+
- System: `Get-Process`, `Get-ComputerInfo`
|
|
23
|
+
- Navigation: `Set-Location`, `Get-Location`
|
|
24
|
+
|
|
25
|
+
**windows/cmd** — Native Windows Command Prompt:
|
|
26
|
+
- Files: `dir`, `type`, `findstr`, `where`
|
|
27
|
+
- System: `tasklist`, `systeminfo`
|
|
28
|
+
- Navigation: `cd`, `echo %CD%`
|
|
29
|
+
|
|
30
|
+
## Execution Pattern
|
|
31
|
+
|
|
32
|
+
1. Check the host's shell type from "Available hosts" below
|
|
33
|
+
2. Use ONLY commands for that shell type
|
|
34
|
+
3. Construct your command using the reference above
|
|
35
|
+
|
|
36
|
+
## Examples
|
|
37
|
+
|
|
38
|
+
<example>
|
|
39
|
+
Task: List files in /home/user on host "server1"
|
|
40
|
+
Host: server1 (10.0.0.1) | linux/bash
|
|
41
|
+
Command: `ls -la /home/user`
|
|
42
|
+
</example>
|
|
43
|
+
|
|
44
|
+
<example>
|
|
45
|
+
Task: Show running processes on host "winbox"
|
|
46
|
+
Host: winbox (192.168.1.5) | windows/cmd
|
|
47
|
+
Command: `tasklist /v`
|
|
48
|
+
</example>
|
|
49
|
+
|
|
50
|
+
<example>
|
|
51
|
+
Task: Check disk usage on host "wsl-dev"
|
|
52
|
+
Host: wsl-dev (192.168.1.10) | windows/bash
|
|
53
|
+
Command: `df -h`
|
|
54
|
+
Note: Windows host with WSL — use Unix commands
|
|
55
|
+
</example>
|
|
56
|
+
|
|
57
|
+
<example>
|
|
58
|
+
Task: Get system info on host "macbook"
|
|
59
|
+
Host: macbook (10.0.0.20) | macos/zsh
|
|
60
|
+
Command: `uname -a && sw_vers`
|
|
61
|
+
</example>
|
|
62
|
+
|
|
63
|
+
## Parameters
|
|
64
|
+
|
|
65
|
+
- **host**: Host name from "Available hosts" below
|
|
66
|
+
- **command**: Command to execute (see Command Reference above)
|
|
67
|
+
- **cwd**: Working directory (optional)
|
|
68
|
+
- **timeout**: Timeout in seconds (optional)
|
|
69
|
+
|
|
70
|
+
## Output
|
|
71
|
+
|
|
72
|
+
Truncated at 50KB. Exit codes captured.
|
|
73
|
+
|
|
74
|
+
**Before executing: verify host shell type below and use matching commands.**
|