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();
}