@autohq/cli 0.1.225 → 0.1.227
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/dist/agent-bridge.js +1235 -5
- package/dist/index.js +1413 -164
- package/package.json +1 -1
package/dist/agent-bridge.js
CHANGED
|
@@ -43,7 +43,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
43
43
|
"use strict";
|
|
44
44
|
var fs = __require("fs");
|
|
45
45
|
var Url = __require("url");
|
|
46
|
-
var
|
|
46
|
+
var spawn2 = __require("child_process").spawn;
|
|
47
47
|
module.exports = XMLHttpRequest3;
|
|
48
48
|
XMLHttpRequest3.XMLHttpRequest = XMLHttpRequest3;
|
|
49
49
|
function XMLHttpRequest3(opts) {
|
|
@@ -339,7 +339,7 @@ var require_XMLHttpRequest = __commonJS({
|
|
|
339
339
|
var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
|
|
340
340
|
fs.writeFileSync(syncFile, "", "utf8");
|
|
341
341
|
var execString = "var http = require('http'), https = require('https'), fs = require('fs');var doRequest = http" + (ssl ? "s" : "") + ".request;var options = " + JSON.stringify(options) + ";var responseText = '';var responseData = Buffer.alloc(0);var req = doRequest(options, function(response) {response.on('data', function(chunk) { var data = Buffer.from(chunk); responseText += data.toString('utf8'); responseData = Buffer.concat([responseData, data]);});response.on('end', function() {fs.writeFileSync('" + contentFile + "', JSON.stringify({err: null, data: {statusCode: response.statusCode, headers: response.headers, text: responseText, data: responseData.toString('base64')}}), 'utf8');fs.unlinkSync('" + syncFile + "');});response.on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});}).on('error', function(error) {fs.writeFileSync('" + contentFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');fs.unlinkSync('" + syncFile + "');});" + (data ? "req.write('" + JSON.stringify(data).slice(1, -1).replace(/'/g, "\\'") + "');" : "") + "req.end();";
|
|
342
|
-
var syncProc =
|
|
342
|
+
var syncProc = spawn2(process.argv[0], ["-e", execString]);
|
|
343
343
|
var statusText;
|
|
344
344
|
while (fs.existsSync(syncFile)) {
|
|
345
345
|
}
|
|
@@ -19545,6 +19545,9 @@ var AgentBridgeClaudeConfigSchema = AgentBridgeHarnessBaseConfigSchema;
|
|
|
19545
19545
|
var AgentBridgeHarnessConfigSchema = external_exports.discriminatedUnion("kind", [
|
|
19546
19546
|
AgentBridgeHarnessBaseConfigSchema.extend({
|
|
19547
19547
|
kind: external_exports.literal("claude-code")
|
|
19548
|
+
}),
|
|
19549
|
+
AgentBridgeHarnessBaseConfigSchema.extend({
|
|
19550
|
+
kind: external_exports.literal("codex")
|
|
19548
19551
|
})
|
|
19549
19552
|
]);
|
|
19550
19553
|
var RuntimeBridgeBootstrapPlaintextSchema = external_exports.object({
|
|
@@ -23319,7 +23322,7 @@ Object.assign(lookup, {
|
|
|
23319
23322
|
// package.json
|
|
23320
23323
|
var package_default = {
|
|
23321
23324
|
name: "@autohq/cli",
|
|
23322
|
-
version: "0.1.
|
|
23325
|
+
version: "0.1.227",
|
|
23323
23326
|
license: "SEE LICENSE IN README.md",
|
|
23324
23327
|
publishConfig: {
|
|
23325
23328
|
access: "public"
|
|
@@ -24597,12 +24600,12 @@ function parseClaudeCodeStreamRecord(parsed) {
|
|
|
24597
24600
|
}
|
|
24598
24601
|
const isError = record2.is_error === true || record2.subtype === "error";
|
|
24599
24602
|
const sdkErrorMessage = record2.errors?.map((error51) => error51.trim()).filter(Boolean).join("\n");
|
|
24600
|
-
const
|
|
24603
|
+
const errorMessage4 = isError ? record2.error ?? record2.result ?? (sdkErrorMessage || void 0) ?? "claude-code reported an error" : void 0;
|
|
24601
24604
|
return {
|
|
24602
24605
|
projections: [],
|
|
24603
24606
|
result: {
|
|
24604
24607
|
isError,
|
|
24605
|
-
errorMessage:
|
|
24608
|
+
errorMessage: errorMessage4
|
|
24606
24609
|
}
|
|
24607
24610
|
};
|
|
24608
24611
|
}
|
|
@@ -24816,6 +24819,250 @@ var CodexFrameSchema = external_exports.object({
|
|
|
24816
24819
|
result: external_exports.unknown().optional(),
|
|
24817
24820
|
error: external_exports.object({ message: external_exports.string().optional() }).passthrough().optional()
|
|
24818
24821
|
}).passthrough();
|
|
24822
|
+
function parseCodexServerFrame(raw) {
|
|
24823
|
+
const frame = CodexFrameSchema.parse(raw);
|
|
24824
|
+
if (frame.method === void 0 && frame.id !== void 0) {
|
|
24825
|
+
return {
|
|
24826
|
+
kind: "response",
|
|
24827
|
+
id: frame.id,
|
|
24828
|
+
...frame.result !== void 0 ? { result: toJsonValue(frame.result) } : {},
|
|
24829
|
+
...frame.error?.message ? { error: frame.error.message } : {}
|
|
24830
|
+
};
|
|
24831
|
+
}
|
|
24832
|
+
if (frame.method === void 0) {
|
|
24833
|
+
return { kind: "ignored" };
|
|
24834
|
+
}
|
|
24835
|
+
if (frame.id !== void 0) {
|
|
24836
|
+
return classifyServerRequest(frame.method, frame.id, frame.params);
|
|
24837
|
+
}
|
|
24838
|
+
const notification = parseNotification(frame.method, frame.params);
|
|
24839
|
+
return notification ? { kind: "notification", notification } : { kind: "ignored" };
|
|
24840
|
+
}
|
|
24841
|
+
function projectCodexItem(input) {
|
|
24842
|
+
const { item, phase } = input;
|
|
24843
|
+
switch (item.type) {
|
|
24844
|
+
case "agentMessage":
|
|
24845
|
+
return phase === "completed" && item.text.length > 0 ? [
|
|
24846
|
+
{
|
|
24847
|
+
role: "assistant",
|
|
24848
|
+
kind: "message",
|
|
24849
|
+
messageId: item.id,
|
|
24850
|
+
content: textContent2(item.text)
|
|
24851
|
+
}
|
|
24852
|
+
] : [];
|
|
24853
|
+
case "commandExecution":
|
|
24854
|
+
return toolProjection({
|
|
24855
|
+
phase,
|
|
24856
|
+
itemId: item.id,
|
|
24857
|
+
name: "shell",
|
|
24858
|
+
input: {
|
|
24859
|
+
command: item.command,
|
|
24860
|
+
...item.cwd ? { cwd: item.cwd } : {}
|
|
24861
|
+
},
|
|
24862
|
+
output: item.aggregatedOutput ?? "",
|
|
24863
|
+
isError: isFailedStatus(item.status) || (item.exitCode ?? 0) !== 0
|
|
24864
|
+
});
|
|
24865
|
+
case "fileChange":
|
|
24866
|
+
return toolProjection({
|
|
24867
|
+
phase,
|
|
24868
|
+
itemId: item.id,
|
|
24869
|
+
name: "apply_patch",
|
|
24870
|
+
input: { changes: toJsonValue(item.changes) },
|
|
24871
|
+
output: { status: item.status ?? "unknown" },
|
|
24872
|
+
isError: isFailedStatus(item.status)
|
|
24873
|
+
});
|
|
24874
|
+
case "mcpToolCall":
|
|
24875
|
+
return toolProjection({
|
|
24876
|
+
phase,
|
|
24877
|
+
itemId: item.id,
|
|
24878
|
+
name: `${item.server}.${item.tool}`,
|
|
24879
|
+
input: toJsonValue(item.arguments),
|
|
24880
|
+
output: item.error ? { error: item.error.message } : toJsonValue(item.result),
|
|
24881
|
+
isError: isFailedStatus(item.status) || item.error != null
|
|
24882
|
+
});
|
|
24883
|
+
default:
|
|
24884
|
+
return [];
|
|
24885
|
+
}
|
|
24886
|
+
}
|
|
24887
|
+
function projectCodexApproval(request) {
|
|
24888
|
+
const subject = request.type === "commandExecution" ? `run the command: ${request.command ?? "(unknown command)"}` : "apply file changes";
|
|
24889
|
+
const question = {
|
|
24890
|
+
question: request.reason ? `Codex requests approval to ${subject}. ${request.reason}` : `Codex requests approval to ${subject}.`,
|
|
24891
|
+
header: "Approval",
|
|
24892
|
+
options: [
|
|
24893
|
+
{ label: APPROVE_OPTION_LABEL, description: "Allow this action." },
|
|
24894
|
+
{ label: DECLINE_OPTION_LABEL, description: "Reject this action." }
|
|
24895
|
+
],
|
|
24896
|
+
multiSelect: false
|
|
24897
|
+
};
|
|
24898
|
+
return {
|
|
24899
|
+
role: "assistant",
|
|
24900
|
+
kind: "question",
|
|
24901
|
+
content: {
|
|
24902
|
+
parts: [
|
|
24903
|
+
{ type: "question", toolCallId: request.itemId, questions: [question] }
|
|
24904
|
+
]
|
|
24905
|
+
}
|
|
24906
|
+
};
|
|
24907
|
+
}
|
|
24908
|
+
var APPROVE_OPTION_LABEL = "Approve";
|
|
24909
|
+
var DECLINE_OPTION_LABEL = "Decline";
|
|
24910
|
+
function codexApprovalDecision(input) {
|
|
24911
|
+
const values = [...Object.values(input.answers), input.response ?? ""];
|
|
24912
|
+
const approved = values.some(
|
|
24913
|
+
(value2) => value2.trim().toLowerCase() === APPROVE_OPTION_LABEL.toLowerCase()
|
|
24914
|
+
);
|
|
24915
|
+
return approved ? "accept" : "decline";
|
|
24916
|
+
}
|
|
24917
|
+
function parseNotification(method, params) {
|
|
24918
|
+
switch (method) {
|
|
24919
|
+
case "thread/started": {
|
|
24920
|
+
const parsed = external_exports.object({ thread: external_exports.object({ id: external_exports.string() }).passthrough() }).passthrough().safeParse(params);
|
|
24921
|
+
return parsed.success ? { type: "threadStarted", threadId: parsed.data.thread.id } : null;
|
|
24922
|
+
}
|
|
24923
|
+
case "turn/started": {
|
|
24924
|
+
const parsed = CodexTurnEnvelopeSchema.safeParse(params);
|
|
24925
|
+
return parsed.success ? {
|
|
24926
|
+
type: "turnStarted",
|
|
24927
|
+
threadId: parsed.data.threadId,
|
|
24928
|
+
turnId: parsed.data.turn.id
|
|
24929
|
+
} : null;
|
|
24930
|
+
}
|
|
24931
|
+
case "turn/completed": {
|
|
24932
|
+
const parsed = CodexTurnEnvelopeSchema.safeParse(params);
|
|
24933
|
+
return parsed.success ? {
|
|
24934
|
+
type: "turnCompleted",
|
|
24935
|
+
threadId: parsed.data.threadId,
|
|
24936
|
+
turnId: parsed.data.turn.id,
|
|
24937
|
+
status: parsed.data.turn.status,
|
|
24938
|
+
...parsed.data.turn.error?.message ? { errorMessage: parsed.data.turn.error.message } : {}
|
|
24939
|
+
} : null;
|
|
24940
|
+
}
|
|
24941
|
+
case "item/started":
|
|
24942
|
+
case "item/completed": {
|
|
24943
|
+
const parsed = CodexItemEnvelopeSchema.safeParse(params);
|
|
24944
|
+
if (!parsed.success || parsed.data.item === null) {
|
|
24945
|
+
return null;
|
|
24946
|
+
}
|
|
24947
|
+
return {
|
|
24948
|
+
type: method === "item/started" ? "itemStarted" : "itemCompleted",
|
|
24949
|
+
threadId: parsed.data.threadId,
|
|
24950
|
+
turnId: parsed.data.turnId,
|
|
24951
|
+
item: parsed.data.item
|
|
24952
|
+
};
|
|
24953
|
+
}
|
|
24954
|
+
case "item/agentMessage/delta": {
|
|
24955
|
+
const parsed = external_exports.object({ itemId: external_exports.string(), delta: external_exports.string() }).passthrough().safeParse(params);
|
|
24956
|
+
return parsed.success ? {
|
|
24957
|
+
type: "agentMessageDelta",
|
|
24958
|
+
itemId: parsed.data.itemId,
|
|
24959
|
+
delta: parsed.data.delta
|
|
24960
|
+
} : null;
|
|
24961
|
+
}
|
|
24962
|
+
case "error": {
|
|
24963
|
+
const parsed = external_exports.object({
|
|
24964
|
+
error: external_exports.object({ message: external_exports.string() }).passthrough(),
|
|
24965
|
+
willRetry: external_exports.boolean().default(false),
|
|
24966
|
+
threadId: external_exports.string().default("")
|
|
24967
|
+
}).passthrough().safeParse(params);
|
|
24968
|
+
return parsed.success ? {
|
|
24969
|
+
type: "error",
|
|
24970
|
+
threadId: parsed.data.threadId,
|
|
24971
|
+
willRetry: parsed.data.willRetry,
|
|
24972
|
+
message: parsed.data.error.message
|
|
24973
|
+
} : null;
|
|
24974
|
+
}
|
|
24975
|
+
default:
|
|
24976
|
+
return null;
|
|
24977
|
+
}
|
|
24978
|
+
}
|
|
24979
|
+
function classifyServerRequest(method, requestId, params) {
|
|
24980
|
+
if (method === "mcpServer/elicitation/request") {
|
|
24981
|
+
const parsed = external_exports.object({ serverName: external_exports.string().default("") }).passthrough().safeParse(params);
|
|
24982
|
+
return {
|
|
24983
|
+
kind: "elicitation",
|
|
24984
|
+
requestId,
|
|
24985
|
+
serverName: parsed.success ? parsed.data.serverName : ""
|
|
24986
|
+
};
|
|
24987
|
+
}
|
|
24988
|
+
const approval = parseApprovalRequest(method, requestId, params);
|
|
24989
|
+
if (approval) {
|
|
24990
|
+
return { kind: "serverRequest", request: approval };
|
|
24991
|
+
}
|
|
24992
|
+
return { kind: "unsupportedRequest", requestId, method };
|
|
24993
|
+
}
|
|
24994
|
+
function parseApprovalRequest(method, requestId, params) {
|
|
24995
|
+
const base = external_exports.object({
|
|
24996
|
+
itemId: external_exports.string(),
|
|
24997
|
+
reason: external_exports.string().nullish(),
|
|
24998
|
+
command: external_exports.string().nullish()
|
|
24999
|
+
}).passthrough();
|
|
25000
|
+
switch (method) {
|
|
25001
|
+
case "item/commandExecution/requestApproval": {
|
|
25002
|
+
const parsed = base.safeParse(params);
|
|
25003
|
+
return parsed.success ? {
|
|
25004
|
+
type: "commandExecution",
|
|
25005
|
+
requestId,
|
|
25006
|
+
itemId: parsed.data.itemId,
|
|
25007
|
+
...parsed.data.reason ? { reason: parsed.data.reason } : {},
|
|
25008
|
+
...parsed.data.command ? { command: parsed.data.command } : {}
|
|
25009
|
+
} : null;
|
|
25010
|
+
}
|
|
25011
|
+
case "item/fileChange/requestApproval": {
|
|
25012
|
+
const parsed = base.safeParse(params);
|
|
25013
|
+
return parsed.success ? {
|
|
25014
|
+
type: "fileChange",
|
|
25015
|
+
requestId,
|
|
25016
|
+
itemId: parsed.data.itemId,
|
|
25017
|
+
...parsed.data.reason ? { reason: parsed.data.reason } : {}
|
|
25018
|
+
} : null;
|
|
25019
|
+
}
|
|
25020
|
+
default:
|
|
25021
|
+
return null;
|
|
25022
|
+
}
|
|
25023
|
+
}
|
|
25024
|
+
function toolProjection(input) {
|
|
25025
|
+
if (input.phase === "started") {
|
|
25026
|
+
return [
|
|
25027
|
+
{
|
|
25028
|
+
role: "assistant",
|
|
25029
|
+
kind: "tool_call",
|
|
25030
|
+
content: {
|
|
25031
|
+
parts: [
|
|
25032
|
+
{
|
|
25033
|
+
type: "tool_call",
|
|
25034
|
+
toolCallId: input.itemId,
|
|
25035
|
+
name: input.name,
|
|
25036
|
+
input: input.input
|
|
25037
|
+
}
|
|
25038
|
+
]
|
|
25039
|
+
}
|
|
25040
|
+
}
|
|
25041
|
+
];
|
|
25042
|
+
}
|
|
25043
|
+
return [
|
|
25044
|
+
{
|
|
25045
|
+
role: "tool",
|
|
25046
|
+
kind: "tool_result",
|
|
25047
|
+
content: {
|
|
25048
|
+
parts: [
|
|
25049
|
+
{
|
|
25050
|
+
type: "tool_result",
|
|
25051
|
+
toolUseId: input.itemId,
|
|
25052
|
+
output: input.output,
|
|
25053
|
+
isError: input.isError
|
|
25054
|
+
}
|
|
25055
|
+
]
|
|
25056
|
+
}
|
|
25057
|
+
}
|
|
25058
|
+
];
|
|
25059
|
+
}
|
|
25060
|
+
function textContent2(text) {
|
|
25061
|
+
return { parts: [{ type: "text", text }] };
|
|
25062
|
+
}
|
|
25063
|
+
function isFailedStatus(status) {
|
|
25064
|
+
return status === "failed" || status === "declined";
|
|
25065
|
+
}
|
|
24819
25066
|
|
|
24820
25067
|
// ../../packages/schemas/src/resources.ts
|
|
24821
25068
|
var ResourceNameSchema = external_exports.string().trim().min(1).max(128).regex(/^[A-Za-z0-9_.-]+$/);
|
|
@@ -48466,6 +48713,981 @@ function commandLogContext(delivery, fields = {}) {
|
|
|
48466
48713
|
};
|
|
48467
48714
|
}
|
|
48468
48715
|
|
|
48716
|
+
// src/commands/agent-bridge/harness/codex/projector.ts
|
|
48717
|
+
var CodexProjector = class {
|
|
48718
|
+
project(notification) {
|
|
48719
|
+
switch (notification.type) {
|
|
48720
|
+
case "agentMessageDelta":
|
|
48721
|
+
return [
|
|
48722
|
+
{
|
|
48723
|
+
type: "delta",
|
|
48724
|
+
delta: {
|
|
48725
|
+
messageId: notification.itemId,
|
|
48726
|
+
partId: "0",
|
|
48727
|
+
role: "assistant",
|
|
48728
|
+
kind: "message",
|
|
48729
|
+
delta: { type: "text", text: notification.delta }
|
|
48730
|
+
}
|
|
48731
|
+
}
|
|
48732
|
+
];
|
|
48733
|
+
case "itemStarted":
|
|
48734
|
+
return entryProjections(
|
|
48735
|
+
projectCodexItem({ item: notification.item, phase: "started" })
|
|
48736
|
+
);
|
|
48737
|
+
case "itemCompleted":
|
|
48738
|
+
return entryProjections(
|
|
48739
|
+
projectCodexItem({ item: notification.item, phase: "completed" })
|
|
48740
|
+
);
|
|
48741
|
+
case "turnCompleted":
|
|
48742
|
+
return [turnCompletionEntry(notification)];
|
|
48743
|
+
case "error":
|
|
48744
|
+
return [errorEntry(notification)];
|
|
48745
|
+
default:
|
|
48746
|
+
return [];
|
|
48747
|
+
}
|
|
48748
|
+
}
|
|
48749
|
+
// An approval parks the turn on operator input, so the question entry also
|
|
48750
|
+
// marks the delivered turn as waiting for input.
|
|
48751
|
+
projectApproval(request) {
|
|
48752
|
+
return {
|
|
48753
|
+
type: "entry",
|
|
48754
|
+
entry: {
|
|
48755
|
+
...projectCodexApproval(request),
|
|
48756
|
+
turnStatus: "waiting_for_input"
|
|
48757
|
+
}
|
|
48758
|
+
};
|
|
48759
|
+
}
|
|
48760
|
+
// Surfaces a session-level failure (e.g. the app-server process dying) as a
|
|
48761
|
+
// durable failed status entry rather than only a diagnostic log line.
|
|
48762
|
+
projectSessionFailure(message) {
|
|
48763
|
+
return {
|
|
48764
|
+
type: "entry",
|
|
48765
|
+
entry: {
|
|
48766
|
+
role: "system",
|
|
48767
|
+
kind: "status",
|
|
48768
|
+
status: "failed",
|
|
48769
|
+
turnStatus: "failed",
|
|
48770
|
+
content: {
|
|
48771
|
+
parts: [{ type: "text", text: `codex failed: ${message}` }]
|
|
48772
|
+
}
|
|
48773
|
+
}
|
|
48774
|
+
};
|
|
48775
|
+
}
|
|
48776
|
+
};
|
|
48777
|
+
function entryProjections(projections) {
|
|
48778
|
+
return projections.map((projection) => ({
|
|
48779
|
+
type: "entry",
|
|
48780
|
+
entry: projection
|
|
48781
|
+
}));
|
|
48782
|
+
}
|
|
48783
|
+
function turnCompletionEntry(notification) {
|
|
48784
|
+
if (notification.status === "failed") {
|
|
48785
|
+
const detail = notification.errorMessage ?? "unknown error";
|
|
48786
|
+
return statusEntry({
|
|
48787
|
+
text: `codex turn failed: ${detail}`,
|
|
48788
|
+
status: "failed",
|
|
48789
|
+
turnStatus: "failed"
|
|
48790
|
+
});
|
|
48791
|
+
}
|
|
48792
|
+
const text = notification.status === "interrupted" ? "codex turn interrupted" : "codex turn completed";
|
|
48793
|
+
return statusEntry({
|
|
48794
|
+
text,
|
|
48795
|
+
status: "completed",
|
|
48796
|
+
turnStatus: "completed"
|
|
48797
|
+
});
|
|
48798
|
+
}
|
|
48799
|
+
function errorEntry(notification) {
|
|
48800
|
+
if (notification.willRetry) {
|
|
48801
|
+
return statusEntry({
|
|
48802
|
+
text: `codex retrying after error: ${notification.message}`,
|
|
48803
|
+
status: "completed"
|
|
48804
|
+
});
|
|
48805
|
+
}
|
|
48806
|
+
return statusEntry({
|
|
48807
|
+
text: `codex error: ${notification.message}`,
|
|
48808
|
+
status: "failed",
|
|
48809
|
+
turnStatus: "failed"
|
|
48810
|
+
});
|
|
48811
|
+
}
|
|
48812
|
+
function statusEntry(input) {
|
|
48813
|
+
return {
|
|
48814
|
+
type: "entry",
|
|
48815
|
+
entry: {
|
|
48816
|
+
role: "system",
|
|
48817
|
+
kind: "status",
|
|
48818
|
+
status: input.status,
|
|
48819
|
+
...input.turnStatus ? { turnStatus: input.turnStatus } : {},
|
|
48820
|
+
content: { parts: [{ type: "text", text: input.text }] }
|
|
48821
|
+
}
|
|
48822
|
+
};
|
|
48823
|
+
}
|
|
48824
|
+
|
|
48825
|
+
// src/commands/agent-bridge/harness/codex/resume-store.ts
|
|
48826
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
48827
|
+
import { dirname as dirname2 } from "path";
|
|
48828
|
+
var AGENT_BRIDGE_RUNTIME_DIR2 = "/tmp/auto-bridge-runtime";
|
|
48829
|
+
var CODEX_THREAD_RESUME_PATH = `${AGENT_BRIDGE_RUNTIME_DIR2}/codex-thread-id`;
|
|
48830
|
+
function fileCodexThreadResumeStore(path2 = CODEX_THREAD_RESUME_PATH) {
|
|
48831
|
+
return {
|
|
48832
|
+
read(sessionId) {
|
|
48833
|
+
if (!existsSync3(path2)) {
|
|
48834
|
+
return null;
|
|
48835
|
+
}
|
|
48836
|
+
const record2 = parseResumeRecord2(readFileSync3(path2, "utf8"));
|
|
48837
|
+
if (!record2 || record2.sessionId !== sessionId) {
|
|
48838
|
+
return null;
|
|
48839
|
+
}
|
|
48840
|
+
return record2.threadId;
|
|
48841
|
+
},
|
|
48842
|
+
write(record2) {
|
|
48843
|
+
mkdirSync3(dirname2(path2), { recursive: true });
|
|
48844
|
+
writeFileSync2(path2, `${JSON.stringify(record2)}
|
|
48845
|
+
`, "utf8");
|
|
48846
|
+
}
|
|
48847
|
+
};
|
|
48848
|
+
}
|
|
48849
|
+
function parseResumeRecord2(raw) {
|
|
48850
|
+
try {
|
|
48851
|
+
const value2 = JSON.parse(raw);
|
|
48852
|
+
if (value2 !== null && typeof value2 === "object" && "sessionId" in value2 && "threadId" in value2 && typeof value2.sessionId === "string" && typeof value2.threadId === "string" && value2.threadId.length > 0) {
|
|
48853
|
+
return { sessionId: value2.sessionId, threadId: value2.threadId };
|
|
48854
|
+
}
|
|
48855
|
+
return null;
|
|
48856
|
+
} catch {
|
|
48857
|
+
return null;
|
|
48858
|
+
}
|
|
48859
|
+
}
|
|
48860
|
+
|
|
48861
|
+
// src/commands/agent-bridge/harness/codex/session.ts
|
|
48862
|
+
import { spawn } from "child_process";
|
|
48863
|
+
import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
48864
|
+
import { join as join2 } from "path";
|
|
48865
|
+
|
|
48866
|
+
// src/commands/agent-bridge/harness/codex/options.ts
|
|
48867
|
+
import { join } from "path";
|
|
48868
|
+
var CODEX_EXECUTABLE_PATH = "codex";
|
|
48869
|
+
var CODEX_DEFAULT_MODEL = "gpt-5.3-codex";
|
|
48870
|
+
var CODEX_HTTP_PROVIDER_ID = "openai-responses-http";
|
|
48871
|
+
var CODEX_OPENAI_BASE_URL = "https://api.openai.com/v1";
|
|
48872
|
+
var CODEX_API_KEY_ENV = "OPENAI_API_KEY";
|
|
48873
|
+
var CODEX_RUNTIME_PROCESS_ENV_KEYS = ["PATH", "HOME"];
|
|
48874
|
+
function codexLaunchOptions(config2) {
|
|
48875
|
+
return {
|
|
48876
|
+
command: codexExecutablePath(),
|
|
48877
|
+
// `--listen stdio://` is the default, but pin it so a config/feature change
|
|
48878
|
+
// cannot silently move the transport off the stdin/stdout the client drives.
|
|
48879
|
+
args: ["app-server", "--listen", "stdio://"],
|
|
48880
|
+
env: codexProcessEnv(config2.env),
|
|
48881
|
+
// Codex reads config/auth/rollout state from its standard home; the caller
|
|
48882
|
+
// writes the rendered config.toml there.
|
|
48883
|
+
codexHome: codexHomeDir(),
|
|
48884
|
+
configToml: renderCodexConfigToml(config2)
|
|
48885
|
+
};
|
|
48886
|
+
}
|
|
48887
|
+
function codexThreadParams(config2) {
|
|
48888
|
+
return {
|
|
48889
|
+
...config2.cwd ? { cwd: config2.cwd } : {},
|
|
48890
|
+
...config2.systemPromptAppend ? { developerInstructions: config2.systemPromptAppend } : {}
|
|
48891
|
+
};
|
|
48892
|
+
}
|
|
48893
|
+
function renderCodexConfigToml(config2) {
|
|
48894
|
+
const lines = [];
|
|
48895
|
+
lines.push(`model = ${tomlString(CODEX_DEFAULT_MODEL)}`);
|
|
48896
|
+
lines.push(`model_provider = ${tomlString(CODEX_HTTP_PROVIDER_ID)}`);
|
|
48897
|
+
lines.push("");
|
|
48898
|
+
lines.push(`[model_providers.${CODEX_HTTP_PROVIDER_ID}]`);
|
|
48899
|
+
lines.push('name = "OpenAI"');
|
|
48900
|
+
lines.push(`base_url = ${tomlString(CODEX_OPENAI_BASE_URL)}`);
|
|
48901
|
+
lines.push('wire_api = "responses"');
|
|
48902
|
+
lines.push(`env_key = ${tomlString(CODEX_API_KEY_ENV)}`);
|
|
48903
|
+
lines.push("supports_websockets = false");
|
|
48904
|
+
for (const [name, server] of Object.entries(config2.mcpServers ?? {})) {
|
|
48905
|
+
lines.push("");
|
|
48906
|
+
lines.push(`[mcp_servers.${tomlKey(name)}]`);
|
|
48907
|
+
lines.push(`url = ${tomlString(server.url)}`);
|
|
48908
|
+
if (server.headers && Object.keys(server.headers).length > 0) {
|
|
48909
|
+
lines.push(`http_headers = ${tomlInlineTable(server.headers)}`);
|
|
48910
|
+
}
|
|
48911
|
+
}
|
|
48912
|
+
return `${lines.join("\n")}
|
|
48913
|
+
`;
|
|
48914
|
+
}
|
|
48915
|
+
function codexProcessEnv(env) {
|
|
48916
|
+
const selected = {};
|
|
48917
|
+
for (const key of CODEX_RUNTIME_PROCESS_ENV_KEYS) {
|
|
48918
|
+
const value2 = process.env[key];
|
|
48919
|
+
if (value2 !== void 0) {
|
|
48920
|
+
selected[key] = value2;
|
|
48921
|
+
}
|
|
48922
|
+
}
|
|
48923
|
+
return {
|
|
48924
|
+
...selected,
|
|
48925
|
+
...env
|
|
48926
|
+
};
|
|
48927
|
+
}
|
|
48928
|
+
function codexHomeDir() {
|
|
48929
|
+
const home = process.env.HOME;
|
|
48930
|
+
if (!home) {
|
|
48931
|
+
throw new Error("codex launch requires HOME to locate the ~/.codex home");
|
|
48932
|
+
}
|
|
48933
|
+
return join(home, ".codex");
|
|
48934
|
+
}
|
|
48935
|
+
function codexExecutablePath() {
|
|
48936
|
+
if (process.env.AUTO_AGENT_BRIDGE_TEST_CODEX_COMMAND === "1" && process.env.AUTO_CODEX_COMMAND?.trim()) {
|
|
48937
|
+
return process.env.AUTO_CODEX_COMMAND.trim();
|
|
48938
|
+
}
|
|
48939
|
+
return CODEX_EXECUTABLE_PATH;
|
|
48940
|
+
}
|
|
48941
|
+
function tomlInlineTable(values) {
|
|
48942
|
+
const entries = Object.entries(values).map(
|
|
48943
|
+
([key, value2]) => `${tomlKey(key)} = ${tomlString(value2)}`
|
|
48944
|
+
);
|
|
48945
|
+
return `{ ${entries.join(", ")} }`;
|
|
48946
|
+
}
|
|
48947
|
+
function tomlKey(key) {
|
|
48948
|
+
return /^[A-Za-z0-9_-]+$/.test(key) ? key : tomlString(key);
|
|
48949
|
+
}
|
|
48950
|
+
function tomlString(value2) {
|
|
48951
|
+
return `"${value2.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
48952
|
+
}
|
|
48953
|
+
|
|
48954
|
+
// src/commands/agent-bridge/harness/codex/session.ts
|
|
48955
|
+
var CODEX_REQUEST_TIMEOUT_MS = 3e4;
|
|
48956
|
+
var CODEX_ITEM_SETTLE_TIMEOUT_MS = 1e4;
|
|
48957
|
+
var CODEX_TOOL_ITEM_TYPES = /* @__PURE__ */ new Set([
|
|
48958
|
+
"commandExecution",
|
|
48959
|
+
"fileChange",
|
|
48960
|
+
"mcpToolCall"
|
|
48961
|
+
]);
|
|
48962
|
+
function startCodexAgentBridgeSession(input) {
|
|
48963
|
+
return new CodexAgentBridgeSessionImpl(input);
|
|
48964
|
+
}
|
|
48965
|
+
var codexAgentBridgeRuntime = {
|
|
48966
|
+
start: startCodexAgentBridgeSession
|
|
48967
|
+
};
|
|
48968
|
+
var CodexAgentBridgeSessionImpl = class {
|
|
48969
|
+
input;
|
|
48970
|
+
proc = null;
|
|
48971
|
+
startup = null;
|
|
48972
|
+
closed = false;
|
|
48973
|
+
nextRequestId = 1;
|
|
48974
|
+
pending = /* @__PURE__ */ new Map();
|
|
48975
|
+
stdoutBuffer = "";
|
|
48976
|
+
threadId = null;
|
|
48977
|
+
activeTurnId = null;
|
|
48978
|
+
// Tool-like items started but not yet completed in the active turn. A non-empty
|
|
48979
|
+
// set means an interrupt/steer would race an unsettled item (FRA-3049 analog).
|
|
48980
|
+
pendingToolItemIds = /* @__PURE__ */ new Set();
|
|
48981
|
+
settlementWaiters = /* @__PURE__ */ new Set();
|
|
48982
|
+
// Messages held in "deferred" mode while a turn is in flight; flushed as a
|
|
48983
|
+
// fresh turn once the active turn completes.
|
|
48984
|
+
deferredMessages = [];
|
|
48985
|
+
constructor(input) {
|
|
48986
|
+
this.input = input;
|
|
48987
|
+
}
|
|
48988
|
+
async prepare() {
|
|
48989
|
+
await this.ensureStarted();
|
|
48990
|
+
}
|
|
48991
|
+
async sendMessage(message, options) {
|
|
48992
|
+
await this.ensureStarted();
|
|
48993
|
+
const threadId = this.requireThreadId();
|
|
48994
|
+
const mode = options?.mode ?? "interrupt";
|
|
48995
|
+
if (this.activeTurnId === null) {
|
|
48996
|
+
await this.startTurn(threadId, message);
|
|
48997
|
+
return;
|
|
48998
|
+
}
|
|
48999
|
+
if (mode === "deferred") {
|
|
49000
|
+
this.deferredMessages.push(message);
|
|
49001
|
+
this.input.writeOutput?.("agent_bridge_codex_message_deferred");
|
|
49002
|
+
return;
|
|
49003
|
+
}
|
|
49004
|
+
await this.injectIntoActiveTurn(threadId, message);
|
|
49005
|
+
}
|
|
49006
|
+
resolveApproval(resolution) {
|
|
49007
|
+
this.writeFrame({
|
|
49008
|
+
jsonrpc: "2.0",
|
|
49009
|
+
id: resolution.requestId,
|
|
49010
|
+
result: { decision: resolution.decision }
|
|
49011
|
+
});
|
|
49012
|
+
this.input.writeOutput?.(
|
|
49013
|
+
`agent_bridge_codex_approval_resolved decision=${resolution.decision}`
|
|
49014
|
+
);
|
|
49015
|
+
}
|
|
49016
|
+
close() {
|
|
49017
|
+
if (this.closed) {
|
|
49018
|
+
return;
|
|
49019
|
+
}
|
|
49020
|
+
this.closed = true;
|
|
49021
|
+
if (this.threadId && this.activeTurnId) {
|
|
49022
|
+
this.writeFrame({
|
|
49023
|
+
jsonrpc: "2.0",
|
|
49024
|
+
id: this.allocRequestId(),
|
|
49025
|
+
method: "turn/interrupt",
|
|
49026
|
+
params: { threadId: this.threadId, turnId: this.activeTurnId }
|
|
49027
|
+
});
|
|
49028
|
+
}
|
|
49029
|
+
this.activeTurnId = null;
|
|
49030
|
+
this.pendingToolItemIds.clear();
|
|
49031
|
+
this.resolveSettlement();
|
|
49032
|
+
this.rejectAllPending(new Error("Codex session is closed"));
|
|
49033
|
+
if (this.deferredMessages.length > 0) {
|
|
49034
|
+
this.input.writeOutput?.(
|
|
49035
|
+
`agent_bridge_codex_deferred_dropped count=${this.deferredMessages.length} reason=session_closed`
|
|
49036
|
+
);
|
|
49037
|
+
this.deferredMessages.length = 0;
|
|
49038
|
+
}
|
|
49039
|
+
this.proc?.kill("SIGTERM");
|
|
49040
|
+
this.proc = null;
|
|
49041
|
+
}
|
|
49042
|
+
// ---------------------------------------------------------------------------
|
|
49043
|
+
// Startup
|
|
49044
|
+
// ---------------------------------------------------------------------------
|
|
49045
|
+
ensureStarted() {
|
|
49046
|
+
if (this.startup) {
|
|
49047
|
+
return this.startup;
|
|
49048
|
+
}
|
|
49049
|
+
this.startup = this.start().catch((error51) => {
|
|
49050
|
+
this.startup = null;
|
|
49051
|
+
this.close();
|
|
49052
|
+
throw error51;
|
|
49053
|
+
});
|
|
49054
|
+
return this.startup;
|
|
49055
|
+
}
|
|
49056
|
+
async start() {
|
|
49057
|
+
const options = codexLaunchOptions(this.input.codex);
|
|
49058
|
+
mkdirSync4(options.codexHome, { recursive: true });
|
|
49059
|
+
writeFileSync3(join2(options.codexHome, "config.toml"), options.configToml);
|
|
49060
|
+
const startedAt = Date.now();
|
|
49061
|
+
this.input.writeOutput?.(
|
|
49062
|
+
`agent_bridge_codex_startup_started codex_home=${options.codexHome}`
|
|
49063
|
+
);
|
|
49064
|
+
const proc = spawn(options.command, options.args, {
|
|
49065
|
+
env: options.env,
|
|
49066
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
49067
|
+
});
|
|
49068
|
+
this.proc = proc;
|
|
49069
|
+
proc.stdout.setEncoding("utf8");
|
|
49070
|
+
proc.stdout.on("data", (chunk) => this.onStdout(chunk));
|
|
49071
|
+
proc.stderr.setEncoding("utf8");
|
|
49072
|
+
proc.stderr.on(
|
|
49073
|
+
"data",
|
|
49074
|
+
(chunk) => this.input.writeOutput?.(`agent_bridge_codex_stderr ${chunk.trimEnd()}`)
|
|
49075
|
+
);
|
|
49076
|
+
proc.on("exit", (code) => this.onProcessExit(code));
|
|
49077
|
+
proc.on("error", (error51) => void this.input.onError(error51));
|
|
49078
|
+
await this.request("initialize", {
|
|
49079
|
+
clientInfo: {
|
|
49080
|
+
name: "auto-agent-bridge",
|
|
49081
|
+
title: "Auto",
|
|
49082
|
+
version: "1"
|
|
49083
|
+
},
|
|
49084
|
+
capabilities: null
|
|
49085
|
+
});
|
|
49086
|
+
this.notify("initialized");
|
|
49087
|
+
await this.openThread();
|
|
49088
|
+
this.input.writeOutput?.(
|
|
49089
|
+
`agent_bridge_codex_startup_ready duration_ms=${Date.now() - startedAt} thread_id=${this.threadId ?? ""}`
|
|
49090
|
+
);
|
|
49091
|
+
}
|
|
49092
|
+
// Resume the stored thread when present, falling back to a fresh thread if the
|
|
49093
|
+
// resume is rejected (e.g. the rollout was pruned). A fresh thread carries the
|
|
49094
|
+
// neutral thread params (cwd + the agent identity as developerInstructions).
|
|
49095
|
+
async openThread() {
|
|
49096
|
+
if (this.input.resumeThreadId) {
|
|
49097
|
+
try {
|
|
49098
|
+
const resumed = await this.request("thread/resume", {
|
|
49099
|
+
threadId: this.input.resumeThreadId
|
|
49100
|
+
});
|
|
49101
|
+
this.adoptThreadId(resumed);
|
|
49102
|
+
return;
|
|
49103
|
+
} catch (error51) {
|
|
49104
|
+
this.input.writeOutput?.(
|
|
49105
|
+
`agent_bridge_codex_resume_fallback thread_id=${this.input.resumeThreadId} error=${errorMessage2(error51)}`
|
|
49106
|
+
);
|
|
49107
|
+
}
|
|
49108
|
+
}
|
|
49109
|
+
const started = await this.request(
|
|
49110
|
+
"thread/start",
|
|
49111
|
+
codexThreadParams(this.input.codex)
|
|
49112
|
+
);
|
|
49113
|
+
this.adoptThreadId(started);
|
|
49114
|
+
}
|
|
49115
|
+
adoptThreadId(result) {
|
|
49116
|
+
const threadId = threadIdFromResult(result);
|
|
49117
|
+
if (!threadId) {
|
|
49118
|
+
throw new Error("Codex thread response did not include a thread id");
|
|
49119
|
+
}
|
|
49120
|
+
this.threadId = threadId;
|
|
49121
|
+
this.input.onThreadId(threadId);
|
|
49122
|
+
}
|
|
49123
|
+
// ---------------------------------------------------------------------------
|
|
49124
|
+
// Turn delivery
|
|
49125
|
+
// ---------------------------------------------------------------------------
|
|
49126
|
+
async startTurn(threadId, message) {
|
|
49127
|
+
await this.request("turn/start", {
|
|
49128
|
+
threadId,
|
|
49129
|
+
input: [userTextInput(message)]
|
|
49130
|
+
});
|
|
49131
|
+
}
|
|
49132
|
+
// Inject a message into the active turn. Prefers `turn/steer` (codex folds the
|
|
49133
|
+
// input into the running turn and owns transcript consistency); falls back to a
|
|
49134
|
+
// hard `turn/interrupt` + fresh `turn/start` when the turn cannot be steered.
|
|
49135
|
+
async injectIntoActiveTurn(threadId, message) {
|
|
49136
|
+
await this.awaitItemSettlement();
|
|
49137
|
+
const expectedTurnId = this.activeTurnId;
|
|
49138
|
+
if (expectedTurnId === null) {
|
|
49139
|
+
await this.startTurn(threadId, message);
|
|
49140
|
+
return;
|
|
49141
|
+
}
|
|
49142
|
+
try {
|
|
49143
|
+
await this.request("turn/steer", {
|
|
49144
|
+
threadId,
|
|
49145
|
+
input: [userTextInput(message)],
|
|
49146
|
+
expectedTurnId
|
|
49147
|
+
});
|
|
49148
|
+
this.input.writeOutput?.("agent_bridge_codex_steered");
|
|
49149
|
+
return;
|
|
49150
|
+
} catch (error51) {
|
|
49151
|
+
this.input.writeOutput?.(
|
|
49152
|
+
`agent_bridge_codex_steer_failed error=${errorMessage2(error51)}`
|
|
49153
|
+
);
|
|
49154
|
+
}
|
|
49155
|
+
if (this.activeTurnId) {
|
|
49156
|
+
await this.interruptActiveTurn(threadId, this.activeTurnId);
|
|
49157
|
+
}
|
|
49158
|
+
await this.startTurn(threadId, message);
|
|
49159
|
+
}
|
|
49160
|
+
async interruptActiveTurn(threadId, turnId) {
|
|
49161
|
+
try {
|
|
49162
|
+
await this.request("turn/interrupt", { threadId, turnId });
|
|
49163
|
+
this.input.writeOutput?.("agent_bridge_codex_interrupted");
|
|
49164
|
+
} catch (error51) {
|
|
49165
|
+
this.input.writeOutput?.(
|
|
49166
|
+
`agent_bridge_codex_interrupt_failed error=${errorMessage2(error51)}`
|
|
49167
|
+
);
|
|
49168
|
+
}
|
|
49169
|
+
}
|
|
49170
|
+
flushDeferredMessages() {
|
|
49171
|
+
if (this.deferredMessages.length === 0 || this.threadId === null) {
|
|
49172
|
+
return;
|
|
49173
|
+
}
|
|
49174
|
+
const pending = this.deferredMessages.splice(0);
|
|
49175
|
+
this.input.writeOutput?.(
|
|
49176
|
+
`agent_bridge_codex_deferred_flush count=${pending.length}`
|
|
49177
|
+
);
|
|
49178
|
+
void (async () => {
|
|
49179
|
+
for (const message of pending) {
|
|
49180
|
+
try {
|
|
49181
|
+
await this.sendMessage(message, { mode: "interrupt" });
|
|
49182
|
+
} catch (error51) {
|
|
49183
|
+
void this.input.onError(error51);
|
|
49184
|
+
}
|
|
49185
|
+
}
|
|
49186
|
+
})();
|
|
49187
|
+
}
|
|
49188
|
+
// ---------------------------------------------------------------------------
|
|
49189
|
+
// Item settlement
|
|
49190
|
+
// ---------------------------------------------------------------------------
|
|
49191
|
+
awaitItemSettlement() {
|
|
49192
|
+
if (this.pendingToolItemIds.size === 0) {
|
|
49193
|
+
return Promise.resolve();
|
|
49194
|
+
}
|
|
49195
|
+
return new Promise((resolve) => {
|
|
49196
|
+
let done = false;
|
|
49197
|
+
const finish = () => {
|
|
49198
|
+
if (done) {
|
|
49199
|
+
return;
|
|
49200
|
+
}
|
|
49201
|
+
done = true;
|
|
49202
|
+
clearTimeout(timer);
|
|
49203
|
+
this.settlementWaiters.delete(waiter);
|
|
49204
|
+
resolve();
|
|
49205
|
+
};
|
|
49206
|
+
const waiter = () => finish();
|
|
49207
|
+
const timer = setTimeout(finish, CODEX_ITEM_SETTLE_TIMEOUT_MS);
|
|
49208
|
+
timer.unref?.();
|
|
49209
|
+
this.settlementWaiters.add(waiter);
|
|
49210
|
+
});
|
|
49211
|
+
}
|
|
49212
|
+
resolveSettlement() {
|
|
49213
|
+
if (this.settlementWaiters.size === 0) {
|
|
49214
|
+
return;
|
|
49215
|
+
}
|
|
49216
|
+
const waiters = [...this.settlementWaiters];
|
|
49217
|
+
this.settlementWaiters.clear();
|
|
49218
|
+
for (const waiter of waiters) {
|
|
49219
|
+
waiter();
|
|
49220
|
+
}
|
|
49221
|
+
}
|
|
49222
|
+
// ---------------------------------------------------------------------------
|
|
49223
|
+
// Incoming frame handling
|
|
49224
|
+
// ---------------------------------------------------------------------------
|
|
49225
|
+
onStdout(chunk) {
|
|
49226
|
+
this.stdoutBuffer += chunk;
|
|
49227
|
+
let newline = this.stdoutBuffer.indexOf("\n");
|
|
49228
|
+
while (newline >= 0) {
|
|
49229
|
+
const line = this.stdoutBuffer.slice(0, newline).trim();
|
|
49230
|
+
this.stdoutBuffer = this.stdoutBuffer.slice(newline + 1);
|
|
49231
|
+
if (line) {
|
|
49232
|
+
this.handleFrame(line);
|
|
49233
|
+
}
|
|
49234
|
+
newline = this.stdoutBuffer.indexOf("\n");
|
|
49235
|
+
}
|
|
49236
|
+
}
|
|
49237
|
+
handleFrame(line) {
|
|
49238
|
+
let message;
|
|
49239
|
+
try {
|
|
49240
|
+
message = parseCodexServerFrame(JSON.parse(line));
|
|
49241
|
+
} catch (error51) {
|
|
49242
|
+
this.input.writeOutput?.(
|
|
49243
|
+
`agent_bridge_codex_parse_failed error=${errorMessage2(error51)}`
|
|
49244
|
+
);
|
|
49245
|
+
return;
|
|
49246
|
+
}
|
|
49247
|
+
switch (message.kind) {
|
|
49248
|
+
case "response":
|
|
49249
|
+
this.settleResponse(message.id, message.result, message.error);
|
|
49250
|
+
return;
|
|
49251
|
+
case "notification":
|
|
49252
|
+
this.trackNotification(message.notification);
|
|
49253
|
+
void this.input.onNotification(message.notification);
|
|
49254
|
+
return;
|
|
49255
|
+
case "serverRequest":
|
|
49256
|
+
void this.input.onServerRequest(message.request);
|
|
49257
|
+
return;
|
|
49258
|
+
case "elicitation":
|
|
49259
|
+
this.writeFrame({
|
|
49260
|
+
jsonrpc: "2.0",
|
|
49261
|
+
id: message.requestId,
|
|
49262
|
+
result: { action: "accept", content: {}, _meta: null }
|
|
49263
|
+
});
|
|
49264
|
+
this.input.writeOutput?.(
|
|
49265
|
+
`agent_bridge_codex_elicitation_accepted server=${message.serverName}`
|
|
49266
|
+
);
|
|
49267
|
+
return;
|
|
49268
|
+
case "unsupportedRequest":
|
|
49269
|
+
this.writeFrame({
|
|
49270
|
+
jsonrpc: "2.0",
|
|
49271
|
+
id: message.requestId,
|
|
49272
|
+
error: { code: -32601, message: "Unsupported by Auto bridge" }
|
|
49273
|
+
});
|
|
49274
|
+
this.input.writeOutput?.(
|
|
49275
|
+
`agent_bridge_codex_unsupported_request method=${message.method}`
|
|
49276
|
+
);
|
|
49277
|
+
return;
|
|
49278
|
+
case "ignored":
|
|
49279
|
+
return;
|
|
49280
|
+
}
|
|
49281
|
+
}
|
|
49282
|
+
// Track turn/item lifecycle so steer/interrupt target the live turn and gate on
|
|
49283
|
+
// tool-item settlement.
|
|
49284
|
+
trackNotification(notification) {
|
|
49285
|
+
switch (notification.type) {
|
|
49286
|
+
case "turnStarted":
|
|
49287
|
+
this.activeTurnId = notification.turnId;
|
|
49288
|
+
return;
|
|
49289
|
+
case "turnCompleted":
|
|
49290
|
+
if (notification.turnId === this.activeTurnId) {
|
|
49291
|
+
this.activeTurnId = null;
|
|
49292
|
+
}
|
|
49293
|
+
this.pendingToolItemIds.clear();
|
|
49294
|
+
this.resolveSettlement();
|
|
49295
|
+
this.flushDeferredMessages();
|
|
49296
|
+
return;
|
|
49297
|
+
case "itemStarted":
|
|
49298
|
+
if (CODEX_TOOL_ITEM_TYPES.has(notification.item.type)) {
|
|
49299
|
+
this.pendingToolItemIds.add(notification.item.id);
|
|
49300
|
+
}
|
|
49301
|
+
return;
|
|
49302
|
+
case "itemCompleted":
|
|
49303
|
+
this.pendingToolItemIds.delete(notification.item.id);
|
|
49304
|
+
if (this.pendingToolItemIds.size === 0) {
|
|
49305
|
+
this.resolveSettlement();
|
|
49306
|
+
}
|
|
49307
|
+
return;
|
|
49308
|
+
default:
|
|
49309
|
+
return;
|
|
49310
|
+
}
|
|
49311
|
+
}
|
|
49312
|
+
onProcessExit(code) {
|
|
49313
|
+
this.rejectAllPending(
|
|
49314
|
+
new Error(`Codex app-server exited (code ${code ?? "unknown"})`)
|
|
49315
|
+
);
|
|
49316
|
+
this.activeTurnId = null;
|
|
49317
|
+
this.pendingToolItemIds.clear();
|
|
49318
|
+
this.resolveSettlement();
|
|
49319
|
+
this.input.writeOutput?.(`agent_bridge_codex_exited code=${code ?? ""}`);
|
|
49320
|
+
if (!this.closed) {
|
|
49321
|
+
this.closed = true;
|
|
49322
|
+
this.input.onExit();
|
|
49323
|
+
}
|
|
49324
|
+
}
|
|
49325
|
+
// ---------------------------------------------------------------------------
|
|
49326
|
+
// JSON-RPC transport
|
|
49327
|
+
// ---------------------------------------------------------------------------
|
|
49328
|
+
request(method, params) {
|
|
49329
|
+
const id = this.allocRequestId();
|
|
49330
|
+
const frame = { jsonrpc: "2.0", id, method, params };
|
|
49331
|
+
return new Promise((resolve, reject) => {
|
|
49332
|
+
this.pending.set(id, { resolve, reject });
|
|
49333
|
+
const timer = setTimeout(() => {
|
|
49334
|
+
if (this.pending.delete(id)) {
|
|
49335
|
+
reject(new Error(`Codex request timed out: ${method}`));
|
|
49336
|
+
}
|
|
49337
|
+
}, CODEX_REQUEST_TIMEOUT_MS);
|
|
49338
|
+
timer.unref?.();
|
|
49339
|
+
this.writeFrame(frame);
|
|
49340
|
+
});
|
|
49341
|
+
}
|
|
49342
|
+
settleResponse(id, result, error51) {
|
|
49343
|
+
if (typeof id !== "number") {
|
|
49344
|
+
return;
|
|
49345
|
+
}
|
|
49346
|
+
const pending = this.pending.get(id);
|
|
49347
|
+
if (!pending) {
|
|
49348
|
+
return;
|
|
49349
|
+
}
|
|
49350
|
+
this.pending.delete(id);
|
|
49351
|
+
if (error51 !== void 0) {
|
|
49352
|
+
pending.reject(new Error(error51));
|
|
49353
|
+
return;
|
|
49354
|
+
}
|
|
49355
|
+
pending.resolve(result);
|
|
49356
|
+
}
|
|
49357
|
+
rejectAllPending(error51) {
|
|
49358
|
+
const pending = [...this.pending.values()];
|
|
49359
|
+
this.pending.clear();
|
|
49360
|
+
for (const request of pending) {
|
|
49361
|
+
request.reject(error51);
|
|
49362
|
+
}
|
|
49363
|
+
}
|
|
49364
|
+
notify(method, params) {
|
|
49365
|
+
this.writeFrame({
|
|
49366
|
+
jsonrpc: "2.0",
|
|
49367
|
+
method,
|
|
49368
|
+
...params !== void 0 ? { params } : {}
|
|
49369
|
+
});
|
|
49370
|
+
}
|
|
49371
|
+
writeFrame(frame) {
|
|
49372
|
+
const proc = this.proc;
|
|
49373
|
+
if (!proc || proc.stdin.destroyed) {
|
|
49374
|
+
return;
|
|
49375
|
+
}
|
|
49376
|
+
proc.stdin.write(`${JSON.stringify(frame)}
|
|
49377
|
+
`);
|
|
49378
|
+
}
|
|
49379
|
+
allocRequestId() {
|
|
49380
|
+
const id = this.nextRequestId;
|
|
49381
|
+
this.nextRequestId += 1;
|
|
49382
|
+
return id;
|
|
49383
|
+
}
|
|
49384
|
+
requireThreadId() {
|
|
49385
|
+
if (this.threadId === null) {
|
|
49386
|
+
throw new Error("Codex session has no active thread");
|
|
49387
|
+
}
|
|
49388
|
+
return this.threadId;
|
|
49389
|
+
}
|
|
49390
|
+
};
|
|
49391
|
+
function userTextInput(text) {
|
|
49392
|
+
return { type: "text", text, text_elements: [] };
|
|
49393
|
+
}
|
|
49394
|
+
function threadIdFromResult(result) {
|
|
49395
|
+
if (result === null || typeof result !== "object") {
|
|
49396
|
+
return null;
|
|
49397
|
+
}
|
|
49398
|
+
const thread = result.thread;
|
|
49399
|
+
if (thread === null || typeof thread !== "object") {
|
|
49400
|
+
return null;
|
|
49401
|
+
}
|
|
49402
|
+
const id = thread.id;
|
|
49403
|
+
return typeof id === "string" && id.length > 0 ? id : null;
|
|
49404
|
+
}
|
|
49405
|
+
function errorMessage2(error51) {
|
|
49406
|
+
return error51 instanceof Error ? error51.message : String(error51);
|
|
49407
|
+
}
|
|
49408
|
+
|
|
49409
|
+
// src/commands/agent-bridge/harness/codex/index.ts
|
|
49410
|
+
function createCodexCommandHandler(input) {
|
|
49411
|
+
return new CodexCommandHandler({
|
|
49412
|
+
...input,
|
|
49413
|
+
sessionResume: input.sessionResume ?? fileCodexThreadResumeStore()
|
|
49414
|
+
});
|
|
49415
|
+
}
|
|
49416
|
+
var CodexCommandHandler = class {
|
|
49417
|
+
constructor(input) {
|
|
49418
|
+
this.input = input;
|
|
49419
|
+
this.outputBuffer = new AgentBridgeOutputBuffer(input);
|
|
49420
|
+
}
|
|
49421
|
+
input;
|
|
49422
|
+
context = null;
|
|
49423
|
+
session = null;
|
|
49424
|
+
injectedCommands = /* @__PURE__ */ new Set();
|
|
49425
|
+
// itemId -> JSON-RPC request id of the parked approval request, so an `answer`
|
|
49426
|
+
// command keyed by toolCallId (= itemId) can resolve the right server request.
|
|
49427
|
+
pendingApprovals = /* @__PURE__ */ new Map();
|
|
49428
|
+
outputBuffer;
|
|
49429
|
+
projector = new CodexProjector();
|
|
49430
|
+
// ---------------------------------------------------------------------------
|
|
49431
|
+
// Lifecycle (public API)
|
|
49432
|
+
// ---------------------------------------------------------------------------
|
|
49433
|
+
setContext(nextContext) {
|
|
49434
|
+
this.context = RuntimeBridgeConnectedPayloadSchema.parse(nextContext);
|
|
49435
|
+
}
|
|
49436
|
+
async replayPendingOutputs() {
|
|
49437
|
+
await this.outputBuffer.replayPendingOutputs();
|
|
49438
|
+
}
|
|
49439
|
+
async prepare() {
|
|
49440
|
+
await this.ensureSession().prepare();
|
|
49441
|
+
}
|
|
49442
|
+
shutdown() {
|
|
49443
|
+
this.session?.close();
|
|
49444
|
+
this.session = null;
|
|
49445
|
+
this.pendingApprovals.clear();
|
|
49446
|
+
}
|
|
49447
|
+
async handleCommand(rawDelivery) {
|
|
49448
|
+
const delivery = RuntimeBridgeCommandDeliverySchema.parse(rawDelivery);
|
|
49449
|
+
const socketId = this.input.socketId?.();
|
|
49450
|
+
const activeContext = this.context;
|
|
49451
|
+
if (!activeContext || delivery.sessionId !== activeContext.sessionId || delivery.runtimeId !== activeContext.runtimeId || delivery.bridgeLeaseId !== activeContext.bridgeLeaseId) {
|
|
49452
|
+
return commandAck({ delivery, socketId, status: "stale_lease" });
|
|
49453
|
+
}
|
|
49454
|
+
if (this.injectedCommands.has(delivery.commandId)) {
|
|
49455
|
+
return commandAck({ delivery, socketId, status: "duplicate" });
|
|
49456
|
+
}
|
|
49457
|
+
if (delivery.kind === "answer") {
|
|
49458
|
+
return this.handleAnswerCommand(delivery, activeContext, socketId);
|
|
49459
|
+
}
|
|
49460
|
+
if (delivery.kind === "stop") {
|
|
49461
|
+
this.injectedCommands.add(delivery.commandId);
|
|
49462
|
+
this.shutdown();
|
|
49463
|
+
return commandAck({ delivery, socketId, status: "injected" });
|
|
49464
|
+
}
|
|
49465
|
+
if (delivery.kind !== "message") {
|
|
49466
|
+
return commandAck({
|
|
49467
|
+
delivery,
|
|
49468
|
+
socketId,
|
|
49469
|
+
status: "failed",
|
|
49470
|
+
error: `Unsupported command kind: ${delivery.kind}`
|
|
49471
|
+
});
|
|
49472
|
+
}
|
|
49473
|
+
const message = deliveryMessage2(delivery);
|
|
49474
|
+
if (message === null) {
|
|
49475
|
+
return commandAck({
|
|
49476
|
+
delivery,
|
|
49477
|
+
socketId,
|
|
49478
|
+
status: "failed",
|
|
49479
|
+
error: "Message command payload must include a string message"
|
|
49480
|
+
});
|
|
49481
|
+
}
|
|
49482
|
+
this.injectedCommands.add(delivery.commandId);
|
|
49483
|
+
try {
|
|
49484
|
+
await this.emitUserMessageEntry(
|
|
49485
|
+
activeContext,
|
|
49486
|
+
delivery.commandId,
|
|
49487
|
+
message
|
|
49488
|
+
);
|
|
49489
|
+
await this.ensureSession().sendMessage(message, {
|
|
49490
|
+
mode: deliveryMode2(delivery)
|
|
49491
|
+
});
|
|
49492
|
+
} catch (error51) {
|
|
49493
|
+
this.injectedCommands.delete(delivery.commandId);
|
|
49494
|
+
return commandAck({
|
|
49495
|
+
delivery,
|
|
49496
|
+
socketId,
|
|
49497
|
+
status: "failed",
|
|
49498
|
+
error: errorMessage3(error51)
|
|
49499
|
+
});
|
|
49500
|
+
}
|
|
49501
|
+
return commandAck({ delivery, socketId, status: "injected" });
|
|
49502
|
+
}
|
|
49503
|
+
// ---------------------------------------------------------------------------
|
|
49504
|
+
// Answering parked approvals
|
|
49505
|
+
// ---------------------------------------------------------------------------
|
|
49506
|
+
async handleAnswerCommand(delivery, activeContext, socketId) {
|
|
49507
|
+
const payload = RuntimeBridgeAnswerCommandPayloadSchema.safeParse(
|
|
49508
|
+
delivery.payload
|
|
49509
|
+
);
|
|
49510
|
+
if (!payload.success) {
|
|
49511
|
+
return commandAck({
|
|
49512
|
+
delivery,
|
|
49513
|
+
socketId,
|
|
49514
|
+
status: "failed",
|
|
49515
|
+
error: "Answer command payload must include toolCallId and answers"
|
|
49516
|
+
});
|
|
49517
|
+
}
|
|
49518
|
+
this.injectedCommands.add(delivery.commandId);
|
|
49519
|
+
const requestId = this.pendingApprovals.get(payload.data.toolCallId);
|
|
49520
|
+
try {
|
|
49521
|
+
if (requestId !== void 0) {
|
|
49522
|
+
this.pendingApprovals.delete(payload.data.toolCallId);
|
|
49523
|
+
const decision = codexApprovalDecision({
|
|
49524
|
+
answers: payload.data.answers,
|
|
49525
|
+
...payload.data.response ? { response: payload.data.response } : {}
|
|
49526
|
+
});
|
|
49527
|
+
this.ensureSession().resolveApproval({ requestId, decision });
|
|
49528
|
+
this.input.writeOutput?.(
|
|
49529
|
+
`agent_bridge_codex_question_answered tool_use_id=${payload.data.toolCallId} decision=${decision}`
|
|
49530
|
+
);
|
|
49531
|
+
} else {
|
|
49532
|
+
const message = answerFallbackMessage2(payload.data);
|
|
49533
|
+
await this.emitUserMessageEntry(
|
|
49534
|
+
activeContext,
|
|
49535
|
+
delivery.commandId,
|
|
49536
|
+
message
|
|
49537
|
+
);
|
|
49538
|
+
await this.ensureSession().sendMessage(message);
|
|
49539
|
+
}
|
|
49540
|
+
} catch (error51) {
|
|
49541
|
+
this.injectedCommands.delete(delivery.commandId);
|
|
49542
|
+
return commandAck({
|
|
49543
|
+
delivery,
|
|
49544
|
+
socketId,
|
|
49545
|
+
status: "failed",
|
|
49546
|
+
error: errorMessage3(error51)
|
|
49547
|
+
});
|
|
49548
|
+
}
|
|
49549
|
+
return commandAck({ delivery, socketId, status: "injected" });
|
|
49550
|
+
}
|
|
49551
|
+
// ---------------------------------------------------------------------------
|
|
49552
|
+
// Session callbacks
|
|
49553
|
+
// ---------------------------------------------------------------------------
|
|
49554
|
+
async emitUserMessageEntry(activeContext, commandId, message) {
|
|
49555
|
+
await this.emit(activeContext, {
|
|
49556
|
+
type: "entry",
|
|
49557
|
+
entry: {
|
|
49558
|
+
messageId: commandId,
|
|
49559
|
+
role: "user",
|
|
49560
|
+
kind: "message",
|
|
49561
|
+
status: "completed",
|
|
49562
|
+
content: { parts: [{ type: "text", text: message }] }
|
|
49563
|
+
}
|
|
49564
|
+
});
|
|
49565
|
+
}
|
|
49566
|
+
async handleNotification(notification) {
|
|
49567
|
+
const activeContext = this.context;
|
|
49568
|
+
if (!activeContext) {
|
|
49569
|
+
return;
|
|
49570
|
+
}
|
|
49571
|
+
for (const projection of this.projector.project(notification)) {
|
|
49572
|
+
await this.emit(activeContext, projection);
|
|
49573
|
+
}
|
|
49574
|
+
}
|
|
49575
|
+
async handleServerRequest(request) {
|
|
49576
|
+
const activeContext = this.context;
|
|
49577
|
+
if (!activeContext) {
|
|
49578
|
+
return;
|
|
49579
|
+
}
|
|
49580
|
+
this.pendingApprovals.set(request.itemId, request.requestId);
|
|
49581
|
+
this.input.writeOutput?.(
|
|
49582
|
+
`agent_bridge_codex_question_pending tool_use_id=${request.itemId}`
|
|
49583
|
+
);
|
|
49584
|
+
await this.emit(activeContext, this.projector.projectApproval(request));
|
|
49585
|
+
}
|
|
49586
|
+
async handleSessionError(error51) {
|
|
49587
|
+
this.input.writeOutput?.(
|
|
49588
|
+
`agent_bridge_codex_session_failed error=${errorMessage3(error51)}`
|
|
49589
|
+
);
|
|
49590
|
+
const activeContext = this.context;
|
|
49591
|
+
if (!activeContext) {
|
|
49592
|
+
return;
|
|
49593
|
+
}
|
|
49594
|
+
await this.emit(
|
|
49595
|
+
activeContext,
|
|
49596
|
+
this.projector.projectSessionFailure(errorMessage3(error51))
|
|
49597
|
+
);
|
|
49598
|
+
}
|
|
49599
|
+
async emit(activeContext, projection) {
|
|
49600
|
+
try {
|
|
49601
|
+
await this.outputBuffer.emitProjection(activeContext, projection);
|
|
49602
|
+
} catch (error51) {
|
|
49603
|
+
this.input.writeOutput?.(
|
|
49604
|
+
`agent_bridge_output_emit_failed error=${errorMessage3(error51)}`
|
|
49605
|
+
);
|
|
49606
|
+
}
|
|
49607
|
+
}
|
|
49608
|
+
ensureSession() {
|
|
49609
|
+
if (this.session) {
|
|
49610
|
+
return this.session;
|
|
49611
|
+
}
|
|
49612
|
+
const resumeThreadId = this.storedResumeThreadId();
|
|
49613
|
+
const session = codexAgentBridgeRuntime.start({
|
|
49614
|
+
codex: this.input.codex,
|
|
49615
|
+
...resumeThreadId ? { resumeThreadId } : {},
|
|
49616
|
+
onNotification: (notification) => this.handleNotification(notification),
|
|
49617
|
+
onServerRequest: (request) => this.handleServerRequest(request),
|
|
49618
|
+
onThreadId: (threadId) => this.persistThreadId(threadId),
|
|
49619
|
+
onError: (error51) => this.handleSessionError(error51),
|
|
49620
|
+
onExit: () => {
|
|
49621
|
+
if (this.session === session) {
|
|
49622
|
+
this.session = null;
|
|
49623
|
+
}
|
|
49624
|
+
this.input.writeOutput?.("agent_bridge_codex_session_exited");
|
|
49625
|
+
},
|
|
49626
|
+
...this.input.writeOutput ? { writeOutput: this.input.writeOutput } : {}
|
|
49627
|
+
});
|
|
49628
|
+
this.session = session;
|
|
49629
|
+
return session;
|
|
49630
|
+
}
|
|
49631
|
+
storedResumeThreadId() {
|
|
49632
|
+
const store = this.input.sessionResume;
|
|
49633
|
+
const activeContext = this.context;
|
|
49634
|
+
if (!store || !activeContext) {
|
|
49635
|
+
return void 0;
|
|
49636
|
+
}
|
|
49637
|
+
try {
|
|
49638
|
+
return store.read(activeContext.sessionId) ?? void 0;
|
|
49639
|
+
} catch (error51) {
|
|
49640
|
+
this.input.writeOutput?.(
|
|
49641
|
+
`agent_bridge_codex_thread_read_failed error=${errorMessage3(error51)}`
|
|
49642
|
+
);
|
|
49643
|
+
return void 0;
|
|
49644
|
+
}
|
|
49645
|
+
}
|
|
49646
|
+
persistThreadId(threadId) {
|
|
49647
|
+
const store = this.input.sessionResume;
|
|
49648
|
+
const activeContext = this.context;
|
|
49649
|
+
if (!store || !activeContext) {
|
|
49650
|
+
return;
|
|
49651
|
+
}
|
|
49652
|
+
try {
|
|
49653
|
+
store.write({ sessionId: activeContext.sessionId, threadId });
|
|
49654
|
+
} catch (error51) {
|
|
49655
|
+
this.input.writeOutput?.(
|
|
49656
|
+
`agent_bridge_codex_thread_persist_failed error=${errorMessage3(error51)}`
|
|
49657
|
+
);
|
|
49658
|
+
}
|
|
49659
|
+
}
|
|
49660
|
+
};
|
|
49661
|
+
function answerFallbackMessage2(answer) {
|
|
49662
|
+
const lines = Object.entries(answer.answers).map(
|
|
49663
|
+
([question, value2]) => `- ${question}: ${value2}`
|
|
49664
|
+
);
|
|
49665
|
+
if (answer.response) {
|
|
49666
|
+
lines.push(`The user also responded: ${answer.response}`);
|
|
49667
|
+
}
|
|
49668
|
+
return ["The user answered your questions:", ...lines].join("\n");
|
|
49669
|
+
}
|
|
49670
|
+
function deliveryMessage2(delivery) {
|
|
49671
|
+
const payload = delivery.payload;
|
|
49672
|
+
if (payload && typeof payload === "object" && "message" in payload && typeof payload.message === "string") {
|
|
49673
|
+
return payload.message;
|
|
49674
|
+
}
|
|
49675
|
+
return null;
|
|
49676
|
+
}
|
|
49677
|
+
function deliveryMode2(delivery) {
|
|
49678
|
+
const payload = delivery.payload;
|
|
49679
|
+
if (payload && typeof payload === "object" && "deliveryMode" in payload) {
|
|
49680
|
+
const parsed = MessageDeliveryModeSchema.safeParse(payload.deliveryMode);
|
|
49681
|
+
if (parsed.success) {
|
|
49682
|
+
return parsed.data;
|
|
49683
|
+
}
|
|
49684
|
+
}
|
|
49685
|
+
return "interrupt";
|
|
49686
|
+
}
|
|
49687
|
+
function errorMessage3(error51) {
|
|
49688
|
+
return error51 instanceof Error ? error51.message : String(error51);
|
|
49689
|
+
}
|
|
49690
|
+
|
|
48469
49691
|
// src/commands/agent-bridge/harness/index.ts
|
|
48470
49692
|
async function runAgentBridgeHarness(options) {
|
|
48471
49693
|
await runAgentBridgeSocket({
|
|
@@ -48491,6 +49713,14 @@ function createHarnessCommandHandler(input) {
|
|
|
48491
49713
|
sessionResume: fileClaudeSessionResumeStore()
|
|
48492
49714
|
});
|
|
48493
49715
|
}
|
|
49716
|
+
case "codex": {
|
|
49717
|
+
const { kind, ...codex } = config2;
|
|
49718
|
+
return createCodexCommandHandler({
|
|
49719
|
+
...base,
|
|
49720
|
+
codex,
|
|
49721
|
+
sessionResume: fileCodexThreadResumeStore()
|
|
49722
|
+
});
|
|
49723
|
+
}
|
|
48494
49724
|
}
|
|
48495
49725
|
}
|
|
48496
49726
|
function resolveHarnessConfig(bootstrap) {
|