From 3cafefdf4dc4a75135418954e82fc505ca13d52a Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Thu, 8 Oct 2026 14:22:27 +0200 Subject: [PATCH 1/4] fix(server): Pi loads every selected skill without losing prompt text --- .../provider-pi/src/server/adapter.test.ts | 62 +------- packages/provider-pi/src/server/adapter.ts | 34 +---- .../provider-pi/src/server/commands.test.ts | 17 --- packages/provider-pi/src/server/commands.ts | 35 ----- .../src/server/mcpExtensionSource.test.ts | 141 +++++++++++++++++- .../src/server/mcpExtensionSource.ts | 36 ++++- 6 files changed, 176 insertions(+), 149 deletions(-) diff --git a/packages/provider-pi/src/server/adapter.test.ts b/packages/provider-pi/src/server/adapter.test.ts index ff9486cb854c..562fb5ca0790 100644 --- a/packages/provider-pi/src/server/adapter.test.ts +++ b/packages/provider-pi/src/server/adapter.test.ts @@ -94,10 +94,6 @@ interface FakePi { readonly allRequests: () => ReadonlyArray; /** Data returned by the next `get_session_stats` acks, consumed in order. */ readonly queueStats: (data: unknown) => void; - /** Data returned by the next `get_commands` acks, consumed in order. */ - readonly queueCommands: (data: unknown) => void; - /** Make the next `get_commands` ack fail. */ - readonly failNextCommands: () => void; /** Close the fake process stdout stream. */ readonly closeStdout: Effect.Effect; readonly lastSpawn: () => { @@ -137,7 +133,6 @@ const makeFakePi: Effect.Effect = Effect.gen(function* () { const messagesQueue: Array = []; const stateQueue: Array> = []; const statsQueue: Array = []; - const commandsQueue: Array<{ readonly success: boolean; readonly data?: unknown }> = []; const allRequests: Array = []; let deferState = false; let deferredStateRequest: PiRpcRecord | undefined; @@ -191,8 +186,6 @@ const makeFakePi: Effect.Effect = Effect.gen(function* () { return { ...base, data: messagesQueue.shift() ?? { messages: [] } }; case "get_session_stats": return { ...base, data: statsQueue.shift() ?? {} }; - case "get_commands": - return { ...base, ...(commandsQueue.shift() ?? { data: { commands: [] } }) }; case "fork": return { ...base, data: { text: "Hello pi", cancelled: false } }; default: @@ -305,8 +298,6 @@ const makeFakePi: Effect.Effect = Effect.gen(function* () { }, queueState: (data) => stateQueue.push(data), queueStats: (data) => statsQueue.push(data), - queueCommands: (data) => commandsQueue.push({ success: true, data }), - failNextCommands: () => commandsQueue.push({ success: false }), closeStdout: Queue.end(stdout), lastSpawn: () => lastSpawn, } satisfies FakePi; @@ -1697,22 +1688,9 @@ describe("PiAdapterV2", () => { }).pipe(Effect.scoped, Effect.provide(layerTest)), ); - it.effect("expands a selected $ skill through Pi's native skill command", () => + it.effect("preserves selected skill references for the Pi extension to expand", () => Effect.gen(function* () { const fake = yield* makeFakePi; - fake.queueCommands({ - commands: [ - { - name: "skill:repo-review", - description: "Review this repository.", - source: "skill", - sourceInfo: { - path: "/workspace/.agents/skills/repo-review/SKILL.md", - scope: "project", - }, - }, - ], - }); const { runtime } = yield* openRuntime(fake); const providerThread = yield* runtime.ensureThread({ threadId: THREAD_ID, @@ -1728,43 +1706,7 @@ describe("PiAdapterV2", () => { "Review this change please $repo-review", ); const prompt = yield* fake.takeRequest("prompt"); - assert.equal(prompt["message"], "/skill:repo-review Review this change please"); - }).pipe(Effect.scoped, Effect.provide(layerTest)), - ); - - it.effect("expands every selected $ skill through Pi native skill commands", () => - Effect.gen(function* () { - const fake = yield* makeFakePi; - fake.queueCommands({ - commands: [ - { - name: "skill:repo-review", - source: "skill", - sourceInfo: { - path: "/workspace/.agents/skills/repo-review/SKILL.md", - scope: "project", - }, - }, - { - name: "skill:deploy", - source: "skill", - sourceInfo: { - path: "/workspace/.agents/skills/deploy/SKILL.md", - scope: "project", - }, - }, - ], - }); - const { runtime } = yield* openRuntime(fake); - const providerThread = yield* runtime.ensureThread({ - threadId: THREAD_ID, - modelSelection: modelSelection("default"), - runtimePolicy, - }); - - yield* startTurn(runtime, providerThread, "default", [], "use $repo-review and $deploy"); - const prompt = yield* fake.takeRequest("prompt"); - assert.equal(prompt["message"], "/skill:repo-review /skill:deploy use and"); + assert.equal(prompt["message"], "Review this change please $repo-review"); }).pipe(Effect.scoped, Effect.provide(layerTest)), ); diff --git a/packages/provider-pi/src/server/adapter.ts b/packages/provider-pi/src/server/adapter.ts index f27fead50605..0f8830cf1221 100644 --- a/packages/provider-pi/src/server/adapter.ts +++ b/packages/provider-pi/src/server/adapter.ts @@ -64,9 +64,7 @@ import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation"; import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession"; import { - expandPiSkillReference, parsePiCompactCommand, - parsePiDiscoveredCommands, type PiCompactCommand, } from "./commands.ts"; import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment"; @@ -114,7 +112,6 @@ const STREAM_FLUSH_MS = 50; const PI_REQUEST_TIMEOUT_MS = 15_000; // Session lifecycle hooks reload extensions, MCP servers and language servers. const PI_SESSION_TIMEOUT_MS = 60_000; -const PI_SKILL_DISCOVERY_TIMEOUT_MS = 4_000; const PI_UNSOLICITED_ACTIVITY_ERROR = "Pi started agent work outside an active T3 turn. The session was stopped to prevent invisible tool execution."; const SETTLE_PROBE_MAX_ATTEMPTS = 3; @@ -458,15 +455,6 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* ( }), ), ); - const discoverSkillNames = connection - .request({ type: "get_commands" }, PI_SKILL_DISCOVERY_TIMEOUT_MS) - .pipe( - Effect.map( - (data) => new Set(parsePiDiscoveredCommands(data).skills.map((skill) => skill.name)), - ), - ); - let skillNames: Set | null = null; - const now = yield* DateTime.now; let sessionEntity: OrchestrationV2ProviderSession = { id: input.providerSessionId, @@ -2117,16 +2105,6 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* ( Effect.forkIn(scope), ); - // Discovery can invoke extension code and therefore raise a blocking - // UI request. Start it only after the event pump exists, and never hold - // session opening on it; startup requests are persisted at session - // scope and can be answered before a turn begins. - yield* discoverSkillNames.pipe( - Effect.tap((discovered) => Effect.sync(() => (skillNames = discovered))), - Effect.ignore, - Effect.forkIn(scope), - ); - // ── session runtime ─────────────────────────────────── const registerThread = Effect.fnUntraced(function* ( @@ -2329,15 +2307,6 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* ( text: string, attachments: ReadonlyArray, ) { - // Provider discovery and the live session are separate Pi processes. - // Retry a failed session-local lookup once at first use so a transient - // startup failure cannot leave a visible $ skill inert for this session. - if (skillNames === null && text.includes("$")) { - skillNames = yield* discoverSkillNames.pipe( - Effect.orElseSucceed(() => new Set()), - ); - } - const expandedText = skillNames === null ? text : expandPiSkillReference(text, skillNames); const images: Array<{ type: "image"; data: string; mimeType: string }> = []; const extraLines: Array = []; for (const attachment of attachments) { @@ -2354,8 +2323,7 @@ export const makePiAdapterV2 = Effect.fn("makePiAdapterV2")(function* ( extraLines.push(`[Attachment saved at ${path}]`); } } - const message = - extraLines.length === 0 ? expandedText : `${expandedText}\n\n${extraLines.join("\n")}`; + const message = extraLines.length === 0 ? text : `${text}\n\n${extraLines.join("\n")}`; return { message, images }; }); diff --git a/packages/provider-pi/src/server/commands.test.ts b/packages/provider-pi/src/server/commands.test.ts index 09f39130dd06..ef7b247ee588 100644 --- a/packages/provider-pi/src/server/commands.test.ts +++ b/packages/provider-pi/src/server/commands.test.ts @@ -1,7 +1,6 @@ import { expect, it } from "@effect/vitest"; import { - expandPiSkillReference, parsePiCompactCommand, parsePiDiscoveredCommands, PI_COMPACT_SLASH_COMMAND, @@ -109,19 +108,3 @@ it("prepends the builtin compact command without duplicating a discovered one", ]), ).toEqual([PI_COMPACT_SLASH_COMMAND, { name: "hello" }]); }); - -it("leaves unrelated dollar-prefixed text unchanged", () => { - expect(expandPiSkillReference("Explain $HOME", new Set(["global-review"]))).toBe("Explain $HOME"); -}); - -it("hoists every known $ skill and keeps the rest of the prompt", () => { - expect(expandPiSkillReference("use $alpha then $beta please", new Set(["alpha", "beta"]))).toBe( - "/skill:alpha /skill:beta use then please", - ); -}); - -it("preserves code indentation and line breaks when expanding a skill", () => { - expect(expandPiSkillReference("$review\n```ts\n const x = 1;\n```", new Set(["review"]))).toBe( - "/skill:review ```ts\n const x = 1;\n```", - ); -}); diff --git a/packages/provider-pi/src/server/commands.ts b/packages/provider-pi/src/server/commands.ts index 3e02b7718a45..07b992dd7c27 100644 --- a/packages/provider-pi/src/server/commands.ts +++ b/packages/provider-pi/src/server/commands.ts @@ -91,41 +91,6 @@ export function parsePiDiscoveredCommands(data: unknown): PiDiscoveredCommands { return { slashCommands, skills }; } -/** - * Pi expands skills only through leading `/skill:name` commands. T3 stores - * skill chips as `$name`, so hoist every known `$skill` to that native - * command position while preserving the rest of the user's prompt. - */ -export function expandPiSkillReference(text: string, skillNames: ReadonlySet): string { - const references = /(^|\s)\$([^\s]+)(?=\s|$)/g; - const found: Array<{ name: string; start: number; end: number }> = []; - for (const match of text.matchAll(references)) { - const name = match[2]; - if (name === undefined || !skillNames.has(name) || match.index === undefined) continue; - const tokenStart = match.index + (match[1]?.length ?? 0); - found.push({ name, start: tokenStart, end: tokenStart + name.length + 1 }); - } - if (found.length === 0) return text; - - const orderedNames: string[] = []; - const seen = new Set(); - for (const token of found) { - if (seen.has(token.name)) continue; - seen.add(token.name); - orderedNames.push(token.name); - } - - let body = text; - for (let index = found.length - 1; index >= 0; index -= 1) { - const token = found[index]; - if (token === undefined) continue; - body = `${body.slice(0, token.start)}${body.slice(token.end)}`; - } - body = body.trim(); - const prefix = orderedNames.map((name) => `/skill:${name}`).join(" "); - return body.length === 0 ? prefix : `${prefix} ${body}`; -} - function recordField(input: unknown, key: string): unknown { return Predicate.isObject(input) ? input[key] : undefined; } diff --git a/packages/provider-pi/src/server/mcpExtensionSource.test.ts b/packages/provider-pi/src/server/mcpExtensionSource.test.ts index 4d4aef33d8fd..133a20ba0b21 100644 --- a/packages/provider-pi/src/server/mcpExtensionSource.test.ts +++ b/packages/provider-pi/src/server/mcpExtensionSource.test.ts @@ -1,3 +1,6 @@ +import * as NodeFSP from "node:fs/promises"; +import * as NodePath from "node:path"; +import * as NodeOS from "node:os"; import * as NodeModule from "node:module"; import * as NodeVM from "node:vm"; import { assert, describe, it } from "@effect/vitest"; @@ -9,6 +12,40 @@ type RequestHook = ( ctx: { model: { provider: string } }, ) => Record | undefined; +interface SkillCommand { + readonly name: string; + readonly source: string; + readonly sourceInfo: { readonly path: string }; +} + +type InputHook = ( + event: { text: string; images?: ReadonlyArray }, + ctx: { ui: { notify: (message: string, level: string) => void } }, +) => Promise<{ action: string; text: string; images?: ReadonlyArray } | undefined>; + +async function loadHooks(commands: ReadonlyArray = []) { + const handlers = new Map(); + // Execute the shipped extension with MCP disabled; this path needs no Typebox. + const source = NodeModule.stripTypeScriptTypes( + PI_T3_MCP_EXTENSION_SOURCE.replace(/^import .*;$/gm, "").replace( + "export default async function", + "async function", + ), + ); + await NodeVM.runInNewContext(`${source}\nt3McpExtension(pi)`, { + process: { env: {} }, + NodeFSP, + NodePath, + // Frontmatter parsing belongs to Pi. These fixtures contain only skill bodies. + stripFrontmatter: (content: string) => content, + pi: { + on: (name: string, handler: unknown) => handlers.set(name, handler), + getCommands: () => commands, + }, + }); + return handlers; +} + interface RegisteredTool { readonly name: string; readonly description: string; @@ -55,7 +92,7 @@ async function loadMcpBridge( { name: "preview_snapshot", description: "Inspect the collaborative browser." }, ].map((tool) => ({ ...tool, inputSchema: { type: "object", properties: {} } })); const source = NodeModule.stripTypeScriptTypes( - PI_T3_MCP_EXTENSION_SOURCE.replace('import { Type } from "typebox";', "").replace( + PI_T3_MCP_EXTENSION_SOURCE.replace(/^import .*;$/gm, "").replace( "export default async function", "async function", ), @@ -271,7 +308,7 @@ describe("Pi tool discovery permissions", () => { let toolCall: ToolCallHook | undefined; let searchPath = "builtin:tool-search"; const source = NodeModule.stripTypeScriptTypes( - PI_T3_MCP_EXTENSION_SOURCE.replace('import { Type } from "typebox";', "").replace( + PI_T3_MCP_EXTENSION_SOURCE.replace(/^import .*;$/gm, "").replace( "export default async function", "async function", ), @@ -315,7 +352,7 @@ async function loadRequestHook(): Promise { const handlers = new Map(); // Execute the shipped extension with MCP disabled; this path needs no Typebox. const source = NodeModule.stripTypeScriptTypes( - PI_T3_MCP_EXTENSION_SOURCE.replace('import { Type } from "typebox";', "").replace( + PI_T3_MCP_EXTENSION_SOURCE.replace(/^import .*;$/gm, "").replace( "export default async function", "async function", ), @@ -359,3 +396,101 @@ describe("Pi upstream output-budget workaround", () => { ); }); }); + +async function loadInputHook(commands: ReadonlyArray) { + const handlers = await loadHooks(commands); + const hook = handlers.get("input"); + assert.isDefined(hook); + return hook as InputHook; +} + + +describe("Pi skill references", () => { + it("loads every selected skill once while preserving inline prose, whitespace, and images", async () => { + const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "pi-skill-mentions-")); + try { + const alpha = NodePath.join(directory, "alpha.md"); + const beta = NodePath.join(directory, "beta.md"); + await NodeFSP.writeFile(alpha, "ALPHA_INSTRUCTIONS"); + await NodeFSP.writeFile(beta, "BETA_INSTRUCTIONS"); + const hook = await loadInputHook([ + { name: "skill:alpha", source: "skill", sourceInfo: { path: alpha } }, + { name: "skill:beta", source: "skill", sourceInfo: { path: beta } }, + ]); + const text = + "Please use the $alpha philosophy, then $beta and $alpha\n```ts\n const x = 1;\n```"; + const images = [{ type: "image", data: "fixture" }]; + const result = await hook({ text, images }, { ui: { notify: assert.fail } }); + assert.equal(result?.action, "transform"); + assert.isTrue(result?.text.startsWith(text + "\n\n")); + assert.equal(result?.text.split("ALPHA_INSTRUCTIONS").length, 2); + assert.equal(result?.text.split("BETA_INSTRUCTIONS").length, 2); + assert.include(result?.text ?? "", `References are relative to ${directory}.`); + assert.strictEqual(result?.images, images); + } finally { + await NodeFSP.rm(directory, { recursive: true }); + } + }); + + it("leaves unknown references and native skill commands for Pi", async () => { + const hook = await loadInputHook([ + { name: "skill:alpha", source: "skill", sourceInfo: { path: "/unused" } }, + ]); + for (const text of ["Explain $HOME", "/skill:alpha use $alpha", "Hello", "$missing"]) { + assert.isUndefined(await hook({ text }, { ui: { notify: assert.fail } })); + } + }); + + it("loads additional chips without duplicating a leading native skill", async () => { + const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "pi-skill-mentions-")); + try { + const path = NodePath.join(directory, "beta.md"); + await NodeFSP.writeFile(path, "BETA_INSTRUCTIONS"); + const hook = await loadInputHook([ + { name: "skill:alpha", source: "skill", sourceInfo: { path: "/unused" } }, + { name: "skill:beta", source: "skill", sourceInfo: { path } }, + ]); + const text = "/skill:alpha use $alpha and $beta"; + const result = await hook({ text }, { ui: { notify: assert.fail } }); + assert.isTrue(result?.text.startsWith(text + "\n\n")); + assert.notInclude(result?.text ?? "", ' { + const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "pi-skill-mentions-")); + try { + const path = NodePath.join(directory, "readable.md"); + await NodeFSP.writeFile(path, "READABLE_INSTRUCTIONS"); + const hook = await loadInputHook([ + { + name: "skill:missing", + source: "skill", + sourceInfo: { path: NodePath.join(directory, "missing.md") }, + }, + { name: "skill:readable", source: "skill", sourceInfo: { path } }, + ]); + const notices: string[] = []; + const text = "Use $missing and $readable"; + const result = await hook( + { text }, + { + ui: { + notify: (message) => { + notices.push(message); + }, + }, + }, + ); + assert.isTrue(result?.text.startsWith(text + "\n\n")); + assert.include(result?.text ?? "", "READABLE_INSTRUCTIONS"); + assert.equal(notices.length, 1); + assert.include(notices[0] ?? "", "Could not load skill missing"); + } finally { + await NodeFSP.rm(directory, { recursive: true }); + } + }); +}); diff --git a/packages/provider-pi/src/server/mcpExtensionSource.ts b/packages/provider-pi/src/server/mcpExtensionSource.ts index e11e953f8433..8279a515ed65 100644 --- a/packages/provider-pi/src/server/mcpExtensionSource.ts +++ b/packages/provider-pi/src/server/mcpExtensionSource.ts @@ -23,7 +23,9 @@ export const T3_PI_RUNTIME_MODE_ENV = "T3_PI_RUNTIME_MODE"; export const PI_FILE_CHANGE_TOOLS = ["edit", "write"] as const; export const PI_T3_MCP_EXTENSION_SOURCE = `\ -import type { ExtensionAPI } from "@earendil-works/pi-coding-agent"; +import { stripFrontmatter, type ExtensionAPI } from "@earendil-works/pi-coding-agent"; +import * as NodeFSP from "node:fs/promises"; +import * as NodePath from "node:path"; import { Type } from "typebox"; const URL_ENV = ${JSON.stringify(T3_MCP_URL_ENV)}; @@ -210,6 +212,38 @@ function createMcpClient(endpoint: string, token: string) { } export default async function t3McpExtension(pi: ExtensionAPI) { + // Keep skill chips in the request. Pi expands only one leading /skill: + // command, so use its own frontmatter loader for every selected chip here. + pi.on("input", async (event, ctx) => { + if (!event.text.includes("$")) return; + const commands = pi.getCommands().filter((command) => command.source === "skill"); + const nativeSkill = /^\\/skill:([^ ]+)(?: |$)/.exec(event.text)?.[1]; + const seen = new Set(nativeSkill === undefined ? [] : [nativeSkill]); + const blocks: string[] = []; + for (const match of event.text.matchAll(/(^|\\s)\\$([^\\s]+)(?=\\s|$)/g)) { + const name = match[2]; + if (name === undefined || seen.has(name)) continue; + const command = commands.find((candidate) => candidate.name === "skill:" + name); + if (command === undefined) continue; + seen.add(name); + const path = command.sourceInfo.path; + try { + const body = stripFrontmatter(await NodeFSP.readFile(path, "utf8")).trim(); + blocks.push( + \`\\nReferences are relative to \${NodePath.dirname(path)}.\\n\\n\${body}\\n\`, + ); + } catch { + ctx.ui.notify(\`Could not load skill \${name} from \${path}.\`, "error"); + } + } + if (blocks.length === 0) return; + return { + action: "transform", + text: event.text + "\\n\\n" + blocks.join("\\n\\n"), + images: event.images, + }; + }); + // Workaround for an upstream Pi context-budgeting bug: pi-ai reuses the // previous response's usage even when a fork's instructions/tools differ, // then reserves almost all remaining context for output. OpenRouter can From 2b2a771815b35a1c36fe2a3f64f49803a50244ae Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Thu, 8 Oct 2026 15:50:40 +0200 Subject: [PATCH 2/4] test(server): update Pi replay startup fixtures --- .../testkit/fixtures/message_steering/pi_transcript.ndjson | 2 -- .../testkit/fixtures/multi_turn/pi_transcript.ndjson | 2 -- .../testkit/fixtures/pi_compaction/pi_transcript.ndjson | 2 -- .../fixtures/provider_thread_resume/pi_transcript.ndjson | 4 ---- .../testkit/fixtures/simple/pi_transcript.ndjson | 2 -- .../testkit/fixtures/thread_rollback/pi_transcript.ndjson | 2 -- .../fixtures/thread_rollback_after_stop/pi_transcript.ndjson | 4 ---- .../fixtures/turn_interrupt_mid_tool/pi_transcript.ndjson | 2 -- packages/provider-pi/src/server/adapter.ts | 5 +---- packages/provider-pi/src/server/mcpExtensionSource.test.ts | 2 +- 10 files changed, 2 insertions(+), 25 deletions(-) diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/message_steering/pi_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/message_steering/pi_transcript.ndjson index 092ab40ad110..32ffdd3987c4 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/message_steering/pi_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/message_steering/pi_transcript.ndjson @@ -1,10 +1,8 @@ {"type":"transcript_start","provider":"pi","protocol":"pi.rpc-jsonl","version":"1.0.0","scenario":"message_steering","metadata":{"generatedBy":"live-pi-recorder","piVersion":"1.0.0","model":"openrouter/deepseek/deepseek-v4-flash","launchArgs":"--provider openrouter --model deepseek/deepseek-v4-flash --no-extensions --no-skills --no-prompt-templates --no-context-files --approve"}} {"type":"expect_outbound","label":"process_start","frame":{"type":"process_start","args":["--mode","rpc","--provider","openrouter","--model","deepseek/deepseek-v4-flash","--no-extensions","--no-skills","--no-prompt-templates","--no-context-files","--approve","--extension",""]}} {"type":"expect_outbound","label":"get_state","frame":{"type":"get_state","id":"t3-0"}} -{"type":"expect_outbound","label":"get_commands","frame":{"type":"get_commands","id":"t3-1"}} {"type":"emit_inbound","label":"extension_ui_request","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000001","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} {"type":"emit_inbound","label":"response:get_state","frame":{"id":"t3-0","type":"response","command":"get_state","success":true,"data":{"model":{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}},"thinkingLevel":"high","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/pi-sessions/session-1.jsonl","sessionId":"00000000-0000-4000-8000-000000000002","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}} -{"type":"emit_inbound","label":"response:get_commands","frame":{"id":"t3-1","type":"response","command":"get_commands","success":true,"data":{"commands":[]}}} {"type":"expect_outbound","label":"get_available_models","frame":{"type":"get_available_models","id":"t3-2"}} {"type":"emit_inbound","label":"response:get_available_models","frame":{"id":"t3-2","type":"response","command":"get_available_models","success":true,"data":{"models":[{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}}]}}} {"type":"expect_outbound","label":"get_entries","frame":{"type":"get_entries","id":"t3-3"}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/pi_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/pi_transcript.ndjson index 2e3fd92409bf..7e32afc0a5b2 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/pi_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/multi_turn/pi_transcript.ndjson @@ -1,10 +1,8 @@ {"type":"transcript_start","provider":"pi","protocol":"pi.rpc-jsonl","version":"1.0.0","scenario":"multi_turn","metadata":{"generatedBy":"live-pi-recorder","piVersion":"1.0.0","model":"openrouter/deepseek/deepseek-v4-flash","launchArgs":"--provider openrouter --model deepseek/deepseek-v4-flash --no-extensions --no-skills --no-prompt-templates --no-context-files --approve"}} {"type":"expect_outbound","label":"process_start","frame":{"type":"process_start","args":["--mode","rpc","--provider","openrouter","--model","deepseek/deepseek-v4-flash","--no-extensions","--no-skills","--no-prompt-templates","--no-context-files","--approve","--extension",""]}} {"type":"expect_outbound","label":"get_state","frame":{"type":"get_state","id":"t3-0"}} -{"type":"expect_outbound","label":"get_commands","frame":{"type":"get_commands","id":"t3-1"}} {"type":"emit_inbound","label":"extension_ui_request","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000001","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} {"type":"emit_inbound","label":"response:get_state","frame":{"id":"t3-0","type":"response","command":"get_state","success":true,"data":{"model":{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}},"thinkingLevel":"high","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/pi-sessions/session-1.jsonl","sessionId":"00000000-0000-4000-8000-000000000002","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}} -{"type":"emit_inbound","label":"response:get_commands","frame":{"id":"t3-1","type":"response","command":"get_commands","success":true,"data":{"commands":[]}}} {"type":"expect_outbound","label":"get_available_models","frame":{"type":"get_available_models","id":"t3-2"}} {"type":"emit_inbound","label":"response:get_available_models","frame":{"id":"t3-2","type":"response","command":"get_available_models","success":true,"data":{"models":[{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}}]}}} {"type":"expect_outbound","label":"get_entries","frame":{"type":"get_entries","id":"t3-3"}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/pi_compaction/pi_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/pi_compaction/pi_transcript.ndjson index ff20ad15dab3..43aadfd2d881 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/pi_compaction/pi_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/pi_compaction/pi_transcript.ndjson @@ -1,10 +1,8 @@ {"type":"transcript_start","provider":"pi","protocol":"pi.rpc-jsonl","version":"1.0.0","scenario":"pi_compaction","metadata":{"generatedBy":"live-pi-recorder","piVersion":"1.0.0","model":"openrouter/deepseek/deepseek-v4-flash","launchArgs":"--provider openrouter --model deepseek/deepseek-v4-flash --no-extensions --no-skills --no-prompt-templates --no-context-files --approve"}} {"type":"expect_outbound","label":"process_start","frame":{"type":"process_start","args":["--mode","rpc","--provider","openrouter","--model","deepseek/deepseek-v4-flash","--no-extensions","--no-skills","--no-prompt-templates","--no-context-files","--approve","--extension",""]}} {"type":"expect_outbound","label":"get_state","frame":{"type":"get_state","id":"t3-0"}} -{"type":"expect_outbound","label":"get_commands","frame":{"type":"get_commands","id":"t3-1"}} {"type":"emit_inbound","label":"extension_ui_request","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000001","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} {"type":"emit_inbound","label":"response:get_state","frame":{"id":"t3-0","type":"response","command":"get_state","success":true,"data":{"model":{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}},"thinkingLevel":"high","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/pi-sessions/session-1.jsonl","sessionId":"00000000-0000-4000-8000-000000000002","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}} -{"type":"emit_inbound","label":"response:get_commands","frame":{"id":"t3-1","type":"response","command":"get_commands","success":true,"data":{"commands":[]}}} {"type":"expect_outbound","label":"get_available_models","frame":{"type":"get_available_models","id":"t3-2"}} {"type":"emit_inbound","label":"response:get_available_models","frame":{"id":"t3-2","type":"response","command":"get_available_models","success":true,"data":{"models":[{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}}]}}} {"type":"expect_outbound","label":"get_entries","frame":{"type":"get_entries","id":"t3-3"}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/pi_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/pi_transcript.ndjson index 06a20a5ceec4..cba3bd8a396d 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/pi_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/provider_thread_resume/pi_transcript.ndjson @@ -1,10 +1,8 @@ {"type":"transcript_start","provider":"pi","protocol":"pi.rpc-jsonl","version":"1.0.0","scenario":"provider_thread_resume","metadata":{"generatedBy":"live-pi-recorder","piVersion":"1.0.0","model":"openrouter/deepseek/deepseek-v4-flash","launchArgs":"--provider openrouter --model deepseek/deepseek-v4-flash --no-extensions --no-skills --no-prompt-templates --no-context-files --approve"}} {"type":"expect_outbound","label":"process_start","frame":{"type":"process_start","args":["--mode","rpc","--provider","openrouter","--model","deepseek/deepseek-v4-flash","--no-extensions","--no-skills","--no-prompt-templates","--no-context-files","--approve","--extension",""]}} {"type":"expect_outbound","label":"get_state","frame":{"type":"get_state","id":"t3-0"}} -{"type":"expect_outbound","label":"get_commands","frame":{"type":"get_commands","id":"t3-1"}} {"type":"emit_inbound","label":"extension_ui_request","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000001","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} {"type":"emit_inbound","label":"response:get_state","frame":{"id":"t3-0","type":"response","command":"get_state","success":true,"data":{"model":{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}},"thinkingLevel":"high","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/pi-sessions/session-1.jsonl","sessionId":"00000000-0000-4000-8000-000000000002","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}} -{"type":"emit_inbound","label":"response:get_commands","frame":{"id":"t3-1","type":"response","command":"get_commands","success":true,"data":{"commands":[]}}} {"type":"expect_outbound","label":"get_available_models","frame":{"type":"get_available_models","id":"t3-2"}} {"type":"emit_inbound","label":"response:get_available_models","frame":{"id":"t3-2","type":"response","command":"get_available_models","success":true,"data":{"models":[{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}}]}}} {"type":"expect_outbound","label":"get_entries","frame":{"type":"get_entries","id":"t3-3"}} @@ -61,9 +59,7 @@ {"type":"emit_inbound","label":"response:get_session_stats","frame":{"id":"t3-8","type":"response","command":"get_session_stats","success":true,"data":{"sessionFile":"/pi-sessions/session-1.jsonl","sessionId":"00000000-0000-4000-8000-000000000002","userMessages":1,"assistantMessages":1,"toolCalls":0,"toolResults":0,"totalMessages":3,"tokens":{"input":1792,"output":85,"cacheRead":0,"cacheWrite":0,"total":1877},"cost":0.00008212932000000001,"contextUsage":{"tokens":1877,"contextWindow":1048576,"percent":0.17900466918945312}}}} {"type":"expect_outbound","label":"process_start@p2","frame":{"type":"process_start","args":["--mode","rpc","--provider","openrouter","--model","deepseek/deepseek-v4-flash","--no-extensions","--no-skills","--no-prompt-templates","--no-context-files","--approve","--extension",""]}} {"type":"expect_outbound","label":"switch_session@p2","frame":{"type":"switch_session","sessionPath":"/pi-sessions/session-1.jsonl","id":"t3-0"}} -{"type":"expect_outbound","label":"get_commands@p2","frame":{"type":"get_commands","id":"t3-1"}} {"type":"emit_inbound","label":"extension_ui_request@p2","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000003","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} -{"type":"emit_inbound","label":"response:get_commands@p2","frame":{"id":"t3-1","type":"response","command":"get_commands","success":true,"data":{"commands":[]}}} {"type":"emit_inbound","label":"extension_ui_request@p2","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000004","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} {"type":"emit_inbound","label":"extension_ui_request@p2","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000005","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} {"type":"emit_inbound","label":"response:switch_session@p2","frame":{"id":"t3-0","type":"response","command":"switch_session","success":true,"data":{"cancelled":false}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/simple/pi_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/simple/pi_transcript.ndjson index 231a1cfdb08d..ba7f84de2878 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/simple/pi_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/simple/pi_transcript.ndjson @@ -1,10 +1,8 @@ {"type":"transcript_start","provider":"pi","protocol":"pi.rpc-jsonl","version":"1.0.0","scenario":"simple","metadata":{"generatedBy":"live-pi-recorder","piVersion":"1.0.0","model":"openrouter/deepseek/deepseek-v4-flash","launchArgs":"--provider openrouter --model deepseek/deepseek-v4-flash --no-extensions --no-skills --no-prompt-templates --no-context-files --approve"}} {"type":"expect_outbound","label":"process_start","frame":{"type":"process_start","args":["--mode","rpc","--provider","openrouter","--model","deepseek/deepseek-v4-flash","--no-extensions","--no-skills","--no-prompt-templates","--no-context-files","--approve","--extension",""]}} {"type":"expect_outbound","label":"get_state","frame":{"type":"get_state","id":"t3-0"}} -{"type":"expect_outbound","label":"get_commands","frame":{"type":"get_commands","id":"t3-1"}} {"type":"emit_inbound","label":"extension_ui_request","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000001","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} {"type":"emit_inbound","label":"response:get_state","frame":{"id":"t3-0","type":"response","command":"get_state","success":true,"data":{"model":{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}},"thinkingLevel":"high","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/pi-sessions/session-1.jsonl","sessionId":"00000000-0000-4000-8000-000000000002","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}} -{"type":"emit_inbound","label":"response:get_commands","frame":{"id":"t3-1","type":"response","command":"get_commands","success":true,"data":{"commands":[]}}} {"type":"expect_outbound","label":"get_available_models","frame":{"type":"get_available_models","id":"t3-2"}} {"type":"emit_inbound","label":"response:get_available_models","frame":{"id":"t3-2","type":"response","command":"get_available_models","success":true,"data":{"models":[{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}}]}}} {"type":"expect_outbound","label":"get_entries","frame":{"type":"get_entries","id":"t3-3"}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/pi_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/pi_transcript.ndjson index f93112215952..2fc3def568e6 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/pi_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback/pi_transcript.ndjson @@ -1,11 +1,9 @@ {"type":"transcript_start","provider":"pi","protocol":"pi.rpc-jsonl","version":"1.0.0","scenario":"thread_rollback","metadata":{"generatedBy":"live-pi-recorder","piVersion":"1.0.0","model":"openrouter/deepseek/deepseek-v4-flash","launchArgs":"--provider openrouter --model deepseek/deepseek-v4-flash --no-extensions --no-skills --no-prompt-templates --no-context-files --approve"}} {"type":"expect_outbound","label":"process_start","frame":{"type":"process_start","args":["--mode","rpc","--provider","openrouter","--model","deepseek/deepseek-v4-flash","--no-extensions","--no-skills","--no-prompt-templates","--no-context-files","--approve","--extension",""]}} {"type":"expect_outbound","label":"get_state","frame":{"type":"get_state","id":"t3-0"}} -{"type":"expect_outbound","label":"get_commands","frame":{"type":"get_commands","id":"t3-1"}} {"type":"emit_inbound","label":"extension_ui_request","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000001","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} {"type":"emit_inbound","label":"response:get_state","frame":{"id":"t3-0","type":"response","command":"get_state","success":true,"data":{"model":{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}},"thinkingLevel":"high","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/pi-sessions/session-1.jsonl","sessionId":"00000000-0000-4000-8000-000000000002","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}} {"type":"expect_outbound","label":"get_available_models","frame":{"type":"get_available_models","id":"t3-2"}} -{"type":"emit_inbound","label":"response:get_commands","frame":{"id":"t3-1","type":"response","command":"get_commands","success":true,"data":{"commands":[]}}} {"type":"emit_inbound","label":"response:get_available_models","frame":{"id":"t3-2","type":"response","command":"get_available_models","success":true,"data":{"models":[{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}}]}}} {"type":"expect_outbound","label":"get_entries","frame":{"type":"get_entries","id":"t3-3"}} {"type":"emit_inbound","label":"response:get_entries","frame":{"id":"t3-3","type":"response","command":"get_entries","success":true,"data":{"entries":[{"type":"model_change","id":"648fe89a","parentId":null,"timestamp":"2026-01-01T00:00:00.000Z","provider":"openrouter","modelId":"deepseek/deepseek-v4-flash"},{"type":"thinking_level_change","id":"00c31b78","parentId":"648fe89a","timestamp":"2026-01-01T00:00:00.000Z","thinkingLevel":"high"}],"leafId":"00c31b78"}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback_after_stop/pi_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback_after_stop/pi_transcript.ndjson index 4bee5483ad42..2d92c45b2adb 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback_after_stop/pi_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/thread_rollback_after_stop/pi_transcript.ndjson @@ -1,10 +1,8 @@ {"type":"transcript_start","provider":"pi","protocol":"pi.rpc-jsonl","version":"1.0.0","scenario":"thread_rollback_after_stop","metadata":{"generatedBy":"live-pi-recorder","piVersion":"1.0.0","model":"openrouter/deepseek/deepseek-v4-flash","launchArgs":"--provider openrouter --model deepseek/deepseek-v4-flash --no-extensions --no-skills --no-prompt-templates --no-context-files --approve"}} {"type":"expect_outbound","label":"process_start","frame":{"type":"process_start","args":["--mode","rpc","--provider","openrouter","--model","deepseek/deepseek-v4-flash","--no-extensions","--no-skills","--no-prompt-templates","--no-context-files","--approve","--extension",""]}} {"type":"expect_outbound","label":"get_state","frame":{"type":"get_state","id":"t3-0"}} -{"type":"expect_outbound","label":"get_commands","frame":{"type":"get_commands","id":"t3-1"}} {"type":"emit_inbound","label":"extension_ui_request","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000001","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} {"type":"emit_inbound","label":"response:get_state","frame":{"id":"t3-0","type":"response","command":"get_state","success":true,"data":{"model":{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}},"thinkingLevel":"high","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/pi-sessions/session-1.jsonl","sessionId":"00000000-0000-4000-8000-000000000002","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}} -{"type":"emit_inbound","label":"response:get_commands","frame":{"id":"t3-1","type":"response","command":"get_commands","success":true,"data":{"commands":[]}}} {"type":"expect_outbound","label":"get_available_models","frame":{"type":"get_available_models","id":"t3-2"}} {"type":"emit_inbound","label":"response:get_available_models","frame":{"id":"t3-2","type":"response","command":"get_available_models","success":true,"data":{"models":[{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}}]}}} {"type":"expect_outbound","label":"get_entries","frame":{"type":"get_entries","id":"t3-3"}} @@ -151,9 +149,7 @@ {"type":"emit_inbound","label":"response:get_entries","frame":{"id":"t3-10","type":"response","command":"get_entries","success":true,"data":{"entries":[{"type":"message","id":"ee73daae","parentId":"b184b5a8","timestamp":"2026-01-01T00:00:01.971Z","message":{"role":"user","content":[{"type":"text","text":"Run this exact local command: `node -e \"console.log('interrupt fixture tool started'); setTimeout(() => {}, 30000)\"`. Do not answer until it completes, then respond exactly: interrupt fixture should not finish naturally."}],"timestamp":1767225601969}},{"type":"message","id":"c3915df7","parentId":"ee73daae","timestamp":"2026-01-01T00:00:06.406Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"The user wants me to run a command that will wait for 30 seconds, and then after it completes, respond with a specific message. But they also say \"Do not answer until it completes\" - but this command will run for 30 seconds and then naturally finish. However, the response they want is \"interrupt fixture should not finish naturally\" which contradicts the fact that it did finish naturally.\n\nWait, let me re-read. They want me to run the command and not answer until it completes. The command is a node script that logs \"interrupt fixture tool started\" and then waits 30 seconds. If I wait for it to complete, it will finish naturally after 30 seconds. But the response they want is \"interrupt fixture should not finish naturally\" - implying that the fixture should have been interrupted before finishing naturally.\n\nThis is a test scenario. Let me just run it and see what happens. Actually, maybe they want me to just run it and respond accordingly. Let me execute the command.\n\nActually, I think this is part of a test where the system might interrupt/kill the process. Let me just run the command with a sufficient timeout.","thinkingSignature":""},{"type":"toolCall","id":"call_8ac3cb4177994abdb9aa1c57","name":"bash","arguments":{"command":"node -e \"console.log('interrupt fixture tool started'); setTimeout(() => {}, 30000)\"","timeout":35}}],"api":"openai-completions","provider":"openrouter","model":"deepseek/deepseek-v4-flash","usage":{"input":77,"output":317,"cacheRead":1792,"cacheWrite":0,"reasoning":284,"totalTokens":2186,"cost":{"input":0.00000322322,"output":0.000026539239999999998,"cacheRead":0.000015002623999999998,"cacheWrite":0,"total":0.000044765084}},"stopReason":"toolUse","timestamp":1767225601972,"responseId":"gen-1790898593-E5hfYfRp6aKBI57pUKss","rawStopReason":"tool_calls","thinkingLevel":"high"}},{"type":"message","id":"641f3d3f","parentId":"c3915df7","timestamp":"2026-01-01T00:00:06.463Z","message":{"role":"toolResult","toolCallId":"call_8ac3cb4177994abdb9aa1c57","toolName":"bash","content":[{"type":"text","text":"interrupt fixture tool started\n\n\nCommand aborted"}],"details":{},"isError":true,"timestamp":1767225606463}},{"type":"message","id":"e3ab1495","parentId":"641f3d3f","timestamp":"2026-01-01T00:00:06.465Z","message":{"role":"assistant","content":[],"api":"openai-completions","provider":"openrouter","model":"deepseek/deepseek-v4-flash","usage":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"totalTokens":0,"cost":{"input":0,"output":0,"cacheRead":0,"cacheWrite":0,"total":0}},"stopReason":"error","errorMessage":"This operation was aborted","timestamp":1767225606465,"thinkingLevel":"high"}}],"leafId":"e3ab1495"}}} {"type":"expect_outbound","label":"process_start@p2","frame":{"type":"process_start","args":["--mode","rpc","--provider","openrouter","--model","deepseek/deepseek-v4-flash","--no-extensions","--no-skills","--no-prompt-templates","--no-context-files","--approve","--extension",""]}} {"type":"expect_outbound","label":"switch_session@p2","frame":{"type":"switch_session","sessionPath":"/pi-sessions/session-1.jsonl","id":"t3-0"}} -{"type":"expect_outbound","label":"get_commands@p2","frame":{"type":"get_commands","id":"t3-1"}} {"type":"emit_inbound","label":"extension_ui_request@p2","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000003","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} -{"type":"emit_inbound","label":"response:get_commands@p2","frame":{"id":"t3-1","type":"response","command":"get_commands","success":true,"data":{"commands":[]}}} {"type":"emit_inbound","label":"extension_ui_request@p2","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000004","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} {"type":"emit_inbound","label":"extension_ui_request@p2","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000005","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} {"type":"emit_inbound","label":"response:switch_session@p2","frame":{"id":"t3-0","type":"response","command":"switch_session","success":true,"data":{"cancelled":false}}} diff --git a/apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/pi_transcript.ndjson b/apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/pi_transcript.ndjson index 35e3e1d21e8d..14e710c1ff9d 100644 --- a/apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/pi_transcript.ndjson +++ b/apps/server/src/orchestration-v2/testkit/fixtures/turn_interrupt_mid_tool/pi_transcript.ndjson @@ -1,10 +1,8 @@ {"type":"transcript_start","provider":"pi","protocol":"pi.rpc-jsonl","version":"1.0.0","scenario":"turn_interrupt_mid_tool","metadata":{"generatedBy":"live-pi-recorder","piVersion":"1.0.0","model":"openrouter/deepseek/deepseek-v4-flash","launchArgs":"--provider openrouter --model deepseek/deepseek-v4-flash --no-extensions --no-skills --no-prompt-templates --no-context-files --approve"}} {"type":"expect_outbound","label":"process_start","frame":{"type":"process_start","args":["--mode","rpc","--provider","openrouter","--model","deepseek/deepseek-v4-flash","--no-extensions","--no-skills","--no-prompt-templates","--no-context-files","--approve","--extension",""]}} {"type":"expect_outbound","label":"get_state","frame":{"type":"get_state","id":"t3-0"}} -{"type":"expect_outbound","label":"get_commands","frame":{"type":"get_commands","id":"t3-1"}} {"type":"emit_inbound","label":"extension_ui_request","frame":{"type":"extension_ui_request","id":"00000000-0000-4000-8000-000000000001","method":"notify","message":"t3-code MCP unavailable: T3_MCP_URL or T3_MCP_BEARER_TOKEN is missing.","notifyType":"warning"}} {"type":"emit_inbound","label":"response:get_state","frame":{"id":"t3-0","type":"response","command":"get_state","success":true,"data":{"model":{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}},"thinkingLevel":"high","isStreaming":false,"isCompacting":false,"steeringMode":"one-at-a-time","followUpMode":"one-at-a-time","sessionFile":"/pi-sessions/session-1.jsonl","sessionId":"00000000-0000-4000-8000-000000000002","autoCompactionEnabled":true,"messageCount":0,"pendingMessageCount":0}}} -{"type":"emit_inbound","label":"response:get_commands","frame":{"id":"t3-1","type":"response","command":"get_commands","success":true,"data":{"commands":[]}}} {"type":"expect_outbound","label":"get_available_models","frame":{"type":"get_available_models","id":"t3-2"}} {"type":"emit_inbound","label":"response:get_available_models","frame":{"id":"t3-2","type":"response","command":"get_available_models","success":true,"data":{"models":[{"type":"chat","id":"deepseek/deepseek-v4-flash","name":"DeepSeek: DeepSeek V4 Flash 0423","api":"openai-completions","baseUrl":"https://openrouter.ai/api/v1","provider":"openrouter","reasoning":true,"thinkingLevelMap":{"off":"none","minimal":null,"low":null,"medium":null,"high":"high","xhigh":"xhigh","max":null},"input":["text"],"cost":{"input":0.04186,"output":0.08372,"cacheRead":0.008372,"cacheWrite":0},"contextWindow":1048576,"maxTokens":131072,"compat":{"supportsDeveloperRole":false,"thinkingFormat":"openrouter","supportsStrictMode":true,"sendSessionAffinityHeaders":true,"requiresReasoningContentOnAssistantMessages":true}}]}}} {"type":"expect_outbound","label":"get_entries","frame":{"type":"get_entries","id":"t3-3"}} diff --git a/packages/provider-pi/src/server/adapter.ts b/packages/provider-pi/src/server/adapter.ts index 0f8830cf1221..d8dedd71d0b6 100644 --- a/packages/provider-pi/src/server/adapter.ts +++ b/packages/provider-pi/src/server/adapter.ts @@ -63,10 +63,7 @@ import { ChildProcessSpawner } from "effect/process"; import * as ProviderHost from "@t3tools/provider-core/server/ProviderHost"; import { mcpToolPresentation } from "@t3tools/provider-core/server/mcpToolPresentation"; import * as McpProviderSession from "@t3tools/provider-core/server/mcpSession"; -import { - parsePiCompactCommand, - type PiCompactCommand, -} from "./commands.ts"; +import { parsePiCompactCommand, type PiCompactCommand } from "./commands.ts"; import { mergeProviderInstanceEnvironment } from "@t3tools/provider-core/server/instanceEnvironment"; import * as IdAllocator from "@t3tools/provider-core/server/IdAllocator"; import * as ProviderAdapter from "@t3tools/provider-core/server/ProviderAdapter"; diff --git a/packages/provider-pi/src/server/mcpExtensionSource.test.ts b/packages/provider-pi/src/server/mcpExtensionSource.test.ts index 133a20ba0b21..3529fd6c0c58 100644 --- a/packages/provider-pi/src/server/mcpExtensionSource.test.ts +++ b/packages/provider-pi/src/server/mcpExtensionSource.test.ts @@ -1,3 +1,4 @@ +// @effect-diagnostics nodeBuiltinImport:off -- Pi extensions run outside Effect; these tests exercise their native filesystem boundary. import * as NodeFSP from "node:fs/promises"; import * as NodePath from "node:path"; import * as NodeOS from "node:os"; @@ -404,7 +405,6 @@ async function loadInputHook(commands: ReadonlyArray) { return hook as InputHook; } - describe("Pi skill references", () => { it("loads every selected skill once while preserving inline prose, whitespace, and images", async () => { const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "pi-skill-mentions-")); From ab52acf6031e97cba66015d80464d6b689179f9e Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Fri, 9 Oct 2026 07:17:20 +0200 Subject: [PATCH 3/4] fix(pi): load skill references next to punctuation --- .../src/server/mcpExtensionSource.test.ts | 38 +++++++++++++++++-- .../src/server/mcpExtensionSource.ts | 15 ++++++-- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/packages/provider-pi/src/server/mcpExtensionSource.test.ts b/packages/provider-pi/src/server/mcpExtensionSource.test.ts index 3529fd6c0c58..0e3be54a0882 100644 --- a/packages/provider-pi/src/server/mcpExtensionSource.test.ts +++ b/packages/provider-pi/src/server/mcpExtensionSource.test.ts @@ -417,8 +417,7 @@ describe("Pi skill references", () => { { name: "skill:alpha", source: "skill", sourceInfo: { path: alpha } }, { name: "skill:beta", source: "skill", sourceInfo: { path: beta } }, ]); - const text = - "Please use the $alpha philosophy, then $beta and $alpha\n```ts\n const x = 1;\n```"; + const text = "Please use ($alpha), then $beta. and $alpha\n```ts\n const x = 1;\n```"; const images = [{ type: "image", data: "fixture" }]; const result = await hook({ text, images }, { ui: { notify: assert.fail } }); assert.equal(result?.action, "transform"); @@ -436,11 +435,44 @@ describe("Pi skill references", () => { const hook = await loadInputHook([ { name: "skill:alpha", source: "skill", sourceInfo: { path: "/unused" } }, ]); - for (const text of ["Explain $HOME", "/skill:alpha use $alpha", "Hello", "$missing"]) { + for (const text of [ + "Explain $HOME", + "/skill:alpha use $alpha", + "Hello", + "$missing", + "$alpha-extra,", + "prefix$alpha", + ]) { assert.isUndefined(await hook({ text }, { ui: { notify: assert.fail } })); } }); + it("loads punctuated references while preserving exact catalog names", async () => { + const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "pi-skill-mentions-")); + try { + const path = NodePath.join(directory, "skill.md"); + await NodeFSP.writeFile(path, "EXACT_SKILL_INSTRUCTIONS"); + const hook = await loadInputHook([ + { name: "skill:alpha", source: "skill", sourceInfo: { path } }, + { name: "skill:alpha.v2", source: "skill", sourceInfo: { path } }, + { name: "skill:alpha!", source: "skill", sourceInfo: { path } }, + ]); + for (const [text, name] of [ + ["Use $alpha, then continue", "alpha"], + ["Use ($alpha)", "alpha"], + ["Use [$alpha.v2].", "alpha.v2"], + ["Use $alpha!", "alpha!"], + ] as const) { + const result = await hook({ text }, { ui: { notify: assert.fail } }); + assert.isTrue(result?.text.startsWith(text + "\n\n")); + assert.include(result?.text ?? "", "EXACT_SKILL_INSTRUCTIONS"); + assert.include(result?.text ?? "", ` { const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "pi-skill-mentions-")); try { diff --git a/packages/provider-pi/src/server/mcpExtensionSource.ts b/packages/provider-pi/src/server/mcpExtensionSource.ts index 8279a515ed65..3235443d30fc 100644 --- a/packages/provider-pi/src/server/mcpExtensionSource.ts +++ b/packages/provider-pi/src/server/mcpExtensionSource.ts @@ -220,11 +220,18 @@ export default async function t3McpExtension(pi: ExtensionAPI) { const nativeSkill = /^\\/skill:([^ ]+)(?: |$)/.exec(event.text)?.[1]; const seen = new Set(nativeSkill === undefined ? [] : [nativeSkill]); const blocks: string[] = []; - for (const match of event.text.matchAll(/(^|\\s)\\$([^\\s]+)(?=\\s|$)/g)) { - const name = match[2]; - if (name === undefined || seen.has(name)) continue; - const command = commands.find((candidate) => candidate.name === "skill:" + name); + for (const match of event.text.matchAll(/(^|[\\s([{])\\$([^\\s]+)/g)) { + const reference = match[2]; + if (reference === undefined) continue; + // Prefer the exact catalog name before treating trailing punctuation as prose. + const command = + commands.find((candidate) => candidate.name === "skill:" + reference) ?? + commands.find( + (candidate) => candidate.name === "skill:" + reference.replace(/[)\\]},.!?:;]+$/, ""), + ); if (command === undefined) continue; + const name = command.name.slice("skill:".length); + if (seen.has(name)) continue; seen.add(name); const path = command.sourceInfo.path; try { From 7c1553df9c9d22106b1f956546ed1af86b70e904 Mon Sep 17 00:00:00 2001 From: Wout Stiens <71498452+StiensWout@users.noreply.github.com> Date: Fri, 9 Oct 2026 07:21:47 +0200 Subject: [PATCH 4/4] fix(pi): keep skill matching aligned with composer tokens --- .../src/server/mcpExtensionSource.test.ts | 34 +++---------------- .../src/server/mcpExtensionSource.ts | 15 +++----- 2 files changed, 9 insertions(+), 40 deletions(-) diff --git a/packages/provider-pi/src/server/mcpExtensionSource.test.ts b/packages/provider-pi/src/server/mcpExtensionSource.test.ts index 0e3be54a0882..25b040c2c002 100644 --- a/packages/provider-pi/src/server/mcpExtensionSource.test.ts +++ b/packages/provider-pi/src/server/mcpExtensionSource.test.ts @@ -417,7 +417,8 @@ describe("Pi skill references", () => { { name: "skill:alpha", source: "skill", sourceInfo: { path: alpha } }, { name: "skill:beta", source: "skill", sourceInfo: { path: beta } }, ]); - const text = "Please use ($alpha), then $beta. and $alpha\n```ts\n const x = 1;\n```"; + const text = + "Please use the $alpha philosophy, then $beta and $alpha\n```ts\n const x = 1;\n```"; const images = [{ type: "image", data: "fixture" }]; const result = await hook({ text, images }, { ui: { notify: assert.fail } }); assert.equal(result?.action, "transform"); @@ -435,44 +436,19 @@ describe("Pi skill references", () => { const hook = await loadInputHook([ { name: "skill:alpha", source: "skill", sourceInfo: { path: "/unused" } }, ]); + // Punctuation-adjacent references are plain text in the composer, not selected chips. for (const text of [ "Explain $HOME", "/skill:alpha use $alpha", "Hello", "$missing", - "$alpha-extra,", - "prefix$alpha", + "Use $alpha, then continue", + "Use ($alpha)", ]) { assert.isUndefined(await hook({ text }, { ui: { notify: assert.fail } })); } }); - it("loads punctuated references while preserving exact catalog names", async () => { - const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "pi-skill-mentions-")); - try { - const path = NodePath.join(directory, "skill.md"); - await NodeFSP.writeFile(path, "EXACT_SKILL_INSTRUCTIONS"); - const hook = await loadInputHook([ - { name: "skill:alpha", source: "skill", sourceInfo: { path } }, - { name: "skill:alpha.v2", source: "skill", sourceInfo: { path } }, - { name: "skill:alpha!", source: "skill", sourceInfo: { path } }, - ]); - for (const [text, name] of [ - ["Use $alpha, then continue", "alpha"], - ["Use ($alpha)", "alpha"], - ["Use [$alpha.v2].", "alpha.v2"], - ["Use $alpha!", "alpha!"], - ] as const) { - const result = await hook({ text }, { ui: { notify: assert.fail } }); - assert.isTrue(result?.text.startsWith(text + "\n\n")); - assert.include(result?.text ?? "", "EXACT_SKILL_INSTRUCTIONS"); - assert.include(result?.text ?? "", ` { const directory = await NodeFSP.mkdtemp(NodePath.join(NodeOS.tmpdir(), "pi-skill-mentions-")); try { diff --git a/packages/provider-pi/src/server/mcpExtensionSource.ts b/packages/provider-pi/src/server/mcpExtensionSource.ts index 3235443d30fc..8279a515ed65 100644 --- a/packages/provider-pi/src/server/mcpExtensionSource.ts +++ b/packages/provider-pi/src/server/mcpExtensionSource.ts @@ -220,18 +220,11 @@ export default async function t3McpExtension(pi: ExtensionAPI) { const nativeSkill = /^\\/skill:([^ ]+)(?: |$)/.exec(event.text)?.[1]; const seen = new Set(nativeSkill === undefined ? [] : [nativeSkill]); const blocks: string[] = []; - for (const match of event.text.matchAll(/(^|[\\s([{])\\$([^\\s]+)/g)) { - const reference = match[2]; - if (reference === undefined) continue; - // Prefer the exact catalog name before treating trailing punctuation as prose. - const command = - commands.find((candidate) => candidate.name === "skill:" + reference) ?? - commands.find( - (candidate) => candidate.name === "skill:" + reference.replace(/[)\\]},.!?:;]+$/, ""), - ); + for (const match of event.text.matchAll(/(^|\\s)\\$([^\\s]+)(?=\\s|$)/g)) { + const name = match[2]; + if (name === undefined || seen.has(name)) continue; + const command = commands.find((candidate) => candidate.name === "skill:" + name); if (command === undefined) continue; - const name = command.name.slice("skill:".length); - if (seen.has(name)) continue; seen.add(name); const path = command.sourceInfo.path; try {