remote-codex 0.11.53 → 0.11.55
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/apps/relay-server/dist/index.js +6 -7
- package/apps/supervisor-api/dist/index.js +284 -22
- package/bin/remote-codex.mjs +4 -1
- package/package.json +1 -1
- package/packages/acp/src/catalog-runtime.test.ts +56 -0
- package/packages/acp/src/catalog-runtime.ts +17 -1
- package/packages/acp/src/runtimeAdapter.test.ts +106 -0
- package/packages/acp/src/runtimeAdapter.ts +162 -5
- package/packages/acp/src/test/fixtures/fake-acp-agent.mjs +56 -2
- package/packages/codex/src/historyItems.test.ts +21 -0
- package/packages/codex/src/historyItems.ts +10 -3
|
@@ -3713,6 +3713,8 @@ var HostedSandboxService = class {
|
|
|
3713
3713
|
var RELAY_REQUEST_TIMEOUT_MS = 3e4;
|
|
3714
3714
|
var RELAY_PORTAL_METADATA_TIMEOUT_MS = 900;
|
|
3715
3715
|
var WEBSOCKET_OPEN = 1;
|
|
3716
|
+
var SUPERVISOR_REPLACED_CLOSE_CODE = 1012;
|
|
3717
|
+
var SUPERVISOR_REPLACED_CLOSE_REASON = "Supervisor connection replaced.";
|
|
3716
3718
|
var hostedBootstrapPromises = /* @__PURE__ */ new Map();
|
|
3717
3719
|
var RELAY_COOKIE_NAME = "remote_codex_relay_session";
|
|
3718
3720
|
var threadAccessSchema = z.enum(["read", "control"]);
|
|
@@ -4654,16 +4656,13 @@ function buildRelayServer(config2, options = {}) {
|
|
|
4654
4656
|
}
|
|
4655
4657
|
const connectedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4656
4658
|
const existing = state.supervisors.get(deviceId);
|
|
4657
|
-
existing?.socket.send(
|
|
4658
|
-
JSON.stringify({
|
|
4659
|
-
type: "relay.connected",
|
|
4660
|
-
timestamp: connectedAt,
|
|
4661
|
-
deviceId
|
|
4662
|
-
})
|
|
4663
|
-
);
|
|
4664
4659
|
existing?.clientSockets.forEach(
|
|
4665
4660
|
(clientConnection) => clientConnection.socket.close()
|
|
4666
4661
|
);
|
|
4662
|
+
existing?.socket.close(
|
|
4663
|
+
SUPERVISOR_REPLACED_CLOSE_CODE,
|
|
4664
|
+
SUPERVISOR_REPLACED_CLOSE_REASON
|
|
4665
|
+
);
|
|
4667
4666
|
const connection = {
|
|
4668
4667
|
deviceId,
|
|
4669
4668
|
socket,
|
|
@@ -8934,13 +8934,16 @@ function deferLargeHistoryItemDetails(turn, deferredDetails) {
|
|
|
8934
8934
|
};
|
|
8935
8935
|
}
|
|
8936
8936
|
function visibleRuntimeTurnItems(items) {
|
|
8937
|
-
const
|
|
8937
|
+
const nonEmptyItems = items.filter(
|
|
8938
|
+
(item) => !((item.kind === "agentMessage" || item.kind === "reasoning") && item.text.trim().length === 0)
|
|
8939
|
+
);
|
|
8940
|
+
const hasFinalAgentMessage = nonEmptyItems.some(
|
|
8938
8941
|
(item) => item.kind === "agentMessage" && !isTransientAgentHistoryItem(item)
|
|
8939
8942
|
);
|
|
8940
8943
|
if (!hasFinalAgentMessage) {
|
|
8941
|
-
return
|
|
8944
|
+
return nonEmptyItems;
|
|
8942
8945
|
}
|
|
8943
|
-
return
|
|
8946
|
+
return nonEmptyItems.filter(
|
|
8944
8947
|
(item) => !(item.kind === "agentMessage" && isTransientAgentHistoryItem(item))
|
|
8945
8948
|
);
|
|
8946
8949
|
}
|
|
@@ -21500,6 +21503,50 @@ function reasoningOptions(configOptions) {
|
|
|
21500
21503
|
defaultEffort: normalizeAcpEffort(option.currentValue)
|
|
21501
21504
|
};
|
|
21502
21505
|
}
|
|
21506
|
+
function legacyModelsFromResponse(response) {
|
|
21507
|
+
if (!response || typeof response !== "object") {
|
|
21508
|
+
return null;
|
|
21509
|
+
}
|
|
21510
|
+
const models = response.models;
|
|
21511
|
+
if (!models || typeof models !== "object") {
|
|
21512
|
+
return null;
|
|
21513
|
+
}
|
|
21514
|
+
const availableModels = models.availableModels;
|
|
21515
|
+
return Array.isArray(availableModels) ? models : null;
|
|
21516
|
+
}
|
|
21517
|
+
function legacyModelOptions(models) {
|
|
21518
|
+
const currentModelId = models.currentModelId ?? null;
|
|
21519
|
+
return (models.availableModels ?? []).flatMap((model, index) => {
|
|
21520
|
+
if (!model.modelId) {
|
|
21521
|
+
return [];
|
|
21522
|
+
}
|
|
21523
|
+
const efforts = (model._meta?.reasoningEfforts ?? []).flatMap((entry) => {
|
|
21524
|
+
const reasoningEffort = normalizeAcpEffort(entry.value ?? entry.id);
|
|
21525
|
+
return reasoningEffort ? [{
|
|
21526
|
+
reasoningEffort,
|
|
21527
|
+
description: entry.description ?? entry.label ?? ""
|
|
21528
|
+
}] : [];
|
|
21529
|
+
});
|
|
21530
|
+
const declaredDefault = model._meta?.reasoningEfforts?.find(
|
|
21531
|
+
(entry) => entry.default
|
|
21532
|
+
);
|
|
21533
|
+
return [{
|
|
21534
|
+
id: model.modelId,
|
|
21535
|
+
model: model.modelId,
|
|
21536
|
+
displayName: model.name ?? model.modelId,
|
|
21537
|
+
description: model.description ?? "",
|
|
21538
|
+
isDefault: model.modelId === currentModelId || !currentModelId && index === 0,
|
|
21539
|
+
hidden: false,
|
|
21540
|
+
supportedReasoningEfforts: efforts,
|
|
21541
|
+
defaultReasoningEffort: normalizeAcpEffort(model._meta?.reasoningEffort) ?? normalizeAcpEffort(declaredDefault?.value ?? declaredDefault?.id),
|
|
21542
|
+
selectionKind: "model"
|
|
21543
|
+
}];
|
|
21544
|
+
});
|
|
21545
|
+
}
|
|
21546
|
+
function selectedLegacyModel(state) {
|
|
21547
|
+
const models = state.legacyModels?.availableModels ?? [];
|
|
21548
|
+
return models.find((model) => model.modelId === state.model) ?? models[0] ?? null;
|
|
21549
|
+
}
|
|
21503
21550
|
function permissionOptions(params) {
|
|
21504
21551
|
return params.options.map((option) => ({
|
|
21505
21552
|
id: option.optionId,
|
|
@@ -21792,7 +21839,32 @@ var AcpRuntimeAdapter = class extends EventEmitter8 {
|
|
|
21792
21839
|
async inspectModelOptions(cwd) {
|
|
21793
21840
|
const sessionCapabilities = this.initializeResponse?.agentCapabilities?.sessionCapabilities;
|
|
21794
21841
|
if (!sessionCapabilities?.delete) {
|
|
21795
|
-
|
|
21842
|
+
const normalizedCwd = path16.resolve(cwd);
|
|
21843
|
+
const loadedSession = [...this.sessions.values()].find(
|
|
21844
|
+
(session) => session.cwd === normalizedCwd && (session.configOptions.length > 0 || session.legacyModels !== null)
|
|
21845
|
+
);
|
|
21846
|
+
if (!loadedSession) {
|
|
21847
|
+
return this.listModels();
|
|
21848
|
+
}
|
|
21849
|
+
if (loadedSession.legacyModels) {
|
|
21850
|
+
return legacyModelOptions(loadedSession.legacyModels);
|
|
21851
|
+
}
|
|
21852
|
+
const reasoning = reasoningOptions(loadedSession.configOptions);
|
|
21853
|
+
const supportsPerformanceMode2 = loadedSession.configOptions.some(
|
|
21854
|
+
(option) => option.id === "fast-mode" || option.category === "model_config"
|
|
21855
|
+
);
|
|
21856
|
+
return [{
|
|
21857
|
+
id: "default",
|
|
21858
|
+
model: "default",
|
|
21859
|
+
displayName: "Agent default",
|
|
21860
|
+
description: "Use the model configured by the ACP agent.",
|
|
21861
|
+
isDefault: true,
|
|
21862
|
+
hidden: false,
|
|
21863
|
+
supportsPerformanceMode: supportsPerformanceMode2,
|
|
21864
|
+
supportedReasoningEfforts: reasoning.efforts,
|
|
21865
|
+
defaultReasoningEffort: reasoning.defaultEffort,
|
|
21866
|
+
selectionKind: "model"
|
|
21867
|
+
}];
|
|
21796
21868
|
}
|
|
21797
21869
|
const context = await this.requireContext();
|
|
21798
21870
|
const response = await context.request(methods.agent.session.new, {
|
|
@@ -21806,6 +21878,10 @@ var AcpRuntimeAdapter = class extends EventEmitter8 {
|
|
|
21806
21878
|
(option) => option.id === "fast-mode" || option.category === "model_config"
|
|
21807
21879
|
);
|
|
21808
21880
|
try {
|
|
21881
|
+
const legacyModels = legacyModelsFromResponse(response);
|
|
21882
|
+
if (legacyModels) {
|
|
21883
|
+
return legacyModelOptions(legacyModels);
|
|
21884
|
+
}
|
|
21809
21885
|
const modelOption = configOptionByCategory(configOptions, "model");
|
|
21810
21886
|
if (!modelOption || modelOption.type !== "select") {
|
|
21811
21887
|
const reasoning = reasoningOptions(configOptions);
|
|
@@ -21991,6 +22067,7 @@ var AcpRuntimeAdapter = class extends EventEmitter8 {
|
|
|
21991
22067
|
}
|
|
21992
22068
|
forked.modes = response.modes ?? forked.modes;
|
|
21993
22069
|
forked.configOptions = response.configOptions ?? forked.configOptions;
|
|
22070
|
+
forked.legacyModels = legacyModelsFromResponse(response) ?? forked.legacyModels;
|
|
21994
22071
|
this.syncStateFromConfigOptions(forked);
|
|
21995
22072
|
return sessionDetail(forked);
|
|
21996
22073
|
}
|
|
@@ -22052,7 +22129,8 @@ var AcpRuntimeAdapter = class extends EventEmitter8 {
|
|
|
22052
22129
|
cwd: path16.resolve(input.cwd),
|
|
22053
22130
|
mcpServers: [],
|
|
22054
22131
|
_meta: {
|
|
22055
|
-
yoloMode: input.approvalMode === "yolo"
|
|
22132
|
+
yoloMode: input.approvalMode === "yolo",
|
|
22133
|
+
...input.reasoningEffort ? { reasoningEffort: input.reasoningEffort } : {}
|
|
22056
22134
|
}
|
|
22057
22135
|
});
|
|
22058
22136
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -22070,6 +22148,7 @@ var AcpRuntimeAdapter = class extends EventEmitter8 {
|
|
|
22070
22148
|
activeMapper: null,
|
|
22071
22149
|
modes: response.modes ?? null,
|
|
22072
22150
|
configOptions: response.configOptions ?? [],
|
|
22151
|
+
legacyModels: legacyModelsFromResponse(response),
|
|
22073
22152
|
availableCommands: [],
|
|
22074
22153
|
hydrationCoverage: null,
|
|
22075
22154
|
goal: null
|
|
@@ -22285,6 +22364,7 @@ var AcpRuntimeAdapter = class extends EventEmitter8 {
|
|
|
22285
22364
|
activeMapper: null,
|
|
22286
22365
|
modes: null,
|
|
22287
22366
|
configOptions: [],
|
|
22367
|
+
legacyModels: null,
|
|
22288
22368
|
availableCommands: [],
|
|
22289
22369
|
hydrationCoverage: null,
|
|
22290
22370
|
goal: null
|
|
@@ -22303,6 +22383,7 @@ var AcpRuntimeAdapter = class extends EventEmitter8 {
|
|
|
22303
22383
|
});
|
|
22304
22384
|
state.modes = response.modes ?? null;
|
|
22305
22385
|
state.configOptions = response.configOptions ?? [];
|
|
22386
|
+
state.legacyModels = legacyModelsFromResponse(response);
|
|
22306
22387
|
state.turns = hydrator.complete();
|
|
22307
22388
|
state.hydrationCoverage = hydrator.coverage();
|
|
22308
22389
|
this.syncStateFromConfigOptions(state);
|
|
@@ -22317,6 +22398,7 @@ var AcpRuntimeAdapter = class extends EventEmitter8 {
|
|
|
22317
22398
|
});
|
|
22318
22399
|
state.modes = response.modes ?? null;
|
|
22319
22400
|
state.configOptions = response.configOptions ?? [];
|
|
22401
|
+
state.legacyModels = legacyModelsFromResponse(response);
|
|
22320
22402
|
this.syncStateFromConfigOptions(state);
|
|
22321
22403
|
} else {
|
|
22322
22404
|
throw new Error("ACP agent does not support session/load or session/resume.");
|
|
@@ -22338,7 +22420,7 @@ var AcpRuntimeAdapter = class extends EventEmitter8 {
|
|
|
22338
22420
|
if (model && model !== "default") {
|
|
22339
22421
|
state.model = await this.setConfigOption(state, "model", model);
|
|
22340
22422
|
}
|
|
22341
|
-
if (reasoningEffort) {
|
|
22423
|
+
if (reasoningEffort && normalizeAcpEffort(reasoningEffort) !== state.reasoningEffort) {
|
|
22342
22424
|
const applied = await this.setConfigOption(
|
|
22343
22425
|
state,
|
|
22344
22426
|
"thought_level",
|
|
@@ -22368,17 +22450,39 @@ var AcpRuntimeAdapter = class extends EventEmitter8 {
|
|
|
22368
22450
|
}
|
|
22369
22451
|
}
|
|
22370
22452
|
async setConfigOption(state, category, value) {
|
|
22371
|
-
const option = state.configOptions
|
|
22372
|
-
(candidate) => candidate.category === category || candidate.id.toLowerCase().includes(category === "model" ? "model" : "thought")
|
|
22373
|
-
);
|
|
22453
|
+
const option = configOptionByCategory(state.configOptions, category);
|
|
22374
22454
|
if ((!option || option.type !== "select") && category === "model") {
|
|
22375
22455
|
const context2 = await this.requireContext();
|
|
22376
22456
|
await context2.request("session/set_model", {
|
|
22377
22457
|
sessionId: state.providerSessionId,
|
|
22378
22458
|
modelId: value
|
|
22379
22459
|
});
|
|
22460
|
+
if (state.legacyModels) {
|
|
22461
|
+
state.legacyModels.currentModelId = value;
|
|
22462
|
+
}
|
|
22380
22463
|
return value;
|
|
22381
22464
|
}
|
|
22465
|
+
if ((!option || option.type !== "select") && category === "thought_level") {
|
|
22466
|
+
const legacyModel = selectedLegacyModel(state);
|
|
22467
|
+
const selected2 = legacyModel?._meta?.reasoningEfforts?.find(
|
|
22468
|
+
(entry) => normalizeAcpEffort(entry.value ?? entry.id) === normalizeAcpEffort(value)
|
|
22469
|
+
);
|
|
22470
|
+
const modeId = selected2?.value ?? selected2?.id;
|
|
22471
|
+
if (modeId) {
|
|
22472
|
+
const context2 = await this.requireContext();
|
|
22473
|
+
const response2 = await context2.request(methods.agent.session.load, {
|
|
22474
|
+
sessionId: state.providerSessionId,
|
|
22475
|
+
cwd: state.cwd,
|
|
22476
|
+
mcpServers: [],
|
|
22477
|
+
_meta: { reasoningEffort: modeId }
|
|
22478
|
+
});
|
|
22479
|
+
state.modes = response2.modes ?? state.modes;
|
|
22480
|
+
state.configOptions = response2.configOptions ?? state.configOptions;
|
|
22481
|
+
state.legacyModels = legacyModelsFromResponse(response2) ?? state.legacyModels;
|
|
22482
|
+
this.syncStateFromConfigOptions(state);
|
|
22483
|
+
return state.reasoningEffort ?? modeId;
|
|
22484
|
+
}
|
|
22485
|
+
}
|
|
22382
22486
|
if (!option || option.type !== "select") {
|
|
22383
22487
|
throw new AgentRuntimeError(
|
|
22384
22488
|
`ACP agent does not expose a ${category} config option.`,
|
|
@@ -22421,6 +22525,16 @@ var AcpRuntimeAdapter = class extends EventEmitter8 {
|
|
|
22421
22525
|
if (thoughtOption?.type === "select") {
|
|
22422
22526
|
state.reasoningEffort = normalizeAcpEffort(thoughtOption.currentValue);
|
|
22423
22527
|
}
|
|
22528
|
+
if (!modelOption && state.legacyModels) {
|
|
22529
|
+
state.model = state.legacyModels.currentModelId ?? state.legacyModels.availableModels?.[0]?.modelId ?? null;
|
|
22530
|
+
}
|
|
22531
|
+
if (!thoughtOption) {
|
|
22532
|
+
const legacyModel = selectedLegacyModel(state);
|
|
22533
|
+
const declaredDefault = legacyModel?._meta?.reasoningEfforts?.find(
|
|
22534
|
+
(entry) => entry.default
|
|
22535
|
+
);
|
|
22536
|
+
state.reasoningEffort = normalizeAcpEffort(legacyModel?._meta?.reasoningEffort) ?? normalizeAcpEffort(declaredDefault?.value ?? declaredDefault?.id);
|
|
22537
|
+
}
|
|
22424
22538
|
}
|
|
22425
22539
|
updateCapabilitiesFromConfigOptions(configOptions) {
|
|
22426
22540
|
this.capabilities.management.models ||= Boolean(
|
|
@@ -23832,7 +23946,13 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter9 {
|
|
|
23832
23946
|
if (models.length === 1 && models[0]?.model === "default") {
|
|
23833
23947
|
const commandModels = await this.catalog.listCommandModels(agentId);
|
|
23834
23948
|
if (commandModels.length > 0) {
|
|
23835
|
-
|
|
23949
|
+
const discoveredDefaults = models[0];
|
|
23950
|
+
models = commandModels.map((model) => ({
|
|
23951
|
+
...model,
|
|
23952
|
+
supportsPerformanceMode: model.supportsPerformanceMode ?? discoveredDefaults.supportsPerformanceMode ?? false,
|
|
23953
|
+
supportedReasoningEfforts: model.supportedReasoningEfforts.length > 0 ? model.supportedReasoningEfforts : discoveredDefaults.supportedReasoningEfforts,
|
|
23954
|
+
defaultReasoningEffort: model.defaultReasoningEffort ?? discoveredDefaults.defaultReasoningEffort
|
|
23955
|
+
}));
|
|
23836
23956
|
}
|
|
23837
23957
|
}
|
|
23838
23958
|
this.modelCache.set(cacheKey, { at: Date.now(), models });
|
|
@@ -23933,6 +24053,7 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter9 {
|
|
|
23933
24053
|
const probedDefaultModel = this.modelCache.get(`${agentId}\0${input.cwd}`)?.models.find((model) => model.isDefault)?.model ?? null;
|
|
23934
24054
|
const resolvedModel = response.model && response.model !== "default" ? response.model : input.model !== "default" ? input.model : probedDefaultModel ?? input.model;
|
|
23935
24055
|
this.refreshAgentSessionCapabilities(agentId, agent);
|
|
24056
|
+
this.modelCache.clear();
|
|
23936
24057
|
const session = scopedSession(agentId, response.session);
|
|
23937
24058
|
return {
|
|
23938
24059
|
...response,
|
|
@@ -23958,6 +24079,7 @@ var AcpCatalogRuntimeAdapter = class extends EventEmitter9 {
|
|
|
23958
24079
|
throw error;
|
|
23959
24080
|
}
|
|
23960
24081
|
this.refreshAgentSessionCapabilities(owner.agentId, agent);
|
|
24082
|
+
this.modelCache.clear();
|
|
23961
24083
|
const session = scopedSession(owner.agentId, response.session);
|
|
23962
24084
|
return {
|
|
23963
24085
|
...response,
|
|
@@ -25906,6 +26028,19 @@ function mergeHistoryItemsBySequence(items, missingItems) {
|
|
|
25906
26028
|
return sortTurnItemsByRecordedSequence(mergedItems);
|
|
25907
26029
|
}
|
|
25908
26030
|
function shouldAppendPersistedMissingItem(turn, item) {
|
|
26031
|
+
if (item.kind === "userMessage" || item.kind === "reasoning") {
|
|
26032
|
+
const persistedText = item.text.replace(/\s+/g, " ").trim();
|
|
26033
|
+
const duplicate = turn.items.some((turnItem) => {
|
|
26034
|
+
if (turnItem.kind !== item.kind) {
|
|
26035
|
+
return false;
|
|
26036
|
+
}
|
|
26037
|
+
const turnText = turnItem.text.replace(/\s+/g, " ").trim();
|
|
26038
|
+
return turnText === persistedText || Math.min(turnText.length, persistedText.length) >= 8 && (turnText.includes(persistedText) || persistedText.includes(turnText));
|
|
26039
|
+
});
|
|
26040
|
+
if (duplicate) {
|
|
26041
|
+
return false;
|
|
26042
|
+
}
|
|
26043
|
+
}
|
|
25909
26044
|
if (item.kind !== "agentMessage") {
|
|
25910
26045
|
return true;
|
|
25911
26046
|
}
|
|
@@ -25940,6 +26075,7 @@ function mergePersistedHistoryItemsIntoTurns(turns, persistedItemsByTurnId, defe
|
|
|
25940
26075
|
}
|
|
25941
26076
|
const persistedItemWithTranscriptOrder = {
|
|
25942
26077
|
...persistedItem,
|
|
26078
|
+
...turn.status === "completed" && persistedItem.status === "running" ? { status: "completed" } : {},
|
|
25943
26079
|
transcriptOrder: transcriptIndex
|
|
25944
26080
|
};
|
|
25945
26081
|
if (shouldUsePersistedUserMessageText(item, persistedItemWithTranscriptOrder)) {
|
|
@@ -26023,6 +26159,7 @@ var ThreadLiveStateStore = class {
|
|
|
26023
26159
|
threadLivePlans = /* @__PURE__ */ new Map();
|
|
26024
26160
|
threadLiveItems = /* @__PURE__ */ new Map();
|
|
26025
26161
|
threadTurnItemOrder = /* @__PURE__ */ new Map();
|
|
26162
|
+
threadTurnItemCreatedAt = /* @__PURE__ */ new Map();
|
|
26026
26163
|
threadNextTurnItemSequence = /* @__PURE__ */ new Map();
|
|
26027
26164
|
threadMaterializedAgentMessageCounts = /* @__PURE__ */ new Map();
|
|
26028
26165
|
threadAgentMessageOrderingHints = /* @__PURE__ */ new Map();
|
|
@@ -26092,11 +26229,13 @@ var ThreadLiveStateStore = class {
|
|
|
26092
26229
|
}
|
|
26093
26230
|
resetRecordedTurnItemOrder(localThreadId, turnId) {
|
|
26094
26231
|
this.threadTurnItemOrder.get(localThreadId)?.delete(turnId);
|
|
26232
|
+
this.threadTurnItemCreatedAt.get(localThreadId)?.delete(turnId);
|
|
26095
26233
|
this.threadNextTurnItemSequence.get(localThreadId)?.delete(turnId);
|
|
26096
26234
|
this.threadAgentMessageOrderingHints.get(localThreadId)?.delete(turnId);
|
|
26097
26235
|
}
|
|
26098
26236
|
clearRecordedTurnItemOrders(localThreadId) {
|
|
26099
26237
|
this.threadTurnItemOrder.delete(localThreadId);
|
|
26238
|
+
this.threadTurnItemCreatedAt.delete(localThreadId);
|
|
26100
26239
|
this.threadNextTurnItemSequence.delete(localThreadId);
|
|
26101
26240
|
this.threadAgentMessageOrderingHints.delete(localThreadId);
|
|
26102
26241
|
}
|
|
@@ -26125,6 +26264,24 @@ var ThreadLiveStateStore = class {
|
|
|
26125
26264
|
turnOrder.set(itemId, sequence);
|
|
26126
26265
|
return sequence;
|
|
26127
26266
|
}
|
|
26267
|
+
recordTurnItemCreatedAt(localThreadId, turnId, itemId, createdAt) {
|
|
26268
|
+
let threadTimestamps = this.threadTurnItemCreatedAt.get(localThreadId);
|
|
26269
|
+
if (!threadTimestamps) {
|
|
26270
|
+
threadTimestamps = /* @__PURE__ */ new Map();
|
|
26271
|
+
this.threadTurnItemCreatedAt.set(localThreadId, threadTimestamps);
|
|
26272
|
+
}
|
|
26273
|
+
let turnTimestamps = threadTimestamps.get(turnId);
|
|
26274
|
+
if (!turnTimestamps) {
|
|
26275
|
+
turnTimestamps = /* @__PURE__ */ new Map();
|
|
26276
|
+
threadTimestamps.set(turnId, turnTimestamps);
|
|
26277
|
+
}
|
|
26278
|
+
const existing = turnTimestamps.get(itemId);
|
|
26279
|
+
if (existing) {
|
|
26280
|
+
return existing;
|
|
26281
|
+
}
|
|
26282
|
+
turnTimestamps.set(itemId, createdAt);
|
|
26283
|
+
return createdAt;
|
|
26284
|
+
}
|
|
26128
26285
|
turnItemOrderSnapshot(localThreadId) {
|
|
26129
26286
|
return this.threadTurnItemOrder.get(localThreadId) ?? /* @__PURE__ */ new Map();
|
|
26130
26287
|
}
|
|
@@ -26243,7 +26400,19 @@ var ThreadLiveStateStore = class {
|
|
|
26243
26400
|
appendLiveAgentMessageDelta(input) {
|
|
26244
26401
|
const current = this.threadLiveItems.get(input.localThreadId);
|
|
26245
26402
|
const currentItems = current?.turnId === input.turnId ? current.items : [];
|
|
26246
|
-
const
|
|
26403
|
+
const currentExisting = currentItems.find(
|
|
26404
|
+
(entry) => entry.id === input.itemId
|
|
26405
|
+
);
|
|
26406
|
+
const existing = currentExisting ?? (() => {
|
|
26407
|
+
const hint = this.threadAgentMessageOrderingHints.get(input.localThreadId)?.get(input.turnId)?.get(input.itemId);
|
|
26408
|
+
return hint ? {
|
|
26409
|
+
id: hint.id,
|
|
26410
|
+
kind: "agentMessage",
|
|
26411
|
+
text: hint.text,
|
|
26412
|
+
sequence: hint.sequence,
|
|
26413
|
+
...hint.createdAt ? { createdAt: hint.createdAt } : {}
|
|
26414
|
+
} : void 0;
|
|
26415
|
+
})();
|
|
26247
26416
|
const nextItem = existing?.kind === "agentMessage" ? {
|
|
26248
26417
|
...existing,
|
|
26249
26418
|
text: `${existing.text}${input.delta}`,
|
|
@@ -26264,7 +26433,7 @@ var ThreadLiveStateStore = class {
|
|
|
26264
26433
|
this.setLiveItems(input.localThreadId, {
|
|
26265
26434
|
turnId: input.turnId,
|
|
26266
26435
|
items: sortHistoryItemsBySequence(
|
|
26267
|
-
|
|
26436
|
+
currentExisting ? currentItems.map(
|
|
26268
26437
|
(entry) => entry.id === input.itemId ? nextItem : entry
|
|
26269
26438
|
) : [...currentItems, nextItem]
|
|
26270
26439
|
),
|
|
@@ -26974,8 +27143,17 @@ var ThreadRuntimeEventProjector = class {
|
|
|
26974
27143
|
event.item.id
|
|
26975
27144
|
);
|
|
26976
27145
|
const eventTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
27146
|
+
const stableCreatedAt = liveState.recordTurnItemCreatedAt(
|
|
27147
|
+
record2.id,
|
|
27148
|
+
displayTurnId,
|
|
27149
|
+
event.item.id,
|
|
27150
|
+
event.item.createdAt ?? eventTimestamp
|
|
27151
|
+
);
|
|
26977
27152
|
const orderedLiveItem = {
|
|
26978
|
-
...withHistoryItemCreatedAt2(
|
|
27153
|
+
...withHistoryItemCreatedAt2(
|
|
27154
|
+
event.item,
|
|
27155
|
+
stableCreatedAt
|
|
27156
|
+
),
|
|
26979
27157
|
sequence
|
|
26980
27158
|
};
|
|
26981
27159
|
const transportLiveItem = deferHistoryItemDetailForTransport(orderedLiveItem);
|
|
@@ -27027,7 +27205,12 @@ var ThreadRuntimeEventProjector = class {
|
|
|
27027
27205
|
displayTurnId,
|
|
27028
27206
|
event.itemId
|
|
27029
27207
|
);
|
|
27030
|
-
const createdAt =
|
|
27208
|
+
const createdAt = liveState.recordTurnItemCreatedAt(
|
|
27209
|
+
record2.id,
|
|
27210
|
+
displayTurnId,
|
|
27211
|
+
event.itemId,
|
|
27212
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
27213
|
+
);
|
|
27031
27214
|
callbacks.appendLiveAgentMessageDelta({
|
|
27032
27215
|
localThreadId: record2.id,
|
|
27033
27216
|
turnId: displayTurnId,
|
|
@@ -27922,7 +28105,7 @@ function conversationMatchScore(turn, persistedItems) {
|
|
|
27922
28105
|
if (!remoteUser || !persistedUser) {
|
|
27923
28106
|
return 0;
|
|
27924
28107
|
}
|
|
27925
|
-
const userScore = remoteUser === persistedUser ? 2 : Math.min(remoteUser.length, persistedUser.length) >=
|
|
28108
|
+
const userScore = remoteUser === persistedUser ? 2 : Math.min(remoteUser.length, persistedUser.length) >= 8 && (remoteUser.includes(persistedUser) || persistedUser.includes(remoteUser)) ? 1 : 0;
|
|
27926
28109
|
if (userScore === 0) {
|
|
27927
28110
|
return 0;
|
|
27928
28111
|
}
|
|
@@ -27954,7 +28137,7 @@ function historyItemMatchScore(remote, persisted) {
|
|
|
27954
28137
|
if (remoteText === persistedText) {
|
|
27955
28138
|
return 50;
|
|
27956
28139
|
}
|
|
27957
|
-
if ((remote.kind === "agentMessage" || remote.kind === "reasoning") && (remoteText.includes(persistedText) || persistedText.includes(remoteText))) {
|
|
28140
|
+
if ((remote.kind === "userMessage" || remote.kind === "agentMessage" || remote.kind === "reasoning") && Math.min(remoteText.length, persistedText.length) >= 8 && (remoteText.includes(persistedText) || persistedText.includes(remoteText))) {
|
|
27958
28141
|
return 40;
|
|
27959
28142
|
}
|
|
27960
28143
|
return 0;
|
|
@@ -27966,7 +28149,7 @@ function alignHydratedItems(remoteItems, persistedItems, turnId) {
|
|
|
27966
28149
|
persisted,
|
|
27967
28150
|
index,
|
|
27968
28151
|
score: historyItemMatchScore(remote, persisted)
|
|
27969
|
-
})).filter((candidate) => candidate.score > 0).sort((left, right) => right.score - left.score || left.index - right.index)[0];
|
|
28152
|
+
})).filter((candidate) => candidate.score > 0).sort((left, right) => Number(left.persisted.id.startsWith("acp-hydrated:")) - Number(right.persisted.id.startsWith("acp-hydrated:")) || right.score - left.score || left.index - right.index)[0];
|
|
27970
28153
|
if (!match) {
|
|
27971
28154
|
return remote.sourceTurnId ? { ...remote, sourceTurnId: turnId } : remote;
|
|
27972
28155
|
}
|
|
@@ -27974,6 +28157,12 @@ function alignHydratedItems(remoteItems, persistedItems, turnId) {
|
|
|
27974
28157
|
return {
|
|
27975
28158
|
...remote,
|
|
27976
28159
|
id: match.persisted.id,
|
|
28160
|
+
...match.persisted.createdAt ? { createdAt: match.persisted.createdAt } : {},
|
|
28161
|
+
...match.persisted.sequence !== void 0 ? { sequence: match.persisted.sequence } : {},
|
|
28162
|
+
...remote.kind === "userMessage" ? {
|
|
28163
|
+
text: match.persisted.text,
|
|
28164
|
+
...match.persisted.detailText !== void 0 ? { detailText: match.persisted.detailText } : {}
|
|
28165
|
+
} : {},
|
|
27977
28166
|
...remote.sourceTurnId || remote.kind === "agentMessage" ? { sourceTurnId: turnId } : {}
|
|
27978
28167
|
};
|
|
27979
28168
|
});
|
|
@@ -27993,14 +28182,16 @@ function alignHydratedAcpTurnsWithPersistedHistory(turns, persistedItemsByTurnId
|
|
|
27993
28182
|
items,
|
|
27994
28183
|
index,
|
|
27995
28184
|
score: conversationMatchScore(turn, items)
|
|
27996
|
-
})).filter((candidate) => candidate.score > 0).sort((left, right) => right.score - left.score || left.index - right.index)[0];
|
|
28185
|
+
})).filter((candidate) => candidate.score > 0).sort((left, right) => Number(left.turnId.startsWith("acp-hydrated:")) - Number(right.turnId.startsWith("acp-hydrated:")) || right.score - left.score || left.index - right.index)[0];
|
|
27997
28186
|
if (!match) {
|
|
27998
28187
|
return turn;
|
|
27999
28188
|
}
|
|
28000
28189
|
used.add(match.turnId);
|
|
28190
|
+
const persistedStartedAt = earliestItemCreatedAt(match.items);
|
|
28001
28191
|
return {
|
|
28002
28192
|
...turn,
|
|
28003
28193
|
providerTurnId: match.turnId,
|
|
28194
|
+
...persistedStartedAt ? { startedAt: persistedStartedAt } : {},
|
|
28004
28195
|
items: alignHydratedItems(turn.items, match.items, match.turnId)
|
|
28005
28196
|
};
|
|
28006
28197
|
});
|
|
@@ -28015,6 +28206,20 @@ function appendPersistedTurnsIfMissing(turns, persistedItemsByTurnId, metadataBy
|
|
|
28015
28206
|
if (existingTurnIds.has(turnId)) {
|
|
28016
28207
|
continue;
|
|
28017
28208
|
}
|
|
28209
|
+
if (turns.some(
|
|
28210
|
+
(turn) => conversationMatchScore(
|
|
28211
|
+
{
|
|
28212
|
+
providerTurnId: turn.id,
|
|
28213
|
+
startedAt: turn.startedAt,
|
|
28214
|
+
status: turn.status,
|
|
28215
|
+
error: turn.error ? { message: turn.error } : null,
|
|
28216
|
+
items: turn.items
|
|
28217
|
+
},
|
|
28218
|
+
items
|
|
28219
|
+
) > 0
|
|
28220
|
+
)) {
|
|
28221
|
+
continue;
|
|
28222
|
+
}
|
|
28018
28223
|
const status = persistedAcpTurnStatus(items);
|
|
28019
28224
|
const error = status === "failed" ? persistedTurnError(items) : null;
|
|
28020
28225
|
missingTurns.push({
|
|
@@ -29120,8 +29325,13 @@ var ThreadSessionCoordinator = class {
|
|
|
29120
29325
|
return { status: "bootstrap_unavailable", error };
|
|
29121
29326
|
}
|
|
29122
29327
|
const effectiveModel = input.resumeInput.model ?? input.currentModel ?? response.model ?? null;
|
|
29328
|
+
const resumedModelRecords = await this.listSessionModels({
|
|
29329
|
+
provider: input.provider,
|
|
29330
|
+
agentId: input.agentId,
|
|
29331
|
+
workspacePath: input.workspacePath
|
|
29332
|
+
}).catch(() => modelRecords);
|
|
29123
29333
|
const resumedReasoning = this.providerRuntime.normalizeReasoningForModel(
|
|
29124
|
-
|
|
29334
|
+
resumedModelRecords,
|
|
29125
29335
|
effectiveModel,
|
|
29126
29336
|
normalizeReasoningEffort2(input.currentReasoningEffort) ?? normalizeReasoningEffort2(response.reasoningEffort)
|
|
29127
29337
|
);
|
|
@@ -29468,6 +29678,9 @@ var ThreadHistoryPersistenceCoordinator = class {
|
|
|
29468
29678
|
}
|
|
29469
29679
|
}
|
|
29470
29680
|
persistHydratedTurns(localThreadId, turns) {
|
|
29681
|
+
const existingItemsByTurnId = this.listPersistedHistoryItemsByTurnId(
|
|
29682
|
+
localThreadId
|
|
29683
|
+
);
|
|
29471
29684
|
for (const turn of turns) {
|
|
29472
29685
|
if (turn.startedAt) {
|
|
29473
29686
|
upsertThreadTurnMetadata(this.db, {
|
|
@@ -29480,14 +29693,15 @@ var ThreadHistoryPersistenceCoordinator = class {
|
|
|
29480
29693
|
if (!shouldPersistRuntimeFinalHistoryItem(item)) {
|
|
29481
29694
|
return;
|
|
29482
29695
|
}
|
|
29483
|
-
const
|
|
29696
|
+
const existingItem = existingItemsByTurnId.get(turn.providerTurnId)?.find((candidate) => candidate.id === item.id);
|
|
29697
|
+
const createdAt = existingItem?.createdAt ?? item.createdAt ?? turn.startedAt;
|
|
29484
29698
|
this.persistProjectedHistoryItem(
|
|
29485
29699
|
localThreadId,
|
|
29486
29700
|
turn.providerTurnId,
|
|
29487
29701
|
{
|
|
29488
29702
|
...item,
|
|
29489
29703
|
...createdAt ? { createdAt } : {},
|
|
29490
|
-
sequence: item.sequence ?? index,
|
|
29704
|
+
sequence: existingItem?.sequence ?? item.sequence ?? index,
|
|
29491
29705
|
...item.kind === "agentMessage" && !item.sourceTurnId ? { sourceTurnId: turn.providerTurnId } : {}
|
|
29492
29706
|
}
|
|
29493
29707
|
);
|
|
@@ -31420,6 +31634,30 @@ function canUseRuntimePagedTurns(cachedDetail, enrichedTurns, options) {
|
|
|
31420
31634
|
}
|
|
31421
31635
|
return cachedDetail.totalTurnCount > enrichedTurns.length;
|
|
31422
31636
|
}
|
|
31637
|
+
function latestTurnItemTimestamp(turn) {
|
|
31638
|
+
return turn.items.map((item) => item.createdAt).filter((value) => Boolean(value)).sort().at(-1) ?? turn.startedAt ?? null;
|
|
31639
|
+
}
|
|
31640
|
+
function localImportThreadStatusPatch(record2, turns) {
|
|
31641
|
+
const latestTurn = turns.at(-1);
|
|
31642
|
+
if (!latestTurn) {
|
|
31643
|
+
return null;
|
|
31644
|
+
}
|
|
31645
|
+
const status = latestTurn.status === "inProgress" ? "running" : latestTurn.status === "completed" ? "idle" : latestTurn.status;
|
|
31646
|
+
const providerTurnId = latestTurn.status === "inProgress" ? latestTurn.id : null;
|
|
31647
|
+
const lastError = latestTurn.status === "failed" ? latestTurn.error : null;
|
|
31648
|
+
const lastTurnStartedAt = latestTurn.startedAt ?? record2.lastTurnStartedAt ?? null;
|
|
31649
|
+
const lastTurnCompletedAt = latestTurn.status === "inProgress" ? null : latestTurnItemTimestamp(latestTurn) ?? record2.lastTurnCompletedAt ?? null;
|
|
31650
|
+
if (record2.status === status && record2.providerTurnId === providerTurnId && record2.lastError === lastError && record2.lastTurnStartedAt === lastTurnStartedAt && record2.lastTurnCompletedAt === lastTurnCompletedAt) {
|
|
31651
|
+
return null;
|
|
31652
|
+
}
|
|
31653
|
+
return {
|
|
31654
|
+
status,
|
|
31655
|
+
providerTurnId,
|
|
31656
|
+
lastError,
|
|
31657
|
+
lastTurnStartedAt,
|
|
31658
|
+
lastTurnCompletedAt
|
|
31659
|
+
};
|
|
31660
|
+
}
|
|
31423
31661
|
var ThreadService = class {
|
|
31424
31662
|
constructor(db, agentRuntimes, eventBus, localSessionStore, workspaceRoot, providerManagement, pluginService, config) {
|
|
31425
31663
|
this.db = db;
|
|
@@ -31786,6 +32024,9 @@ var ThreadService = class {
|
|
|
31786
32024
|
if (!local) {
|
|
31787
32025
|
continue;
|
|
31788
32026
|
}
|
|
32027
|
+
if (local.source === "local_codex_import" && local.isConnected === false) {
|
|
32028
|
+
continue;
|
|
32029
|
+
}
|
|
31789
32030
|
updateThreadRecord(
|
|
31790
32031
|
this.db,
|
|
31791
32032
|
local.id,
|
|
@@ -31861,6 +32102,12 @@ var ThreadService = class {
|
|
|
31861
32102
|
turnMetadataById,
|
|
31862
32103
|
options
|
|
31863
32104
|
});
|
|
32105
|
+
if (record2.source === "local_codex_import" && record2.isConnected === false) {
|
|
32106
|
+
const statusPatch = localImportThreadStatusPatch(record2, cachedDetail.turns);
|
|
32107
|
+
if (statusPatch) {
|
|
32108
|
+
updateThreadRecord(this.db, record2.id, statusPatch);
|
|
32109
|
+
}
|
|
32110
|
+
}
|
|
31864
32111
|
const updated = getThreadRecordById(this.db, record2.id);
|
|
31865
32112
|
const enrichedTurns = this.pluginService?.enrichTurnsWithArtifacts({
|
|
31866
32113
|
threadId: updated.id,
|
|
@@ -37994,6 +38241,7 @@ var RelayTunnelClient = class {
|
|
|
37994
38241
|
}
|
|
37995
38242
|
}, RELAY_CONNECT_TIMEOUT_MS);
|
|
37996
38243
|
socket.addEventListener("open", () => {
|
|
38244
|
+
console.log(`Relay tunnel connected to ${url2.origin}.`);
|
|
37997
38245
|
this.clearConnectTimeout();
|
|
37998
38246
|
this.reconnectDelayMs = RELAY_RECONNECT_INITIAL_DELAY_MS;
|
|
37999
38247
|
this.sendHeartbeat();
|
|
@@ -38003,10 +38251,23 @@ var RelayTunnelClient = class {
|
|
|
38003
38251
|
this.sendHeartbeat();
|
|
38004
38252
|
}, RELAY_HEARTBEAT_INTERVAL_MS);
|
|
38005
38253
|
});
|
|
38006
|
-
socket.addEventListener("close", () => {
|
|
38254
|
+
socket.addEventListener("close", (event) => {
|
|
38255
|
+
if (this.stopped || this.socket !== socket) {
|
|
38256
|
+
return;
|
|
38257
|
+
}
|
|
38258
|
+
const closeEvent = event;
|
|
38259
|
+
const reason = closeEvent?.reason?.trim() || "no reason provided";
|
|
38260
|
+
const code = closeEvent?.code ?? "unknown";
|
|
38261
|
+
console.error(
|
|
38262
|
+
`Relay tunnel closed (code ${code}: ${reason}); reconnecting.`
|
|
38263
|
+
);
|
|
38007
38264
|
this.closeAndReconnect(socket);
|
|
38008
38265
|
});
|
|
38009
38266
|
socket.addEventListener("error", () => {
|
|
38267
|
+
if (this.stopped || this.socket !== socket) {
|
|
38268
|
+
return;
|
|
38269
|
+
}
|
|
38270
|
+
console.error("Relay tunnel websocket error; reconnecting.");
|
|
38010
38271
|
this.closeAndReconnect(socket);
|
|
38011
38272
|
});
|
|
38012
38273
|
socket.addEventListener("message", (event) => {
|
|
@@ -38162,6 +38423,7 @@ var RelayTunnelClient = class {
|
|
|
38162
38423
|
this.reconnectHandle = null;
|
|
38163
38424
|
this.start();
|
|
38164
38425
|
}, delayMs);
|
|
38426
|
+
console.error(`Relay tunnel reconnect scheduled in ${delayMs} ms.`);
|
|
38165
38427
|
}
|
|
38166
38428
|
clearReconnect() {
|
|
38167
38429
|
if (this.reconnectHandle) {
|
package/bin/remote-codex.mjs
CHANGED
|
@@ -685,6 +685,8 @@ async function startRelaySupervisorTmux() {
|
|
|
685
685
|
return;
|
|
686
686
|
}
|
|
687
687
|
|
|
688
|
+
fs.mkdirSync(path.dirname(relaySupervisorLogPath), { recursive: true });
|
|
689
|
+
rotateRelaySupervisorLog();
|
|
688
690
|
const command = relaySupervisorTmuxCommand();
|
|
689
691
|
const result = spawnSync('tmux', ['new-session', '-d', '-s', relaySupervisorTmuxSession, command], {
|
|
690
692
|
cwd: packageRoot,
|
|
@@ -721,7 +723,7 @@ function relaySupervisorTmuxCommand() {
|
|
|
721
723
|
const envPrefix = nonEmptyEnv('REMOTE_CODEX_RELAY_SUPERVISOR_CONFIG')
|
|
722
724
|
? `REMOTE_CODEX_RELAY_SUPERVISOR_CONFIG=${shellQuote(process.env.REMOTE_CODEX_RELAY_SUPERVISOR_CONFIG)}`
|
|
723
725
|
: '';
|
|
724
|
-
const command = `${shellQuote(process.execPath)} ${shellQuote(fileURLToPath(import.meta.url))} relay-supervisor run`;
|
|
726
|
+
const command = `${shellQuote(process.execPath)} ${shellQuote(fileURLToPath(import.meta.url))} relay-supervisor run 2>&1 | tee -a ${shellQuote(relaySupervisorLogPath)}`;
|
|
725
727
|
return envPrefix ? `${envPrefix} ${command}` : command;
|
|
726
728
|
}
|
|
727
729
|
|
|
@@ -776,6 +778,7 @@ function stopRelaySupervisorTmux() {
|
|
|
776
778
|
|
|
777
779
|
function printRelaySupervisorTmuxCommands() {
|
|
778
780
|
console.log(`Attach logs: tmux attach -t ${shellQuote(relaySupervisorTmuxSession)}`);
|
|
781
|
+
console.log(`Persistent logs: ${relaySupervisorLogPath}`);
|
|
779
782
|
console.log('Status: remote-codex relay-supervisor status');
|
|
780
783
|
console.log('Stop: remote-codex relay-supervisor stop');
|
|
781
784
|
}
|
package/package.json
CHANGED
|
@@ -168,6 +168,62 @@ describe('AcpCatalogRuntimeAdapter', () => {
|
|
|
168
168
|
expect(forked.agentId).toBe('fixture-agent');
|
|
169
169
|
}, 15_000);
|
|
170
170
|
|
|
171
|
+
it('refreshes command models with reasoning options from a close-only session', async () => {
|
|
172
|
+
const fixture = path.resolve('src/test/fixtures/fake-acp-agent.mjs');
|
|
173
|
+
vi.stubEnv('REMOTE_CODEX_FAKE_ACP_NO_SESSION_DELETE', '1');
|
|
174
|
+
const runtime = new AcpCatalogRuntimeAdapter({
|
|
175
|
+
catalog: new AcpAgentCatalog({
|
|
176
|
+
definitions: [{
|
|
177
|
+
id: 'grok-fixture',
|
|
178
|
+
displayName: 'Grok fixture',
|
|
179
|
+
description: 'Close-only ACP fixture',
|
|
180
|
+
transport: 'native',
|
|
181
|
+
baseCommand: process.execPath,
|
|
182
|
+
baseProbeCommand: `"${process.execPath}" --version`,
|
|
183
|
+
serverCommand: `"${process.execPath}" "${fixture}"`,
|
|
184
|
+
serverProbeCommand: `"${process.execPath}" --version`,
|
|
185
|
+
installCommand: null,
|
|
186
|
+
modelListCommand: `"${process.execPath}" -e "console.log('Default model: fixture-model\\nAvailable models:\\n * fixture-model (default)\\n - fixture-fast')"`,
|
|
187
|
+
}],
|
|
188
|
+
}),
|
|
189
|
+
startupTimeoutMs: 5_000,
|
|
190
|
+
});
|
|
191
|
+
runtimes.push(runtime);
|
|
192
|
+
await runtime.start();
|
|
193
|
+
|
|
194
|
+
expect(await runtime.listModelsForAgent('grok-fixture', process.cwd())).toEqual(
|
|
195
|
+
expect.arrayContaining([
|
|
196
|
+
expect.objectContaining({
|
|
197
|
+
model: 'fixture-model',
|
|
198
|
+
supportedReasoningEfforts: [],
|
|
199
|
+
}),
|
|
200
|
+
]),
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
await runtime.startSession({
|
|
204
|
+
cwd: process.cwd(),
|
|
205
|
+
agentId: 'grok-fixture',
|
|
206
|
+
model: 'fixture-model',
|
|
207
|
+
reasoningEffort: 'high',
|
|
208
|
+
approvalMode: 'yolo',
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(await runtime.listModelsForAgent('grok-fixture', process.cwd())).toEqual(
|
|
212
|
+
expect.arrayContaining([
|
|
213
|
+
expect.objectContaining({
|
|
214
|
+
model: 'fixture-model',
|
|
215
|
+
supportedReasoningEfforts: [
|
|
216
|
+
expect.objectContaining({ reasoningEffort: 'low' }),
|
|
217
|
+
expect.objectContaining({ reasoningEffort: 'medium' }),
|
|
218
|
+
expect.objectContaining({ reasoningEffort: 'high' }),
|
|
219
|
+
],
|
|
220
|
+
defaultReasoningEffort: 'high',
|
|
221
|
+
}),
|
|
222
|
+
expect.objectContaining({ model: 'fixture-fast' }),
|
|
223
|
+
]),
|
|
224
|
+
);
|
|
225
|
+
});
|
|
226
|
+
|
|
171
227
|
it('delegates Codex child controls while preserving scoped goal identity', async () => {
|
|
172
228
|
const fixture = path.resolve('src/test/fixtures/fake-acp-agent.mjs');
|
|
173
229
|
vi.stubEnv('REMOTE_CODEX_FAKE_ACP_AGENT_KIND', 'codex');
|
|
@@ -255,7 +255,21 @@ export class AcpCatalogRuntimeAdapter extends EventEmitter implements AgentRunti
|
|
|
255
255
|
if (models.length === 1 && models[0]?.model === 'default') {
|
|
256
256
|
const commandModels = await this.catalog.listCommandModels(agentId);
|
|
257
257
|
if (commandModels.length > 0) {
|
|
258
|
-
|
|
258
|
+
const discoveredDefaults = models[0];
|
|
259
|
+
models = commandModels.map((model) => ({
|
|
260
|
+
...model,
|
|
261
|
+
supportsPerformanceMode:
|
|
262
|
+
model.supportsPerformanceMode ??
|
|
263
|
+
discoveredDefaults.supportsPerformanceMode ??
|
|
264
|
+
false,
|
|
265
|
+
supportedReasoningEfforts:
|
|
266
|
+
model.supportedReasoningEfforts.length > 0
|
|
267
|
+
? model.supportedReasoningEfforts
|
|
268
|
+
: discoveredDefaults.supportedReasoningEfforts,
|
|
269
|
+
defaultReasoningEffort:
|
|
270
|
+
model.defaultReasoningEffort ??
|
|
271
|
+
discoveredDefaults.defaultReasoningEffort,
|
|
272
|
+
}));
|
|
259
273
|
}
|
|
260
274
|
}
|
|
261
275
|
this.modelCache.set(cacheKey, { at: Date.now(), models });
|
|
@@ -381,6 +395,7 @@ export class AcpCatalogRuntimeAdapter extends EventEmitter implements AgentRunti
|
|
|
381
395
|
? input.model
|
|
382
396
|
: probedDefaultModel ?? input.model;
|
|
383
397
|
this.refreshAgentSessionCapabilities(agentId, agent);
|
|
398
|
+
this.modelCache.clear();
|
|
384
399
|
const session = scopedSession(agentId, response.session);
|
|
385
400
|
return {
|
|
386
401
|
...response,
|
|
@@ -407,6 +422,7 @@ export class AcpCatalogRuntimeAdapter extends EventEmitter implements AgentRunti
|
|
|
407
422
|
throw error;
|
|
408
423
|
}
|
|
409
424
|
this.refreshAgentSessionCapabilities(owner.agentId, agent);
|
|
425
|
+
this.modelCache.clear();
|
|
410
426
|
const session = scopedSession(owner.agentId, response.session);
|
|
411
427
|
return {
|
|
412
428
|
...response,
|
|
@@ -350,6 +350,112 @@ describe('AcpRuntimeAdapter', () => {
|
|
|
350
350
|
await expect(adapter.listSessions()).resolves.toEqual([]);
|
|
351
351
|
});
|
|
352
352
|
|
|
353
|
+
it('reads reasoning options from a loaded close-only agent session', async () => {
|
|
354
|
+
const adapter = new AcpRuntimeAdapter({
|
|
355
|
+
command: `"${process.execPath}" "${fixture}"`,
|
|
356
|
+
env: { REMOTE_CODEX_FAKE_ACP_NO_SESSION_DELETE: '1' },
|
|
357
|
+
startupTimeoutMs: 5_000,
|
|
358
|
+
});
|
|
359
|
+
adapters.push(adapter);
|
|
360
|
+
await adapter.start();
|
|
361
|
+
|
|
362
|
+
const started = await adapter.startSession({
|
|
363
|
+
cwd: process.cwd(),
|
|
364
|
+
model: 'fixture-model',
|
|
365
|
+
reasoningEffort: 'high',
|
|
366
|
+
approvalMode: 'yolo',
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
await expect(adapter.inspectModelOptions(process.cwd())).resolves.toEqual([
|
|
370
|
+
expect.objectContaining({
|
|
371
|
+
model: 'default',
|
|
372
|
+
supportedReasoningEfforts: [
|
|
373
|
+
expect.objectContaining({ reasoningEffort: 'low' }),
|
|
374
|
+
expect.objectContaining({ reasoningEffort: 'medium' }),
|
|
375
|
+
expect.objectContaining({ reasoningEffort: 'high' }),
|
|
376
|
+
],
|
|
377
|
+
defaultReasoningEffort: 'high',
|
|
378
|
+
}),
|
|
379
|
+
]);
|
|
380
|
+
await expect(adapter.listSessions()).resolves.toEqual([
|
|
381
|
+
expect.objectContaining({ providerSessionId: started.providerSessionId }),
|
|
382
|
+
]);
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it('maps and writes Grok-style legacy model reasoning metadata', async () => {
|
|
386
|
+
const adapter = new AcpRuntimeAdapter({
|
|
387
|
+
command: `"${process.execPath}" "${fixture}"`,
|
|
388
|
+
env: {
|
|
389
|
+
REMOTE_CODEX_FAKE_ACP_NO_SESSION_DELETE: '1',
|
|
390
|
+
REMOTE_CODEX_FAKE_ACP_LEGACY_MODELS: '1',
|
|
391
|
+
REMOTE_CODEX_FAKE_ACP_SKIP_PERMISSION: '1',
|
|
392
|
+
},
|
|
393
|
+
startupTimeoutMs: 5_000,
|
|
394
|
+
});
|
|
395
|
+
adapters.push(adapter);
|
|
396
|
+
await adapter.start();
|
|
397
|
+
|
|
398
|
+
const started = await adapter.startSession({
|
|
399
|
+
cwd: process.cwd(),
|
|
400
|
+
model: 'fixture-fast',
|
|
401
|
+
reasoningEffort: 'low',
|
|
402
|
+
approvalMode: 'yolo',
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
expect(started).toMatchObject({
|
|
406
|
+
model: 'fixture-fast',
|
|
407
|
+
reasoningEffort: 'low',
|
|
408
|
+
rawSession: {
|
|
409
|
+
models: {
|
|
410
|
+
availableModels: [
|
|
411
|
+
expect.objectContaining({
|
|
412
|
+
_meta: expect.objectContaining({ reasoningEffort: 'low' }),
|
|
413
|
+
}),
|
|
414
|
+
expect.anything(),
|
|
415
|
+
],
|
|
416
|
+
},
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
await expect(adapter.inspectModelOptions(process.cwd())).resolves.toEqual([
|
|
420
|
+
expect.objectContaining({
|
|
421
|
+
model: 'fixture-model',
|
|
422
|
+
supportedReasoningEfforts: [
|
|
423
|
+
expect.objectContaining({ reasoningEffort: 'high' }),
|
|
424
|
+
expect.objectContaining({ reasoningEffort: 'medium' }),
|
|
425
|
+
expect.objectContaining({ reasoningEffort: 'low' }),
|
|
426
|
+
],
|
|
427
|
+
}),
|
|
428
|
+
expect.objectContaining({
|
|
429
|
+
model: 'fixture-fast',
|
|
430
|
+
defaultReasoningEffort: 'low',
|
|
431
|
+
supportedReasoningEfforts: [
|
|
432
|
+
expect.objectContaining({ reasoningEffort: 'high' }),
|
|
433
|
+
expect.objectContaining({ reasoningEffort: 'low' }),
|
|
434
|
+
],
|
|
435
|
+
}),
|
|
436
|
+
]);
|
|
437
|
+
|
|
438
|
+
const completed = new Promise<void>((resolve) => {
|
|
439
|
+
adapter.on('event', (event: AgentRuntimeEvent) => {
|
|
440
|
+
if (event.type === 'turn.completed') resolve();
|
|
441
|
+
});
|
|
442
|
+
});
|
|
443
|
+
await adapter.startTurn({
|
|
444
|
+
providerSessionId: started.providerSessionId,
|
|
445
|
+
prompt: 'Verify changed effort.',
|
|
446
|
+
reasoningEffort: 'high',
|
|
447
|
+
});
|
|
448
|
+
await completed;
|
|
449
|
+
await expect(adapter.inspectModelOptions(process.cwd())).resolves.toEqual(
|
|
450
|
+
expect.arrayContaining([
|
|
451
|
+
expect.objectContaining({
|
|
452
|
+
model: 'fixture-fast',
|
|
453
|
+
defaultReasoningEffort: 'high',
|
|
454
|
+
}),
|
|
455
|
+
]),
|
|
456
|
+
);
|
|
457
|
+
});
|
|
458
|
+
|
|
353
459
|
it('deletes temporary model-probe sessions', async () => {
|
|
354
460
|
const adapter = new AcpRuntimeAdapter({
|
|
355
461
|
command: `"${process.execPath}" "${fixture}"`,
|
|
@@ -79,11 +79,33 @@ interface AcpSessionState {
|
|
|
79
79
|
activeMapper: AcpTurnItemMapper | null;
|
|
80
80
|
modes: acp.SessionModeState | null;
|
|
81
81
|
configOptions: acp.SessionConfigOption[];
|
|
82
|
+
legacyModels: AcpLegacyModelState | null;
|
|
82
83
|
availableCommands: acp.AvailableCommand[];
|
|
83
84
|
hydrationCoverage: AgentSessionHistoryCoverage | null;
|
|
84
85
|
goal: AgentGoal | null;
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
interface AcpLegacyModelState {
|
|
89
|
+
currentModelId?: string;
|
|
90
|
+
availableModels?: AcpLegacyModelInfo[];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
interface AcpLegacyModelInfo {
|
|
94
|
+
modelId: string;
|
|
95
|
+
name?: string;
|
|
96
|
+
description?: string;
|
|
97
|
+
_meta?: {
|
|
98
|
+
reasoningEffort?: string;
|
|
99
|
+
reasoningEfforts?: Array<{
|
|
100
|
+
id?: string;
|
|
101
|
+
value?: string;
|
|
102
|
+
label?: string;
|
|
103
|
+
description?: string;
|
|
104
|
+
default?: boolean;
|
|
105
|
+
}>;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
87
109
|
interface PendingPermission {
|
|
88
110
|
params: acp.RequestPermissionRequest;
|
|
89
111
|
resolve: (response: acp.RequestPermissionResponse) => void;
|
|
@@ -239,6 +261,60 @@ function reasoningOptions(configOptions: acp.SessionConfigOption[]) {
|
|
|
239
261
|
};
|
|
240
262
|
}
|
|
241
263
|
|
|
264
|
+
function legacyModelsFromResponse(response: unknown): AcpLegacyModelState | null {
|
|
265
|
+
if (!response || typeof response !== 'object') {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
const models = (response as { models?: unknown }).models;
|
|
269
|
+
if (!models || typeof models !== 'object') {
|
|
270
|
+
return null;
|
|
271
|
+
}
|
|
272
|
+
const availableModels = (models as { availableModels?: unknown }).availableModels;
|
|
273
|
+
return Array.isArray(availableModels)
|
|
274
|
+
? models as AcpLegacyModelState
|
|
275
|
+
: null;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function legacyModelOptions(models: AcpLegacyModelState): AgentModel[] {
|
|
279
|
+
const currentModelId = models.currentModelId ?? null;
|
|
280
|
+
return (models.availableModels ?? []).flatMap((model, index) => {
|
|
281
|
+
if (!model.modelId) {
|
|
282
|
+
return [];
|
|
283
|
+
}
|
|
284
|
+
const efforts = (model._meta?.reasoningEfforts ?? []).flatMap((entry) => {
|
|
285
|
+
const reasoningEffort = normalizeAcpEffort(entry.value ?? entry.id);
|
|
286
|
+
return reasoningEffort
|
|
287
|
+
? [{
|
|
288
|
+
reasoningEffort,
|
|
289
|
+
description: entry.description ?? entry.label ?? '',
|
|
290
|
+
}]
|
|
291
|
+
: [];
|
|
292
|
+
});
|
|
293
|
+
const declaredDefault = model._meta?.reasoningEfforts?.find(
|
|
294
|
+
(entry) => entry.default,
|
|
295
|
+
);
|
|
296
|
+
return [{
|
|
297
|
+
id: model.modelId,
|
|
298
|
+
model: model.modelId,
|
|
299
|
+
displayName: model.name ?? model.modelId,
|
|
300
|
+
description: model.description ?? '',
|
|
301
|
+
isDefault:
|
|
302
|
+
model.modelId === currentModelId || (!currentModelId && index === 0),
|
|
303
|
+
hidden: false,
|
|
304
|
+
supportedReasoningEfforts: efforts,
|
|
305
|
+
defaultReasoningEffort:
|
|
306
|
+
normalizeAcpEffort(model._meta?.reasoningEffort) ??
|
|
307
|
+
normalizeAcpEffort(declaredDefault?.value ?? declaredDefault?.id),
|
|
308
|
+
selectionKind: 'model',
|
|
309
|
+
}];
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function selectedLegacyModel(state: AcpSessionState) {
|
|
314
|
+
const models = state.legacyModels?.availableModels ?? [];
|
|
315
|
+
return models.find((model) => model.modelId === state.model) ?? models[0] ?? null;
|
|
316
|
+
}
|
|
317
|
+
|
|
242
318
|
function permissionOptions(params: acp.RequestPermissionRequest) {
|
|
243
319
|
return params.options.map((option) => ({
|
|
244
320
|
id: option.optionId,
|
|
@@ -578,7 +654,34 @@ export class AcpRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
578
654
|
const sessionCapabilities =
|
|
579
655
|
this.initializeResponse?.agentCapabilities?.sessionCapabilities;
|
|
580
656
|
if (!sessionCapabilities?.delete) {
|
|
581
|
-
|
|
657
|
+
const normalizedCwd = path.resolve(cwd);
|
|
658
|
+
const loadedSession = [...this.sessions.values()].find(
|
|
659
|
+
(session) =>
|
|
660
|
+
session.cwd === normalizedCwd &&
|
|
661
|
+
(session.configOptions.length > 0 || session.legacyModels !== null),
|
|
662
|
+
);
|
|
663
|
+
if (!loadedSession) {
|
|
664
|
+
return this.listModels();
|
|
665
|
+
}
|
|
666
|
+
if (loadedSession.legacyModels) {
|
|
667
|
+
return legacyModelOptions(loadedSession.legacyModels);
|
|
668
|
+
}
|
|
669
|
+
const reasoning = reasoningOptions(loadedSession.configOptions);
|
|
670
|
+
const supportsPerformanceMode = loadedSession.configOptions.some(
|
|
671
|
+
(option) => option.id === 'fast-mode' || option.category === 'model_config',
|
|
672
|
+
);
|
|
673
|
+
return [{
|
|
674
|
+
id: 'default',
|
|
675
|
+
model: 'default',
|
|
676
|
+
displayName: 'Agent default',
|
|
677
|
+
description: 'Use the model configured by the ACP agent.',
|
|
678
|
+
isDefault: true,
|
|
679
|
+
hidden: false,
|
|
680
|
+
supportsPerformanceMode,
|
|
681
|
+
supportedReasoningEfforts: reasoning.efforts,
|
|
682
|
+
defaultReasoningEffort: reasoning.defaultEffort,
|
|
683
|
+
selectionKind: 'model',
|
|
684
|
+
}];
|
|
582
685
|
}
|
|
583
686
|
const context = await this.requireContext();
|
|
584
687
|
const response = await context.request(acp.methods.agent.session.new, {
|
|
@@ -592,6 +695,10 @@ export class AcpRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
592
695
|
(option) => option.id === 'fast-mode' || option.category === 'model_config',
|
|
593
696
|
);
|
|
594
697
|
try {
|
|
698
|
+
const legacyModels = legacyModelsFromResponse(response);
|
|
699
|
+
if (legacyModels) {
|
|
700
|
+
return legacyModelOptions(legacyModels);
|
|
701
|
+
}
|
|
595
702
|
const modelOption = configOptionByCategory(configOptions, 'model');
|
|
596
703
|
if (!modelOption || modelOption.type !== 'select') {
|
|
597
704
|
const reasoning = reasoningOptions(configOptions);
|
|
@@ -792,6 +899,7 @@ export class AcpRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
792
899
|
}
|
|
793
900
|
forked.modes = response.modes ?? forked.modes;
|
|
794
901
|
forked.configOptions = response.configOptions ?? forked.configOptions;
|
|
902
|
+
forked.legacyModels = legacyModelsFromResponse(response) ?? forked.legacyModels;
|
|
795
903
|
this.syncStateFromConfigOptions(forked);
|
|
796
904
|
return sessionDetail(forked);
|
|
797
905
|
}
|
|
@@ -880,6 +988,9 @@ export class AcpRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
880
988
|
mcpServers: [],
|
|
881
989
|
_meta: {
|
|
882
990
|
yoloMode: input.approvalMode === 'yolo',
|
|
991
|
+
...(input.reasoningEffort
|
|
992
|
+
? { reasoningEffort: input.reasoningEffort }
|
|
993
|
+
: {}),
|
|
883
994
|
},
|
|
884
995
|
});
|
|
885
996
|
const now = new Date().toISOString();
|
|
@@ -897,6 +1008,7 @@ export class AcpRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
897
1008
|
activeMapper: null,
|
|
898
1009
|
modes: response.modes ?? null,
|
|
899
1010
|
configOptions: response.configOptions ?? [],
|
|
1011
|
+
legacyModels: legacyModelsFromResponse(response),
|
|
900
1012
|
availableCommands: [],
|
|
901
1013
|
hydrationCoverage: null,
|
|
902
1014
|
goal: null,
|
|
@@ -1134,6 +1246,7 @@ export class AcpRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1134
1246
|
activeMapper: null,
|
|
1135
1247
|
modes: null,
|
|
1136
1248
|
configOptions: [],
|
|
1249
|
+
legacyModels: null,
|
|
1137
1250
|
availableCommands: [],
|
|
1138
1251
|
hydrationCoverage: null,
|
|
1139
1252
|
goal: null,
|
|
@@ -1152,6 +1265,7 @@ export class AcpRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1152
1265
|
});
|
|
1153
1266
|
state.modes = response.modes ?? null;
|
|
1154
1267
|
state.configOptions = response.configOptions ?? [];
|
|
1268
|
+
state.legacyModels = legacyModelsFromResponse(response);
|
|
1155
1269
|
state.turns = hydrator.complete();
|
|
1156
1270
|
state.hydrationCoverage = hydrator.coverage();
|
|
1157
1271
|
this.syncStateFromConfigOptions(state);
|
|
@@ -1166,6 +1280,7 @@ export class AcpRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1166
1280
|
});
|
|
1167
1281
|
state.modes = response.modes ?? null;
|
|
1168
1282
|
state.configOptions = response.configOptions ?? [];
|
|
1283
|
+
state.legacyModels = legacyModelsFromResponse(response);
|
|
1169
1284
|
this.syncStateFromConfigOptions(state);
|
|
1170
1285
|
} else {
|
|
1171
1286
|
throw new Error('ACP agent does not support session/load or session/resume.');
|
|
@@ -1195,7 +1310,10 @@ export class AcpRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1195
1310
|
if (model && model !== 'default') {
|
|
1196
1311
|
state.model = await this.setConfigOption(state, 'model', model);
|
|
1197
1312
|
}
|
|
1198
|
-
if (
|
|
1313
|
+
if (
|
|
1314
|
+
reasoningEffort &&
|
|
1315
|
+
normalizeAcpEffort(reasoningEffort) !== state.reasoningEffort
|
|
1316
|
+
) {
|
|
1199
1317
|
const applied = await this.setConfigOption(
|
|
1200
1318
|
state,
|
|
1201
1319
|
'thought_level',
|
|
@@ -1239,17 +1357,42 @@ export class AcpRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1239
1357
|
category: 'model' | 'thought_level',
|
|
1240
1358
|
value: string,
|
|
1241
1359
|
) {
|
|
1242
|
-
const option = state.configOptions
|
|
1243
|
-
candidate.category === category || candidate.id.toLowerCase().includes(category === 'model' ? 'model' : 'thought'),
|
|
1244
|
-
);
|
|
1360
|
+
const option = configOptionByCategory(state.configOptions, category);
|
|
1245
1361
|
if ((!option || option.type !== 'select') && category === 'model') {
|
|
1246
1362
|
const context = await this.requireContext();
|
|
1247
1363
|
await context.request('session/set_model', {
|
|
1248
1364
|
sessionId: state.providerSessionId,
|
|
1249
1365
|
modelId: value,
|
|
1250
1366
|
});
|
|
1367
|
+
if (state.legacyModels) {
|
|
1368
|
+
state.legacyModels.currentModelId = value;
|
|
1369
|
+
}
|
|
1251
1370
|
return value;
|
|
1252
1371
|
}
|
|
1372
|
+
if ((!option || option.type !== 'select') && category === 'thought_level') {
|
|
1373
|
+
const legacyModel = selectedLegacyModel(state);
|
|
1374
|
+
const selected = legacyModel?._meta?.reasoningEfforts?.find(
|
|
1375
|
+
(entry) =>
|
|
1376
|
+
normalizeAcpEffort(entry.value ?? entry.id) ===
|
|
1377
|
+
normalizeAcpEffort(value),
|
|
1378
|
+
);
|
|
1379
|
+
const modeId = selected?.value ?? selected?.id;
|
|
1380
|
+
if (modeId) {
|
|
1381
|
+
const context = await this.requireContext();
|
|
1382
|
+
const response = await context.request(acp.methods.agent.session.load, {
|
|
1383
|
+
sessionId: state.providerSessionId,
|
|
1384
|
+
cwd: state.cwd,
|
|
1385
|
+
mcpServers: [],
|
|
1386
|
+
_meta: { reasoningEffort: modeId },
|
|
1387
|
+
});
|
|
1388
|
+
state.modes = response.modes ?? state.modes;
|
|
1389
|
+
state.configOptions = response.configOptions ?? state.configOptions;
|
|
1390
|
+
state.legacyModels =
|
|
1391
|
+
legacyModelsFromResponse(response) ?? state.legacyModels;
|
|
1392
|
+
this.syncStateFromConfigOptions(state);
|
|
1393
|
+
return state.reasoningEffort ?? modeId;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1253
1396
|
if (!option || option.type !== 'select') {
|
|
1254
1397
|
throw new AgentRuntimeError(
|
|
1255
1398
|
`ACP agent does not expose a ${category} config option.`,
|
|
@@ -1293,6 +1436,20 @@ export class AcpRuntimeAdapter extends EventEmitter implements AgentRuntime {
|
|
|
1293
1436
|
if (thoughtOption?.type === 'select') {
|
|
1294
1437
|
state.reasoningEffort = normalizeAcpEffort(thoughtOption.currentValue);
|
|
1295
1438
|
}
|
|
1439
|
+
if (!modelOption && state.legacyModels) {
|
|
1440
|
+
state.model = state.legacyModels.currentModelId ??
|
|
1441
|
+
state.legacyModels.availableModels?.[0]?.modelId ??
|
|
1442
|
+
null;
|
|
1443
|
+
}
|
|
1444
|
+
if (!thoughtOption) {
|
|
1445
|
+
const legacyModel = selectedLegacyModel(state);
|
|
1446
|
+
const declaredDefault = legacyModel?._meta?.reasoningEfforts?.find(
|
|
1447
|
+
(entry) => entry.default,
|
|
1448
|
+
);
|
|
1449
|
+
state.reasoningEffort =
|
|
1450
|
+
normalizeAcpEffort(legacyModel?._meta?.reasoningEffort) ??
|
|
1451
|
+
normalizeAcpEffort(declaredDefault?.value ?? declaredDefault?.id);
|
|
1452
|
+
}
|
|
1296
1453
|
}
|
|
1297
1454
|
|
|
1298
1455
|
private updateCapabilitiesFromConfigOptions(
|
|
@@ -25,6 +25,7 @@ const agentKind = process.env.REMOTE_CODEX_FAKE_ACP_AGENT_KIND ?? 'fixture';
|
|
|
25
25
|
const supportsFork = process.env.REMOTE_CODEX_FAKE_ACP_FORK === '1';
|
|
26
26
|
const noSessionCleanup = process.env.REMOTE_CODEX_FAKE_ACP_NO_SESSION_CLEANUP === '1';
|
|
27
27
|
const noSessionDelete = process.env.REMOTE_CODEX_FAKE_ACP_NO_SESSION_DELETE === '1';
|
|
28
|
+
const legacyModels = process.env.REMOTE_CODEX_FAKE_ACP_LEGACY_MODELS === '1';
|
|
28
29
|
const goalVersion = process.env.REMOTE_CODEX_FAKE_ACP_GOAL_VERSION ?? '1';
|
|
29
30
|
const goalActions = (process.env.REMOTE_CODEX_FAKE_ACP_GOAL_ACTIONS ?? 'get,set,clear')
|
|
30
31
|
.split(',')
|
|
@@ -90,6 +91,38 @@ function modes(session) {
|
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
function responseState(session) {
|
|
94
|
+
if (legacyModels) {
|
|
95
|
+
return {
|
|
96
|
+
models: {
|
|
97
|
+
currentModelId: session.config.model,
|
|
98
|
+
availableModels: [
|
|
99
|
+
{
|
|
100
|
+
modelId: 'fixture-model',
|
|
101
|
+
name: 'Fixture model',
|
|
102
|
+
_meta: {
|
|
103
|
+
reasoningEffort: session.config.thought,
|
|
104
|
+
reasoningEfforts: [
|
|
105
|
+
{ id: 'high', value: 'high', label: 'High', default: true },
|
|
106
|
+
{ id: 'medium', value: 'medium', label: 'Medium' },
|
|
107
|
+
{ id: 'low', value: 'low', label: 'Low' },
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
modelId: 'fixture-fast',
|
|
113
|
+
name: 'Fixture fast model',
|
|
114
|
+
_meta: {
|
|
115
|
+
reasoningEffort: session.config.thought,
|
|
116
|
+
reasoningEfforts: [
|
|
117
|
+
{ id: 'high', value: 'high', label: 'High', default: true },
|
|
118
|
+
{ id: 'low', value: 'low', label: 'Low' },
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
}
|
|
93
126
|
return {
|
|
94
127
|
modes: modes(session),
|
|
95
128
|
configOptions: configOptions(session),
|
|
@@ -290,13 +323,20 @@ acp.agent({ name: 'remote-codex-fake-acp-agent' })
|
|
|
290
323
|
}))
|
|
291
324
|
.onRequest(acp.methods.agent.session.new, async (context) => {
|
|
292
325
|
const now = new Date().toISOString();
|
|
326
|
+
const config = defaultConfig();
|
|
327
|
+
if (
|
|
328
|
+
legacyModels &&
|
|
329
|
+
typeof context.params._meta?.reasoningEffort === 'string'
|
|
330
|
+
) {
|
|
331
|
+
config.thought = context.params._meta.reasoningEffort;
|
|
332
|
+
}
|
|
293
333
|
const session = {
|
|
294
334
|
sessionId: randomUUID(),
|
|
295
335
|
cwd: path.resolve(context.params.cwd),
|
|
296
336
|
title: 'Fixture session',
|
|
297
337
|
createdAt: now,
|
|
298
338
|
updatedAt: now,
|
|
299
|
-
config
|
|
339
|
+
config,
|
|
300
340
|
turns: [],
|
|
301
341
|
pendingPrompt: null,
|
|
302
342
|
};
|
|
@@ -315,6 +355,12 @@ acp.agent({ name: 'remote-codex-fake-acp-agent' })
|
|
|
315
355
|
}))
|
|
316
356
|
.onRequest(acp.methods.agent.session.load, async (context) => {
|
|
317
357
|
const session = requireSession(context.params.sessionId);
|
|
358
|
+
if (
|
|
359
|
+
legacyModels &&
|
|
360
|
+
typeof context.params._meta?.reasoningEffort === 'string'
|
|
361
|
+
) {
|
|
362
|
+
session.config.thought = context.params._meta.reasoningEffort;
|
|
363
|
+
}
|
|
318
364
|
await replaySession(context.client, session);
|
|
319
365
|
return responseState(session);
|
|
320
366
|
})
|
|
@@ -348,7 +394,15 @@ acp.agent({ name: 'remote-codex-fake-acp-agent' })
|
|
|
348
394
|
})
|
|
349
395
|
.onRequest(acp.methods.agent.session.setMode, async (context) => {
|
|
350
396
|
const session = requireSession(context.params.sessionId);
|
|
351
|
-
session.config.
|
|
397
|
+
if (legacyModels) session.config.thought = context.params.modeId;
|
|
398
|
+
else session.config.mode = context.params.modeId;
|
|
399
|
+
session.updatedAt = new Date().toISOString();
|
|
400
|
+
await saveState();
|
|
401
|
+
return {};
|
|
402
|
+
})
|
|
403
|
+
.onRequest('session/set_model', (params) => params, async (context) => {
|
|
404
|
+
const session = requireSession(context.params.sessionId);
|
|
405
|
+
session.config.model = context.params.modelId;
|
|
352
406
|
session.updatedAt = new Date().toISOString();
|
|
353
407
|
await saveState();
|
|
354
408
|
return {};
|
|
@@ -55,6 +55,27 @@ describe('codex history item persistence policy', () => {
|
|
|
55
55
|
).toEqual([{ ...finalMessage, transcriptOrder: 0 }]);
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
it('omits empty assistant and reasoning rows from runtime history', () => {
|
|
59
|
+
const finalMessage: AgentHistoryItem = {
|
|
60
|
+
id: 'agent-final-1',
|
|
61
|
+
kind: 'agentMessage',
|
|
62
|
+
text: 'Final answer',
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
expect(
|
|
66
|
+
agentTurnToThreadTurnDto({
|
|
67
|
+
providerTurnId: 'turn-1',
|
|
68
|
+
status: 'completed',
|
|
69
|
+
error: null,
|
|
70
|
+
items: [
|
|
71
|
+
{ id: 'reasoning-empty', kind: 'reasoning', text: ' \n' },
|
|
72
|
+
{ id: 'agent-empty', kind: 'agentMessage', text: '' },
|
|
73
|
+
finalMessage,
|
|
74
|
+
],
|
|
75
|
+
}).items,
|
|
76
|
+
).toEqual([{ ...finalMessage, transcriptOrder: 0 }]);
|
|
77
|
+
});
|
|
78
|
+
|
|
58
79
|
it('maps Codex collab agent tool calls to dedicated agent tool call items', () => {
|
|
59
80
|
const turn = codexTurnToAgentTurn({
|
|
60
81
|
id: 'turn-1',
|
|
@@ -541,14 +541,21 @@ export function shouldPersistRuntimeFinalHistoryItem(
|
|
|
541
541
|
}
|
|
542
542
|
|
|
543
543
|
function visibleRuntimeTurnItems(items: ThreadHistoryItemDto[]) {
|
|
544
|
-
const
|
|
544
|
+
const nonEmptyItems = items.filter(
|
|
545
|
+
(item) =>
|
|
546
|
+
!(
|
|
547
|
+
(item.kind === 'agentMessage' || item.kind === 'reasoning') &&
|
|
548
|
+
item.text.trim().length === 0
|
|
549
|
+
),
|
|
550
|
+
);
|
|
551
|
+
const hasFinalAgentMessage = nonEmptyItems.some(
|
|
545
552
|
(item) => item.kind === 'agentMessage' && !isTransientAgentHistoryItem(item),
|
|
546
553
|
);
|
|
547
554
|
if (!hasFinalAgentMessage) {
|
|
548
|
-
return
|
|
555
|
+
return nonEmptyItems;
|
|
549
556
|
}
|
|
550
557
|
|
|
551
|
-
return
|
|
558
|
+
return nonEmptyItems.filter(
|
|
552
559
|
(item) => !(item.kind === 'agentMessage' && isTransientAgentHistoryItem(item)),
|
|
553
560
|
);
|
|
554
561
|
}
|