feat: surface codex thread collaboration capabilities

This commit is contained in:
AI Bot
2026-06-04 14:34:34 +08:00
parent de9f85bd21
commit 5bf2216cb0
12 changed files with 359 additions and 121 deletions

View File

@@ -57,6 +57,10 @@ function parseCodexVersion(raw) {
return match?.[1] || "unknown";
}
function stripTrailingWhitespace(text) {
return String(text || "").replace(/[ \t]+$/gm, "");
}
async function listFiles(root) {
const entries = await readdir(root, { withFileTypes: true });
const files = [];
@@ -71,21 +75,106 @@ async function listFiles(root) {
return files;
}
function extractProtocolMethodsFromText(text) {
const methods = new Set();
const pattern = /"([A-Za-z][A-Za-z0-9]*(?:\/[A-Za-z0-9_-]+)+)"/g;
const PROTOCOL_METHOD_PREFIXES = [
"account",
"app",
"collaborationMode",
"command",
"config",
"configRequirements",
"experimentalFeature",
"externalAgentConfig",
"feedback",
"fs",
"fuzzyFileSearch",
"hooks",
"item",
"marketplace",
"mcpServer",
"mcpServerStatus",
"model",
"modelProvider",
"permissionProfile",
"plugin",
"process",
"rawResponseItem",
"remoteControl",
"review",
"serverRequest",
"skills",
"thread",
"turn",
"windows",
"windowsSandbox",
];
const PROTOCOL_EXACT_METHODS = new Set([
"fuzzyFileSearch",
"getAuthStatus",
"getConversationSummary",
"gitDiffToRemote",
"initialize",
"initialized",
]);
const THREAD_ITEM_TYPES = new Set([
"agentMessage",
"collabToolCall",
"commandExecution",
"contextCompaction",
"dynamicToolCall",
"enteredReviewMode",
"exitedReviewMode",
"fileChange",
"imageGeneration",
"imageView",
"mcpToolCall",
"plan",
"reasoning",
"userMessage",
"webSearch",
]);
function isProtocolMethod(value) {
return (
PROTOCOL_EXACT_METHODS.has(value) ||
PROTOCOL_METHOD_PREFIXES.some((prefix) => value.startsWith(`${prefix}/`))
);
}
function extractQuotedValues(text) {
const values = [];
const pattern = /"([A-Za-z][A-Za-z0-9_]*(?:\/[A-Za-z0-9_-]+)*)"/g;
let match;
while ((match = pattern.exec(text))) {
const value = match[1];
if (/^(thread|turn|item|rawResponseItem|model|modelProvider|experimentalFeature|permissionProfile|process|command|review|account|config|mcpServer|plugin|marketplace|skills|hooks|fs|remoteControl|externalAgentConfig|fuzzyFileSearch|windowsSandbox)\//.test(value)) {
values.push(match[1]);
}
return values;
}
function extractProtocolMethodsFromText(text) {
const methods = new Set();
for (const value of extractQuotedValues(text)) {
if (isProtocolMethod(value)) {
methods.add(value);
}
}
return methods;
}
async function extractProtocolMethods(root) {
function extractThreadItemTypesFromText(text) {
const itemTypes = new Set();
for (const value of extractQuotedValues(text)) {
if (THREAD_ITEM_TYPES.has(value)) {
itemTypes.add(value);
}
}
return itemTypes;
}
async function extractProtocolArtifacts(root) {
const methods = new Set();
const itemTypes = new Set();
for (const filePath of await listFiles(root)) {
if (!/\.(json|ts)$/.test(filePath)) {
continue;
@@ -94,17 +183,30 @@ async function extractProtocolMethods(root) {
for (const method of extractProtocolMethodsFromText(text)) {
methods.add(method);
}
for (const itemType of extractThreadItemTypesFromText(text)) {
itemTypes.add(itemType);
}
}
return Array.from(methods).sort();
return {
methods: Array.from(methods).sort(),
itemTypes: Array.from(itemTypes).sort(),
};
}
function buildSupportMatrix({ helpText, methods }) {
function buildSupportMatrix({ helpText, methods, itemTypes }) {
const methodSet = new Set(methods);
const itemTypeSet = new Set(itemTypes);
return {
stdioTransport: /stdio:\/\//.test(helpText) || /stdio/.test(helpText),
unixTransport: /unix:\/\//.test(helpText),
wsTransport: /ws:\/\/|websocket/i.test(helpText),
wsAuth: /--ws-auth/.test(helpText),
threadStart: methodSet.has("thread/start"),
threadResume: methodSet.has("thread/resume"),
threadRead: methodSet.has("thread/read"),
threadList: methodSet.has("thread/list"),
threadLoadedList: methodSet.has("thread/loaded/list"),
threadTurnHistory: methodSet.has("thread/turns/list"),
threadInjectItems: methodSet.has("thread/inject_items"),
threadRollback: methodSet.has("thread/rollback"),
threadArchive: methodSet.has("thread/archive"),
@@ -121,6 +223,11 @@ function buildSupportMatrix({ helpText, methods }) {
commandExec: methodSet.has("command/exec"),
realtimeThread: methods.some((method) => method.startsWith("thread/realtime/")),
modelList: methodSet.has("model/list"),
appList: methodSet.has("app/list"),
appListUpdated: methodSet.has("app/list/updated"),
collaborationModeList: methodSet.has("collaborationMode/list"),
configRequirementsRead: methodSet.has("configRequirements/read"),
mcpServerStatusList: methodSet.has("mcpServerStatus/list"),
skillsExtraRoots: methodSet.has("skills/extraRoots/set"),
hooksList: methodSet.has("hooks/list"),
pluginInstall: methodSet.has("plugin/install"),
@@ -197,6 +304,8 @@ function buildSupportMatrix({ helpText, methods }) {
methodSet.has("item/commandExecution/outputDelta"),
terminalInteraction: methodSet.has("item/commandExecution/terminalInteraction"),
fileChangeOutputDelta: methodSet.has("item/fileChange/outputDelta"),
threadCollaborationItems: itemTypeSet.has("collabToolCall"),
contextCompactionItem: itemTypeSet.has("contextCompaction"),
};
}
@@ -216,18 +325,16 @@ async function main() {
await mkdir(schemaDir, { recursive: true });
await mkdir(typescriptDir, { recursive: true });
const helpText = run(options.codexBin, ["app-server", "--help"]);
const helpText = stripTrailingWhitespace(run(options.codexBin, ["app-server", "--help"]));
await writeFile(path.join(snapshotDir, "app-server-help.txt"), helpText, "utf8");
run(options.codexBin, ["app-server", "generate-json-schema", "--out", schemaDir]);
run(options.codexBin, ["app-server", "generate-ts", "--out", typescriptDir]);
const methods = Array.from(
new Set([
...(await extractProtocolMethods(schemaDir)),
...(await extractProtocolMethods(typescriptDir)),
]),
).sort();
const schemaArtifacts = await extractProtocolArtifacts(schemaDir);
const typescriptArtifacts = await extractProtocolArtifacts(typescriptDir);
const methods = Array.from(new Set([...schemaArtifacts.methods, ...typescriptArtifacts.methods])).sort();
const itemTypes = Array.from(new Set([...schemaArtifacts.itemTypes, ...typescriptArtifacts.itemTypes])).sort();
const manifest = {
generatedAt: new Date().toISOString(),
@@ -236,7 +343,8 @@ async function main() {
codexBin: options.codexBin,
snapshotDir,
methods,
supports: buildSupportMatrix({ helpText, methods }),
itemTypes,
supports: buildSupportMatrix({ helpText, methods, itemTypes }),
};
await writeFile(path.join(snapshotDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
console.log(JSON.stringify({ ok: true, snapshotDir, codexVersion, methodCount: methods.length }));