diff --git a/crates/pi-natives/src/html.rs b/crates/pi-natives/src/html.rs index 2b7d2b755..c6ee38d0f 100644 --- a/crates/pi-natives/src/html.rs +++ b/crates/pi-natives/src/html.rs @@ -40,6 +40,7 @@ pub fn html_to_markdown( remove_navigation: true, remove_forms: true, }, + tier_strategy: html_to_markdown_rs::TierStrategy::Tier2, ..Default::default() }; diff --git a/packages/ai/test/proxy.test.ts b/packages/ai/test/proxy.test.ts index 30548b25f..80708f087 100644 --- a/packages/ai/test/proxy.test.ts +++ b/packages/ai/test/proxy.test.ts @@ -24,6 +24,8 @@ async function createSilentProxyServer(): Promise { const accepted = Promise.withResolvers(); const server = net.createServer(socket => { sockets.add(socket); + socket.resume(); + socket.on("end", () => socket.destroy()); socket.once("close", () => sockets.delete(socket)); accepted.resolve(socket); }); diff --git a/packages/coding-agent/src/edit/hashline/execute.ts b/packages/coding-agent/src/edit/hashline/execute.ts index e5fd8ed95..2e0025b20 100644 --- a/packages/coding-agent/src/edit/hashline/execute.ts +++ b/packages/coding-agent/src/edit/hashline/execute.ts @@ -111,7 +111,7 @@ const BLOCK_OP_LABELS: Record = { }; function formatBlockResolution(resolution: BlockResolution): string { - const op = BLOCK_OP_LABELS[resolution.op]; + const op = BLOCK_OP_LABELS[resolution.op].replace("N", String(resolution.anchorLine)); const lines = resolution.end - resolution.start + 1; const span = resolution.start === resolution.end ? `line ${resolution.start}` : `lines ${resolution.start}-${resolution.end}`; @@ -121,7 +121,7 @@ function formatBlockResolution(resolution: BlockResolution): string { : resolution.op === "paste_after" ? `; clipboard lands after line ${resolution.end}` : ""; - return `${op} ${resolution.anchorLine} → resolved ${span} (${lines} line${lines === 1 ? "" : "s"})${suffix}`; + return `${op} → resolved ${span} (${lines} line${lines === 1 ? "" : "s"})${suffix}`; } function renderSection( diff --git a/packages/coding-agent/test/core/block-replace.test.ts b/packages/coding-agent/test/core/block-replace.test.ts index 93b85c5f8..28daa6942 100644 --- a/packages/coding-agent/test/core/block-replace.test.ts +++ b/packages/coding-agent/test/core/block-replace.test.ts @@ -122,7 +122,7 @@ describe("PUT N*: — native tree-sitter resolution end-to-end", () => { "", ].join("\n"), ); - expect(text).toContain("PUT >N*: 1 → resolved lines 1-3 (3 lines); body lands after line 3"); + expect(text).toContain("PUT >1*: → resolved lines 1-3 (3 lines); body lands after line 3"); }); }); it("inserts after an extensionless .emacs top-level form", async () => { @@ -162,7 +162,7 @@ describe("PUT N*: — native tree-sitter resolution end-to-end", () => { const text = result.content.map(part => (part.type === "text" ? part.text : "")).join("\n"); // `function x() {` opens on line 1; tree-sitter resolves the whole body (lines 1-4). - expect(text).toContain("PUT N*: 1 → resolved lines 1-4 (4 lines)"); + expect(text).toContain("PUT 1*: → resolved lines 1-4 (4 lines)"); }); }); @@ -189,7 +189,7 @@ describe("PUT N*: — native tree-sitter resolution end-to-end", () => { // Steers to the concrete form and previews the file around the anchor (`*`-marked). await expect(executeHashlineSingle(executeOptions(tempDir, input, session))).rejects.toThrow( - /could not resolve a syntactic block beginning on line 3.*SWAP 3\.=M:.*^ 1:function x\(\) \{$.*^\*3: {2}\}$/ms, + /could not resolve a syntactic block beginning on line 3.*PUT 3\.=M:.*^ 1:function x\(\) \{$.*^\*3: {2}\}$/ms, ); // Disk untouched — refusal never leaves a partial write. expect(await Bun.file(filePath).text()).toBe(TS_SOURCE); @@ -218,7 +218,7 @@ describe("PUT N*: — native tree-sitter resolution end-to-end", () => { const input = `${header}\nPUT 2*:\n+ stop();`; await expect(executeHashlineSingle(executeOptions(tempDir, input, session))).rejects.toThrow( - "For only this statement use `PUT 2-2:`. The nearest enclosing multi-line block begins at line 1 and ends at line 3; use `PUT 1*:` to target it.", + "For only this statement use `PUT 2:`. The nearest enclosing multi-line block begins at line 1 and ends at line 3; use `PUT 1*:` to target it.", ); expect(await Bun.file(filePath).text()).toBe(source); }); @@ -305,7 +305,7 @@ describe("block ops on markdown headings — whole-section resolution end-to-end "", ].join("\n"), ); - expect(text).toContain("PUT >N*: 4 → resolved lines 4-10 (7 lines); body lands after line 10"); + expect(text).toContain("PUT >4*: → resolved lines 4-10 (7 lines); body lands after line 10"); }); }); }); diff --git a/packages/coding-agent/test/core/hashline.test.ts b/packages/coding-agent/test/core/hashline.test.ts index 5f762acdb..19a5ea43f 100644 --- a/packages/coding-agent/test/core/hashline.test.ts +++ b/packages/coding-agent/test/core/hashline.test.ts @@ -48,7 +48,7 @@ function header(filePath: string, tag: string): string { } function sameLineRange(anchor: string): string { - return `SWAP ${anchor}..${anchor}:`; + return `PUT ${anchor}.=${anchor}:`; } async function withTempDir(fn: (tempDir: string) => Promise): Promise { @@ -256,7 +256,7 @@ describe("hashline executor", () => { repl("L2h"), repl("L2i"), header("a.ts", originalTag), - `PUT > ${tag(8, "L8")}:`, + `PUT >${tag(8, "L8")}:`, repl("INSERTED"), ].join("\n"); diff --git a/packages/coding-agent/test/extension-context-async-jobs.test.ts b/packages/coding-agent/test/extension-context-async-jobs.test.ts index 611385b93..1bb9b4756 100644 --- a/packages/coding-agent/test/extension-context-async-jobs.test.ts +++ b/packages/coding-agent/test/extension-context-async-jobs.test.ts @@ -12,7 +12,7 @@ function createRunner(getAsyncJobSnapshot?: () => AsyncJobSnapshot | null): Exte [], runtime, "/tmp", - {} as never, + { getCwd: () => "/tmp" } as never, {} as never, undefined, undefined, diff --git a/packages/coding-agent/test/input-controller-escape.test.ts b/packages/coding-agent/test/input-controller-escape.test.ts index a5a06dea1..85e515c75 100644 --- a/packages/coding-agent/test/input-controller-escape.test.ts +++ b/packages/coding-agent/test/input-controller-escape.test.ts @@ -163,6 +163,7 @@ function createContext(): { abortEval, clearQueue, getQueuedMessages, + maybeStartTitleGeneration: vi.fn(), prompt, subscribe: vi.fn((listener: (event: { type: string }) => void) => { sessionListeners.push(listener); diff --git a/packages/coding-agent/test/input-controller-python-prefix.test.ts b/packages/coding-agent/test/input-controller-python-prefix.test.ts index 6e48d1af4..b315c83b4 100644 --- a/packages/coding-agent/test/input-controller-python-prefix.test.ts +++ b/packages/coding-agent/test/input-controller-python-prefix.test.ts @@ -49,6 +49,7 @@ function createContext() { isBashRunning: false, isEvalRunning: false, extensionRunner: undefined, + maybeStartTitleGeneration: vi.fn(), prompt, queuedMessageCount: 0, getQueuedMessages: () => ({ steering: [], followUp: [] }), diff --git a/packages/coding-agent/test/issue-2750-subagent-runtime-fallback.test.ts b/packages/coding-agent/test/issue-2750-subagent-runtime-fallback.test.ts index 85f15021d..2235f6fd4 100644 --- a/packages/coding-agent/test/issue-2750-subagent-runtime-fallback.test.ts +++ b/packages/coding-agent/test/issue-2750-subagent-runtime-fallback.test.ts @@ -32,6 +32,7 @@ function createYieldingSession(): AgentSession { getActiveToolNames: () => ["yield"], getEnabledToolNames: () => ["yield"], setActiveToolsByName: async () => {}, + setIrcWakeTurnObserver: () => {}, subscribe: (listener: (event: { type: string; [key: string]: unknown }) => void) => { listeners.push(listener); return () => {}; diff --git a/packages/coding-agent/test/sdk-session-isolation.test.ts b/packages/coding-agent/test/sdk-session-isolation.test.ts index 2850af256..826898550 100644 --- a/packages/coding-agent/test/sdk-session-isolation.test.ts +++ b/packages/coding-agent/test/sdk-session-isolation.test.ts @@ -349,7 +349,7 @@ describe("createAgentSession session storage isolation", () => { await session.dispose(); } }); - it("loads obfuscator only when secrets exist", async () => { + it("loads configured secrets per session alongside built-in credential redaction", async () => { await withClearedSecretEnv(async () => { const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), `pi-sdk-secrets-${Snowflake.next()}-`)); tempDirs.push(tempDir); @@ -370,6 +370,7 @@ describe("createAgentSession session storage isolation", () => { enableMCP: false, enableLsp: false, }; + const configuredSecret = "sdk-secret-token-123456"; const existingKeySpy = spyOn(secrets, "getExistingSecretPlaceholderKey").mockImplementation( async () => undefined, @@ -377,7 +378,9 @@ describe("createAgentSession session storage isolation", () => { try { const withoutSecrets = await createAgentSession(commonOptions); try { - expect(withoutSecrets.session.obfuscator?.hasSecrets()).toBeFalsy(); + const obfuscator = withoutSecrets.session.obfuscator; + expect(obfuscator?.hasSecrets()).toBe(true); + expect(obfuscator?.obfuscate(configuredSecret)).toBe(configuredSecret); } finally { await withoutSecrets.session.dispose(); } @@ -386,11 +389,13 @@ describe("createAgentSession session storage isolation", () => { } fs.mkdirSync(path.join(cwd, ".omp"), { recursive: true }); - fs.writeFileSync(path.join(cwd, ".omp", "secrets.yml"), "- type: plain\n content: sdk-secret-token-123456\n"); + fs.writeFileSync(path.join(cwd, ".omp", "secrets.yml"), `- type: plain\n content: ${configuredSecret}\n`); const withSecrets = await createAgentSession(commonOptions); try { - expect(withSecrets.session.obfuscator?.hasSecrets()).toBe(true); + const obfuscator = withSecrets.session.obfuscator; + expect(obfuscator?.hasSecrets()).toBe(true); + expect(obfuscator?.obfuscate(configuredSecret)).not.toContain(configuredSecret); } finally { await withSecrets.session.dispose(); }