freecodego 0.1.6-alpha.2
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/LICENSE +23 -0
- package/README.i18n.yaml +6 -0
- package/README.md +85 -0
- package/README.zh.md +85 -0
- package/cordis.patch.yml +56 -0
- package/dist/agent-team.js +16296 -0
- package/dist/assets/engineering/skills/ask-matt/PHASE-BOUNDARIES.md +55 -0
- package/dist/assets/engineering/skills/ask-matt/SKILL.md +89 -0
- package/dist/assets/engineering/skills/ask-matt/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills/code-review/SKILL.md +87 -0
- package/dist/assets/engineering/skills/code-review/agents/openai.yaml +3 -0
- package/dist/assets/engineering/skills/codebase-design/DEEPENING.md +37 -0
- package/dist/assets/engineering/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/dist/assets/engineering/skills/codebase-design/SKILL.md +114 -0
- package/dist/assets/engineering/skills/codebase-design/agents/openai.yaml +3 -0
- package/dist/assets/engineering/skills/domain-modeling/ADR-FORMAT.md +47 -0
- package/dist/assets/engineering/skills/domain-modeling/CONTEXT-FORMAT.md +60 -0
- package/dist/assets/engineering/skills/domain-modeling/SKILL.md +74 -0
- package/dist/assets/engineering/skills/domain-modeling/agents/openai.yaml +3 -0
- package/dist/assets/engineering/skills/engineering-code-review/SKILL.md +13 -0
- package/dist/assets/engineering/skills/engineering-context-control/SKILL.md +13 -0
- package/dist/assets/engineering/skills/engineering-release-readiness/SKILL.md +13 -0
- package/dist/assets/engineering/skills/engineering-security-review/SKILL.md +13 -0
- package/dist/assets/engineering/skills/engineering-silent-failure/SKILL.md +13 -0
- package/dist/assets/engineering/skills/engineering-spec-mining/SKILL.md +13 -0
- package/dist/assets/engineering/skills/engineering-tdd/SKILL.md +13 -0
- package/dist/assets/engineering/skills/grill-with-docs/SKILL.md +7 -0
- package/dist/assets/engineering/skills/grill-with-docs/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills/implement/SKILL.md +15 -0
- package/dist/assets/engineering/skills/implement/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills/improve-codebase-architecture/HTML-REPORT.md +123 -0
- package/dist/assets/engineering/skills/improve-codebase-architecture/SKILL.md +71 -0
- package/dist/assets/engineering/skills/improve-codebase-architecture/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills/prototype/LOGIC.md +67 -0
- package/dist/assets/engineering/skills/prototype/SKILL.md +26 -0
- package/dist/assets/engineering/skills/prototype/UI.md +112 -0
- package/dist/assets/engineering/skills/prototype/agents/openai.yaml +3 -0
- package/dist/assets/engineering/skills/resolving-merge-conflicts/SKILL.md +14 -0
- package/dist/assets/engineering/skills/resolving-merge-conflicts/agents/openai.yaml +3 -0
- package/dist/assets/engineering/skills/setup-matt-pocock-skills/SKILL.md +116 -0
- package/dist/assets/engineering/skills/setup-matt-pocock-skills/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills/setup-matt-pocock-skills/domain.md +51 -0
- package/dist/assets/engineering/skills/setup-matt-pocock-skills/issue-tracker-github.md +45 -0
- package/dist/assets/engineering/skills/setup-matt-pocock-skills/issue-tracker-gitlab.md +46 -0
- package/dist/assets/engineering/skills/setup-matt-pocock-skills/issue-tracker-local.md +30 -0
- package/dist/assets/engineering/skills/setup-matt-pocock-skills/triage-labels.md +15 -0
- package/dist/assets/engineering/skills/to-spec/SKILL.md +75 -0
- package/dist/assets/engineering/skills/to-spec/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills/to-tickets/SKILL.md +105 -0
- package/dist/assets/engineering/skills/to-tickets/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills/triage/AGENT-BRIEF.md +207 -0
- package/dist/assets/engineering/skills/triage/OUT-OF-SCOPE.md +105 -0
- package/dist/assets/engineering/skills/triage/SKILL.md +112 -0
- package/dist/assets/engineering/skills/triage/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills/wayfinder/SKILL.md +128 -0
- package/dist/assets/engineering/skills/wayfinder/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills/wizard/SKILL.md +44 -0
- package/dist/assets/engineering/skills/wizard/agents/openai.yaml +3 -0
- package/dist/assets/engineering/skills/wizard/template.sh +204 -0
- package/dist/assets/engineering/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/dist/assets/engineering/skills/writing-for-agents/SKILL.md +81 -0
- package/dist/assets/engineering/skills/writing-for-agents/agents/openai.yaml +3 -0
- package/dist/assets/engineering/skills-starter/engineering-debug/SKILL.md +13 -0
- package/dist/assets/engineering/skills-starter/engineering-plan/SKILL.md +26 -0
- package/dist/assets/engineering/skills-starter/engineering-search-first/SKILL.md +13 -0
- package/dist/assets/engineering/skills-starter/engineering-verification/SKILL.md +67 -0
- package/dist/assets/engineering/skills-starter/grill-me/SKILL.md +7 -0
- package/dist/assets/engineering/skills-starter/grill-me/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills-starter/grilling/SKILL.md +28 -0
- package/dist/assets/engineering/skills-starter/grilling/agents/openai.yaml +3 -0
- package/dist/assets/engineering/skills-starter/handoff/SKILL.md +16 -0
- package/dist/assets/engineering/skills-starter/handoff/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills-starter/prompt-techniques/SKILL.md +84 -0
- package/dist/assets/engineering/skills-starter/to-questionnaire/SKILL.md +54 -0
- package/dist/assets/engineering/skills-starter/to-questionnaire/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills-starter/wait-what/SKILL.md +7 -0
- package/dist/assets/engineering/skills-starter/wait-what/agents/openai.yaml +5 -0
- package/dist/assets/engineering/skills-superpowers/brainstorming/SKILL.md +240 -0
- package/dist/assets/engineering/skills-superpowers/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/dist/assets/engineering/skills-superpowers/dispatching-parallel-agents/SKILL.md +167 -0
- package/dist/assets/engineering/skills-superpowers/executing-plans/SKILL.md +64 -0
- package/dist/assets/engineering/skills-superpowers/finishing-a-development-branch/SKILL.md +225 -0
- package/dist/assets/engineering/skills-superpowers/receiving-code-review/SKILL.md +205 -0
- package/dist/assets/engineering/skills-superpowers/subagent-driven-development/SKILL.md +567 -0
- package/dist/assets/engineering/skills-superpowers/subagent-driven-development/final-reviewer-prompt.md +181 -0
- package/dist/assets/engineering/skills-superpowers/subagent-driven-development/implementer-prompt.md +154 -0
- package/dist/assets/engineering/skills-superpowers/subagent-driven-development/re-review-prompt.md +115 -0
- package/dist/assets/engineering/skills-superpowers/subagent-driven-development/scripts/review-package +46 -0
- package/dist/assets/engineering/skills-superpowers/subagent-driven-development/scripts/sdd-workspace +40 -0
- package/dist/assets/engineering/skills-superpowers/subagent-driven-development/scripts/task-brief +41 -0
- package/dist/assets/engineering/skills-superpowers/subagent-driven-development/task-reviewer-prompt.md +207 -0
- package/dist/assets/engineering/skills-superpowers/using-git-worktrees/SKILL.md +167 -0
- package/dist/assets/engineering/skills-superpowers/writing-plans/SKILL.md +171 -0
- package/dist/assets/engineering/skills-superpowers/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/dist/assets/presets/augmentcode/agent.cordis.yml +238 -0
- package/dist/assets/presets/augmentcode/preset.yml +4 -0
- package/dist/assets/presets/freecodego/agent.cordis.yml +220 -0
- package/dist/assets/presets/freecodego/preset.yml +4 -0
- package/dist/auto-review.js +429 -0
- package/dist/bootstrap.js +65223 -0
- package/dist/client.cjs +39382 -0
- package/dist/schedule.js +15846 -0
- package/dist/session-events.js +10 -0
- package/dist/tool-agent-team.js +16825 -0
- package/dist/workers/codex-worker.js +1127 -0
- package/package.json +113 -0
|
@@ -0,0 +1,1127 @@
|
|
|
1
|
+
// packages/freecodego/runtime-codex/lib/worker.mjs
|
|
2
|
+
import { access, copyFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { spawn } from "node:child_process";
|
|
5
|
+
import { createInterface } from "node:readline";
|
|
6
|
+
import { randomBytes, randomUUID } from "node:crypto";
|
|
7
|
+
import { createServer } from "node:http";
|
|
8
|
+
import { effectiveSandboxMode, redactSecrets } from "@deepseek-ai/dsh-freecodego-native-runtime-protocol";
|
|
9
|
+
var MAX_WORKER_FRAME_BYTES = 1e6;
|
|
10
|
+
var MAX_TOOL_RESULT_TEXT_BYTES = 9e5;
|
|
11
|
+
var FRAME_TOO_LARGE = "FRAME_TOO_LARGE";
|
|
12
|
+
function bytesOf(text) {
|
|
13
|
+
return Buffer.byteLength(text, "utf8");
|
|
14
|
+
}
|
|
15
|
+
function boundToolResultText(text, budget = MAX_TOOL_RESULT_TEXT_BYTES) {
|
|
16
|
+
const limit = Number.isFinite(budget) && budget > 0 ? Math.max(256, Math.floor(budget)) : MAX_TOOL_RESULT_TEXT_BYTES;
|
|
17
|
+
if (bytesOf(text) <= limit) return text;
|
|
18
|
+
const marker = "\n\n\u2026 [result truncated: this output exceeded the transport limit; ask for a narrower range, a page, or a filter to see the rest]";
|
|
19
|
+
const markerBytes = bytesOf(marker);
|
|
20
|
+
let kept = "";
|
|
21
|
+
let used = 0;
|
|
22
|
+
for (const character of text) {
|
|
23
|
+
const size = bytesOf(character);
|
|
24
|
+
if (used + size + markerBytes > limit) break;
|
|
25
|
+
kept += character;
|
|
26
|
+
used += size;
|
|
27
|
+
}
|
|
28
|
+
return `${kept}${marker}`;
|
|
29
|
+
}
|
|
30
|
+
function workerFrame(message, maxBytes = MAX_WORKER_FRAME_BYTES) {
|
|
31
|
+
const ceiling = Number.isFinite(maxBytes) && maxBytes > 0 ? Math.max(256, Math.floor(maxBytes)) : MAX_WORKER_FRAME_BYTES;
|
|
32
|
+
const line = `${JSON.stringify(message) ?? "null"}
|
|
33
|
+
`;
|
|
34
|
+
const originalBytes = bytesOf(line);
|
|
35
|
+
if (originalBytes <= ceiling) return {
|
|
36
|
+
line,
|
|
37
|
+
replaced: false,
|
|
38
|
+
originalBytes
|
|
39
|
+
};
|
|
40
|
+
const id = message?.id;
|
|
41
|
+
if (typeof id === "string") {
|
|
42
|
+
const reply2 = `${JSON.stringify({
|
|
43
|
+
id,
|
|
44
|
+
error: {
|
|
45
|
+
code: FRAME_TOO_LARGE,
|
|
46
|
+
message: `this reply is ${String(originalBytes)} bytes and the worker transport carries at most ${String(ceiling)}; ask for a narrower range, a page, or a filter and retry`
|
|
47
|
+
}
|
|
48
|
+
})}
|
|
49
|
+
`;
|
|
50
|
+
if (bytesOf(reply2) <= ceiling) return {
|
|
51
|
+
line: reply2,
|
|
52
|
+
replaced: true,
|
|
53
|
+
originalBytes
|
|
54
|
+
};
|
|
55
|
+
return {
|
|
56
|
+
line: `${JSON.stringify({
|
|
57
|
+
id,
|
|
58
|
+
error: {
|
|
59
|
+
code: FRAME_TOO_LARGE,
|
|
60
|
+
message: "the reply exceeded the frame ceiling"
|
|
61
|
+
}
|
|
62
|
+
})}
|
|
63
|
+
`,
|
|
64
|
+
replaced: true,
|
|
65
|
+
originalBytes
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
const method = message?.method;
|
|
69
|
+
const notice = `${JSON.stringify({
|
|
70
|
+
...typeof method === "string" ? { method } : {},
|
|
71
|
+
params: {
|
|
72
|
+
truncated: true,
|
|
73
|
+
originalBytes,
|
|
74
|
+
reason: `this frame was ${String(originalBytes)} bytes, over the ${String(ceiling)}-byte ceiling this worker writes within, so its payload was dropped`
|
|
75
|
+
}
|
|
76
|
+
})}
|
|
77
|
+
`;
|
|
78
|
+
if (bytesOf(notice) <= ceiling) return {
|
|
79
|
+
line: notice,
|
|
80
|
+
replaced: true,
|
|
81
|
+
originalBytes
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
line: `${JSON.stringify({ params: {
|
|
85
|
+
truncated: true,
|
|
86
|
+
originalBytes
|
|
87
|
+
} })}
|
|
88
|
+
`,
|
|
89
|
+
replaced: true,
|
|
90
|
+
originalBytes
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function record$2(value) {
|
|
94
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
95
|
+
}
|
|
96
|
+
function stringify(value) {
|
|
97
|
+
return typeof value === "string" ? value : JSON.stringify(value ?? null);
|
|
98
|
+
}
|
|
99
|
+
function mcpToolResult(value) {
|
|
100
|
+
const result = record$2(value);
|
|
101
|
+
const content = (Array.isArray(result.content) ? result.content : []).flatMap((block) => projectBlock(block)).map((block) => ({
|
|
102
|
+
type: "text",
|
|
103
|
+
text: boundToolResultText(block.text)
|
|
104
|
+
}));
|
|
105
|
+
return {
|
|
106
|
+
content: content.length === 0 ? [{
|
|
107
|
+
type: "text",
|
|
108
|
+
text: boundToolResultText(stringify(value))
|
|
109
|
+
}] : content,
|
|
110
|
+
...isFailure(result) ? { isError: true } : {}
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
function isFailure(result) {
|
|
114
|
+
const error = result.error;
|
|
115
|
+
if (typeof error === "string" ? error !== "" : error !== void 0 && error !== null) return true;
|
|
116
|
+
const flag = result.isError;
|
|
117
|
+
if (flag === void 0 || flag === null || flag === false || flag === 0 || flag === "") return false;
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
function projectBlock(block) {
|
|
121
|
+
const candidate = record$2(block);
|
|
122
|
+
if (candidate.type === "text" && typeof candidate.text === "string") return [{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: candidate.text
|
|
125
|
+
}];
|
|
126
|
+
if (candidate.type === "image") {
|
|
127
|
+
const attachment = record$2(candidate.attachment);
|
|
128
|
+
return [{
|
|
129
|
+
type: "text",
|
|
130
|
+
text: typeof attachment.attachmentId === "string" ? `Generated image attachment: ${attachment.attachmentId}` : "Generated image (unavailable to this transport)"
|
|
131
|
+
}];
|
|
132
|
+
}
|
|
133
|
+
try {
|
|
134
|
+
return [{
|
|
135
|
+
type: "text",
|
|
136
|
+
text: JSON.stringify(block)
|
|
137
|
+
}];
|
|
138
|
+
} catch {
|
|
139
|
+
return [{
|
|
140
|
+
type: "text",
|
|
141
|
+
text: String(block)
|
|
142
|
+
}];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
var ACCEPT_DECLINE_METHODS = /* @__PURE__ */ new Set(["item/commandExecution/requestApproval", "item/fileChange/requestApproval"]);
|
|
146
|
+
var APPROVED_DENIED_METHODS = /* @__PURE__ */ new Set(["applyPatchApproval", "execCommandApproval"]);
|
|
147
|
+
var PERMISSIONS_METHOD = "item/permissions/requestApproval";
|
|
148
|
+
function isApprovalMethod(method) {
|
|
149
|
+
return ACCEPT_DECLINE_METHODS.has(method) || APPROVED_DENIED_METHODS.has(method) || method === PERMISSIONS_METHOD;
|
|
150
|
+
}
|
|
151
|
+
function record$1(value) {
|
|
152
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
153
|
+
}
|
|
154
|
+
function nonEmptyString(value) {
|
|
155
|
+
return typeof value === "string" && value !== "" ? value : void 0;
|
|
156
|
+
}
|
|
157
|
+
function approvalResponse(method, response, params) {
|
|
158
|
+
const outcome = record$1(response);
|
|
159
|
+
const approved = outcome.type === "approved";
|
|
160
|
+
if (ACCEPT_DECLINE_METHODS.has(method)) return { decision: approved ? "accept" : "decline" };
|
|
161
|
+
if (APPROVED_DENIED_METHODS.has(method)) {
|
|
162
|
+
if (approved) return { decision: "approved" };
|
|
163
|
+
return { decision: { denied: { rejection: nonEmptyString(outcome.message) ?? "User denied this request" } } };
|
|
164
|
+
}
|
|
165
|
+
if (method === PERMISSIONS_METHOD) {
|
|
166
|
+
const requested = record$1(record$1(params).permissions);
|
|
167
|
+
return approved ? {
|
|
168
|
+
permissions: requested,
|
|
169
|
+
scope: "turn"
|
|
170
|
+
} : { permissions: {} };
|
|
171
|
+
}
|
|
172
|
+
return response;
|
|
173
|
+
}
|
|
174
|
+
function record(value) {
|
|
175
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
176
|
+
}
|
|
177
|
+
function questionResponse(answer) {
|
|
178
|
+
const entries = record(answer).answers;
|
|
179
|
+
const answers = {};
|
|
180
|
+
if (!Array.isArray(entries)) return { answers };
|
|
181
|
+
for (const entry of entries) {
|
|
182
|
+
const item = record(entry);
|
|
183
|
+
const id = typeof item.id === "string" ? item.id : "";
|
|
184
|
+
if (id === "") continue;
|
|
185
|
+
const selected = Array.isArray(item.selected) ? item.selected.filter((value) => typeof value === "string") : [];
|
|
186
|
+
const custom = typeof item.custom === "string" && item.custom !== "" ? [item.custom] : [];
|
|
187
|
+
const collected = [...selected, ...custom];
|
|
188
|
+
answers[id] = { answers: [...answers[id]?.answers ?? [], ...collected] };
|
|
189
|
+
}
|
|
190
|
+
return { answers };
|
|
191
|
+
}
|
|
192
|
+
function codexThreadSandboxParams(fields) {
|
|
193
|
+
const mode = effectiveSandboxMode(fields);
|
|
194
|
+
if (mode === void 0) return {};
|
|
195
|
+
if (mode !== "read-only") return { sandbox: mode };
|
|
196
|
+
return fields.readOnly === true ? {
|
|
197
|
+
sandbox: "read-only",
|
|
198
|
+
approvalPolicy: "never"
|
|
199
|
+
} : {
|
|
200
|
+
sandbox: "read-only",
|
|
201
|
+
approvalPolicy: "on-request"
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function harnessMcpRoute(capabilities2, name) {
|
|
205
|
+
if (name === "freecodego_skill_discover" || name === "freecodego_skill_load") {
|
|
206
|
+
if (!capabilities2.skillEnabled) throw new Error(`Harness tool "${name}" is unavailable`);
|
|
207
|
+
return {
|
|
208
|
+
bridge: "skill",
|
|
209
|
+
op: name === "freecodego_skill_discover" ? "list" : "load"
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
if (capabilities2.mcpEnabled && capabilities2.mcpTools.some((tool) => tool.name === name)) return {
|
|
213
|
+
bridge: "mcp",
|
|
214
|
+
op: "execute"
|
|
215
|
+
};
|
|
216
|
+
if (!capabilities2.harnessTools.some((tool) => tool.name === name)) throw new Error(`Harness tool "${name}" is unavailable`);
|
|
217
|
+
return {
|
|
218
|
+
bridge: "tool",
|
|
219
|
+
op: "execute"
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
var sequence = 0;
|
|
223
|
+
var rpcId = 0;
|
|
224
|
+
var child;
|
|
225
|
+
var threadId;
|
|
226
|
+
var turnId;
|
|
227
|
+
var harnessSessionId;
|
|
228
|
+
var modelId;
|
|
229
|
+
var modelProvider;
|
|
230
|
+
var selectedProvider;
|
|
231
|
+
var stderr = "";
|
|
232
|
+
var pending = /* @__PURE__ */ new Map();
|
|
233
|
+
var approvals = /* @__PURE__ */ new Map();
|
|
234
|
+
var questions = /* @__PURE__ */ new Map();
|
|
235
|
+
var pendingBridges = /* @__PURE__ */ new Map();
|
|
236
|
+
var MANAGED_MCP_START = "# freecodego-managed-mcp:start";
|
|
237
|
+
var MANAGED_MCP_END = "# freecodego-managed-mcp:end";
|
|
238
|
+
var DISPOSE_GRACE_MS = 5e3;
|
|
239
|
+
var capabilities = {
|
|
240
|
+
mcpEnabled: false,
|
|
241
|
+
skillEnabled: false,
|
|
242
|
+
skillRoots: [],
|
|
243
|
+
mcpTools: [],
|
|
244
|
+
harnessTools: []
|
|
245
|
+
};
|
|
246
|
+
var capabilityEnvironment = {};
|
|
247
|
+
var harnessMcpServer;
|
|
248
|
+
var harnessMcpUrl;
|
|
249
|
+
var harnessMcpToken = randomBytes(32).toString("base64url");
|
|
250
|
+
function explicitModel() {
|
|
251
|
+
return modelId === void 0 || modelId === "" || modelId === "codex-auto" ? void 0 : modelId;
|
|
252
|
+
}
|
|
253
|
+
function usesOpenAiBridge() {
|
|
254
|
+
return process.env.FREECODEGO_CODEX_PROVIDER_OVERRIDE === "openai";
|
|
255
|
+
}
|
|
256
|
+
function appServerProvider() {
|
|
257
|
+
return usesOpenAiBridge() ? "openai" : void 0;
|
|
258
|
+
}
|
|
259
|
+
function appServerModel(reasoningEffort) {
|
|
260
|
+
const model = explicitModel();
|
|
261
|
+
if (model === void 0 || !usesOpenAiBridge()) return model;
|
|
262
|
+
const provider = (selectedProvider ?? "freecodego").trim();
|
|
263
|
+
return `freecodego-route:${Buffer.from(provider, "utf8").toString("base64url")}.${Buffer.from(model, "utf8").toString("base64url")}${reasoningEffort === "off" || reasoningEffort === "low" || reasoningEffort === "medium" || reasoningEffort === "high" || reasoningEffort === "xhigh" || reasoningEffort === "max" ? `.${reasoningEffort}` : ""}`;
|
|
264
|
+
}
|
|
265
|
+
function selectedReasoningEffort(value) {
|
|
266
|
+
return value === "off" || value === "low" || value === "medium" || value === "high" || value === "xhigh" || value === "max" ? value : "high";
|
|
267
|
+
}
|
|
268
|
+
function codexReasoningEffort(value) {
|
|
269
|
+
const effort = selectedReasoningEffort(value);
|
|
270
|
+
return effort === "off" ? "low" : effort;
|
|
271
|
+
}
|
|
272
|
+
function codexTurnStatus(value) {
|
|
273
|
+
const status = asString(value);
|
|
274
|
+
if (status === "completed") return "completed";
|
|
275
|
+
if (status === "interrupted") return "cancelled";
|
|
276
|
+
return "failed";
|
|
277
|
+
}
|
|
278
|
+
function appServerTurnFailure(error) {
|
|
279
|
+
const detail = asRecord(error);
|
|
280
|
+
const message = asString(detail.message) ?? "";
|
|
281
|
+
const additional = asString(detail.additionalDetails) ?? "";
|
|
282
|
+
if (message === "") return additional === "" ? "Codex turn failed" : redactAppServerDetail(additional);
|
|
283
|
+
return redactAppServerDetail(additional === "" || message.includes(additional) ? message : `${message} (${additional})`);
|
|
284
|
+
}
|
|
285
|
+
function codexConfig(systemPrompt, reasoningEffort) {
|
|
286
|
+
return {
|
|
287
|
+
model_reasoning_effort: reasoningEffort,
|
|
288
|
+
model_reasoning_summary: "detailed",
|
|
289
|
+
...typeof systemPrompt === "string" && systemPrompt.trim() !== "" ? { developer_instructions: systemPrompt } : {}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function send(message) {
|
|
293
|
+
const frame = workerFrame(message);
|
|
294
|
+
if (frame.replaced) process.stderr.write(`freecodego-codex-worker: replaced an oversized frame (${String(frame.originalBytes)} bytes over the ${String(MAX_WORKER_FRAME_BYTES)}-byte ceiling)
|
|
295
|
+
`);
|
|
296
|
+
process.stdout.write(frame.line);
|
|
297
|
+
}
|
|
298
|
+
function event(method, params = {}) {
|
|
299
|
+
if (threadId === void 0 || harnessSessionId === void 0) return;
|
|
300
|
+
send({
|
|
301
|
+
method,
|
|
302
|
+
params: {
|
|
303
|
+
runtimeSessionId: threadId,
|
|
304
|
+
harnessSessionId,
|
|
305
|
+
sequence: ++sequence,
|
|
306
|
+
...params
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
function request(method, params) {
|
|
311
|
+
if (child === void 0) return Promise.reject(/* @__PURE__ */ new Error("Codex app-server is not running"));
|
|
312
|
+
const id = ++rpcId;
|
|
313
|
+
const promise = new Promise((resolve, reject) => pending.set(id, {
|
|
314
|
+
resolve,
|
|
315
|
+
reject
|
|
316
|
+
}));
|
|
317
|
+
try {
|
|
318
|
+
child.stdin.write(`${JSON.stringify({
|
|
319
|
+
id,
|
|
320
|
+
method,
|
|
321
|
+
params
|
|
322
|
+
})}
|
|
323
|
+
`);
|
|
324
|
+
} catch (error) {
|
|
325
|
+
pending.delete(id);
|
|
326
|
+
return Promise.reject(error instanceof Error ? error : new Error(String(error)));
|
|
327
|
+
}
|
|
328
|
+
return promise;
|
|
329
|
+
}
|
|
330
|
+
var CODEX_TURN_IDLE_MS = 15 * 6e4;
|
|
331
|
+
function turnIdleMs() {
|
|
332
|
+
const override = Number(process.env.FREECODEGO_CODEX_TURN_IDLE_MS);
|
|
333
|
+
return Number.isFinite(override) && override > 0 ? override : CODEX_TURN_IDLE_MS;
|
|
334
|
+
}
|
|
335
|
+
function armTurnWatchdog() {
|
|
336
|
+
if (turnWatchdog !== void 0) clearTimeout(turnWatchdog);
|
|
337
|
+
turnWatchdog = setTimeout(() => {
|
|
338
|
+
turnWatchdog = void 0;
|
|
339
|
+
const hungTurn = turnId;
|
|
340
|
+
turnId = void 0;
|
|
341
|
+
const message = "Codex turn produced no events within the watchdog window";
|
|
342
|
+
clearPendingBridges(message);
|
|
343
|
+
event("session/completed", {
|
|
344
|
+
status: "failed",
|
|
345
|
+
message
|
|
346
|
+
});
|
|
347
|
+
if (hungTurn !== void 0 && threadId !== void 0) request("turn/interrupt", {
|
|
348
|
+
threadId,
|
|
349
|
+
turnId: hungTurn
|
|
350
|
+
}).catch(() => void 0);
|
|
351
|
+
}, turnIdleMs());
|
|
352
|
+
}
|
|
353
|
+
function disarmTurnWatchdog() {
|
|
354
|
+
if (turnWatchdog !== void 0) {
|
|
355
|
+
clearTimeout(turnWatchdog);
|
|
356
|
+
turnWatchdog = void 0;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function refreshTurnWatchdog() {
|
|
360
|
+
if (turnWatchdog === void 0 && turnId === void 0) return;
|
|
361
|
+
armTurnWatchdog();
|
|
362
|
+
}
|
|
363
|
+
var turnWatchdog;
|
|
364
|
+
function reply(id, result, error) {
|
|
365
|
+
send(error === void 0 ? {
|
|
366
|
+
id,
|
|
367
|
+
result
|
|
368
|
+
} : {
|
|
369
|
+
id,
|
|
370
|
+
error: {
|
|
371
|
+
code: "CODEX_WORKER",
|
|
372
|
+
message: redactAppServerDetail(error.message)
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
function start() {
|
|
377
|
+
if (child !== void 0) return child;
|
|
378
|
+
const executable = process.env.FREECODEGO_CODEX_APP_SERVER;
|
|
379
|
+
if (executable === void 0 || executable.length === 0) throw new Error("FREECODEGO_CODEX_APP_SERVER is required and must be set from a verified runtime manifest");
|
|
380
|
+
const args = process.env.FREECODEGO_CODEX_APP_SERVER_ARGS?.split("").filter(Boolean) ?? [];
|
|
381
|
+
const codexHome = process.env.FREECODEGO_CODEX_HOME;
|
|
382
|
+
if (codexHome === void 0 || codexHome.length === 0) throw new Error("FREECODEGO_CODEX_HOME is required and must be a plugin-owned state directory");
|
|
383
|
+
child = spawn(executable, args, {
|
|
384
|
+
cwd: process.cwd(),
|
|
385
|
+
env: {
|
|
386
|
+
PATH: process.env.PATH ?? "",
|
|
387
|
+
CODEX_HOME: codexHome,
|
|
388
|
+
...process.env.OPENAI_API_KEY === void 0 ? {} : { OPENAI_API_KEY: process.env.OPENAI_API_KEY },
|
|
389
|
+
...process.env.OPENAI_BASE_URL === void 0 ? {} : { OPENAI_BASE_URL: process.env.OPENAI_BASE_URL },
|
|
390
|
+
...capabilityEnvironment
|
|
391
|
+
},
|
|
392
|
+
windowsHide: true,
|
|
393
|
+
stdio: [
|
|
394
|
+
"pipe",
|
|
395
|
+
"pipe",
|
|
396
|
+
"pipe"
|
|
397
|
+
]
|
|
398
|
+
});
|
|
399
|
+
createInterface({ input: child.stdout }).on("line", (line) => {
|
|
400
|
+
const trimmed = line.trim();
|
|
401
|
+
if (trimmed === "" || !(trimmed.startsWith("{") || trimmed.startsWith("["))) {
|
|
402
|
+
if (trimmed !== "") stderr = `${stderr}${trimmed}
|
|
403
|
+
`.slice(-16 * 1024);
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
try {
|
|
407
|
+
receiveRpc(JSON.parse(trimmed));
|
|
408
|
+
} catch (error) {
|
|
409
|
+
stderr = `${stderr}unparsable stdout frame: ${String(error)}
|
|
410
|
+
`.slice(-16 * 1024);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
child.stderr.setEncoding("utf8");
|
|
414
|
+
child.stderr.on("data", (chunk) => {
|
|
415
|
+
stderr = `${stderr}${chunk}`.slice(-16 * 1024);
|
|
416
|
+
});
|
|
417
|
+
child.stdin.on("error", () => void 0);
|
|
418
|
+
const instance = child;
|
|
419
|
+
child.on("close", () => {
|
|
420
|
+
if (child !== instance) return;
|
|
421
|
+
disarmTurnWatchdog();
|
|
422
|
+
const detail = redactAppServerDetail(stderr.trim());
|
|
423
|
+
const error = /* @__PURE__ */ new Error(detail === "" ? "Codex app-server exited" : `Codex app-server exited: ${detail}`);
|
|
424
|
+
for (const entry of pending.values()) entry.reject(error);
|
|
425
|
+
pending.clear();
|
|
426
|
+
clearPendingBridges("Codex app-server exited");
|
|
427
|
+
child = void 0;
|
|
428
|
+
event("session/failed", { message: error.message });
|
|
429
|
+
});
|
|
430
|
+
return child;
|
|
431
|
+
}
|
|
432
|
+
async function prepareCodexHome() {
|
|
433
|
+
const target = process.env.FREECODEGO_CODEX_HOME;
|
|
434
|
+
if (!target) return;
|
|
435
|
+
await mkdir(target, { recursive: true });
|
|
436
|
+
if (process.env.FREECODEGO_CODEX_SYNC_GLOBAL_AUTH === "1") {
|
|
437
|
+
const home = process.env.CODEX_GLOBAL_HOME?.trim() || join(process.env.USERPROFILE || process.env.HOME || "", ".codex");
|
|
438
|
+
if (home && home !== target) for (const name of ["auth.json"]) {
|
|
439
|
+
const source = join(home, name);
|
|
440
|
+
const destination = join(target, name);
|
|
441
|
+
try {
|
|
442
|
+
await access(destination);
|
|
443
|
+
continue;
|
|
444
|
+
} catch {
|
|
445
|
+
}
|
|
446
|
+
try {
|
|
447
|
+
await copyFile(source, destination);
|
|
448
|
+
} catch {
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
await ensureHarnessMcpServer();
|
|
453
|
+
await writeManagedMcpConfig(target);
|
|
454
|
+
}
|
|
455
|
+
async function writeManagedMcpConfig(home) {
|
|
456
|
+
const target = join(home, "config.toml");
|
|
457
|
+
let existing = "";
|
|
458
|
+
try {
|
|
459
|
+
existing = await readFile(target, "utf8");
|
|
460
|
+
} catch {
|
|
461
|
+
}
|
|
462
|
+
const start2 = existing.indexOf(MANAGED_MCP_START);
|
|
463
|
+
const end = existing.indexOf(MANAGED_MCP_END);
|
|
464
|
+
const hasManagedSegment = start2 >= 0 && end >= start2;
|
|
465
|
+
if (hasManagedSegment) existing = `${existing.slice(0, start2)}${existing.slice(end + 28)}`.trimEnd();
|
|
466
|
+
existing = stripMcpServerTables(existing);
|
|
467
|
+
const rendered = renderManagedMcpConfig();
|
|
468
|
+
if (rendered === "" && !hasManagedSegment) return;
|
|
469
|
+
const temporary = `${target}.tmp-${process.pid}`;
|
|
470
|
+
await writeFile(temporary, `${existing}${existing === "" || rendered === "" ? "" : "\n\n"}${rendered}${rendered === "" ? "" : "\n"}`, "utf8");
|
|
471
|
+
await rename(temporary, target);
|
|
472
|
+
}
|
|
473
|
+
function renderManagedMcpConfig() {
|
|
474
|
+
capabilityEnvironment = {};
|
|
475
|
+
if (harnessMcpUrl === void 0) return "";
|
|
476
|
+
capabilityEnvironment.FREECODEGO_CODEX_HARNESS_MCP_TOKEN = harnessMcpToken;
|
|
477
|
+
return [
|
|
478
|
+
MANAGED_MCP_START,
|
|
479
|
+
"[mcp_servers.freecodego-harness]",
|
|
480
|
+
`url = ${tomlString(harnessMcpUrl)}`,
|
|
481
|
+
'bearer_token_env_var = "FREECODEGO_CODEX_HARNESS_MCP_TOKEN"',
|
|
482
|
+
MANAGED_MCP_END
|
|
483
|
+
].join("\n\n");
|
|
484
|
+
}
|
|
485
|
+
function tomlString(value) {
|
|
486
|
+
return JSON.stringify(value);
|
|
487
|
+
}
|
|
488
|
+
function stripMcpServerTables(value) {
|
|
489
|
+
let inMcpSection = false;
|
|
490
|
+
const retained = [];
|
|
491
|
+
for (const line of value.split(/\r?\n/)) {
|
|
492
|
+
const header = line.match(/^\s*\[\[?\s*([^\]]+?)\s*\]\]?\s*(?:#.*)?$/);
|
|
493
|
+
if (header !== null) {
|
|
494
|
+
inMcpSection = /^mcp_servers(?:\s*\.|$)/.test((header[1] ?? "").trim());
|
|
495
|
+
if (!inMcpSection) retained.push(line);
|
|
496
|
+
continue;
|
|
497
|
+
}
|
|
498
|
+
if (!inMcpSection && !/^\s*mcp_servers\s*(?:\.|=)/.test(line)) retained.push(line);
|
|
499
|
+
}
|
|
500
|
+
return retained.join("\n").trimEnd();
|
|
501
|
+
}
|
|
502
|
+
function readCapabilities(value) {
|
|
503
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return {
|
|
504
|
+
mcpEnabled: false,
|
|
505
|
+
skillEnabled: false,
|
|
506
|
+
skillRoots: [],
|
|
507
|
+
mcpTools: [],
|
|
508
|
+
harnessTools: []
|
|
509
|
+
};
|
|
510
|
+
const input = value;
|
|
511
|
+
return {
|
|
512
|
+
mcpEnabled: input.mcpEnabled === true,
|
|
513
|
+
skillEnabled: input.skillEnabled === true,
|
|
514
|
+
skillRoots: input.skillEnabled === true ? strings(input.skillRoots) : [],
|
|
515
|
+
mcpTools: input.mcpEnabled === true ? readHarnessTools(input.mcpTools) : [],
|
|
516
|
+
harnessTools: readHarnessTools(input.harnessTools)
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
function readHarnessTools(value) {
|
|
520
|
+
if (!Array.isArray(value)) return [];
|
|
521
|
+
const tools = [];
|
|
522
|
+
const names = /* @__PURE__ */ new Set();
|
|
523
|
+
for (const item of value) {
|
|
524
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) continue;
|
|
525
|
+
const tool = item;
|
|
526
|
+
if (typeof tool.name !== "string" || !/^[A-Za-z0-9_-]{1,96}$/.test(tool.name) || names.has(tool.name)) continue;
|
|
527
|
+
names.add(tool.name);
|
|
528
|
+
tools.push({
|
|
529
|
+
name: tool.name,
|
|
530
|
+
description: typeof tool.description === "string" ? tool.description : tool.name,
|
|
531
|
+
parameters: typeof tool.parameters === "object" && tool.parameters !== null && !Array.isArray(tool.parameters) ? tool.parameters : {
|
|
532
|
+
type: "object",
|
|
533
|
+
properties: {}
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
return tools;
|
|
538
|
+
}
|
|
539
|
+
function strings(value) {
|
|
540
|
+
return Array.isArray(value) ? value.filter((item) => typeof item === "string" && item.trim() !== "") : [];
|
|
541
|
+
}
|
|
542
|
+
function asRecord(value) {
|
|
543
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
544
|
+
}
|
|
545
|
+
function asString(value) {
|
|
546
|
+
return typeof value === "string" ? value : void 0;
|
|
547
|
+
}
|
|
548
|
+
function asMessage(value) {
|
|
549
|
+
const direct = asString(value);
|
|
550
|
+
if (direct !== void 0) return direct;
|
|
551
|
+
if (value === void 0 || value === null) return "";
|
|
552
|
+
try {
|
|
553
|
+
return JSON.stringify(value);
|
|
554
|
+
} catch {
|
|
555
|
+
return "";
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
async function configureCodexSkills() {
|
|
559
|
+
await request("skills/extraRoots/set", { extraRoots: capabilities.skillEnabled ? capabilities.skillRoots : [] });
|
|
560
|
+
}
|
|
561
|
+
async function ensureHarnessMcpServer() {
|
|
562
|
+
if (harnessMcpUrl !== void 0 || capabilities.harnessTools.length === 0 && capabilities.mcpTools.length === 0 && !capabilities.skillEnabled) return;
|
|
563
|
+
const server = createServer((request2, response) => {
|
|
564
|
+
handleHarnessMcpRequest(request2, response).catch((error) => {
|
|
565
|
+
mcpError(response, void 0, error);
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
await new Promise((resolve, reject) => {
|
|
569
|
+
const onError = (error) => {
|
|
570
|
+
server.close(() => void 0);
|
|
571
|
+
reject(error);
|
|
572
|
+
};
|
|
573
|
+
server.once("error", onError);
|
|
574
|
+
server.listen(0, "127.0.0.1", () => {
|
|
575
|
+
server.off("error", onError);
|
|
576
|
+
const address = server.address();
|
|
577
|
+
if (address === null || typeof address === "string") {
|
|
578
|
+
server.close(() => void 0);
|
|
579
|
+
reject(/* @__PURE__ */ new Error("Codex Harness MCP server has no TCP address"));
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
harnessMcpServer = server;
|
|
583
|
+
harnessMcpUrl = `http://127.0.0.1:${address.port}/mcp`;
|
|
584
|
+
resolve();
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
async function handleHarnessMcpRequest(request2, response) {
|
|
589
|
+
if (request2.method !== "POST" || request2.url !== "/mcp" || request2.headers.authorization !== `Bearer ${harnessMcpToken}`) {
|
|
590
|
+
response.writeHead(404);
|
|
591
|
+
response.end();
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
const body = await readHttpBody(request2);
|
|
595
|
+
const message = JSON.parse(body);
|
|
596
|
+
if (message.id === void 0) {
|
|
597
|
+
response.writeHead(202);
|
|
598
|
+
response.end();
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (message.method === "initialize") {
|
|
602
|
+
mcpResult(response, message.id, {
|
|
603
|
+
protocolVersion: "2024-11-05",
|
|
604
|
+
capabilities: { tools: {} },
|
|
605
|
+
serverInfo: {
|
|
606
|
+
name: "freecodego-harness",
|
|
607
|
+
version: "1.0.0"
|
|
608
|
+
}
|
|
609
|
+
});
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
if (message.method === "tools/list") {
|
|
613
|
+
mcpResult(response, message.id, { tools: harnessMcpTools() });
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
if (message.method === "tools/call") {
|
|
617
|
+
const params = typeof message.params === "object" && message.params !== null && !Array.isArray(message.params) ? message.params : {};
|
|
618
|
+
const name = typeof params.name === "string" ? params.name : "";
|
|
619
|
+
const args = typeof params.arguments === "object" && params.arguments !== null && !Array.isArray(params.arguments) ? params.arguments : {};
|
|
620
|
+
try {
|
|
621
|
+
const result = await invokeHarnessMcpTool(name, args);
|
|
622
|
+
mcpResult(response, message.id, mcpToolResult(result));
|
|
623
|
+
return;
|
|
624
|
+
} catch (error) {
|
|
625
|
+
mcpResult(response, message.id, {
|
|
626
|
+
content: [{
|
|
627
|
+
type: "text",
|
|
628
|
+
text: redactAppServerDetail(error instanceof Error ? error.message : String(error))
|
|
629
|
+
}],
|
|
630
|
+
isError: true
|
|
631
|
+
});
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
mcpError(response, message.id, /* @__PURE__ */ new Error(`unsupported Harness MCP method ${String(message.method)}`), -32601);
|
|
636
|
+
}
|
|
637
|
+
function harnessMcpTools() {
|
|
638
|
+
const mcpTools = capabilities.mcpEnabled ? capabilities.mcpTools : [];
|
|
639
|
+
const mcpNames = new Set(mcpTools.map((tool) => tool.name));
|
|
640
|
+
return [
|
|
641
|
+
...capabilities.harnessTools.filter((tool) => !mcpNames.has(tool.name)).map((tool) => ({
|
|
642
|
+
name: tool.name,
|
|
643
|
+
description: tool.description,
|
|
644
|
+
inputSchema: tool.parameters
|
|
645
|
+
})),
|
|
646
|
+
...mcpTools.map((tool) => ({
|
|
647
|
+
name: tool.name,
|
|
648
|
+
description: tool.description,
|
|
649
|
+
inputSchema: tool.parameters
|
|
650
|
+
})),
|
|
651
|
+
...capabilities.skillEnabled ? [{
|
|
652
|
+
name: "freecodego_skill_discover",
|
|
653
|
+
description: "List enabled DeepSeek Harness Skills.",
|
|
654
|
+
inputSchema: {
|
|
655
|
+
type: "object",
|
|
656
|
+
properties: {}
|
|
657
|
+
}
|
|
658
|
+
}, {
|
|
659
|
+
name: "freecodego_skill_load",
|
|
660
|
+
description: "Load one enabled DeepSeek Harness Skill.",
|
|
661
|
+
inputSchema: {
|
|
662
|
+
type: "object",
|
|
663
|
+
properties: { name: { type: "string" } },
|
|
664
|
+
required: ["name"]
|
|
665
|
+
}
|
|
666
|
+
}] : []
|
|
667
|
+
];
|
|
668
|
+
}
|
|
669
|
+
async function invokeHarnessMcpTool(name, args) {
|
|
670
|
+
const route = harnessMcpRoute(capabilities, name);
|
|
671
|
+
if (route.bridge === "skill") return await bridgeCall("skill", route.op, route.op === "load" ? { name: args.name } : {});
|
|
672
|
+
if (route.bridge === "mcp") return await bridgeCall("mcp", "execute", {
|
|
673
|
+
name,
|
|
674
|
+
arguments: args
|
|
675
|
+
});
|
|
676
|
+
return await bridgeCall("tool", "execute", {
|
|
677
|
+
name,
|
|
678
|
+
arguments: args
|
|
679
|
+
});
|
|
680
|
+
}
|
|
681
|
+
async function bridgeCall(bridge, op, input) {
|
|
682
|
+
const requestId = randomUUID();
|
|
683
|
+
const promise = Promise.withResolvers();
|
|
684
|
+
pendingBridges.set(requestId, promise);
|
|
685
|
+
event("bridge/requested", {
|
|
686
|
+
requestId,
|
|
687
|
+
bridge,
|
|
688
|
+
op,
|
|
689
|
+
input
|
|
690
|
+
});
|
|
691
|
+
return await promise.promise;
|
|
692
|
+
}
|
|
693
|
+
function clearPendingBridges(reason) {
|
|
694
|
+
for (const pending2 of pendingBridges.values()) pending2.reject(new Error(reason));
|
|
695
|
+
pendingBridges.clear();
|
|
696
|
+
}
|
|
697
|
+
function mcpResult(response, id, result) {
|
|
698
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
699
|
+
response.end(JSON.stringify({
|
|
700
|
+
jsonrpc: "2.0",
|
|
701
|
+
id: id ?? null,
|
|
702
|
+
result
|
|
703
|
+
}));
|
|
704
|
+
}
|
|
705
|
+
function mcpError(response, id, error, code = -32e3) {
|
|
706
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
707
|
+
response.end(JSON.stringify({
|
|
708
|
+
jsonrpc: "2.0",
|
|
709
|
+
id: id ?? null,
|
|
710
|
+
error: {
|
|
711
|
+
code,
|
|
712
|
+
message: redactAppServerDetail(error instanceof Error ? error.message : String(error))
|
|
713
|
+
}
|
|
714
|
+
}));
|
|
715
|
+
}
|
|
716
|
+
function readHttpBody(request2) {
|
|
717
|
+
return new Promise((resolve, reject) => {
|
|
718
|
+
const chunks = [];
|
|
719
|
+
request2.on("data", (chunk) => {
|
|
720
|
+
chunks.push(Buffer.from(chunk));
|
|
721
|
+
});
|
|
722
|
+
request2.on("end", () => {
|
|
723
|
+
resolve(Buffer.concat(chunks).toString("utf8"));
|
|
724
|
+
});
|
|
725
|
+
request2.on("error", reject);
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
function receiveRpc(message) {
|
|
729
|
+
if (message.method === "turn/completed") disarmTurnWatchdog();
|
|
730
|
+
else refreshTurnWatchdog();
|
|
731
|
+
if (message.id !== void 0 && (message.result !== void 0 || message.error !== void 0)) {
|
|
732
|
+
const call = pending.get(message.id);
|
|
733
|
+
if (call === void 0) return;
|
|
734
|
+
pending.delete(message.id);
|
|
735
|
+
if (message.error === void 0) call.resolve(message.result);
|
|
736
|
+
else call.reject(new Error(redactAppServerDetail(message.error.message)));
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
if (message.id !== void 0 && message.method !== void 0) {
|
|
740
|
+
const id = message.id;
|
|
741
|
+
const requestedQuestions = normalizeQuestions(message.params);
|
|
742
|
+
if (requestedQuestions !== void 0) {
|
|
743
|
+
const requestId = `question-${id}`;
|
|
744
|
+
questions.set(requestId, (value) => {
|
|
745
|
+
sendRpc({
|
|
746
|
+
id,
|
|
747
|
+
result: questionResponse(value)
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
event("question/requested", {
|
|
751
|
+
requestId,
|
|
752
|
+
request: { questions: requestedQuestions }
|
|
753
|
+
});
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const method = message.method;
|
|
757
|
+
if (!isApprovalMethod(method)) {
|
|
758
|
+
sendRpc({
|
|
759
|
+
id,
|
|
760
|
+
error: {
|
|
761
|
+
code: -32601,
|
|
762
|
+
message: `FreeCodeGo does not implement the Codex App Server request "${method}"`
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
const approvalId = `approval-${id}`;
|
|
768
|
+
const requestParams = message.params;
|
|
769
|
+
approvals.set(approvalId, (value) => {
|
|
770
|
+
sendRpc({
|
|
771
|
+
id,
|
|
772
|
+
result: approvalResponse(method, value, requestParams)
|
|
773
|
+
});
|
|
774
|
+
});
|
|
775
|
+
event("permission/requested", {
|
|
776
|
+
approvalId,
|
|
777
|
+
method,
|
|
778
|
+
detail: message.params ?? {}
|
|
779
|
+
});
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
const params = asRecord(message.params);
|
|
783
|
+
if (message.method === "item/agentMessage/delta") event("assistant/delta", { text: asString(params.delta) ?? "" });
|
|
784
|
+
else if (message.method === "item/reasoning/textDelta" || message.method === "item/reasoning/summaryTextDelta") event("assistant/reasoning/delta", {
|
|
785
|
+
text: asString(params.delta) ?? asString(params.text) ?? "",
|
|
786
|
+
source: "codex-app-server",
|
|
787
|
+
sourceLabel: "Codex App Server"
|
|
788
|
+
});
|
|
789
|
+
else if (message.method === "item/reasoning/summaryPartAdded") {
|
|
790
|
+
const part = asRecord(params.part);
|
|
791
|
+
const added = asString(part.text) ?? asString(part.content) ?? "";
|
|
792
|
+
if (added !== "") event("assistant/reasoning/delta", {
|
|
793
|
+
text: added,
|
|
794
|
+
source: "codex-app-server",
|
|
795
|
+
sourceLabel: "Codex App Server"
|
|
796
|
+
});
|
|
797
|
+
} else if (message.method === "item/completed" && asRecord(params.item).type === "agentMessage") event("assistant/final", { text: asString(asRecord(params.item).text) ?? "" });
|
|
798
|
+
else if (message.method === "item/completed" && asRecord(params.item).type === "reasoning") {
|
|
799
|
+
const item = asRecord(params.item);
|
|
800
|
+
const completed = typeof item.text === "string" ? item.text : Array.isArray(item.summary) ? item.summary.map((part) => asString(asRecord(part).text) ?? "").join("") : "";
|
|
801
|
+
if (completed !== "") event("assistant/reasoning/final", {
|
|
802
|
+
text: completed,
|
|
803
|
+
source: "codex-app-server",
|
|
804
|
+
sourceLabel: "Codex App Server"
|
|
805
|
+
});
|
|
806
|
+
} else if (message.method === "turn/started") turnId = asString(asRecord(params.turn).id) ?? turnId;
|
|
807
|
+
else if (message.method === "turn/completed") {
|
|
808
|
+
disarmTurnWatchdog();
|
|
809
|
+
clearPendingBridges("Codex turn completed");
|
|
810
|
+
turnId = void 0;
|
|
811
|
+
const status = codexTurnStatus(asRecord(params.turn).status);
|
|
812
|
+
const reason = status === "failed" ? appServerTurnFailure(asRecord(params.turn).error) : void 0;
|
|
813
|
+
event("session/completed", reason === void 0 ? { status } : {
|
|
814
|
+
status,
|
|
815
|
+
message: reason
|
|
816
|
+
});
|
|
817
|
+
} else if (message.method !== void 0) event("tool/progress", {
|
|
818
|
+
method: message.method,
|
|
819
|
+
detail: message.params ?? {}
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
function sendRpc(message) {
|
|
823
|
+
child?.stdin.write(`${JSON.stringify(message)}
|
|
824
|
+
`);
|
|
825
|
+
}
|
|
826
|
+
function redactAppServerDetail(value) {
|
|
827
|
+
return redactSecrets(value, [
|
|
828
|
+
process.env.OPENAI_API_KEY,
|
|
829
|
+
harnessMcpToken,
|
|
830
|
+
...Object.values(capabilityEnvironment)
|
|
831
|
+
]);
|
|
832
|
+
}
|
|
833
|
+
function killAppServerTree(dying) {
|
|
834
|
+
if (process.platform === "win32" && dying.pid !== void 0) {
|
|
835
|
+
spawn("taskkill", [
|
|
836
|
+
"/pid",
|
|
837
|
+
String(dying.pid),
|
|
838
|
+
"/t",
|
|
839
|
+
"/f"
|
|
840
|
+
], {
|
|
841
|
+
windowsHide: true,
|
|
842
|
+
stdio: "ignore"
|
|
843
|
+
}).on("error", () => void 0);
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
dying.kill();
|
|
847
|
+
}
|
|
848
|
+
async function stopAppServer(dying) {
|
|
849
|
+
const exited = new Promise((resolve) => {
|
|
850
|
+
if (dying.exitCode !== null || dying.signalCode !== null) {
|
|
851
|
+
resolve();
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
dying.once("close", () => {
|
|
855
|
+
resolve();
|
|
856
|
+
});
|
|
857
|
+
});
|
|
858
|
+
dying.stdin.end();
|
|
859
|
+
const deadline = setTimeout(() => {
|
|
860
|
+
killAppServerTree(dying);
|
|
861
|
+
}, DISPOSE_GRACE_MS);
|
|
862
|
+
try {
|
|
863
|
+
await exited;
|
|
864
|
+
} finally {
|
|
865
|
+
clearTimeout(deadline);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
async function handle(message) {
|
|
869
|
+
if (message.method === "initialize") return {
|
|
870
|
+
protocolAbi: message.params.protocolVersion,
|
|
871
|
+
engines: ["codex"],
|
|
872
|
+
runtimeVersion: "codex-app-server-worker/1"
|
|
873
|
+
};
|
|
874
|
+
if (message.method === "host/configure") {
|
|
875
|
+
capabilities = readCapabilities(message.params);
|
|
876
|
+
if (child !== void 0) {
|
|
877
|
+
await ensureHarnessMcpServer();
|
|
878
|
+
const home = process.env.FREECODEGO_CODEX_HOME;
|
|
879
|
+
if (home !== void 0 && home !== "") await writeManagedMcpConfig(home);
|
|
880
|
+
await configureCodexSkills();
|
|
881
|
+
}
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
if (message.method === "session/create") {
|
|
885
|
+
await prepareCodexHome();
|
|
886
|
+
start();
|
|
887
|
+
harnessSessionId = asString(message.params.harnessSessionId);
|
|
888
|
+
modelId = asString(message.params.modelId);
|
|
889
|
+
selectedProvider = asString(message.params.provider);
|
|
890
|
+
modelProvider = appServerProvider();
|
|
891
|
+
const selectedEffort = selectedReasoningEffort(message.params.reasoningEffort);
|
|
892
|
+
await request("initialize", {
|
|
893
|
+
clientInfo: {
|
|
894
|
+
name: "DeepSeek Harness",
|
|
895
|
+
version: "1"
|
|
896
|
+
},
|
|
897
|
+
capabilities: {}
|
|
898
|
+
});
|
|
899
|
+
sendRpc({
|
|
900
|
+
method: "initialized",
|
|
901
|
+
params: {}
|
|
902
|
+
});
|
|
903
|
+
await configureCodexSkills();
|
|
904
|
+
const started = asRecord(await request("thread/start", {
|
|
905
|
+
cwd: message.params.workspace,
|
|
906
|
+
...codexThreadSandboxParams(message.params),
|
|
907
|
+
...appServerModel(selectedEffort) === void 0 ? {} : { model: appServerModel(selectedEffort) },
|
|
908
|
+
...modelProvider ? { modelProvider } : {},
|
|
909
|
+
config: codexConfig(message.params.systemPrompt, codexReasoningEffort(selectedEffort))
|
|
910
|
+
}));
|
|
911
|
+
const startedThread = asRecord(started.thread);
|
|
912
|
+
const startedThreadId = startedThread.id;
|
|
913
|
+
if (typeof startedThreadId !== "string" || startedThreadId === "") throw new Error("Codex app-server returned no thread id for thread/start");
|
|
914
|
+
threadId = startedThreadId;
|
|
915
|
+
if (!usesOpenAiBridge()) {
|
|
916
|
+
modelId = asString(started.model) ?? asString(startedThread.model) ?? modelId;
|
|
917
|
+
modelProvider = asString(started.modelProvider) ?? asString(startedThread.modelProvider) ?? modelProvider;
|
|
918
|
+
}
|
|
919
|
+
event("session/started", {
|
|
920
|
+
model: modelId ?? "",
|
|
921
|
+
provider: selectedProvider ?? modelProvider ?? ""
|
|
922
|
+
});
|
|
923
|
+
return {
|
|
924
|
+
runtimeSessionId: threadId,
|
|
925
|
+
modelId,
|
|
926
|
+
provider: selectedProvider ?? modelProvider
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
if (message.method === "session/resume") {
|
|
930
|
+
await prepareCodexHome();
|
|
931
|
+
start();
|
|
932
|
+
harnessSessionId = asString(message.params.harnessSessionId);
|
|
933
|
+
modelId = asString(message.params.modelId);
|
|
934
|
+
selectedProvider = asString(message.params.provider);
|
|
935
|
+
modelProvider = appServerProvider();
|
|
936
|
+
const resumeId = message.params.runtimeSessionId;
|
|
937
|
+
if (typeof resumeId !== "string" || resumeId.length === 0) throw new Error("Codex resume requires a persisted runtimeSessionId");
|
|
938
|
+
const selectedEffort = selectedReasoningEffort(message.params.reasoningEffort);
|
|
939
|
+
await request("initialize", {
|
|
940
|
+
clientInfo: {
|
|
941
|
+
name: "DeepSeek Harness",
|
|
942
|
+
version: "1"
|
|
943
|
+
},
|
|
944
|
+
capabilities: {}
|
|
945
|
+
});
|
|
946
|
+
sendRpc({
|
|
947
|
+
method: "initialized",
|
|
948
|
+
params: {}
|
|
949
|
+
});
|
|
950
|
+
await configureCodexSkills();
|
|
951
|
+
const resumed = asRecord(await request("thread/resume", {
|
|
952
|
+
threadId: resumeId,
|
|
953
|
+
cwd: message.params.workspace,
|
|
954
|
+
...codexThreadSandboxParams(message.params),
|
|
955
|
+
...appServerModel(selectedEffort) === void 0 ? {} : { model: appServerModel(selectedEffort) },
|
|
956
|
+
...modelProvider ? { modelProvider } : {},
|
|
957
|
+
config: codexConfig(message.params.systemPrompt, codexReasoningEffort(selectedEffort))
|
|
958
|
+
}));
|
|
959
|
+
const resumedThread = asRecord(resumed.thread);
|
|
960
|
+
threadId = asString(resumedThread.id) ?? resumeId;
|
|
961
|
+
if (!usesOpenAiBridge()) {
|
|
962
|
+
modelId = asString(resumed.model) ?? asString(resumedThread.model) ?? modelId;
|
|
963
|
+
modelProvider = asString(resumed.modelProvider) ?? asString(resumedThread.modelProvider) ?? modelProvider;
|
|
964
|
+
}
|
|
965
|
+
event("session/started", {
|
|
966
|
+
model: modelId ?? "",
|
|
967
|
+
provider: selectedProvider ?? modelProvider ?? ""
|
|
968
|
+
});
|
|
969
|
+
return {
|
|
970
|
+
runtimeSessionId: threadId,
|
|
971
|
+
modelId,
|
|
972
|
+
provider: selectedProvider ?? modelProvider
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
if (message.method === "session/prompt") {
|
|
976
|
+
if (!threadId) throw new Error("Codex session is not initialized");
|
|
977
|
+
if (typeof message.params.modelId === "string" && message.params.modelId.trim() !== "") modelId = message.params.modelId;
|
|
978
|
+
if (typeof message.params.provider === "string" && message.params.provider.trim() !== "") selectedProvider = message.params.provider;
|
|
979
|
+
const selectedEffort = selectedReasoningEffort(message.params.reasoningEffort);
|
|
980
|
+
armTurnWatchdog();
|
|
981
|
+
try {
|
|
982
|
+
turnId = asString(asRecord(asRecord(await request("turn/start", {
|
|
983
|
+
threadId,
|
|
984
|
+
input: [{
|
|
985
|
+
type: "text",
|
|
986
|
+
text: message.params.content,
|
|
987
|
+
text_elements: []
|
|
988
|
+
}],
|
|
989
|
+
...message.params.workspace ? { cwd: message.params.workspace } : {},
|
|
990
|
+
...appServerModel(selectedEffort) === void 0 ? {} : { model: appServerModel(selectedEffort) },
|
|
991
|
+
effort: codexReasoningEffort(selectedEffort),
|
|
992
|
+
summary: "detailed"
|
|
993
|
+
})).turn).id) ?? turnId;
|
|
994
|
+
} catch (error) {
|
|
995
|
+
disarmTurnWatchdog();
|
|
996
|
+
throw error;
|
|
997
|
+
}
|
|
998
|
+
return null;
|
|
999
|
+
}
|
|
1000
|
+
if (message.method === "session/cancel") {
|
|
1001
|
+
disarmTurnWatchdog();
|
|
1002
|
+
clearPendingBridges("Codex turn cancelled");
|
|
1003
|
+
if (turnId === void 0) {
|
|
1004
|
+
event("session/completed", { status: "cancelled" });
|
|
1005
|
+
return null;
|
|
1006
|
+
}
|
|
1007
|
+
try {
|
|
1008
|
+
await request("turn/interrupt", {
|
|
1009
|
+
threadId,
|
|
1010
|
+
turnId
|
|
1011
|
+
});
|
|
1012
|
+
} catch (error) {
|
|
1013
|
+
event("session/completed", {
|
|
1014
|
+
status: "failed",
|
|
1015
|
+
message: error instanceof Error ? redactAppServerDetail(error.message) : "Codex turn interrupt failed"
|
|
1016
|
+
});
|
|
1017
|
+
return null;
|
|
1018
|
+
}
|
|
1019
|
+
turnId = void 0;
|
|
1020
|
+
disarmTurnWatchdog();
|
|
1021
|
+
event("session/completed", { status: "cancelled" });
|
|
1022
|
+
return null;
|
|
1023
|
+
}
|
|
1024
|
+
if (message.method === "permission/respond") {
|
|
1025
|
+
const requestId = asString(message.params.requestId) ?? "";
|
|
1026
|
+
const resolve = approvals.get(requestId);
|
|
1027
|
+
if (resolve === void 0) throw new Error("unknown Codex approval request");
|
|
1028
|
+
approvals.delete(requestId);
|
|
1029
|
+
resolve(message.params.response);
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1032
|
+
if (message.method === "question/respond") {
|
|
1033
|
+
const requestId = asString(message.params.requestId) ?? "";
|
|
1034
|
+
const resolve = questions.get(requestId);
|
|
1035
|
+
if (resolve === void 0) throw new Error("unknown Codex question request");
|
|
1036
|
+
questions.delete(requestId);
|
|
1037
|
+
resolve(message.params.response);
|
|
1038
|
+
return null;
|
|
1039
|
+
}
|
|
1040
|
+
if (message.method === "bridge/respond") {
|
|
1041
|
+
const bridgeId = asString(message.params.requestId) ?? "";
|
|
1042
|
+
const pending2 = pendingBridges.get(bridgeId);
|
|
1043
|
+
if (pending2 === void 0) return null;
|
|
1044
|
+
pendingBridges.delete(bridgeId);
|
|
1045
|
+
const failure = message.params.error;
|
|
1046
|
+
if (failure !== void 0 && failure !== null) pending2.reject(new Error(asMessage(failure) || "bridge call failed"));
|
|
1047
|
+
else pending2.resolve(message.params.result);
|
|
1048
|
+
return null;
|
|
1049
|
+
}
|
|
1050
|
+
if (message.method === "session/dispose") {
|
|
1051
|
+
disarmTurnWatchdog();
|
|
1052
|
+
const dying = child;
|
|
1053
|
+
child = void 0;
|
|
1054
|
+
threadId = void 0;
|
|
1055
|
+
harnessSessionId = void 0;
|
|
1056
|
+
approvals.clear();
|
|
1057
|
+
questions.clear();
|
|
1058
|
+
clearPendingBridges("Codex session disposed");
|
|
1059
|
+
const server = harnessMcpServer;
|
|
1060
|
+
harnessMcpServer = void 0;
|
|
1061
|
+
harnessMcpUrl = void 0;
|
|
1062
|
+
if (dying !== void 0) await stopAppServer(dying);
|
|
1063
|
+
await new Promise((resolve) => {
|
|
1064
|
+
if (server === void 0) resolve();
|
|
1065
|
+
else server.close(() => {
|
|
1066
|
+
resolve();
|
|
1067
|
+
});
|
|
1068
|
+
});
|
|
1069
|
+
return null;
|
|
1070
|
+
}
|
|
1071
|
+
throw new Error(`unsupported Codex worker method ${message.method}`);
|
|
1072
|
+
}
|
|
1073
|
+
createInterface({ input: process.stdin }).on("line", (line) => {
|
|
1074
|
+
try {
|
|
1075
|
+
const message = parseHostFrame(line);
|
|
1076
|
+
if (message === void 0) throw new Error("Host frame is not a { id, method, params } request object");
|
|
1077
|
+
handle(message).then((result) => {
|
|
1078
|
+
reply(message.id, result);
|
|
1079
|
+
}, (error) => {
|
|
1080
|
+
reply(message.id, void 0, error instanceof Error ? error : new Error(String(error)));
|
|
1081
|
+
});
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
send({ error: {
|
|
1084
|
+
code: "INVALID_JSON",
|
|
1085
|
+
message: String(error)
|
|
1086
|
+
} });
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
function parseHostFrame(line) {
|
|
1090
|
+
const parsed = JSON.parse(line);
|
|
1091
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return void 0;
|
|
1092
|
+
const frame = parsed;
|
|
1093
|
+
if (typeof frame.id !== "string" || typeof frame.method !== "string") return void 0;
|
|
1094
|
+
const params = typeof frame.params === "object" && frame.params !== null && !Array.isArray(frame.params) ? frame.params : {};
|
|
1095
|
+
return {
|
|
1096
|
+
id: frame.id,
|
|
1097
|
+
method: frame.method,
|
|
1098
|
+
params
|
|
1099
|
+
};
|
|
1100
|
+
}
|
|
1101
|
+
function normalizeQuestions(value) {
|
|
1102
|
+
const candidate = typeof value === "object" && value !== null && !Array.isArray(value) ? value.questions : void 0;
|
|
1103
|
+
if (!Array.isArray(candidate) || candidate.length === 0) return void 0;
|
|
1104
|
+
const out = [];
|
|
1105
|
+
for (const item of candidate) {
|
|
1106
|
+
if (typeof item !== "object" || item === null || Array.isArray(item)) return void 0;
|
|
1107
|
+
const record2 = item;
|
|
1108
|
+
if (typeof record2.question !== "string" || record2.question.trim() === "") return void 0;
|
|
1109
|
+
const options = Array.isArray(record2.options) ? record2.options.flatMap((option) => {
|
|
1110
|
+
if (typeof option !== "object" || option === null || Array.isArray(option)) return [];
|
|
1111
|
+
const value2 = option;
|
|
1112
|
+
return typeof value2.label === "string" && value2.label.trim() !== "" ? [{
|
|
1113
|
+
label: value2.label,
|
|
1114
|
+
...typeof value2.description === "string" ? { description: value2.description } : {}
|
|
1115
|
+
}] : [];
|
|
1116
|
+
}) : void 0;
|
|
1117
|
+
out.push({
|
|
1118
|
+
id: typeof record2.id === "string" && record2.id.trim() !== "" ? record2.id : `question-${out.length + 1}`,
|
|
1119
|
+
question: record2.question,
|
|
1120
|
+
...typeof record2.header === "string" ? { header: record2.header } : {},
|
|
1121
|
+
...typeof record2.detail === "string" ? { detail: record2.detail } : {},
|
|
1122
|
+
...typeof record2.multiSelect === "boolean" ? { multiSelect: record2.multiSelect } : {},
|
|
1123
|
+
...options === void 0 || options.length === 0 ? {} : { options }
|
|
1124
|
+
});
|
|
1125
|
+
}
|
|
1126
|
+
return out;
|
|
1127
|
+
}
|