mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-20 22:44:06 +00:00
* feat(acp): carry _meta.toolName on permission frame; agent drawer (vscode) WIP: producer mirrors _meta.toolName onto session/request_permission; webui PermissionDrawer + vscode webview map it to render 'Launch this agent?' for the Agent tool without a protocol kind. Daemon/web-shell surface + tests follow. * feat(web-shell): dedicated agent permission prompt via _meta.toolName Thread the canonical tool name from the permission frame's _meta.toolName through web-shell's PermissionRequest so ToolApproval renders 'Launch this agent?' for the Agent tool, mirroring the vscode PermissionDrawer. Add tests for the toolName extraction and the agent drawer title. * fix(acp): mirror _meta.toolName on second producer path + address review Address @wenshao's review on #5105: - [Critical] SubAgentTracker's approval handler builds its own RequestPermissionRequest (the second producer path, for nested sub-agent tool calls) and was missing `_meta: { toolName }`. Session.ts adds it on the primary path; mirror it here so nested agents (and any future tool relying on _meta.toolName for specialized UI) don't fall back to the generic prompt. Locked with a _meta assertion in the approval test. - Dedupe the three hardcoded 'agent' string matches behind a single shared `AGENT_TOOL_NAME` / `isAgentTool` in @qwen-code/webui (re-exported via daemon-react-sdk, same pattern as DAEMON_APPROVAL_MODES), consumed by PermissionDrawer (webui) and ToolApproval (web-shell). - Move the agent check to the top of PermissionDrawer.getTitle() so it wins over kind-based checks, matching ToolApproval's isAgent-first ordering across the two surfaces. - Extract the _meta.toolName lifting logic in useWebViewMessages into a testable `liftToolNameFromMeta` helper and cover the three cases wenshao flagged: lift onto toolName, preserve a pre-existing toolName, no-op when _meta is absent (plus undefined-toolCall guard). 🤖 Generated with [Qwen Code](https://github.com/QwenLM/qwen-code)
97 lines
2.6 KiB
TypeScript
97 lines
2.6 KiB
TypeScript
import type { DaemonTranscriptBlock } from '@qwen-code/webui/daemon-react-sdk';
|
|
import type { PermissionRequest, PermissionOptionKind } from './types';
|
|
|
|
type PermissionTranscriptBlock = Extract<
|
|
DaemonTranscriptBlock,
|
|
{ kind: 'permission' }
|
|
>;
|
|
|
|
export function extractPendingPermission(
|
|
blocks: readonly DaemonTranscriptBlock[],
|
|
): PermissionRequest | null {
|
|
for (const block of blocks) {
|
|
if (!isPermissionBlock(block)) continue;
|
|
const perm = block;
|
|
if (perm.resolved) continue;
|
|
const toolCallRecord = getRecord(perm.toolCall);
|
|
const toolCallId =
|
|
typeof toolCallRecord?.['toolCallId'] === 'string'
|
|
? toolCallRecord['toolCallId']
|
|
: typeof toolCallRecord?.['id'] === 'string'
|
|
? toolCallRecord['id']
|
|
: undefined;
|
|
const toolKind =
|
|
typeof toolCallRecord?.['kind'] === 'string'
|
|
? toolCallRecord['kind']
|
|
: undefined;
|
|
const metaRecord = getRecord(toolCallRecord?.['_meta']);
|
|
const toolName =
|
|
typeof metaRecord?.['toolName'] === 'string'
|
|
? metaRecord['toolName']
|
|
: undefined;
|
|
return {
|
|
id: perm.requestId,
|
|
sessionId: perm.sessionId,
|
|
toolCallId,
|
|
title: perm.title,
|
|
toolKind,
|
|
toolName,
|
|
content: [
|
|
{
|
|
type: 'text',
|
|
text: perm.title || 'Tool permission',
|
|
},
|
|
],
|
|
options: perm.options.map((opt) => ({
|
|
id: opt.optionId,
|
|
label: opt.label,
|
|
kind: getPermissionOptionKind(opt.raw),
|
|
})),
|
|
rawInput: getPermissionRawInput(perm.toolCall),
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function isPermissionBlock(
|
|
block: DaemonTranscriptBlock,
|
|
): block is PermissionTranscriptBlock {
|
|
return block.kind === 'permission';
|
|
}
|
|
|
|
function getPermissionRawInput(
|
|
toolCall: unknown,
|
|
): Record<string, unknown> | undefined {
|
|
const record = getRecord(toolCall);
|
|
if (!record) {
|
|
return undefined;
|
|
}
|
|
|
|
const nested =
|
|
getRecord(record['rawInput']) ??
|
|
getRecord(record['input']) ??
|
|
getRecord(record['args']);
|
|
return nested ?? record;
|
|
}
|
|
|
|
function getRecord(value: unknown): Record<string, unknown> | undefined {
|
|
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
return undefined;
|
|
}
|
|
return value as Record<string, unknown>;
|
|
}
|
|
|
|
function getPermissionOptionKind(
|
|
raw: unknown,
|
|
): PermissionOptionKind | undefined {
|
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
return undefined;
|
|
}
|
|
const kind = (raw as Record<string, unknown>).kind;
|
|
return kind === 'allow_once' ||
|
|
kind === 'allow_always' ||
|
|
kind === 'reject_once' ||
|
|
kind === 'reject_always'
|
|
? kind
|
|
: undefined;
|
|
}
|