fix: close PI-090 review findings

This commit is contained in:
2026-08-23 15:58:32 +08:00
parent 13ab383e53
commit 7e024093b1
11 changed files with 325 additions and 66 deletions

View File

@@ -88,7 +88,20 @@ interface ChangeRefreshBridgeRequest {
resourceId: string;
}
type BridgeRequest = LeaseBridgeRequest | SubagentBridgeRequest | ProductToolBridgeRequest | ChangeRefreshBridgeRequest;
interface ChangeTouchedBridgeRequest {
action: 'changes.touched';
conversationId: string;
workerGeneration: number;
runId: string;
resourceId: string;
paths: string[];
}
type BridgeRequest = LeaseBridgeRequest
| SubagentBridgeRequest
| ProductToolBridgeRequest
| ChangeRefreshBridgeRequest
| ChangeTouchedBridgeRequest;
export interface PiExtensionSubagentBridge {
scheduler: PiSubagentScheduler;
@@ -111,6 +124,12 @@ function bridgeRequest(value: unknown): value is BridgeRequest {
return typeof value.toolName === 'string' && 'input' in value;
}
if (value.action === 'changes.bash') return true;
if (value.action === 'changes.touched') {
return Array.isArray(value.paths)
&& value.paths.length > 0
&& value.paths.length <= 200
&& value.paths.every((filePath) => typeof filePath === 'string');
}
return (value.action === 'lease.acquire' || value.action === 'lease.release')
&& (value.leaseId === undefined || typeof value.leaseId === 'string');
}
@@ -304,6 +323,19 @@ export class PiManagedExtensionHost {
this.respond(response, 200, { marked: true });
return;
}
if (value.action === 'changes.touched') {
if (!this.productTools) {
this.respond(response, 503, { error: 'Conversation change tracker is unavailable' });
return;
}
await this.productTools.recordTouchedPaths(
record.conversationId,
value.runId,
value.paths,
);
this.respond(response, 200, { recorded: true });
return;
}
if (value.action === 'product.invoke') {
if (record.role !== 'parent') {
this.respond(response, 403, { error: 'Child workers cannot invoke parent product tools' });

View File

@@ -29,7 +29,7 @@ export async function reportChangedFiles(
const paths = snapshot.files.map((file) => file.path);
const details: ChangedFileDetailsV1 = { schema: 'changed-file.v1', paths };
return {
content: [{ type: 'text' as const, text: JSON.stringify({ paths, changes: snapshot.files }) }],
content: [{ type: 'text' as const, text: `${paths.length} changed path(s) recorded` }],
details,
};
}

View File

@@ -306,7 +306,10 @@ export default function makeloreRuntime(pi) {
const paths = touchedPaths.get(event.toolCallId);
touchedPaths.delete(event.toolCallId);
if (paths) {
await invokeProduct(event.toolCallId, 'changed_file', { paths, refresh: true }).catch(() => undefined);
await bridge('changes.touched', {
resourceId: event.toolCallId,
paths,
}).catch(() => undefined);
}
});
pi.on('agent_end', releaseAll);

View File

@@ -69,6 +69,10 @@ export class PiProductTools {
await this.changeTracker.markProjectRefresh(conversationId, runId);
}
recordTouchedPaths(conversationId: string, runId: string, paths: readonly string[]) {
return this.changeTracker.recordTouchedPaths(conversationId, runId, paths);
}
async execute(
toolName: PiProductToolName,
context: PiProductToolContext,

View File

@@ -598,11 +598,11 @@ export class PiConversationRuntime implements CodingConversationRuntime {
};
const generation = this.pool.getState(input.conversationId)?.generation;
if (generation) this.extensionUi.beginRun(input.conversationId, generation, runId);
if (this.extensionHost && generation) {
await this.extensionHost.bindRun(input.conversationId, generation, runId);
}
let ticket;
try {
if (this.extensionHost && generation) {
await this.extensionHost.bindRun(input.conversationId, generation, runId);
}
ticket = this.pool.startTopLevel({
conversationId: input.conversationId,
runId,
@@ -744,11 +744,11 @@ export class PiConversationRuntime implements CodingConversationRuntime {
const runId = this.id('run');
const generation = this.pool.getState(conversationId)?.generation;
if (generation) this.extensionUi.beginRun(conversationId, generation, runId);
if (this.extensionHost && generation) {
await this.extensionHost.bindRun(conversationId, generation, runId);
}
let ticket;
try {
if (this.extensionHost && generation) {
await this.extensionHost.bindRun(conversationId, generation, runId);
}
ticket = this.pool.startTopLevel({
conversationId,
runId,