@nyssance/codex-acp-v2 0.7.12 → 0.7.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +1 -1
- package/dist/index.js +55 -141
- package/docs/protocol.md +6 -8
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.14 — 2026-10-16
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- Sub-agent activity carries `model` and `reasoningEffort` in `rawInput` and `_meta.codex.subagent`.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Generated against Codex 0.163.0 (`@openai/codex` ^0.163.0); the version floor follows.
|
|
12
|
+
`generate-types` clears `src/app-server` first, so types Codex removed no longer linger.
|
|
13
|
+
- Command approvals offer exactly the `availableDecisions` Codex sends; a missing list, or one naming
|
|
14
|
+
an amendment Codex did not propose, is cancelled. The adapter no longer builds its own decision set.
|
|
15
|
+
- Review exits produce no update: Codex follows every exit with an assistant message carrying the
|
|
16
|
+
conclusion, and the adapter maps that message. Exit buffering and deduplication are removed.
|
|
17
|
+
- Fast mode availability reads `serviceTiers` only; the deprecated `additionalSpeedTiers` is ignored.
|
|
18
|
+
- `codexErrorInfo` values Codex has no name for classify as `unknown`.
|
|
19
|
+
|
|
20
|
+
## 0.7.13 — 2026-10-13
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Match gateway model capabilities by upstream model, including hidden native models,
|
|
25
|
+
and omit reasoning effort settings when the model declares no supported levels.
|
|
26
|
+
- Normalize unsupported reasoning efforts when selecting a model.
|
|
27
|
+
- Recognize local commands after leading file attachments and preserve attachments
|
|
28
|
+
when `/plan <task>` starts a turn.
|
|
29
|
+
|
|
3
30
|
## 0.7.12 — 2026-10-13
|
|
4
31
|
|
|
5
32
|
### Added
|
package/README.md
CHANGED
|
@@ -59,7 +59,7 @@ See [`docs/protocol.md`](docs/protocol.md) for the exact wire contract.
|
|
|
59
59
|
|
|
60
60
|
## Requirements
|
|
61
61
|
|
|
62
|
-
- Codex CLI **0.
|
|
62
|
+
- Codex CLI **0.163.0 or newer** — the app-server types under `src/app-server` are generated
|
|
63
63
|
from the `@openai/codex` release named in `optionalDependencies`, and the agent checks
|
|
64
64
|
`codex --version` before starting the server; an older Codex fails at startup with an
|
|
65
65
|
upgrade hint.
|
package/dist/index.js
CHANGED
|
@@ -22168,7 +22168,7 @@ var package_default = {
|
|
|
22168
22168
|
publishConfig: {
|
|
22169
22169
|
access: "public"
|
|
22170
22170
|
},
|
|
22171
|
-
version: "0.7.
|
|
22171
|
+
version: "0.7.14",
|
|
22172
22172
|
description: "Native ACP v2 agent for the OpenAI Codex app-server",
|
|
22173
22173
|
packageManager: "bun@1.4.0",
|
|
22174
22174
|
engines: {
|
|
@@ -22203,7 +22203,7 @@ var package_default = {
|
|
|
22203
22203
|
"package:win-x64": "cd dist/bin && zip codex-acp-v2-x64-windows.zip codex-acp-v2-x64-windows.exe",
|
|
22204
22204
|
"package:win-arm64": "cd dist/bin && zip codex-acp-v2-arm64-windows.zip codex-acp-v2-arm64-windows.exe",
|
|
22205
22205
|
start: "bun src/index.ts",
|
|
22206
|
-
"generate-types": "codex app-server generate-ts --experimental --out src/app-server",
|
|
22206
|
+
"generate-types": "rm -rf src/app-server && codex app-server generate-ts --experimental --out src/app-server",
|
|
22207
22207
|
test: "vitest run --exclude src/__tests__/e2e.test.ts",
|
|
22208
22208
|
"test:watch": "vitest",
|
|
22209
22209
|
typecheck: "tsc --noEmit",
|
|
@@ -22241,7 +22241,7 @@ var package_default = {
|
|
|
22241
22241
|
"vscode-jsonrpc": "^9.0.2"
|
|
22242
22242
|
},
|
|
22243
22243
|
optionalDependencies: {
|
|
22244
|
-
"@openai/codex": "^0.
|
|
22244
|
+
"@openai/codex": "^0.163.0"
|
|
22245
22245
|
}
|
|
22246
22246
|
};
|
|
22247
22247
|
|
|
@@ -22385,6 +22385,7 @@ class ClientTools {
|
|
|
22385
22385
|
}
|
|
22386
22386
|
|
|
22387
22387
|
// src/agent/turnErrors.ts
|
|
22388
|
+
var CONNECTION_FAILURES = ["httpConnectionFailed", "responseStreamConnectionFailed", "responseStreamDisconnected", "responseTooManyFailedAttempts"];
|
|
22388
22389
|
function classifyTurnError(info) {
|
|
22389
22390
|
const failure2 = (category, retryable = false, stopReason = "error") => ({ category, retryable, stopReason });
|
|
22390
22391
|
if (info === null)
|
|
@@ -22392,7 +22393,9 @@ function classifyTurnError(info) {
|
|
|
22392
22393
|
if (typeof info === "object") {
|
|
22393
22394
|
if ("activeTurnNotSteerable" in info)
|
|
22394
22395
|
return failure2("turn_not_steerable");
|
|
22395
|
-
const status =
|
|
22396
|
+
const status = connectionStatus(info);
|
|
22397
|
+
if (status === undefined)
|
|
22398
|
+
return failure2("unknown");
|
|
22396
22399
|
return failure2("connection", status === null || status === 408 || status === 429 || status >= 500);
|
|
22397
22400
|
}
|
|
22398
22401
|
switch (info) {
|
|
@@ -22426,6 +22429,14 @@ function classifyTurnError(info) {
|
|
|
22426
22429
|
case "other":
|
|
22427
22430
|
return failure2("unknown");
|
|
22428
22431
|
}
|
|
22432
|
+
return failure2("unknown");
|
|
22433
|
+
}
|
|
22434
|
+
function connectionStatus(info) {
|
|
22435
|
+
for (const key of CONNECTION_FAILURES) {
|
|
22436
|
+
if (key in info)
|
|
22437
|
+
return info[key].httpStatusCode;
|
|
22438
|
+
}
|
|
22439
|
+
return;
|
|
22429
22440
|
}
|
|
22430
22441
|
|
|
22431
22442
|
// src/agent/CodexAgent.ts
|
|
@@ -23672,8 +23683,8 @@ function subAgentActivity(item, status, create) {
|
|
|
23672
23683
|
toolCallId: item.id,
|
|
23673
23684
|
...create ? { name: ToolName.SubAgent, title: titles[item.kind], kind: "other" } : {},
|
|
23674
23685
|
status,
|
|
23675
|
-
rawInput: { agentThreadId: item.agentThreadId, agentPath: item.agentPath, activity: item.kind },
|
|
23676
|
-
_meta: { codex: { subagent: { threadId: item.agentThreadId, path: item.agentPath, activity: item.kind } } }
|
|
23686
|
+
rawInput: { model: item.model, reasoningEffort: item.reasoningEffort, agentThreadId: item.agentThreadId, agentPath: item.agentPath, activity: item.kind },
|
|
23687
|
+
_meta: { codex: { subagent: { model: item.model, reasoningEffort: item.reasoningEffort, threadId: item.agentThreadId, path: item.agentPath, activity: item.kind } } }
|
|
23677
23688
|
});
|
|
23678
23689
|
}
|
|
23679
23690
|
function collabToolCall(item, create) {
|
|
@@ -24263,23 +24274,14 @@ function itemSnapshot(item, command) {
|
|
|
24263
24274
|
case "contextCompaction":
|
|
24264
24275
|
return [compactionUpdate(item.id, "completed")];
|
|
24265
24276
|
case "enteredReviewMode":
|
|
24277
|
+
case "exitedReviewMode":
|
|
24266
24278
|
return [];
|
|
24267
|
-
case "exitedReviewMode": {
|
|
24268
|
-
const text = item.review.trim();
|
|
24269
|
-
return text.length > 0 ? [{ sessionUpdate: "agent_message", messageId: item.id, content: [{ type: "text", text }] }] : [];
|
|
24270
|
-
}
|
|
24271
24279
|
case "hookPrompt":
|
|
24272
24280
|
case "functionCallOutput":
|
|
24273
24281
|
case "sleep":
|
|
24274
24282
|
return [];
|
|
24275
24283
|
}
|
|
24276
24284
|
}
|
|
24277
|
-
function isDuplicateReview(item, items) {
|
|
24278
|
-
if (item.type !== "exitedReviewMode")
|
|
24279
|
-
return false;
|
|
24280
|
-
const text = item.review.trim();
|
|
24281
|
-
return text.length > 0 && Array.from(items).some((candidate) => candidate.type === "agentMessage" && candidate.phase !== "commentary" && candidate.text.trim() === text);
|
|
24282
|
-
}
|
|
24283
24285
|
|
|
24284
24286
|
// src/bridge/turnMetadata.ts
|
|
24285
24287
|
function withTurnId(update, turnId, startedAt) {
|
|
@@ -24321,8 +24323,6 @@ class EventBridge {
|
|
|
24321
24323
|
turnPrompt = null;
|
|
24322
24324
|
openToolCalls = new Set;
|
|
24323
24325
|
announcedTools = new Set;
|
|
24324
|
-
pendingReviews = new Map;
|
|
24325
|
-
reviewMessages = new Map;
|
|
24326
24326
|
messagePhases = new Map;
|
|
24327
24327
|
promptMeta = new Map;
|
|
24328
24328
|
reasoningWithDeltas = new Set;
|
|
@@ -24386,8 +24386,6 @@ class EventBridge {
|
|
|
24386
24386
|
this.openToolCalls.clear();
|
|
24387
24387
|
this.announcedTools.clear();
|
|
24388
24388
|
this.messagePhases.clear();
|
|
24389
|
-
this.reviewMessages.clear();
|
|
24390
|
-
this.pendingReviews.clear();
|
|
24391
24389
|
this.reasoningWithDeltas.clear();
|
|
24392
24390
|
this.terminalItems.clear();
|
|
24393
24391
|
this.imageGenerations.clear();
|
|
@@ -24410,8 +24408,6 @@ class EventBridge {
|
|
|
24410
24408
|
for (const item of items) {
|
|
24411
24409
|
if (item.type === "userMessage")
|
|
24412
24410
|
this.unresolvedUserItems.add(item.id);
|
|
24413
|
-
if (item.type === "agentMessage")
|
|
24414
|
-
this.reviewMessages.set(item.id, item);
|
|
24415
24411
|
if (!replay && itemSnapshot(item).some((update) => update.sessionUpdate === "tool_call_update")) {
|
|
24416
24412
|
this.unannouncedItems.set(item.id, { item, turnId });
|
|
24417
24413
|
}
|
|
@@ -24424,12 +24420,8 @@ class EventBridge {
|
|
|
24424
24420
|
if (receipt)
|
|
24425
24421
|
this.restoredCommands.set(item.id, receipt);
|
|
24426
24422
|
this.unresolvedUserItems.delete(item.id);
|
|
24427
|
-
if (!replay
|
|
24428
|
-
continue;
|
|
24429
|
-
if (item.type === "exitedReviewMode") {
|
|
24430
|
-
this.pendingReviews.set(item.id, { item, turnId });
|
|
24423
|
+
if (!replay)
|
|
24431
24424
|
continue;
|
|
24432
|
-
}
|
|
24433
24425
|
if (item.type === "commandExecution" && item.status === "inProgress" && usesTerminal(item))
|
|
24434
24426
|
this.terminalItems.add(item.id);
|
|
24435
24427
|
for (const update of itemSnapshot(item, receipt ?? undefined)) {
|
|
@@ -24549,7 +24541,6 @@ class EventBridge {
|
|
|
24549
24541
|
}
|
|
24550
24542
|
}
|
|
24551
24543
|
async finishOpenToolCalls(status) {
|
|
24552
|
-
await this.flushReviews();
|
|
24553
24544
|
this.unannouncedItems.clear();
|
|
24554
24545
|
const ids = [...this.openToolCalls];
|
|
24555
24546
|
const terminals = [...this.terminalItems];
|
|
@@ -24574,18 +24565,6 @@ class EventBridge {
|
|
|
24574
24565
|
}
|
|
24575
24566
|
async flush() {
|
|
24576
24567
|
await this.flushPlans();
|
|
24577
|
-
await this.flushReviews();
|
|
24578
|
-
}
|
|
24579
|
-
async flushReviews() {
|
|
24580
|
-
const pending = [...this.pendingReviews.values()];
|
|
24581
|
-
this.pendingReviews.clear();
|
|
24582
|
-
for (const { item, turnId } of pending) {
|
|
24583
|
-
if (isDuplicateReview(item, this.reviewMessages.values()))
|
|
24584
|
-
continue;
|
|
24585
|
-
for (const update of itemSnapshot(item)) {
|
|
24586
|
-
await this.client.update(this.withTiming(withTurnId(update, turnId, this.turnTime?.startedAt), turnId));
|
|
24587
|
-
}
|
|
24588
|
-
}
|
|
24589
24568
|
}
|
|
24590
24569
|
dispose() {
|
|
24591
24570
|
this.beginTurn();
|
|
@@ -24709,6 +24688,8 @@ ${details}` : summary)];
|
|
|
24709
24688
|
case "thread/settings/updated":
|
|
24710
24689
|
case "thread/goal/updated":
|
|
24711
24690
|
case "thread/goal/cleared":
|
|
24691
|
+
case "thread/readState/changed":
|
|
24692
|
+
case "thread/prediction/updated":
|
|
24712
24693
|
case "thread/project/updated":
|
|
24713
24694
|
case "thread/environment/connected":
|
|
24714
24695
|
case "thread/environment/disconnected":
|
|
@@ -24825,7 +24806,6 @@ ${details}` : summary)];
|
|
|
24825
24806
|
async itemCompleted(item, turnId) {
|
|
24826
24807
|
switch (item.type) {
|
|
24827
24808
|
case "agentMessage":
|
|
24828
|
-
this.reviewMessages.set(item.id, item);
|
|
24829
24809
|
this.messagePhases.delete(item.id);
|
|
24830
24810
|
return itemSnapshot(item);
|
|
24831
24811
|
case "reasoning": {
|
|
@@ -24864,15 +24844,12 @@ ${details}` : summary)];
|
|
|
24864
24844
|
return [compactionUpdate(item.id, "completed")];
|
|
24865
24845
|
case "plan":
|
|
24866
24846
|
return await this.planCompleted(item);
|
|
24867
|
-
case "exitedReviewMode": {
|
|
24868
|
-
this.pendingReviews.set(item.id, { item, turnId });
|
|
24869
|
-
return [];
|
|
24870
|
-
}
|
|
24871
24847
|
case "userMessage":
|
|
24872
24848
|
case "hookPrompt":
|
|
24873
24849
|
case "functionCallOutput":
|
|
24874
24850
|
case "sleep":
|
|
24875
24851
|
case "enteredReviewMode":
|
|
24852
|
+
case "exitedReviewMode":
|
|
24876
24853
|
return [];
|
|
24877
24854
|
}
|
|
24878
24855
|
}
|
|
@@ -25224,6 +25201,7 @@ class AppServerClient {
|
|
|
25224
25201
|
threadId,
|
|
25225
25202
|
turn: {
|
|
25226
25203
|
id: turnId,
|
|
25204
|
+
rootTurnId: null,
|
|
25227
25205
|
items: [],
|
|
25228
25206
|
itemsView: "notLoaded",
|
|
25229
25207
|
status: "interrupted",
|
|
@@ -25237,7 +25215,7 @@ class AppServerClient {
|
|
|
25237
25215
|
failTurn(threadId, turnId, error40) {
|
|
25238
25216
|
this.recordTurnCompleted({
|
|
25239
25217
|
threadId,
|
|
25240
|
-
turn: { id: turnId, items: [], itemsView: "notLoaded", status: "failed", error: error40, startedAt: null, completedAt: null, durationMs: null }
|
|
25218
|
+
turn: { id: turnId, rootTurnId: null, items: [], itemsView: "notLoaded", status: "failed", error: error40, startedAt: null, completedAt: null, durationMs: null }
|
|
25241
25219
|
});
|
|
25242
25220
|
}
|
|
25243
25221
|
recordTurnCompleted(event) {
|
|
@@ -25390,7 +25368,7 @@ class AppServerClient {
|
|
|
25390
25368
|
const cursors = new Set;
|
|
25391
25369
|
let cursor = null;
|
|
25392
25370
|
do {
|
|
25393
|
-
const page = await this.modelList({ cursor, limit: null });
|
|
25371
|
+
const page = await this.modelList({ cursor, limit: null, includeHidden: true });
|
|
25394
25372
|
models.push(...page.data);
|
|
25395
25373
|
cursor = page.nextCursor;
|
|
25396
25374
|
if (cursor) {
|
|
@@ -25805,12 +25783,12 @@ class ProviderRouting {
|
|
|
25805
25783
|
if (first.mode === "route") {
|
|
25806
25784
|
if (first.models.length === 0)
|
|
25807
25785
|
return codexCatalog;
|
|
25808
|
-
return first.models.map((model, index) => gatewayModel(model, index === 0, codexCatalog
|
|
25786
|
+
return first.models.map((model, index) => gatewayModel(model, index === 0, codexCatalog.find((entry) => entry.model === model.id), first.catalogEntries.get(model.id)));
|
|
25809
25787
|
}
|
|
25810
25788
|
const own = [];
|
|
25811
25789
|
for (const gateway of this.gateways.values()) {
|
|
25812
25790
|
for (const model of gateway.models)
|
|
25813
|
-
own.push({ ...gatewayModel(model, false, codexCatalog
|
|
25791
|
+
own.push({ ...gatewayModel(model, false, codexCatalog.find((entry) => entry.model === model.id), gateway.catalogEntries.get(model.id)), id: gatewayModelId(gateway.id, model.id) });
|
|
25814
25792
|
}
|
|
25815
25793
|
return [...codexCatalog, ...own];
|
|
25816
25794
|
}
|
|
@@ -25917,7 +25895,7 @@ function readCatalogFile(catalogPath) {
|
|
|
25917
25895
|
return entries;
|
|
25918
25896
|
}
|
|
25919
25897
|
function gatewayModel(model, isDefault, template, entry) {
|
|
25920
|
-
const efforts = entry
|
|
25898
|
+
const efforts = entry ? entry.efforts : template?.supportedReasoningEfforts ?? [];
|
|
25921
25899
|
const defaultEffort = entry?.defaultEffort ?? template?.defaultReasoningEffort ?? "medium";
|
|
25922
25900
|
return {
|
|
25923
25901
|
id: model.id,
|
|
@@ -25952,13 +25930,13 @@ var FAST_SERVICE_TIER = "fast";
|
|
|
25952
25930
|
function resolveModelSelection(catalog, modelId, effort) {
|
|
25953
25931
|
const listed = modelId === null ? undefined : catalog.find((model) => model.id === modelId);
|
|
25954
25932
|
if (listed)
|
|
25955
|
-
return { model: listed.id, effort: effort ?? listed.defaultReasoningEffort };
|
|
25933
|
+
return { model: listed.id, effort: coerceEffort(listed, effort ?? listed.defaultReasoningEffort) };
|
|
25956
25934
|
if (modelId)
|
|
25957
25935
|
return { model: modelId, effort: effort ?? DEFAULT_EFFORT };
|
|
25958
25936
|
const fallback = catalog.find((model) => model.isDefault) ?? catalog[0];
|
|
25959
25937
|
if (!fallback)
|
|
25960
25938
|
throw new Error("Codex did not return any models");
|
|
25961
|
-
return { model: fallback.id, effort: effort ?? fallback.defaultReasoningEffort };
|
|
25939
|
+
return { model: fallback.id, effort: coerceEffort(fallback, effort ?? fallback.defaultReasoningEffort) };
|
|
25962
25940
|
}
|
|
25963
25941
|
function findModel(catalog, modelId) {
|
|
25964
25942
|
return catalog.find((model) => model.id === modelId);
|
|
@@ -25975,9 +25953,7 @@ function coerceEffort(model, effort) {
|
|
|
25975
25953
|
return model?.defaultReasoningEffort ?? options[0].reasoningEffort;
|
|
25976
25954
|
}
|
|
25977
25955
|
function modelSupportsFast(model) {
|
|
25978
|
-
|
|
25979
|
-
return false;
|
|
25980
|
-
return model.serviceTiers.some((tier) => tier.id === FAST_SERVICE_TIER) || model.additionalSpeedTiers.includes(FAST_SERVICE_TIER);
|
|
25956
|
+
return model?.serviceTiers.some((tier) => tier.id === FAST_SERVICE_TIER) ?? false;
|
|
25981
25957
|
}
|
|
25982
25958
|
function modelSupportsImages(model) {
|
|
25983
25959
|
return model?.inputModalities.includes("image") ?? true;
|
|
@@ -26077,81 +26053,20 @@ function capitalize(value) {
|
|
|
26077
26053
|
|
|
26078
26054
|
// src/permissions/commandDecisions.ts
|
|
26079
26055
|
function availableCommandDecisions(params) {
|
|
26080
|
-
|
|
26081
|
-
|
|
26082
|
-
}
|
|
26083
|
-
if (!Array.isArray(params.availableDecisions) || params.availableDecisions.length === 0)
|
|
26056
|
+
const decisions = params.availableDecisions;
|
|
26057
|
+
if (!decisions || decisions.length === 0)
|
|
26084
26058
|
return;
|
|
26085
|
-
|
|
26086
|
-
for (const candidate of params.availableDecisions) {
|
|
26087
|
-
const decision = parseDecision(candidate, params);
|
|
26088
|
-
if (!decision)
|
|
26089
|
-
return;
|
|
26090
|
-
decisions.push(decision);
|
|
26091
|
-
}
|
|
26092
|
-
return decisions;
|
|
26093
|
-
}
|
|
26094
|
-
function defaultDecisions(params) {
|
|
26095
|
-
if (params.networkApprovalContext) {
|
|
26096
|
-
const decisions2 = ["accept", "acceptForSession"];
|
|
26097
|
-
for (const amendment of params.proposedNetworkPolicyAmendments ?? []) {
|
|
26098
|
-
decisions2.push({ applyNetworkPolicyAmendment: { network_policy_amendment: amendment } });
|
|
26099
|
-
}
|
|
26100
|
-
decisions2.push("decline", "cancel");
|
|
26101
|
-
return decisions2;
|
|
26102
|
-
}
|
|
26103
|
-
if (params.additionalPermissions)
|
|
26104
|
-
return ["accept", "cancel"];
|
|
26105
|
-
const decisions = ["accept", "acceptForSession"];
|
|
26106
|
-
if (params.proposedExecpolicyAmendment && params.proposedExecpolicyAmendment.length > 0) {
|
|
26107
|
-
decisions.push({ acceptWithExecpolicyAmendment: { execpolicy_amendment: params.proposedExecpolicyAmendment } });
|
|
26108
|
-
}
|
|
26109
|
-
decisions.push("decline", "cancel");
|
|
26110
|
-
return decisions;
|
|
26059
|
+
return decisions.every((decision) => proposed(decision, params)) ? decisions : undefined;
|
|
26111
26060
|
}
|
|
26112
|
-
function
|
|
26113
|
-
if (
|
|
26114
|
-
return
|
|
26115
|
-
|
|
26116
|
-
|
|
26117
|
-
return;
|
|
26118
|
-
if ("acceptWithExecpolicyAmendment" in candidate) {
|
|
26119
|
-
const value = candidate["acceptWithExecpolicyAmendment"];
|
|
26120
|
-
if (!isRecord2(value))
|
|
26121
|
-
return;
|
|
26122
|
-
const amendment = value["execpolicy_amendment"];
|
|
26123
|
-
if (!isStringArray(amendment) || amendment.length === 0)
|
|
26124
|
-
return;
|
|
26125
|
-
if (!sameStrings(amendment, params.proposedExecpolicyAmendment))
|
|
26126
|
-
return;
|
|
26127
|
-
return { acceptWithExecpolicyAmendment: { execpolicy_amendment: [...amendment] } };
|
|
26128
|
-
}
|
|
26129
|
-
if ("applyNetworkPolicyAmendment" in candidate) {
|
|
26130
|
-
const value = candidate["applyNetworkPolicyAmendment"];
|
|
26131
|
-
if (!isRecord2(value))
|
|
26132
|
-
return;
|
|
26133
|
-
const amendment = parseNetworkAmendment(value["network_policy_amendment"]);
|
|
26134
|
-
if (!amendment || !params.networkApprovalContext)
|
|
26135
|
-
return;
|
|
26136
|
-
if (amendment.host !== params.networkApprovalContext.host)
|
|
26137
|
-
return;
|
|
26138
|
-
const proposed = params.proposedNetworkPolicyAmendments ?? [];
|
|
26139
|
-
if (!proposed.some((entry) => entry.host === amendment.host && entry.action === amendment.action))
|
|
26140
|
-
return;
|
|
26141
|
-
return { applyNetworkPolicyAmendment: { network_policy_amendment: amendment } };
|
|
26061
|
+
function proposed(decision, params) {
|
|
26062
|
+
if (typeof decision === "string")
|
|
26063
|
+
return true;
|
|
26064
|
+
if ("acceptWithExecpolicyAmendment" in decision) {
|
|
26065
|
+
const amendment2 = decision.acceptWithExecpolicyAmendment.execpolicy_amendment;
|
|
26066
|
+
return amendment2.length > 0 && sameStrings(amendment2, params.proposedExecpolicyAmendment);
|
|
26142
26067
|
}
|
|
26143
|
-
|
|
26144
|
-
|
|
26145
|
-
function parseNetworkAmendment(value) {
|
|
26146
|
-
if (!isRecord2(value) || typeof value["host"] !== "string")
|
|
26147
|
-
return;
|
|
26148
|
-
const action = value["action"];
|
|
26149
|
-
if (action !== "allow" && action !== "deny")
|
|
26150
|
-
return;
|
|
26151
|
-
return { host: value["host"], action };
|
|
26152
|
-
}
|
|
26153
|
-
function isStringArray(value) {
|
|
26154
|
-
return Array.isArray(value) && value.every((entry) => typeof entry === "string");
|
|
26068
|
+
const amendment = decision.applyNetworkPolicyAmendment.network_policy_amendment;
|
|
26069
|
+
return amendment.host === params.networkApprovalContext?.host && (params.proposedNetworkPolicyAmendments ?? []).some((entry) => entry.host === amendment.host && entry.action === amendment.action);
|
|
26155
26070
|
}
|
|
26156
26071
|
function sameStrings(left, right) {
|
|
26157
26072
|
return !!right && left.length === right.length && left.every((value, index) => value === right[index]);
|
|
@@ -26394,8 +26309,7 @@ class CodexApprovalHandler {
|
|
|
26394
26309
|
this.signal = signal;
|
|
26395
26310
|
}
|
|
26396
26311
|
async handleCommandExecution(params) {
|
|
26397
|
-
const
|
|
26398
|
-
const decisions = commandDecisionOptions(approvalParams);
|
|
26312
|
+
const decisions = commandDecisionOptions(params);
|
|
26399
26313
|
if (!decisions) {
|
|
26400
26314
|
logger.log("cancelling command approval: no complete decision set", { itemId: params.itemId });
|
|
26401
26315
|
return { decision: "cancel" };
|
|
@@ -26404,7 +26318,7 @@ class CodexApprovalHandler {
|
|
|
26404
26318
|
const response = await this.client.requestPermission({
|
|
26405
26319
|
title: params.networkApprovalContext ? NETWORK_TITLE : COMMAND_TITLE,
|
|
26406
26320
|
description: nonBlank(params.reason),
|
|
26407
|
-
subject: { type: "tool_call", toolCall: commandSubject(
|
|
26321
|
+
subject: { type: "tool_call", toolCall: commandSubject(params) },
|
|
26408
26322
|
options: decisions.map(({ option }) => option)
|
|
26409
26323
|
}, this.signal());
|
|
26410
26324
|
return { decision: selectedDecision(response, decisions) ?? "cancel" };
|
|
@@ -27814,7 +27728,7 @@ var BUILTIN_COMMANDS = [
|
|
|
27814
27728
|
{ name: "logout", description: "Sign out of the Codex account" }
|
|
27815
27729
|
];
|
|
27816
27730
|
function parseCommand(prompt) {
|
|
27817
|
-
const first = prompt
|
|
27731
|
+
const first = prompt.find(ContentBlock.isText);
|
|
27818
27732
|
if (!first || first.type !== "text")
|
|
27819
27733
|
return null;
|
|
27820
27734
|
const text2 = first.text.trim();
|
|
@@ -27899,7 +27813,7 @@ function statusMessage(session, rateLimits) {
|
|
|
27899
27813
|
"**Session**",
|
|
27900
27814
|
`**Session ID:** ${session.id}`,
|
|
27901
27815
|
`**Working directory:** ${session.cwd}`,
|
|
27902
|
-
`**Model:** ${session.model.model} (${session.model.effort})`,
|
|
27816
|
+
`**Model:** ${session.model.model}${supportedEfforts(findModel(session.catalog, session.model.model)).length > 0 ? ` (${session.model.effort})` : ""}`,
|
|
27903
27817
|
`**Mode:** ${session.mode.name}`,
|
|
27904
27818
|
`**Collaboration:** ${session.collaborationMode}`,
|
|
27905
27819
|
`**Fast mode:** ${session.fastMode ? "on" : "off"}`,
|
|
@@ -27962,7 +27876,7 @@ function sessionConfigOptions(session) {
|
|
|
27962
27876
|
const options = [
|
|
27963
27877
|
modeConfigOption(session.mode),
|
|
27964
27878
|
modelConfigOption(session.catalog, session.model.model, session.gatewayGroups),
|
|
27965
|
-
effortConfigOption(supportedEfforts(model), session.model.effort),
|
|
27879
|
+
...supportedEfforts(model).length > 0 ? [effortConfigOption(supportedEfforts(model), session.model.effort)] : [],
|
|
27966
27880
|
collaborationModeConfigOption(session.collaborationMode)
|
|
27967
27881
|
];
|
|
27968
27882
|
if (modelSupportsFast(model))
|
|
@@ -27990,7 +27904,7 @@ async function applyConfigOption(session, codex, request) {
|
|
|
27990
27904
|
const effort = stringValue(request);
|
|
27991
27905
|
const model = findModel(session.catalog, session.model.model);
|
|
27992
27906
|
const efforts = supportedEfforts(model);
|
|
27993
|
-
if (
|
|
27907
|
+
if (!efforts.some((option) => option.reasoningEffort === effort)) {
|
|
27994
27908
|
throw RequestError.invalidParams({ configId: request.configId }, `Model ${session.model.model} does not support effort "${effort}"`);
|
|
27995
27909
|
}
|
|
27996
27910
|
session.model = { ...session.model, effort };
|
|
@@ -28014,7 +27928,7 @@ async function applyConfigOption(session, codex, request) {
|
|
|
28014
27928
|
threadId: session.id,
|
|
28015
27929
|
collaborationMode: {
|
|
28016
27930
|
mode,
|
|
28017
|
-
settings: { model: findModel(session.catalog, session.model.model)?.model ?? session.model.model, reasoning_effort: session.model.effort, developer_instructions: null }
|
|
27931
|
+
settings: { model: findModel(session.catalog, session.model.model)?.model ?? session.model.model, reasoning_effort: supportedEfforts(findModel(session.catalog, session.model.model)).length > 0 ? session.model.effort : null, developer_instructions: null }
|
|
28018
27932
|
}
|
|
28019
27933
|
});
|
|
28020
27934
|
session.collaborationMode = mode;
|
|
@@ -28037,8 +27951,6 @@ function historyUpdates(turns, commandReceipts = new Map) {
|
|
|
28037
27951
|
for (const turn of turns) {
|
|
28038
27952
|
const openingUser = turn.items.find((item) => item.type === "userMessage");
|
|
28039
27953
|
for (const item of turn.items) {
|
|
28040
|
-
if (isDuplicateReview(item, turn.items))
|
|
28041
|
-
continue;
|
|
28042
27954
|
const command = item.type === "userMessage" ? commandReceipts.get(item.id) : undefined;
|
|
28043
27955
|
updates.push(...itemSnapshot(item, command).map((update) => {
|
|
28044
27956
|
const replay = withTurnId(update, turn.id, turn.startedAt);
|
|
@@ -28302,7 +28214,7 @@ async function readSessionHistory(codex, raw, signal, commandReceipts) {
|
|
|
28302
28214
|
if (view === "full")
|
|
28303
28215
|
await read.call(() => assertCommandHistory(codex, start.thread, commandReceipts, signal));
|
|
28304
28216
|
const turns = await read.call(() => Promise.all(page.data.map(async (turn) => {
|
|
28305
|
-
const projections = view === "full" ? await Promise.all(turn.items.
|
|
28217
|
+
const projections = view === "full" ? await Promise.all(turn.items.map(async (item) => projectHistoryItem(item, turn.id, turn.startedAt, item.type === "userMessage" ? await commandReceipts?.read(item.id) : null))) : [];
|
|
28306
28218
|
return {
|
|
28307
28219
|
turnId: turn.id,
|
|
28308
28220
|
status: turn.status,
|
|
@@ -29664,7 +29576,9 @@ class CodexAgent {
|
|
|
29664
29576
|
case "config":
|
|
29665
29577
|
await this.setSessionConfigOption({ sessionId: session.id, configId: outcome.configId, type: "id", value: outcome.value });
|
|
29666
29578
|
if (outcome.promptText) {
|
|
29667
|
-
|
|
29579
|
+
const promptText = outcome.promptText;
|
|
29580
|
+
const commandText = params.prompt.find(ContentBlock.isText);
|
|
29581
|
+
completed = await this.runCodexTurn(runtime, turn, params.prompt.map((block) => block === commandText ? { ...block, type: "text", text: promptText } : block));
|
|
29668
29582
|
} else {
|
|
29669
29583
|
await client.update(agentMessage(`command:${turn.clientUserMessageId}`, `Plan mode ${outcome.value === PLAN_COLLABORATION_MODE ? "enabled" : "disabled"}.`));
|
|
29670
29584
|
}
|
|
@@ -29717,7 +29631,7 @@ class CodexAgent {
|
|
|
29717
29631
|
approvalsReviewer: session.mode.approvalsReviewer,
|
|
29718
29632
|
sandboxPolicy: withWritableRoots(session.mode.sandboxPolicy, session.additionalDirectories),
|
|
29719
29633
|
model: this.providers.upstreamModel(session.model.model),
|
|
29720
|
-
effort: session.model.effort,
|
|
29634
|
+
effort: supportedEfforts(model).length > 0 ? session.model.effort : null,
|
|
29721
29635
|
summary: disableSummary ? "none" : "auto",
|
|
29722
29636
|
serviceTier: session.fastMode ? FAST_SERVICE_TIER : null
|
|
29723
29637
|
}, (turnId) => {
|
|
@@ -30089,7 +30003,7 @@ function usageOf(session) {
|
|
|
30089
30003
|
function interruptedTurn(threadId) {
|
|
30090
30004
|
return {
|
|
30091
30005
|
threadId,
|
|
30092
|
-
turn: { id: "", items: [], itemsView: "notLoaded", status: "interrupted", error: null, startedAt: null, completedAt: null, durationMs: null }
|
|
30006
|
+
turn: { id: "", rootTurnId: null, items: [], itemsView: "notLoaded", status: "interrupted", error: null, startedAt: null, completedAt: null, durationMs: null }
|
|
30093
30007
|
};
|
|
30094
30008
|
}
|
|
30095
30009
|
function parseSessionListParams(raw) {
|
package/docs/protocol.md
CHANGED
|
@@ -309,7 +309,7 @@ perform the separate reversible `_codex/session_archive` operation.
|
|
|
309
309
|
| --- | --- | --- | --- |
|
|
310
310
|
| `mode` | `mode` | select | `read-only`, `agent`, `agent-full-access` |
|
|
311
311
|
| `model` | `model` | select | Codex catalog ids (the current id is always listed) |
|
|
312
|
-
| `effort` | `thought_level` | select | efforts supported by the current model |
|
|
312
|
+
| `effort` | `thought_level` | select | efforts supported by the current model; omitted when no levels are declared |
|
|
313
313
|
| `collaboration_mode` | `model_config` | select | `default`, `plan` |
|
|
314
314
|
| `fast_mode` | `model_config` | boolean | only when the model offers the `fast` service tier |
|
|
315
315
|
|
|
@@ -490,7 +490,7 @@ to status `cancelled`, and a cancelled turn marks every still-open tool call
|
|
|
490
490
|
| `web_search` | `fetch` | web search |
|
|
491
491
|
| `view_image` | `read` | image view |
|
|
492
492
|
| `image_generation` | `other` | image generation; result as image content |
|
|
493
|
-
| `subagent`, `collab` | `other` | sub-agent activity and collaboration calls; `_meta.codex.subagent` / `_meta.codex.collaboration` |
|
|
493
|
+
| `subagent`, `collab` | `other` | sub-agent activity and collaboration calls; `_meta.codex.subagent` (`model`, `reasoningEffort`, `threadId`, `path`, `activity`) / `_meta.codex.collaboration` |
|
|
494
494
|
| `fuzzy_file_search` | `search` | Codex fuzzy file search sessions |
|
|
495
495
|
| `guardian_review` | `think` | auto-approval reviews |
|
|
496
496
|
| `mcp_startup` | `other` | failed MCP server startups (status `failed`) |
|
|
@@ -505,7 +505,7 @@ unknown ids, and transport errors fail closed.
|
|
|
505
505
|
|
|
506
506
|
| Title | Options (`optionId` → Codex decision) |
|
|
507
507
|
| --- | --- |
|
|
508
|
-
| `Run command?` / `Allow network access?` | `allow_once` → accept, `allow_for_session` → acceptForSession, `accept_execpolicy_amendment` → acceptWithExecpolicyAmendment, `apply_network_policy_amendment:<n>` → applyNetworkPolicyAmendment, `decline`, `cancel`.
|
|
508
|
+
| `Run command?` / `Allow network access?` | `allow_once` → accept, `allow_for_session` → acceptForSession, `accept_execpolicy_amendment` → acceptWithExecpolicyAmendment, `apply_network_policy_amendment:<n>` → applyNetworkPolicyAmendment, `decline`, `cancel`. Codex always sends `availableDecisions` and that list is authoritative; a missing list, or one naming an amendment Codex did not propose, fails closed with `cancel`. |
|
|
509
509
|
| `Make edits?` | `allow_once` → accept, `allow_for_session` → acceptForSession, `cancel` |
|
|
510
510
|
| `Grant permissions?` | `allow_permissions_turn`, `allow_permissions_turn_strict_auto_review`, `allow_permissions_session`, `reject_permissions` |
|
|
511
511
|
| MCP approvals | `allow_once` / `accept`, `allow_session`, `allow_always` (only when Codex advertises `persist`), `decline`, `cancel` |
|
|
@@ -777,11 +777,9 @@ also contains ordered `{text, question?}` display parts. Receipt-adopting client
|
|
|
777
777
|
that retain optimistic prompt content use these parts for display and copying;
|
|
778
778
|
this does not alter the original input sent to Codex.
|
|
779
779
|
|
|
780
|
-
Review results:
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
A standalone or different review result remains visible. Item-level history retains
|
|
784
|
-
its per-item projection because the matching answer may be outside the requested page.
|
|
780
|
+
Review results: Codex follows every `exitedReviewMode` item with an assistant message
|
|
781
|
+
carrying the same conclusion, including the notice for an interrupted review. The adapter
|
|
782
|
+
maps only that message; review entry and exit markers produce no updates, live or in history.
|
|
785
783
|
|
|
786
784
|
### Plugin enablement
|
|
787
785
|
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"publishConfig": {
|
|
4
4
|
"access": "public"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.7.
|
|
6
|
+
"version": "0.7.14",
|
|
7
7
|
"description": "Native ACP v2 agent for the OpenAI Codex app-server",
|
|
8
8
|
"packageManager": "bun@1.4.0",
|
|
9
9
|
"engines": {
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"package:win-x64": "cd dist/bin && zip codex-acp-v2-x64-windows.zip codex-acp-v2-x64-windows.exe",
|
|
39
39
|
"package:win-arm64": "cd dist/bin && zip codex-acp-v2-arm64-windows.zip codex-acp-v2-arm64-windows.exe",
|
|
40
40
|
"start": "bun src/index.ts",
|
|
41
|
-
"generate-types": "codex app-server generate-ts --experimental --out src/app-server",
|
|
41
|
+
"generate-types": "rm -rf src/app-server && codex app-server generate-ts --experimental --out src/app-server",
|
|
42
42
|
"test": "vitest run --exclude src/__tests__/e2e.test.ts",
|
|
43
43
|
"test:watch": "vitest",
|
|
44
44
|
"typecheck": "tsc --noEmit",
|
|
@@ -76,6 +76,6 @@
|
|
|
76
76
|
"vscode-jsonrpc": "^9.0.2"
|
|
77
77
|
},
|
|
78
78
|
"optionalDependencies": {
|
|
79
|
-
"@openai/codex": "^0.
|
|
79
|
+
"@openai/codex": "^0.163.0"
|
|
80
80
|
}
|
|
81
81
|
}
|