@oh-my-pi/pi-coding-agent 16.1.8 → 16.1.10
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 +40 -0
- package/dist/cli.js +4775 -3942
- package/dist/types/cli/flag-tables.d.ts +17 -0
- package/dist/types/cli-commands.d.ts +4 -2
- package/dist/types/config/settings-schema.d.ts +10 -0
- package/dist/types/edit/streaming.d.ts +5 -5
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/selector-controller.d.ts +0 -1
- package/dist/types/modes/interactive-mode.d.ts +1 -1
- package/dist/types/modes/types.d.ts +1 -1
- package/dist/types/sdk.d.ts +1 -0
- package/dist/types/session/agent-session.d.ts +13 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/tools/browser/aria/aria-snapshot.d.ts +39 -0
- package/dist/types/tools/browser/cmux/cmux-tab.d.ts +3 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/browser.d.ts +1 -0
- package/dist/types/tools/find.d.ts +1 -2
- package/dist/types/tools/write.d.ts +1 -2
- package/package.json +12 -12
- package/scripts/generate-aria-snapshot.ts +134 -0
- package/src/cli/args.ts +0 -1
- package/src/cli/flag-tables.ts +42 -0
- package/src/cli/profile-bootstrap.ts +1 -11
- package/src/cli-commands.ts +48 -3
- package/src/config/model-registry.ts +6 -7
- package/src/config/settings-schema.ts +12 -0
- package/src/edit/streaming.ts +5 -5
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/lsp/client.ts +39 -25
- package/src/modes/controllers/command-controller.ts +18 -3
- package/src/modes/controllers/event-controller.ts +20 -0
- package/src/modes/controllers/mcp-command-controller.ts +40 -12
- package/src/modes/controllers/selector-controller.ts +3 -5
- package/src/modes/interactive-mode.ts +1 -1
- package/src/modes/types.ts +1 -1
- package/src/prompts/system/project-prompt.md +2 -0
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/browser.md +2 -0
- package/src/sdk.ts +8 -1
- package/src/session/agent-session.ts +36 -1
- package/src/slash-commands/builtin-registry.ts +24 -66
- package/src/system-prompt.ts +15 -3
- package/src/tools/bash.ts +3 -3
- package/src/tools/browser/aria/aria-snapshot.bundle.txt +7 -0
- package/src/tools/browser/aria/aria-snapshot.ts +103 -0
- package/src/tools/browser/cmux/cmux-tab.ts +36 -1
- package/src/tools/browser/launch.ts +107 -31
- package/src/tools/browser/tab-worker.ts +89 -17
- package/src/tools/browser.ts +5 -0
- package/src/tools/find.ts +1 -2
- package/src/tools/index.ts +1 -1
- package/src/tools/puppeteer/00_stealth_tampering.txt +16 -35
- package/src/tools/puppeteer/01_stealth_activity.txt +79 -19
- package/src/tools/puppeteer/02_stealth_hairline.txt +57 -11
- package/src/tools/puppeteer/03_stealth_botd.txt +10 -14
- package/src/tools/puppeteer/04_stealth_iframe.txt +156 -63
- package/src/tools/puppeteer/05_stealth_webgl.txt +222 -64
- package/src/tools/puppeteer/06_stealth_screen.txt +255 -67
- package/src/tools/puppeteer/07_stealth_fonts.txt +17 -15
- package/src/tools/puppeteer/08_stealth_audio.txt +32 -20
- package/src/tools/puppeteer/09_stealth_locale.txt +45 -40
- package/src/tools/puppeteer/10_stealth_plugins.txt +12 -8
- package/src/tools/puppeteer/11_stealth_hardware.txt +57 -6
- package/src/tools/puppeteer/12_stealth_codecs.txt +20 -18
- package/src/tools/puppeteer/13_stealth_worker.txt +206 -45
- package/src/tools/write.ts +1 -2
- package/src/utils/git.ts +3 -2
package/src/lsp/client.ts
CHANGED
|
@@ -327,9 +327,45 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
327
327
|
try {
|
|
328
328
|
const message: LspJsonRpcResponse | LspJsonRpcNotification = JSON.parse(messageText);
|
|
329
329
|
|
|
330
|
-
// Route message
|
|
331
|
-
|
|
332
|
-
|
|
330
|
+
// Route message. A JSON-RPC message carrying a `method` is always
|
|
331
|
+
// server-originated: a request when it also has an `id`, a
|
|
332
|
+
// notification otherwise. A message with only an `id` is a response
|
|
333
|
+
// to one of our requests. Disambiguate on `method` FIRST: a
|
|
334
|
+
// server's request ids live in its own id space and routinely
|
|
335
|
+
// collide with our in-flight client request ids (e.g. a
|
|
336
|
+
// basedpyright `workspace/configuration` pull arriving while a
|
|
337
|
+
// `documentSymbol` request with the same id is pending). Matching
|
|
338
|
+
// pending requests first would swallow that pull as a bogus
|
|
339
|
+
// response -- dropping the config answer the server blocks on and
|
|
340
|
+
// resolving our request with `undefined`, wedging the lazy
|
|
341
|
+
// cold-start handshake (#3001).
|
|
342
|
+
if ("method" in message) {
|
|
343
|
+
if ("id" in message && message.id !== undefined) {
|
|
344
|
+
// Server-initiated request: must be answered.
|
|
345
|
+
await handleServerRequest(client, message as LspJsonRpcRequest);
|
|
346
|
+
} else {
|
|
347
|
+
// Server notification
|
|
348
|
+
if (message.method === "textDocument/publishDiagnostics" && message.params) {
|
|
349
|
+
const params = message.params as PublishDiagnosticsParams;
|
|
350
|
+
client.diagnostics.set(params.uri, {
|
|
351
|
+
diagnostics: params.diagnostics,
|
|
352
|
+
version: params.version ?? null,
|
|
353
|
+
});
|
|
354
|
+
client.diagnosticsVersion += 1;
|
|
355
|
+
} else if (message.method === "$/progress" && message.params) {
|
|
356
|
+
const params = message.params as { token: string | number; value?: { kind?: string } };
|
|
357
|
+
if (params.value?.kind === "begin") {
|
|
358
|
+
client.activeProgressTokens.add(params.token);
|
|
359
|
+
} else if (params.value?.kind === "end") {
|
|
360
|
+
client.activeProgressTokens.delete(params.token);
|
|
361
|
+
if (client.activeProgressTokens.size === 0) {
|
|
362
|
+
client.resolveProjectLoaded();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
} else if ("id" in message && message.id !== undefined) {
|
|
368
|
+
// Response to one of our requests.
|
|
333
369
|
const pending = client.pendingRequests.get(message.id);
|
|
334
370
|
if (pending) {
|
|
335
371
|
client.pendingRequests.delete(message.id);
|
|
@@ -338,28 +374,6 @@ async function startMessageReader(client: LspClient): Promise<void> {
|
|
|
338
374
|
} else {
|
|
339
375
|
pending.resolve(message.result);
|
|
340
376
|
}
|
|
341
|
-
} else if ("method" in message) {
|
|
342
|
-
await handleServerRequest(client, message as LspJsonRpcRequest);
|
|
343
|
-
}
|
|
344
|
-
} else if ("method" in message) {
|
|
345
|
-
// Server notification
|
|
346
|
-
if (message.method === "textDocument/publishDiagnostics" && message.params) {
|
|
347
|
-
const params = message.params as PublishDiagnosticsParams;
|
|
348
|
-
client.diagnostics.set(params.uri, {
|
|
349
|
-
diagnostics: params.diagnostics,
|
|
350
|
-
version: params.version ?? null,
|
|
351
|
-
});
|
|
352
|
-
client.diagnosticsVersion += 1;
|
|
353
|
-
} else if (message.method === "$/progress" && message.params) {
|
|
354
|
-
const params = message.params as { token: string | number; value?: { kind?: string } };
|
|
355
|
-
if (params.value?.kind === "begin") {
|
|
356
|
-
client.activeProgressTokens.add(params.token);
|
|
357
|
-
} else if (params.value?.kind === "end") {
|
|
358
|
-
client.activeProgressTokens.delete(params.token);
|
|
359
|
-
if (client.activeProgressTokens.size === 0) {
|
|
360
|
-
client.resolveProjectLoaded();
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
377
|
}
|
|
364
378
|
}
|
|
365
379
|
} catch (err) {
|
|
@@ -85,15 +85,30 @@ export class CommandController {
|
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
handleDumpCommand() {
|
|
88
|
+
async handleDumpCommand(): Promise<void> {
|
|
89
89
|
try {
|
|
90
90
|
const formatted = this.ctx.session.formatSessionAsText();
|
|
91
91
|
if (!formatted) {
|
|
92
92
|
this.ctx.showError("No messages to dump yet.");
|
|
93
93
|
return;
|
|
94
94
|
}
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
// Build the LLM request JSON sidecar first so its path (and a
|
|
96
|
+
// raw-context warning) can be appended to the copied transcript.
|
|
97
|
+
let sidecarPath: string | undefined;
|
|
98
|
+
let sidecarError: string | undefined;
|
|
99
|
+
try {
|
|
100
|
+
sidecarPath = await this.ctx.session.dumpLlmRequestToTmpDir();
|
|
101
|
+
} catch (error: unknown) {
|
|
102
|
+
sidecarError = error instanceof Error ? error.message : "Unknown error";
|
|
103
|
+
}
|
|
104
|
+
const doc = sidecarPath
|
|
105
|
+
? `${formatted}\n\n---\nLLM request JSON: ${sidecarPath}\nThis file persists on disk and may contain raw context/secrets — treat accordingly.`
|
|
106
|
+
: formatted;
|
|
107
|
+
await copyToClipboard(doc);
|
|
108
|
+
const statusParts = ["Session copied to clipboard"];
|
|
109
|
+
if (sidecarPath) statusParts.push(`LLM request JSON: ${sidecarPath}`);
|
|
110
|
+
if (sidecarError) statusParts.push(`LLM request JSON unavailable: ${sidecarError}`);
|
|
111
|
+
this.ctx.showStatus(statusParts.join("\n"));
|
|
97
112
|
} catch (error: unknown) {
|
|
98
113
|
this.ctx.showError(`Failed to copy session: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
99
114
|
}
|
|
@@ -738,6 +738,26 @@ export class EventController {
|
|
|
738
738
|
this.ctx.chatContainer.addChild(component);
|
|
739
739
|
this.ctx.pendingTools.set(event.toolCallId, component);
|
|
740
740
|
this.ctx.ui.requestRender();
|
|
741
|
+
} else {
|
|
742
|
+
// The tool is about to run, so its arguments are final and validated.
|
|
743
|
+
// A pending component created while args streamed (message_update) may
|
|
744
|
+
// still show a mid-reveal prefix — or, when the closing full-args
|
|
745
|
+
// `message_update` never lands (smooth-streaming off leaving the
|
|
746
|
+
// throttled `arguments` stale, an owned-dialect projector, or a
|
|
747
|
+
// superseded/aborted turn that still executes the call), a stale body
|
|
748
|
+
// the result render then freezes at its `…` placeholder. Reconcile the
|
|
749
|
+
// authoritative args here and drop any live reveal so a late tick can't
|
|
750
|
+
// re-truncate them: tool_execution_start is the one event every
|
|
751
|
+
// execution path emits with the full args immediately before the result.
|
|
752
|
+
this.#toolArgsReveal.finish(event.toolCallId);
|
|
753
|
+
const component = this.ctx.pendingTools.get(event.toolCallId);
|
|
754
|
+
if (component && typeof component.updateArgs === "function") {
|
|
755
|
+
component.updateArgs(event.args, event.toolCallId);
|
|
756
|
+
if (typeof component.setArgsComplete === "function") {
|
|
757
|
+
component.setArgsComplete(event.toolCallId);
|
|
758
|
+
}
|
|
759
|
+
this.ctx.ui.requestRender();
|
|
760
|
+
}
|
|
741
761
|
}
|
|
742
762
|
}
|
|
743
763
|
|
|
@@ -8,7 +8,7 @@ import { type Component, replaceTabs, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
|
8
8
|
import { getMCPConfigPath, getProjectDir } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import type { SourceMeta } from "../../capability/types";
|
|
10
10
|
import { expandEnvVarsDeep } from "../../discovery/helpers";
|
|
11
|
-
import { analyzeAuthError, discoverOAuthEndpoints, MCPManager } from "../../mcp";
|
|
11
|
+
import { analyzeAuthError, discoverOAuthEndpoints, loadAllMCPConfigs, MCPManager } from "../../mcp";
|
|
12
12
|
import { connectToServer, disconnectServer, listTools } from "../../mcp/client";
|
|
13
13
|
import {
|
|
14
14
|
addMCPServer,
|
|
@@ -1383,7 +1383,7 @@ export class MCPCommandController {
|
|
|
1383
1383
|
}
|
|
1384
1384
|
await setServerDisabled(userConfigPath, name, !enabled);
|
|
1385
1385
|
if (enabled) {
|
|
1386
|
-
await this.#
|
|
1386
|
+
await this.#connectEnabledMCPServer(name);
|
|
1387
1387
|
const state = await this.#waitForServerConnectionWithAnimation(name);
|
|
1388
1388
|
const status =
|
|
1389
1389
|
state === "connected"
|
|
@@ -1419,7 +1419,12 @@ export class MCPCommandController {
|
|
|
1419
1419
|
|
|
1420
1420
|
const updated: MCPServerConfig = { ...found.config, enabled };
|
|
1421
1421
|
await updateMCPServer(found.filePath, name, updated);
|
|
1422
|
-
|
|
1422
|
+
if (enabled) {
|
|
1423
|
+
await this.#connectEnabledMCPServer(name);
|
|
1424
|
+
} else {
|
|
1425
|
+
await this.ctx.mcpManager?.disconnectServer(name);
|
|
1426
|
+
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager?.getTools() ?? []);
|
|
1427
|
+
}
|
|
1423
1428
|
|
|
1424
1429
|
let status = "";
|
|
1425
1430
|
if (enabled) {
|
|
@@ -1671,6 +1676,37 @@ export class MCPCommandController {
|
|
|
1671
1676
|
}
|
|
1672
1677
|
}
|
|
1673
1678
|
|
|
1679
|
+
async #connectEnabledMCPServer(name: string): Promise<void> {
|
|
1680
|
+
if (!this.ctx.mcpManager) {
|
|
1681
|
+
return;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
const { configs, sources } = await loadAllMCPConfigs(getProjectDir());
|
|
1685
|
+
const config = configs[name];
|
|
1686
|
+
if (!config) {
|
|
1687
|
+
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager.getTools());
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
|
|
1691
|
+
const source = sources[name];
|
|
1692
|
+
const result = await this.ctx.mcpManager.connectServers({ [name]: config }, source ? { [name]: source } : {});
|
|
1693
|
+
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager.getTools());
|
|
1694
|
+
this.#showMCPConnectionErrors(result.errors);
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
#showMCPConnectionErrors(errors: Map<string, string>): void {
|
|
1698
|
+
if (errors.size === 0) {
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
const errorLines = ["", theme.fg("warning", "Some servers failed to connect:"), ""];
|
|
1703
|
+
for (const [serverName, error] of errors.entries()) {
|
|
1704
|
+
errorLines.push(` ${serverName}: ${error}`);
|
|
1705
|
+
}
|
|
1706
|
+
errorLines.push("");
|
|
1707
|
+
this.#showMessage(errorLines.join("\n"));
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1674
1710
|
/**
|
|
1675
1711
|
* Reload MCP manager with new configs
|
|
1676
1712
|
*/
|
|
@@ -1686,15 +1722,7 @@ export class MCPCommandController {
|
|
|
1686
1722
|
const result = await this.ctx.mcpManager.discoverAndConnect();
|
|
1687
1723
|
await this.ctx.session.refreshMCPTools(this.ctx.mcpManager.getTools());
|
|
1688
1724
|
|
|
1689
|
-
|
|
1690
|
-
if (result.errors.size > 0) {
|
|
1691
|
-
const errorLines = ["", theme.fg("warning", "Some servers failed to connect:"), ""];
|
|
1692
|
-
for (const [serverName, error] of result.errors.entries()) {
|
|
1693
|
-
errorLines.push(` ${serverName}: ${error}`);
|
|
1694
|
-
}
|
|
1695
|
-
errorLines.push("");
|
|
1696
|
-
this.#showMessage(errorLines.join("\n"));
|
|
1697
|
-
}
|
|
1725
|
+
this.#showMCPConnectionErrors(result.errors);
|
|
1698
1726
|
}
|
|
1699
1727
|
|
|
1700
1728
|
/**
|
|
@@ -71,10 +71,6 @@ import { buildCopyTargets } from "../utils/copy-targets";
|
|
|
71
71
|
|
|
72
72
|
const MANUAL_LOGIN_TIP = "Tip: You can complete pairing with /login <redirect URL>.";
|
|
73
73
|
|
|
74
|
-
export function formatTemporaryModelStatus(modelLabel: string, roleSelectorHint = "Alt+M"): string {
|
|
75
|
-
return `Temporary model selection is session-only: ${modelLabel}. Use ${roleSelectorHint} or /model for role models (default/smol/plan/task/slow/custom roles).`;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
74
|
export class SelectorController {
|
|
79
75
|
constructor(private ctx: InteractiveModeContext) {}
|
|
80
76
|
|
|
@@ -499,7 +495,9 @@ export class SelectorController {
|
|
|
499
495
|
this.ctx.statusLine.invalidate();
|
|
500
496
|
this.ctx.updateEditorBorderColor();
|
|
501
497
|
const roleSelectorHint = this.ctx.keybindings.getKeys("app.model.select")[0] ?? "Alt+M";
|
|
502
|
-
this.ctx.showStatus(
|
|
498
|
+
this.ctx.showStatus(
|
|
499
|
+
`Session-only model: ${selector ?? model.id}. Use ${roleSelectorHint} or /model for roles.`,
|
|
500
|
+
);
|
|
503
501
|
done();
|
|
504
502
|
this.ctx.ui.requestRender();
|
|
505
503
|
} else if (role === "default") {
|
|
@@ -3491,7 +3491,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
3491
3491
|
return this.#commandController.handleExportCommand(text);
|
|
3492
3492
|
}
|
|
3493
3493
|
|
|
3494
|
-
handleDumpCommand() {
|
|
3494
|
+
async handleDumpCommand(): Promise<void> {
|
|
3495
3495
|
return this.#commandController.handleDumpCommand();
|
|
3496
3496
|
}
|
|
3497
3497
|
|
package/src/modes/types.ts
CHANGED
|
@@ -290,7 +290,7 @@ export interface InteractiveModeContext {
|
|
|
290
290
|
handleHotkeysCommand(): void;
|
|
291
291
|
handleToolsCommand(): void;
|
|
292
292
|
handleContextCommand(): void;
|
|
293
|
-
handleDumpCommand(): void
|
|
293
|
+
handleDumpCommand(): Promise<void>;
|
|
294
294
|
handleAdvisorDumpCommand(isRaw?: boolean): void;
|
|
295
295
|
handleDebugTranscriptCommand(): Promise<void>;
|
|
296
296
|
handleClearCommand(): Promise<void>;
|
|
@@ -29,6 +29,7 @@ Before making changes within these directories, you MUST read:
|
|
|
29
29
|
The context files above are loaded automatically. You NEVER `search`/`find` for `AGENTS.md`, `CLAUDE.md`, `.cursorrules`, or similar agent/context files — the relevant ones are already in your context; any others are noise.
|
|
30
30
|
{{/ifAny}}
|
|
31
31
|
|
|
32
|
+
{{#if includeWorkspaceTree}}
|
|
32
33
|
{{#if workspaceTree.rendered}}
|
|
33
34
|
<workspace-tree>
|
|
34
35
|
Working directory layout (sorted by mtime, recent first; depth ≤ 3):
|
|
@@ -38,6 +39,7 @@ Working directory layout (sorted by mtime, recent first; depth ≤ 3):
|
|
|
38
39
|
{{/if}}
|
|
39
40
|
</workspace-tree>
|
|
40
41
|
{{/if}}
|
|
42
|
+
{{/if}}
|
|
41
43
|
|
|
42
44
|
Today is {{date}}, and the current working directory is '{{cwd}}'.
|
|
43
45
|
|
|
@@ -17,7 +17,6 @@ You are a helpful assistant the team trusts with load-bearing changes, operating
|
|
|
17
17
|
- You are not alone in this repo. Treat unexpected changes as the user's work and adapt.
|
|
18
18
|
- In terminal prose and final chat, you MAY use LaTeX math (`$`, `$$`, `\text`, `\times`) and color (`\textcolor`, `\colorbox`, `\fcolorbox`).
|
|
19
19
|
- To show a diagram, you MAY emit a ` ```mermaid ` block — the terminal renders it as ASCII. Use it for genuine structure or flow, not trivia.
|
|
20
|
-
- For a visual separator between sections, use `─` (U+2500).
|
|
21
20
|
|
|
22
21
|
RUNTIME
|
|
23
22
|
==============
|
|
@@ -15,6 +15,8 @@ Drives real Chromium tab; full puppeteer access via JS.
|
|
|
15
15
|
- `tab` helpers; drop to raw puppeteer `page` for anything uncovered:
|
|
16
16
|
- `tab.goto(url, { waitUntil? })` — navigate.
|
|
17
17
|
- `tab.observe({ includeAll?, viewportOnly? })` — accessibility snapshot: `{ url, title, viewport, scroll, elements: [{ id, role, name, value, states, … }] }`. Ids stable until next observe/goto.
|
|
18
|
+
- `tab.ariaSnapshot(selector?, { depth?, boxes? })` — Playwright-format ARIA-tree YAML (nested roles + accessible names + `/url`/`/placeholder`), scoped to `selector` or the whole document. Every node carries a `[ref=eN]` id; `[cursor=pointer]` flags clickables. Captures dense, hierarchical structure/text that `observe()`'s flat list flattens away. Refs renumber from e1 each call and stay valid until the next `ariaSnapshot()`.
|
|
19
|
+
- `tab.ref("e5")` — `[ref=eN]` from the last ariaSnapshot → element handle with the common action methods (`.click()`, `.type()`, `.fill()`, `.hover()`, `.evaluate()`, …); the primary way to act on a ref. For convenience `aria-ref=e5` also works inline in `tab.click`/`type`/`fill`/`waitFor`/`scrollIntoView` (e.g. `tab.click("aria-ref=e5")`).
|
|
18
20
|
- `tab.id(n)` — id from last observe → `ElementHandle` (`.click()`, `.type()`, …).
|
|
19
21
|
- `tab.click(selector)` / `tab.type(selector, text)` / `tab.fill(selector, value)` / `tab.press(key, { selector? })` / `tab.scroll(dx, dy)`.
|
|
20
22
|
- `tab.waitFor(selector)` — wait until attached; returns `ElementHandle`.
|
package/src/sdk.ts
CHANGED
|
@@ -796,6 +796,7 @@ export interface BuildSystemPromptOptions {
|
|
|
796
796
|
customPrompt?: string;
|
|
797
797
|
appendPrompt?: string;
|
|
798
798
|
inlineToolDescriptors?: boolean;
|
|
799
|
+
includeWorkspaceTree?: boolean;
|
|
799
800
|
}
|
|
800
801
|
|
|
801
802
|
/**
|
|
@@ -813,6 +814,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
813
814
|
contextFiles: options.contextFiles,
|
|
814
815
|
appendSystemPrompt: options.appendPrompt,
|
|
815
816
|
inlineToolDescriptors: options.inlineToolDescriptors,
|
|
817
|
+
includeWorkspaceTree: options.includeWorkspaceTree,
|
|
816
818
|
toolNames: options.tools?.map(tool => tool.name),
|
|
817
819
|
tools: toolMap ? buildSystemPromptToolMetadata(toolMap) : undefined,
|
|
818
820
|
});
|
|
@@ -1120,9 +1122,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1120
1122
|
// startup does not perform a second recursive filesystem search. Subagents
|
|
1121
1123
|
// inherit the parent's resolved values via options.
|
|
1122
1124
|
const STARTUP_SCAN_DEADLINE_MS = 5000;
|
|
1125
|
+
const includeWorkspaceTree = settings.get("includeWorkspaceTree") ?? false;
|
|
1123
1126
|
const workspaceTreePromise: Promise<WorkspaceTree> = options.workspaceTree
|
|
1124
1127
|
? Promise.resolve(options.workspaceTree)
|
|
1125
|
-
:
|
|
1128
|
+
: includeWorkspaceTree
|
|
1129
|
+
? logger.time("buildWorkspaceTree", () => buildWorkspaceTree(cwd, { timeoutMs: STARTUP_SCAN_DEADLINE_MS }))
|
|
1130
|
+
: Promise.resolve({ rootPath: cwd, rendered: "", truncated: false, totalLines: 0, agentsMdFiles: [] });
|
|
1126
1131
|
workspaceTreePromise.catch(() => {});
|
|
1127
1132
|
|
|
1128
1133
|
// Independent discoveries that depend only on cwd/agentDir — kicked off in parallel and awaited
|
|
@@ -2101,6 +2106,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2101
2106
|
const eagerTasks = settings.get("task.eager") !== "default";
|
|
2102
2107
|
const eagerTasksAlways = settings.get("task.eager") === "always";
|
|
2103
2108
|
const intentField = $flag("PI_INTENT_TRACING", settings.get("tools.intentTracing")) ? INTENT_FIELD : undefined;
|
|
2109
|
+
const includeWorkspaceTree = settings.get("includeWorkspaceTree") ?? false;
|
|
2104
2110
|
const rebuildSystemPrompt = async (
|
|
2105
2111
|
toolNames: string[],
|
|
2106
2112
|
tools: Map<string, AgentTool>,
|
|
@@ -2194,6 +2200,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
2194
2200
|
taskBatch: settings.get("task.batch"),
|
|
2195
2201
|
secretsEnabled,
|
|
2196
2202
|
workspaceTree: workspaceTreePromise,
|
|
2203
|
+
includeWorkspaceTree,
|
|
2197
2204
|
memoryRootEnabled: memoryBackend.id === "local",
|
|
2198
2205
|
model: settings.get("includeModelInPrompt") ? getActiveModelString() : undefined,
|
|
2199
2206
|
personality: agentKind === "sub" ? "none" : settings.get("personality"),
|
|
@@ -103,7 +103,7 @@ import {
|
|
|
103
103
|
resolveServiceTier,
|
|
104
104
|
streamSimple,
|
|
105
105
|
} from "@oh-my-pi/pi-ai";
|
|
106
|
-
import { stripToolDescriptions } from "@oh-my-pi/pi-ai/utils/schema";
|
|
106
|
+
import { stripToolDescriptions, toolWireSchema } from "@oh-my-pi/pi-ai/utils/schema";
|
|
107
107
|
import { THINKING_LOOP_ERROR_MARKER } from "@oh-my-pi/pi-ai/utils/thinking-loop";
|
|
108
108
|
import { isFireworksFastModelId, toFireworksBaseModelId } from "@oh-my-pi/pi-catalog/fireworks-model-id";
|
|
109
109
|
import { getSupportedEfforts } from "@oh-my-pi/pi-catalog/model-thinking";
|
|
@@ -12606,6 +12606,41 @@ export class AgentSession {
|
|
|
12606
12606
|
});
|
|
12607
12607
|
}
|
|
12608
12608
|
|
|
12609
|
+
/**
|
|
12610
|
+
* Dump the current session's LLM-facing request context as JSON to a
|
|
12611
|
+
* auto-named file in `os.tmpdir()`. This is the synchronous
|
|
12612
|
+
* `convertToLlm`-boundary snapshot — system prompt, tools (wire schemas),
|
|
12613
|
+
* thinking/service tier, and converted messages — with no network round-trip
|
|
12614
|
+
* and no arming flag, so advisor/side requests cannot intercept it.
|
|
12615
|
+
*
|
|
12616
|
+
* The file persists on disk and may contain the same raw context/secrets
|
|
12617
|
+
* as `/dump`; treat the path accordingly.
|
|
12618
|
+
*
|
|
12619
|
+
* @returns the written file path, or `undefined` when there are no messages.
|
|
12620
|
+
*/
|
|
12621
|
+
async dumpLlmRequestToTmpDir(): Promise<string | undefined> {
|
|
12622
|
+
const messages = this.messages;
|
|
12623
|
+
if (messages.length === 0) return undefined;
|
|
12624
|
+
const llmMessages = await this.convertMessagesToLlm(messages);
|
|
12625
|
+
const payload = {
|
|
12626
|
+
model: this.agent.state.model ?? null,
|
|
12627
|
+
thinkingLevel: this.#thinkingLevel ?? null,
|
|
12628
|
+
serviceTier: this.agent.serviceTier ?? null,
|
|
12629
|
+
systemPrompt: this.agent.state.systemPrompt,
|
|
12630
|
+
tools: this.agent.state.tools.map(tool => ({
|
|
12631
|
+
name: tool.name,
|
|
12632
|
+
description: tool.description,
|
|
12633
|
+
parameters: toolWireSchema(tool),
|
|
12634
|
+
...(tool.strict !== undefined ? { strict: tool.strict } : {}),
|
|
12635
|
+
...(tool.customWireName ? { customWireName: tool.customWireName } : {}),
|
|
12636
|
+
})),
|
|
12637
|
+
messages: llmMessages,
|
|
12638
|
+
};
|
|
12639
|
+
const filePath = path.join(os.tmpdir(), `omp-llm-request-${Snowflake.next()}.json`);
|
|
12640
|
+
await Bun.write(filePath, `${JSON.stringify(payload, null, 2)}\n`);
|
|
12641
|
+
return filePath;
|
|
12642
|
+
}
|
|
12643
|
+
|
|
12609
12644
|
/**
|
|
12610
12645
|
* Enable or disable the advisor for this session. The setting is overridden for the session,
|
|
12611
12646
|
* and the runtime is started or stopped to match.
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
|
-
import * as os from "node:os";
|
|
3
2
|
import * as path from "node:path";
|
|
4
3
|
import { getOAuthProviders } from "@oh-my-pi/pi-ai/oauth";
|
|
5
|
-
import { setNextRequestDebugPath } from "@oh-my-pi/pi-ai/utils/request-debug";
|
|
6
4
|
import { type AutocompleteItem, Spacer } from "@oh-my-pi/pi-tui";
|
|
7
5
|
import { APP_NAME, setProjectDir } from "@oh-my-pi/pi-utils";
|
|
8
6
|
import { COLLAB_GUEST_ALLOWED_COMMANDS, CollabGuestLink } from "../collab/guest";
|
|
@@ -192,46 +190,6 @@ async function handleUsageResetCommand(
|
|
|
192
190
|
await output(describeRedeemOutcome(outcome, target.label));
|
|
193
191
|
}
|
|
194
192
|
|
|
195
|
-
const DEBUG_DUMP_NEXT_REQUEST_USAGE = "Usage: /debug dump-next-request <path>";
|
|
196
|
-
|
|
197
|
-
function resolveDebugRequestDumpPath(target: string, cwd: string): string {
|
|
198
|
-
const expanded =
|
|
199
|
-
target === "~"
|
|
200
|
-
? os.homedir()
|
|
201
|
-
: target.startsWith("~/") || target.startsWith("~\\")
|
|
202
|
-
? path.join(os.homedir(), target.slice(2))
|
|
203
|
-
: target;
|
|
204
|
-
return path.resolve(cwd, expanded);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
async function handleDebugSubcommand(
|
|
208
|
-
args: string,
|
|
209
|
-
cwd: string,
|
|
210
|
-
output: (text: string) => Promise<void> | void,
|
|
211
|
-
): Promise<SlashCommandResult> {
|
|
212
|
-
const { verb, rest } = parseSubcommand(args);
|
|
213
|
-
switch (verb) {
|
|
214
|
-
case "":
|
|
215
|
-
await output(DEBUG_DUMP_NEXT_REQUEST_USAGE);
|
|
216
|
-
return commandConsumed();
|
|
217
|
-
case "dump-next-request":
|
|
218
|
-
case "dump-request":
|
|
219
|
-
case "next-request": {
|
|
220
|
-
if (!rest) {
|
|
221
|
-
await output(DEBUG_DUMP_NEXT_REQUEST_USAGE);
|
|
222
|
-
return commandConsumed();
|
|
223
|
-
}
|
|
224
|
-
const requestPath = resolveDebugRequestDumpPath(rest, cwd);
|
|
225
|
-
setNextRequestDebugPath(requestPath);
|
|
226
|
-
await output(`Next AI provider request will be dumped to ${requestPath}`);
|
|
227
|
-
return commandConsumed();
|
|
228
|
-
}
|
|
229
|
-
default:
|
|
230
|
-
await output(`Unknown /debug subcommand "${verb}". ${DEBUG_DUMP_NEXT_REQUEST_USAGE}`);
|
|
231
|
-
return commandConsumed();
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
193
|
/** Parse the `/shake` subcommand into a {@link ShakeMode}; empty defaults to elide. */
|
|
236
194
|
function parseShakeMode(args: string): ShakeMode | { error: string } {
|
|
237
195
|
const verb = args.trim().toLowerCase();
|
|
@@ -612,16 +570,33 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
612
570
|
},
|
|
613
571
|
{
|
|
614
572
|
name: "dump",
|
|
615
|
-
description: "Copy session transcript to clipboard",
|
|
616
|
-
acpDescription: "Return full transcript as plain text",
|
|
573
|
+
description: "Copy session transcript to clipboard (and write LLM request JSON to tmp)",
|
|
574
|
+
acpDescription: "Return full transcript as plain text, with LLM request JSON path",
|
|
617
575
|
allowArgs: true,
|
|
618
576
|
handle: async (_command, runtime) => {
|
|
619
577
|
const text = runtime.session.formatSessionAsText();
|
|
620
|
-
|
|
578
|
+
if (!text) {
|
|
579
|
+
await runtime.output("No messages to dump yet.");
|
|
580
|
+
return commandConsumed();
|
|
581
|
+
}
|
|
582
|
+
let sidecarPath: string | undefined;
|
|
583
|
+
try {
|
|
584
|
+
sidecarPath = await runtime.session.dumpLlmRequestToTmpDir();
|
|
585
|
+
} catch {
|
|
586
|
+
// Sidecar is best-effort; the transcript is still output below.
|
|
587
|
+
}
|
|
588
|
+
const lines = [text];
|
|
589
|
+
if (sidecarPath)
|
|
590
|
+
lines.push(
|
|
591
|
+
"",
|
|
592
|
+
`LLM request JSON: ${sidecarPath}`,
|
|
593
|
+
"This file persists on disk and may contain raw context/secrets — treat accordingly.",
|
|
594
|
+
);
|
|
595
|
+
await runtime.output(lines.join("\n"));
|
|
621
596
|
return commandConsumed();
|
|
622
597
|
},
|
|
623
|
-
handleTui: (_command, runtime) => {
|
|
624
|
-
runtime.ctx.handleDumpCommand();
|
|
598
|
+
handleTui: async (_command, runtime) => {
|
|
599
|
+
await runtime.ctx.handleDumpCommand();
|
|
625
600
|
runtime.ctx.editor.setText("");
|
|
626
601
|
},
|
|
627
602
|
},
|
|
@@ -1509,25 +1484,8 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<SlashCommandSpec> = [
|
|
|
1509
1484
|
{
|
|
1510
1485
|
name: "debug",
|
|
1511
1486
|
description: "Open debug tools selector",
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
{
|
|
1515
|
-
name: "dump-next-request",
|
|
1516
|
-
description: "Dump the next AI provider HTTP request as JSON",
|
|
1517
|
-
usage: "<path>",
|
|
1518
|
-
},
|
|
1519
|
-
],
|
|
1520
|
-
handle: async (command, runtime) =>
|
|
1521
|
-
handleDebugSubcommand(command.args, runtime.cwd, text => runtime.output(text)),
|
|
1522
|
-
handleTui: async (command, runtime) => {
|
|
1523
|
-
const args = command.args.trim();
|
|
1524
|
-
if (args.length === 0) {
|
|
1525
|
-
runtime.ctx.showDebugSelector();
|
|
1526
|
-
} else {
|
|
1527
|
-
await handleDebugSubcommand(args, runtime.ctx.sessionManager.getCwd(), text =>
|
|
1528
|
-
runtime.ctx.showStatus(text),
|
|
1529
|
-
);
|
|
1530
|
-
}
|
|
1487
|
+
handleTui: async (_command, runtime) => {
|
|
1488
|
+
await runtime.ctx.showDebugSelector();
|
|
1531
1489
|
runtime.ctx.editor.setText("");
|
|
1532
1490
|
},
|
|
1533
1491
|
},
|
package/src/system-prompt.ts
CHANGED
|
@@ -421,6 +421,8 @@ export interface BuildSystemPromptOptions {
|
|
|
421
421
|
model?: string;
|
|
422
422
|
/** Personality preset rendered into the default system prompt. "none" omits the block. Default: "default" */
|
|
423
423
|
personality?: Personality;
|
|
424
|
+
/** Whether to include the workspace directory tree in the system prompt. Default: false */
|
|
425
|
+
includeWorkspaceTree?: boolean;
|
|
424
426
|
}
|
|
425
427
|
|
|
426
428
|
/** Result of building provider-facing system prompt messages. */
|
|
@@ -461,6 +463,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
461
463
|
memoryRootEnabled = false,
|
|
462
464
|
model,
|
|
463
465
|
personality = "default",
|
|
466
|
+
includeWorkspaceTree = false,
|
|
464
467
|
} = options;
|
|
465
468
|
const inlineToolDescriptors = providedInlineToolDescriptors ?? false;
|
|
466
469
|
const resolvedCwd = cwd ?? getProjectDir();
|
|
@@ -523,9 +526,17 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
523
526
|
const workspaceTreePromise =
|
|
524
527
|
providedWorkspaceTree !== undefined
|
|
525
528
|
? Promise.resolve(providedWorkspaceTree)
|
|
526
|
-
:
|
|
527
|
-
|
|
528
|
-
|
|
529
|
+
: includeWorkspaceTree
|
|
530
|
+
? logger.time("buildWorkspaceTree", () =>
|
|
531
|
+
buildWorkspaceTree(resolvedCwd, { timeoutMs: SYSTEM_PROMPT_PREP_TIMEOUT_MS }),
|
|
532
|
+
)
|
|
533
|
+
: Promise.resolve({
|
|
534
|
+
rootPath: resolvedCwd,
|
|
535
|
+
rendered: "",
|
|
536
|
+
truncated: false,
|
|
537
|
+
totalLines: 0,
|
|
538
|
+
agentsMdFiles: [],
|
|
539
|
+
});
|
|
529
540
|
const skillsPromise: Promise<Skill[]> =
|
|
530
541
|
providedSkills !== undefined
|
|
531
542
|
? Promise.resolve(providedSkills)
|
|
@@ -670,6 +681,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
670
681
|
secretsEnabled,
|
|
671
682
|
hasMemoryRoot: memoryRootEnabled,
|
|
672
683
|
hasObsidian: hasObsidian(),
|
|
684
|
+
includeWorkspaceTree,
|
|
673
685
|
};
|
|
674
686
|
const rendered = prompt.render(resolvedCustomPrompt ? customSystemPromptTemplate : systemPromptTemplate, data);
|
|
675
687
|
const systemPrompt = [rendered];
|
package/src/tools/bash.ts
CHANGED
|
@@ -1132,9 +1132,9 @@ function getPartialJson<TArgs>(args: TArgs | undefined): string | undefined {
|
|
|
1132
1132
|
}
|
|
1133
1133
|
|
|
1134
1134
|
export function getBashEnvForDisplay(args: BashRenderArgs): Record<string, string> | undefined {
|
|
1135
|
-
//
|
|
1136
|
-
//
|
|
1137
|
-
//
|
|
1135
|
+
// The parsed args don't always mirror the exact current stream prefix, so recover
|
|
1136
|
+
// env from the raw JSON buffer to surface `NAME="..." cmd` in the preview as it
|
|
1137
|
+
// streams rather than only once the args object finishes.
|
|
1138
1138
|
const partialEnv = extractPartialBashEnv(args.__partialJson);
|
|
1139
1139
|
if (partialEnv && args.env) return { ...partialEnv, ...args.env };
|
|
1140
1140
|
return args.env ?? partialEnv;
|