@loicngr/kobo 1.7.7 → 1.7.8
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/AGENTS.md +29 -0
- package/README.md +62 -4
- package/dist/mcp-server/kobo-tasks-server.js +27 -0
- package/dist/server/routes/health.js +14 -0
- package/dist/server/routes/workspaces.js +33 -9
- package/dist/server/services/agent/engines/claude-code/capabilities.js +7 -0
- package/dist/server/services/agent/engines/codex/capabilities.js +18 -0
- package/dist/server/services/agent/engines/codex/client.js +36 -0
- package/dist/server/services/agent/engines/codex/engine.js +276 -0
- package/dist/server/services/agent/engines/codex/event-mapper.js +473 -0
- package/dist/server/services/agent/engines/codex/jsonrpc/peer.js +60 -0
- package/dist/server/services/agent/engines/codex/jsonrpc/transport.js +31 -0
- package/dist/server/services/agent/engines/codex/options-builder.js +81 -0
- package/dist/server/services/agent/engines/codex/protocol/types.js +11 -0
- package/dist/server/services/agent/engines/codex/server-requests.js +99 -0
- package/dist/server/services/agent/engines/codex/spawn.js +27 -0
- package/dist/server/services/agent/engines/registry.js +2 -0
- package/dist/server/services/agent/orchestrator.js +1 -1
- package/dist/server/services/settings-service.js +68 -6
- package/dist/server/utils/paths.js +7 -0
- package/dist/shared/codex-models.js +43 -0
- package/package.json +2 -1
- package/src/client/dist/spa/assets/ActivityFeed-CPZdjJpH.js +8 -0
- package/src/client/dist/spa/assets/{ActivityFeed-tE4LVYck.css → ActivityFeed-WjiQ9716.css} +1 -1
- package/src/client/dist/spa/assets/{ClosePopup-DTcbxsC0.js → ClosePopup-C5JlH6Hy.js} +1 -1
- package/src/client/dist/spa/assets/CreatePage-CdfbFlXf.js +2 -0
- package/src/client/dist/spa/assets/CreatePage-ZyBHUbl0.css +1 -0
- package/src/client/dist/spa/assets/{DiffViewer-D-uNbBq0.js → DiffViewer-DkiP6nWz.js} +3 -3
- package/src/client/dist/spa/assets/HealthPage-BHGZJTgS.js +1 -0
- package/src/client/dist/spa/assets/{MainLayout-DdkKM2ba.js → MainLayout-C0tClQZl.js} +17 -17
- package/src/client/dist/spa/assets/{MainLayout-drolsINz.css → MainLayout-DKnTGN_Q.css} +1 -1
- package/src/client/dist/spa/assets/{QExpansionItem-BGg74no1.js → QExpansionItem-CW6sPoP9.js} +1 -1
- package/src/client/dist/spa/assets/{QMenu-D6uqosRg.js → QMenu-CaVfoMu6.js} +1 -1
- package/src/client/dist/spa/assets/{QTabPanels-ClPY9y4T.js → QTabPanels-E66qDYmr.js} +1 -1
- package/src/client/dist/spa/assets/{QTooltip-DUGPNNeQ.js → QTooltip-DYey0zHV.js} +1 -1
- package/src/client/dist/spa/assets/{SearchPage-C07dgzT9.js → SearchPage-BaI3iU58.js} +1 -1
- package/src/client/dist/spa/assets/SettingsPage-BqBOQKeM.js +9 -0
- package/src/client/dist/spa/assets/SettingsPage-Zeu2cZqi.css +1 -0
- package/src/client/dist/spa/assets/{TouchPan-DvVlszwO.js → TouchPan-DQILDzd3.js} +1 -1
- package/src/client/dist/spa/assets/{WorkspacePage-CRIcsASQ.css → WorkspacePage-C9eT5LAo.css} +1 -1
- package/src/client/dist/spa/assets/WorkspacePage-DqMyUSFG.js +4 -0
- package/src/client/dist/spa/assets/{build-path-tree-CCMckvpr.js → build-path-tree-BpcCBm9A.js} +1 -1
- package/src/client/dist/spa/assets/{cssMode-D6XTTdwy.js → cssMode-BaeNVqUm.js} +1 -1
- package/src/client/dist/spa/assets/{editor.api-6hDVHddO.js → editor.api-DMLl_PBy.js} +1 -1
- package/src/client/dist/spa/assets/{editor.main-DsLU1RWu.js → editor.main-D2pRsQAX.js} +3 -3
- package/src/client/dist/spa/assets/{AutoLoopChip-CkSzkC0C.js → engineFeatures-RffgP255.js} +1 -1
- package/src/client/dist/spa/assets/{expand-template-Crz1uiBt.js → expand-template-z2wIJOD2.js} +1 -1
- package/src/client/dist/spa/assets/{freemarker2-Bn1f0t2U.js → freemarker2-Bh6ItnVy.js} +1 -1
- package/src/client/dist/spa/assets/{handlebars-O92Cbq66.js → handlebars-D8OXeysi.js} +1 -1
- package/src/client/dist/spa/assets/{html-Ck95BMBU.js → html-9Y1AHhvw.js} +1 -1
- package/src/client/dist/spa/assets/{htmlMode-DDYhH2FJ.js → htmlMode-z00se0fQ.js} +1 -1
- package/src/client/dist/spa/assets/i18n-C-VMW7h5.js +1 -0
- package/src/client/dist/spa/assets/index-BLlWqEZC.js +2 -0
- package/src/client/dist/spa/assets/{javascript-Cy2ddqHg.js → javascript-D0LSb7WU.js} +1 -1
- package/src/client/dist/spa/assets/{jsonMode-BIfVcp5z.js → jsonMode-BSmyaoX3.js} +1 -1
- package/src/client/dist/spa/assets/{liquid-B287eegh.js → liquid-BsY5UXNl.js} +1 -1
- package/src/client/dist/spa/assets/{mdx-B8HSzGai.js → mdx-BUcXih4e.js} +1 -1
- package/src/client/dist/spa/assets/{monaco.contribution-CofcHzEf.js → monaco.contribution-DrpufOT3.js} +2 -2
- package/src/client/dist/spa/assets/{notifications-BPnKFW60.js → notifications-C255ApfS.js} +1 -1
- package/src/client/dist/spa/assets/permissionModes-BocOmzU8.js +1 -0
- package/src/client/dist/spa/assets/{purify.es-BCEwTYRx.js → purify.es-aV6SU8N4.js} +1 -1
- package/src/client/dist/spa/assets/{python-csaKR6_U.js → python-C0PoB7M8.js} +1 -1
- package/src/client/dist/spa/assets/{razor-C2wEv-nX.js → razor-Bu0-fwxD.js} +1 -1
- package/src/client/dist/spa/assets/{render-chat-markdown-Bjcei0vn.js → render-chat-markdown-DALCdDVE.js} +1 -1
- package/src/client/dist/spa/assets/{tsMode-DGLVs57K.js → tsMode-Blc1d2dp.js} +1 -1
- package/src/client/dist/spa/assets/{typescript-w0GWHzZ3.js → typescript-CV4ME9fo.js} +1 -1
- package/src/client/dist/spa/assets/{use-panel-CbJ44rqY.js → use-panel-DCPiSURS.js} +1 -1
- package/src/client/dist/spa/assets/{xml-CTn-vnEd.js → xml-DLYRBBbI.js} +1 -1
- package/src/client/dist/spa/assets/{yaml-CTyUSvLZ.js → yaml-QIBjI5Dl.js} +1 -1
- package/src/client/dist/spa/index.html +2 -2
- package/src/mcp-server/kobo-tasks-server.ts +27 -0
- package/src/client/dist/spa/assets/ActivityFeed-DlPVoOGb.js +0 -7
- package/src/client/dist/spa/assets/CreatePage-BoRappO3.css +0 -1
- package/src/client/dist/spa/assets/CreatePage-DpCVNwYk.js +0 -2
- package/src/client/dist/spa/assets/HealthPage-xZ0PP4F-.js +0 -1
- package/src/client/dist/spa/assets/SettingsPage-CLNmI0Rr.css +0 -1
- package/src/client/dist/spa/assets/SettingsPage-D0CZNqkA.js +0 -9
- package/src/client/dist/spa/assets/WorkspacePage-CKeCLPi0.js +0 -4
- package/src/client/dist/spa/assets/i18n-BLgknHpf.js +0 -1
- package/src/client/dist/spa/assets/index-CdHDdk1y.js +0 -2
- package/src/client/dist/spa/assets/models-Bd_v3W7Q.js +0 -1
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { getPackageVersion } from '../../../../utils/paths.js';
|
|
2
|
+
import { CODEX_CAPABILITIES } from './capabilities.js';
|
|
3
|
+
import { createAppServerClient } from './client.js';
|
|
4
|
+
import { createMapperState, emitSessionStarted, handleAgentMessageDelta, handleItemCompleted, handleItemStarted, handleRateLimitsUpdated, handleTurnCompleted, QUOTA_PATTERN, tryEmitQuota, } from './event-mapper.js';
|
|
5
|
+
import { buildCodexOptions } from './options-builder.js';
|
|
6
|
+
import { buildResponseForResolve, handleServerRequest } from './server-requests.js';
|
|
7
|
+
import { spawnAppServer } from './spawn.js';
|
|
8
|
+
/**
|
|
9
|
+
* Heuristic for detecting a stale/expired thread id on `thread/resume`.
|
|
10
|
+
* Canonical wording isn't captured yet — when matched, the engine emits
|
|
11
|
+
* `error/resume_failed` so the orchestrator can restart with a fresh thread.
|
|
12
|
+
*/
|
|
13
|
+
export const RESUME_FAILED_PATTERN = /(thread\b.*\bnot found|session\b.*\bnot found|no\s+(such\s+)?thread|thread.*expired|conversation\b.*\bnot found|invalid\s+thread\s+id)/i;
|
|
14
|
+
export function createCodexEngine() {
|
|
15
|
+
return {
|
|
16
|
+
id: 'codex',
|
|
17
|
+
displayName: 'OpenAI Codex',
|
|
18
|
+
capabilities: CODEX_CAPABILITIES,
|
|
19
|
+
async start(options, onEvent) {
|
|
20
|
+
const { threadParams, input, isResume, collaborationMode } = buildCodexOptions({
|
|
21
|
+
prompt: options.prompt,
|
|
22
|
+
model: options.model,
|
|
23
|
+
effort: options.effort,
|
|
24
|
+
agentPermissionMode: options.agentPermissionMode ?? 'bypass',
|
|
25
|
+
resumeFromEngineSessionId: options.resumeFromEngineSessionId,
|
|
26
|
+
workingDir: options.workingDir,
|
|
27
|
+
mcpServers: options.mcpServers,
|
|
28
|
+
});
|
|
29
|
+
const mapperState = createMapperState();
|
|
30
|
+
const abortController = new AbortController();
|
|
31
|
+
const pendingByCallId = new Map();
|
|
32
|
+
let iteratorRunning = false;
|
|
33
|
+
let userInterrupted = false;
|
|
34
|
+
let discoveredSessionId = options.resumeFromEngineSessionId;
|
|
35
|
+
const safeEmit = (ev) => {
|
|
36
|
+
try {
|
|
37
|
+
onEvent(ev);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
console.error('[codex-engine] onEvent handler threw:', err);
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
const child = spawnAppServer({ cwd: options.workingDir, signal: abortController.signal });
|
|
44
|
+
if (child.stderr) {
|
|
45
|
+
child.stderr.setEncoding('utf8');
|
|
46
|
+
child.stderr.on('data', (chunk) => {
|
|
47
|
+
const text = chunk.toString();
|
|
48
|
+
if (QUOTA_PATTERN.test(text)) {
|
|
49
|
+
tryEmitQuota(mapperState, safeEmit, text.trim());
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.warn('[codex] stderr:', text.trimEnd());
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
let resolveTurnDone;
|
|
57
|
+
let rejectTurnDone;
|
|
58
|
+
const turnDonePromise = new Promise((resolve, reject) => {
|
|
59
|
+
resolveTurnDone = resolve;
|
|
60
|
+
rejectTurnDone = reject;
|
|
61
|
+
});
|
|
62
|
+
abortController.signal.addEventListener('abort', () => {
|
|
63
|
+
const err = new Error('AbortError');
|
|
64
|
+
err.name = 'AbortError';
|
|
65
|
+
rejectTurnDone(err);
|
|
66
|
+
});
|
|
67
|
+
const client = createAppServerClient({
|
|
68
|
+
stdin: child.stdin,
|
|
69
|
+
stdout: child.stdout,
|
|
70
|
+
clientInfo: { name: 'kobo', version: getPackageVersion() },
|
|
71
|
+
onNotification(method, params) {
|
|
72
|
+
// Ignored notifications — harmless bookkeeping by the server
|
|
73
|
+
if (method === 'mcpServer/startupStatus/updated' ||
|
|
74
|
+
method === 'thread/started' ||
|
|
75
|
+
method === 'thread/status/changed' ||
|
|
76
|
+
method === 'remoteControl/status/changed' ||
|
|
77
|
+
method === 'turn/started') {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
if (method === 'item/started') {
|
|
81
|
+
const n = params;
|
|
82
|
+
for (const ev of handleItemStarted(n.item, mapperState))
|
|
83
|
+
safeEmit(ev);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (method === 'item/completed') {
|
|
87
|
+
const n = params;
|
|
88
|
+
for (const ev of handleItemCompleted(n.item, mapperState))
|
|
89
|
+
safeEmit(ev);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (method === 'item/agentMessage/delta') {
|
|
93
|
+
const n = params;
|
|
94
|
+
for (const ev of handleAgentMessageDelta(n, mapperState))
|
|
95
|
+
safeEmit(ev);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
if (method === 'turn/completed') {
|
|
99
|
+
const n = params;
|
|
100
|
+
for (const ev of handleTurnCompleted(n, mapperState))
|
|
101
|
+
safeEmit(ev);
|
|
102
|
+
resolveTurnDone();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (method === 'thread/tokenUsage/updated') {
|
|
106
|
+
const p = params;
|
|
107
|
+
if (p?.tokenUsage?.last) {
|
|
108
|
+
const last = p.tokenUsage.last;
|
|
109
|
+
safeEmit({
|
|
110
|
+
kind: 'usage',
|
|
111
|
+
inputTokens: last.inputTokens,
|
|
112
|
+
outputTokens: last.outputTokens + last.reasoningOutputTokens,
|
|
113
|
+
cacheRead: last.cachedInputTokens,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (method === 'account/rateLimits/updated') {
|
|
119
|
+
for (const ev of handleRateLimitsUpdated(params, mapperState))
|
|
120
|
+
safeEmit(ev);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (method === 'error') {
|
|
124
|
+
const n = params;
|
|
125
|
+
const msg = n?.message ?? 'unknown error';
|
|
126
|
+
if (QUOTA_PATTERN.test(msg)) {
|
|
127
|
+
tryEmitQuota(mapperState, safeEmit, msg);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
mapperState.sawErrorResult = true;
|
|
131
|
+
safeEmit({ kind: 'error', category: 'other', message: msg });
|
|
132
|
+
}
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
onServerRequest(id, method, params) {
|
|
137
|
+
handleServerRequest({
|
|
138
|
+
requestId: id,
|
|
139
|
+
method,
|
|
140
|
+
params,
|
|
141
|
+
emit: safeEmit,
|
|
142
|
+
register(callId, pending) {
|
|
143
|
+
pendingByCallId.set(callId, pending);
|
|
144
|
+
},
|
|
145
|
+
respondError: (reqId, code, message) => client.peer.respondError(reqId, code, message),
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
onError(err) {
|
|
149
|
+
console.error('[codex] JSON-RPC transport error:', err);
|
|
150
|
+
rejectTurnDone(err);
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
const iteratorPromise = (async () => {
|
|
154
|
+
iteratorRunning = true;
|
|
155
|
+
try {
|
|
156
|
+
await client.connect();
|
|
157
|
+
if (isResume && options.resumeFromEngineSessionId) {
|
|
158
|
+
await client.resumeThread({
|
|
159
|
+
threadId: options.resumeFromEngineSessionId,
|
|
160
|
+
cwd: options.workingDir,
|
|
161
|
+
persistExtendedHistory: false,
|
|
162
|
+
...(threadParams.model != null ? { model: threadParams.model } : {}),
|
|
163
|
+
...(threadParams.approvalPolicy != null ? { approvalPolicy: threadParams.approvalPolicy } : {}),
|
|
164
|
+
...(threadParams.sandbox != null ? { sandbox: threadParams.sandbox } : {}),
|
|
165
|
+
...(threadParams.modelReasoningEffort != null
|
|
166
|
+
? { modelReasoningEffort: threadParams.modelReasoningEffort }
|
|
167
|
+
: {}),
|
|
168
|
+
...(threadParams.config != null ? { config: threadParams.config } : {}),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
const startResp = await client.startThread(threadParams);
|
|
173
|
+
discoveredSessionId = startResp.thread.id;
|
|
174
|
+
}
|
|
175
|
+
for (const ev of emitSessionStarted(discoveredSessionId, mapperState))
|
|
176
|
+
safeEmit(ev);
|
|
177
|
+
// collaborationMode is sticky server-side — always send it explicitly,
|
|
178
|
+
// never omit (would leave a Bypass turn stuck in a previous Plan mode).
|
|
179
|
+
await client.startTurn({
|
|
180
|
+
threadId: discoveredSessionId,
|
|
181
|
+
input,
|
|
182
|
+
collaborationMode,
|
|
183
|
+
});
|
|
184
|
+
await turnDonePromise;
|
|
185
|
+
const reason = mapperState.sawErrorResult
|
|
186
|
+
? 'error'
|
|
187
|
+
: mapperState.sawTurnInterrupted
|
|
188
|
+
? 'killed'
|
|
189
|
+
: 'completed';
|
|
190
|
+
safeEmit({
|
|
191
|
+
kind: 'session:ended',
|
|
192
|
+
reason,
|
|
193
|
+
exitCode: reason === 'completed' ? 0 : null,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catch (err) {
|
|
197
|
+
const error = err;
|
|
198
|
+
const message = error.message ?? String(err);
|
|
199
|
+
const isAbort = userInterrupted || error.name === 'AbortError' || abortController.signal.aborted;
|
|
200
|
+
const isResumeAttempt = options.resumeFromEngineSessionId !== undefined;
|
|
201
|
+
if (isAbort) {
|
|
202
|
+
safeEmit({ kind: 'session:ended', reason: 'killed', exitCode: null });
|
|
203
|
+
}
|
|
204
|
+
else if (QUOTA_PATTERN.test(message)) {
|
|
205
|
+
tryEmitQuota(mapperState, safeEmit, message);
|
|
206
|
+
safeEmit({ kind: 'session:ended', reason: 'error', exitCode: null });
|
|
207
|
+
}
|
|
208
|
+
else if (isResumeAttempt && RESUME_FAILED_PATTERN.test(message)) {
|
|
209
|
+
safeEmit({ kind: 'error', category: 'resume_failed', message });
|
|
210
|
+
safeEmit({ kind: 'session:ended', reason: 'error', exitCode: null });
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
safeEmit({ kind: 'error', category: 'spawn_failed', message });
|
|
214
|
+
safeEmit({ kind: 'session:ended', reason: 'error', exitCode: null });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
iteratorRunning = false;
|
|
219
|
+
client.close();
|
|
220
|
+
try {
|
|
221
|
+
child.kill('SIGTERM');
|
|
222
|
+
}
|
|
223
|
+
catch {
|
|
224
|
+
// best-effort
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
})();
|
|
228
|
+
const engineProcess = {
|
|
229
|
+
get pid() {
|
|
230
|
+
return child.pid;
|
|
231
|
+
},
|
|
232
|
+
get engineSessionId() {
|
|
233
|
+
return discoveredSessionId;
|
|
234
|
+
},
|
|
235
|
+
isAlive() {
|
|
236
|
+
return iteratorRunning;
|
|
237
|
+
},
|
|
238
|
+
sendMessage() {
|
|
239
|
+
throw new Error('sendMessage not supported in Codex app-server single-shot mode');
|
|
240
|
+
},
|
|
241
|
+
interrupt() {
|
|
242
|
+
userInterrupted = true;
|
|
243
|
+
abortController.abort();
|
|
244
|
+
if (discoveredSessionId) {
|
|
245
|
+
client.interruptTurn({ threadId: discoveredSessionId }).catch(() => { });
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
async stop() {
|
|
249
|
+
abortController.abort();
|
|
250
|
+
try {
|
|
251
|
+
await iteratorPromise;
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
// swallow — best effort
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
child.stdin?.end();
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
// swallow
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
resolvePendingUserInput(callId, response) {
|
|
264
|
+
const pending = pendingByCallId.get(callId);
|
|
265
|
+
if (!pending)
|
|
266
|
+
return false;
|
|
267
|
+
pendingByCallId.delete(callId);
|
|
268
|
+
const result = buildResponseForResolve(pending, response);
|
|
269
|
+
client.peer.respond(pending.requestId, result);
|
|
270
|
+
return true;
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
return engineProcess;
|
|
274
|
+
},
|
|
275
|
+
};
|
|
276
|
+
}
|