@loicngr/kobo 1.7.6 → 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 +146 -4
- package/dist/mcp-server/kobo-tasks-server.js +27 -0
- package/dist/server/index.js +2 -0
- package/dist/server/routes/health.js +14 -0
- package/dist/server/routes/voice.js +149 -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 +125 -6
- package/dist/server/services/transcription-service.js +206 -0
- package/dist/server/utils/paths.js +7 -0
- package/dist/shared/codex-models.js +43 -0
- package/package.json +13 -10
- 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-D_UAdwkA.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-CblFgn8w.js → DiffViewer-DkiP6nWz.js} +3 -3
- package/src/client/dist/spa/assets/HealthPage-BHGZJTgS.js +1 -0
- package/src/client/dist/spa/assets/{MainLayout-DhaYycak.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/{QBadge-DWH42dbo.js → QBadge-C7r6oPSi.js} +1 -1
- package/src/client/dist/spa/assets/{QBtn-a6jxWjmW.js → QBtn-DEuWKHbR.js} +1 -1
- package/src/client/dist/spa/assets/{QCheckbox-D5jfsxLV.js → QCheckbox-BvHfXBFY.js} +1 -1
- package/src/client/dist/spa/assets/{QChip-ByxK0Tuf.js → QChip-erWIZgxW.js} +1 -1
- package/src/client/dist/spa/assets/{QExpansionItem-CH1ipL9n.js → QExpansionItem-CW6sPoP9.js} +1 -1
- package/src/client/dist/spa/assets/QIcon-qfJNZLIW.js +1 -0
- package/src/client/dist/spa/assets/{QInput-Cm5-AGQ4.js → QInput-DCJEwE8V.js} +1 -1
- package/src/client/dist/spa/assets/{QItemLabel-DrTxqTqV.js → QItemLabel-CHkgkZVj.js} +1 -1
- package/src/client/dist/spa/assets/{QItemSection-5YpFpPDm.js → QItemSection-CQUDd0Vg.js} +1 -1
- package/src/client/dist/spa/assets/{QList-D0FtnQJI.js → QList-BbnN_oNX.js} +1 -1
- package/src/client/dist/spa/assets/{QMenu-B4xMxMGd.js → QMenu-CaVfoMu6.js} +1 -1
- package/src/client/dist/spa/assets/{QPage-DFi3K093.js → QPage-Co2h9wd_.js} +1 -1
- package/src/client/dist/spa/assets/{QRadio-B3aKjCVu.js → QRadio-DJxOyOA3.js} +1 -1
- package/src/client/dist/spa/assets/QSpace-DKIph84L.js +1 -0
- package/src/client/dist/spa/assets/{QSpinnerDots-CszPQQ9J.js → QSpinnerDots-Bfl2RMy4.js} +1 -1
- package/src/client/dist/spa/assets/{QTabPanels-D2ks0UIA.js → QTabPanels-E66qDYmr.js} +1 -1
- package/src/client/dist/spa/assets/{QToggle-1-N9qWq4.js → QToggle-DNOTC_3a.js} +1 -1
- package/src/client/dist/spa/assets/{QTooltip-fDNzBEfN.js → QTooltip-DYey0zHV.js} +1 -1
- package/src/client/dist/spa/assets/{SearchPage-cZTwP4Lf.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-DoE24Io3.js → TouchPan-DQILDzd3.js} +1 -1
- package/src/client/dist/spa/assets/WorkspacePage-C9eT5LAo.css +1 -0
- package/src/client/dist/spa/assets/WorkspacePage-DqMyUSFG.js +4 -0
- package/src/client/dist/spa/assets/{build-path-tree-B1Lvvqto.js → build-path-tree-BpcCBm9A.js} +1 -1
- package/src/client/dist/spa/assets/{cssMode-BFLYiiEw.js → cssMode-BaeNVqUm.js} +1 -1
- package/src/client/dist/spa/assets/{documents-kx0vLfSG.js → documents-soWtna0O.js} +1 -1
- package/src/client/dist/spa/assets/{editor.api-2asmmhth.js → editor.api-DMLl_PBy.js} +1 -1
- package/src/client/dist/spa/assets/{editor.main-ChCYZyez.js → editor.main-D2pRsQAX.js} +3 -3
- package/src/client/dist/spa/assets/{AutoLoopChip-w8D77bI5.js → engineFeatures-RffgP255.js} +1 -1
- package/src/client/dist/spa/assets/{expand-template-CXQFkQOJ.js → expand-template-z2wIJOD2.js} +1 -1
- package/src/client/dist/spa/assets/{formatters-DCAQ6ANJ.js → formatters-guwb-rzl.js} +1 -1
- package/src/client/dist/spa/assets/{freemarker2-BaBL9E9G.js → freemarker2-Bh6ItnVy.js} +1 -1
- package/src/client/dist/spa/assets/{handlebars-BxDour4L.js → handlebars-D8OXeysi.js} +1 -1
- package/src/client/dist/spa/assets/{html-C6hnkfIL.js → html-9Y1AHhvw.js} +1 -1
- package/src/client/dist/spa/assets/{htmlMode-9zT3-dmz.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-C3YjvKbE.js → javascript-D0LSb7WU.js} +1 -1
- package/src/client/dist/spa/assets/{jsonMode-DcJDgMzf.js → jsonMode-BSmyaoX3.js} +1 -1
- package/src/client/dist/spa/assets/{liquid-CsT8SjJM.js → liquid-BsY5UXNl.js} +1 -1
- package/src/client/dist/spa/assets/{mdx-CT3yVSyc.js → mdx-BUcXih4e.js} +1 -1
- package/src/client/dist/spa/assets/{monaco.contribution-DKGNz1oQ.js → monaco.contribution-DrpufOT3.js} +2 -2
- package/src/client/dist/spa/assets/{notifications-OnPq4FrH.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-CPieV82n.js → purify.es-aV6SU8N4.js} +1 -1
- package/src/client/dist/spa/assets/{python-Ca5miKgj.js → python-C0PoB7M8.js} +1 -1
- package/src/client/dist/spa/assets/{razor-7qzusGRc.js → razor-Bu0-fwxD.js} +1 -1
- package/src/client/dist/spa/assets/{render-chat-markdown-Bqq2G-yI.js → render-chat-markdown-DALCdDVE.js} +1 -1
- package/src/client/dist/spa/assets/runtime-core.esm-bundler-9Z0QAO_7.js +1 -0
- package/src/client/dist/spa/assets/{tsMode-BdvO8jZ2.js → tsMode-Blc1d2dp.js} +1 -1
- package/src/client/dist/spa/assets/{typescript-BfVNzhgs.js → typescript-CV4ME9fo.js} +1 -1
- package/src/client/dist/spa/assets/{use-checkbox-D7zmRxGI.js → use-checkbox-y_fOkYZN.js} +1 -1
- package/src/client/dist/spa/assets/{use-id-CuaR1RiE.js → use-id-_7wiRcgb.js} +1 -1
- package/src/client/dist/spa/assets/{use-panel-D-8nAQns.js → use-panel-DCPiSURS.js} +1 -1
- package/src/client/dist/spa/assets/use-quasar-DQYS47mh.js +1 -0
- package/src/client/dist/spa/assets/{vue-i18n-BcfTCFFS.js → vue-i18n-DI-gS-CC.js} +1 -1
- package/src/client/dist/spa/assets/{xml-DGNXGqXL.js → xml-DLYRBBbI.js} +1 -1
- package/src/client/dist/spa/assets/{yaml-CtAtOyt5.js → yaml-QIBjI5Dl.js} +1 -1
- package/src/client/dist/spa/index.html +12 -12
- package/src/mcp-server/kobo-tasks-server.ts +27 -0
- package/src/client/dist/spa/assets/ActivityFeed-BboSPm4b.js +0 -7
- package/src/client/dist/spa/assets/CreatePage-BDObLDJc.js +0 -2
- package/src/client/dist/spa/assets/CreatePage-DssmsAsV.css +0 -1
- package/src/client/dist/spa/assets/HealthPage-CBSw7e5q.js +0 -1
- package/src/client/dist/spa/assets/QIcon-BJuyqdsT.js +0 -1
- package/src/client/dist/spa/assets/QSpace-CLtL3aPy.js +0 -1
- package/src/client/dist/spa/assets/SettingsPage-C1efO0VM.js +0 -1
- package/src/client/dist/spa/assets/SettingsPage-CMyeQ9_u.css +0 -1
- package/src/client/dist/spa/assets/WorkspacePage-3jcof896.js +0 -4
- package/src/client/dist/spa/assets/WorkspacePage-CCtIrBiR.css +0 -1
- package/src/client/dist/spa/assets/i18n-CLY0XI9-.js +0 -1
- package/src/client/dist/spa/assets/index-D6wj_wQ9.js +0 -2
- package/src/client/dist/spa/assets/models-BsjWUKqM.js +0 -1
- package/src/client/dist/spa/assets/runtime-core.esm-bundler-C3IgBgY5.js +0 -1
- package/src/client/dist/spa/assets/use-quasar-Sdcq6zzV.js +0 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export function handleServerRequest(args) {
|
|
2
|
+
const { method, params, requestId, emit, register, respondError } = args;
|
|
3
|
+
const p = (params ?? {});
|
|
4
|
+
const callId = typeof p.callId === 'string' ? p.callId : `srv_${requestId}`;
|
|
5
|
+
if (method === 'mcpServer/elicitation/request') {
|
|
6
|
+
// Codex asks an external MCP server's elicitation prompt to be surfaced to
|
|
7
|
+
// the user. Kōbō doesn't model MCP elicitations yet — respond with a
|
|
8
|
+
// JSON-RPC "method not supported" error so the server doesn't block.
|
|
9
|
+
respondError?.(requestId, -32601, 'MCP elicitations not supported by this client');
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
// v2 and v1 method aliases for the same approval semantics. v1 legacy names
|
|
13
|
+
// (`execCommandApproval`, `applyPatchApproval`) are kept for compat with
|
|
14
|
+
// older Codex CLI builds that haven't transitioned to the v2 namespace.
|
|
15
|
+
if (method === 'item/commandExecution/requestApproval' || method === 'execCommandApproval') {
|
|
16
|
+
register(callId, { requestId, kind: 'command', payload: p });
|
|
17
|
+
emit({
|
|
18
|
+
kind: 'session:user-input-requested',
|
|
19
|
+
requestKind: 'permission',
|
|
20
|
+
toolCallId: callId,
|
|
21
|
+
toolName: 'Bash',
|
|
22
|
+
payload: { command: p.command, cwd: p.cwd, reason: p.reason },
|
|
23
|
+
});
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
if (method === 'item/fileChange/requestApproval' || method === 'applyPatchApproval') {
|
|
27
|
+
register(callId, { requestId, kind: 'file_change', payload: p });
|
|
28
|
+
emit({
|
|
29
|
+
kind: 'session:user-input-requested',
|
|
30
|
+
requestKind: 'permission',
|
|
31
|
+
toolCallId: callId,
|
|
32
|
+
toolName: 'Edit',
|
|
33
|
+
payload: { changes: p.changes, reason: p.reason },
|
|
34
|
+
});
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
if (method === 'item/tool/requestUserInput') {
|
|
38
|
+
register(callId, { requestId, kind: 'user_input', payload: p });
|
|
39
|
+
emit({
|
|
40
|
+
kind: 'session:user-input-requested',
|
|
41
|
+
requestKind: 'question',
|
|
42
|
+
toolCallId: callId,
|
|
43
|
+
toolName: 'AskUserQuestion',
|
|
44
|
+
payload: { questions: p.questions },
|
|
45
|
+
});
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (method === 'item/permissions/requestApproval') {
|
|
49
|
+
register(callId, { requestId, kind: 'permissions', payload: p });
|
|
50
|
+
emit({
|
|
51
|
+
kind: 'session:user-input-requested',
|
|
52
|
+
requestKind: 'permission',
|
|
53
|
+
toolCallId: callId,
|
|
54
|
+
toolName: 'Permissions',
|
|
55
|
+
payload: p,
|
|
56
|
+
});
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
return false; // unknown method
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Build the JSON-RPC response Codex expects for a given pending request.
|
|
63
|
+
*
|
|
64
|
+
* Decision enum values come from
|
|
65
|
+
* `codex-rs/protocol/src/approvals.rs:CommandExecutionApprovalDecision`
|
|
66
|
+
* (and the matching `FileChangeApprovalDecision`): `'accept' | 'acceptForSession' | 'decline' | 'cancel'`.
|
|
67
|
+
* NOT `'approve' / 'reject'` — those would be silently rejected as unknown
|
|
68
|
+
* variants, which breaks the strict and interactive permission modes.
|
|
69
|
+
*
|
|
70
|
+
* `PermissionsRequestApprovalResponse` has a completely different shape:
|
|
71
|
+
* `{ permissions, scope, strictAutoReview? }` — no `decision` field. Since
|
|
72
|
+
* Kōbō doesn't yet model permission grants, we deny the request by sending
|
|
73
|
+
* an empty permissions response. A future iteration could add a UI for
|
|
74
|
+
* granular permission grants.
|
|
75
|
+
*/
|
|
76
|
+
export function buildResponseForResolve(pending, response) {
|
|
77
|
+
if (pending.kind === 'command' || pending.kind === 'file_change') {
|
|
78
|
+
if (response.kind === 'permission-allow')
|
|
79
|
+
return { decision: 'accept' };
|
|
80
|
+
return { decision: 'decline' };
|
|
81
|
+
}
|
|
82
|
+
if (pending.kind === 'permissions') {
|
|
83
|
+
// Codex's PermissionsRequestApprovalResponse shape — not { decision }.
|
|
84
|
+
// We don't yet model granular permission grants, so deny by returning an
|
|
85
|
+
// empty permissions object; Codex falls back to the existing turn policy.
|
|
86
|
+
return { permissions: {}, scope: 'turn' };
|
|
87
|
+
}
|
|
88
|
+
if (pending.kind === 'user_input') {
|
|
89
|
+
if (response.kind === 'question') {
|
|
90
|
+
const answers = {};
|
|
91
|
+
for (const [qid, val] of Object.entries(response.answers)) {
|
|
92
|
+
answers[qid] = { answers: [val] };
|
|
93
|
+
}
|
|
94
|
+
return { answers };
|
|
95
|
+
}
|
|
96
|
+
return { answers: {} };
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
const requireFn = createRequire(import.meta.url);
|
|
4
|
+
export function resolveCodexBinary() {
|
|
5
|
+
try {
|
|
6
|
+
const pkgPath = requireFn.resolve('@openai/codex/package.json');
|
|
7
|
+
const pkg = requireFn(pkgPath);
|
|
8
|
+
const binRel = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.codex;
|
|
9
|
+
if (binRel) {
|
|
10
|
+
const url = new URL(binRel, `file://${pkgPath}`);
|
|
11
|
+
return url.pathname;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
// fall through to default
|
|
16
|
+
}
|
|
17
|
+
return 'codex';
|
|
18
|
+
}
|
|
19
|
+
export function spawnAppServer(opts) {
|
|
20
|
+
const bin = resolveCodexBinary();
|
|
21
|
+
return nodeSpawn(bin, ['app-server'], {
|
|
22
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
23
|
+
cwd: opts.cwd,
|
|
24
|
+
env: opts.env,
|
|
25
|
+
signal: opts.signal,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createClaudeCodeEngine } from './claude-code/engine.js';
|
|
2
|
+
import { createCodexEngine } from './codex/engine.js';
|
|
2
3
|
const ENGINES = {
|
|
3
4
|
'claude-code': createClaudeCodeEngine(),
|
|
5
|
+
codex: createCodexEngine(),
|
|
4
6
|
};
|
|
5
7
|
export function listEngines() {
|
|
6
8
|
return Object.values(ENGINES);
|
|
@@ -298,7 +298,7 @@ function readEffectiveSettingsSafe(projectPath) {
|
|
|
298
298
|
catch (err) {
|
|
299
299
|
console.warn('[orchestrator] Failed to load settings, using defaults:', err);
|
|
300
300
|
return {
|
|
301
|
-
model: '
|
|
301
|
+
model: 'auto',
|
|
302
302
|
dangerouslySkipPermissions: true,
|
|
303
303
|
prPromptTemplate: '',
|
|
304
304
|
reviewPromptTemplate: '',
|
|
@@ -266,6 +266,94 @@ const settingsMigrations = [
|
|
|
266
266
|
}
|
|
267
267
|
},
|
|
268
268
|
},
|
|
269
|
+
{
|
|
270
|
+
version: 17,
|
|
271
|
+
name: 'add-voice-transcription-settings',
|
|
272
|
+
migrate({ global }) {
|
|
273
|
+
if (typeof global.voiceEnabled !== 'boolean')
|
|
274
|
+
global.voiceEnabled = false;
|
|
275
|
+
if (global.voicePttKey !== 'alt' && global.voicePttKey !== 'ctrl+space')
|
|
276
|
+
global.voicePttKey = 'alt';
|
|
277
|
+
if (typeof global.voiceLanguage !== 'string' || global.voiceLanguage.length === 0)
|
|
278
|
+
global.voiceLanguage = 'auto';
|
|
279
|
+
if (typeof global.voiceModel !== 'string' && global.voiceModel !== null)
|
|
280
|
+
global.voiceModel = null;
|
|
281
|
+
if (typeof global.voiceCommandPath !== 'string')
|
|
282
|
+
global.voiceCommandPath = '';
|
|
283
|
+
if (typeof global.voiceFfmpegPath !== 'string')
|
|
284
|
+
global.voiceFfmpegPath = '';
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
version: 18,
|
|
289
|
+
name: 'add-voice-advanced-settings',
|
|
290
|
+
migrate({ global }) {
|
|
291
|
+
const t = Number(global.voiceTemperature);
|
|
292
|
+
if (!Number.isFinite(t) || t < 0 || t > 1)
|
|
293
|
+
global.voiceTemperature = 0;
|
|
294
|
+
if (typeof global.voicePrompt !== 'string')
|
|
295
|
+
global.voicePrompt = '';
|
|
296
|
+
if (typeof global.voiceTranslateToEnglish !== 'boolean')
|
|
297
|
+
global.voiceTranslateToEnglish = false;
|
|
298
|
+
if (typeof global.voiceSuppressNonSpeechTokens !== 'boolean')
|
|
299
|
+
global.voiceSuppressNonSpeechTokens = true;
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
version: 19,
|
|
304
|
+
name: 'split-default-model-by-engine',
|
|
305
|
+
// Codex was added alongside Claude Code; the single `defaultModel` is now
|
|
306
|
+
// ambiguous (different engines have different model catalogues). Split it
|
|
307
|
+
// into a per-engine map. Preserve the legacy value as the claude-code
|
|
308
|
+
// default for back-compat, and seed codex with `'auto'`.
|
|
309
|
+
migrate({ global }) {
|
|
310
|
+
const existing = global.defaultModelByEngine;
|
|
311
|
+
if (typeof existing !== 'object' || existing === null || Array.isArray(existing)) {
|
|
312
|
+
const legacyModel = typeof global.defaultModel === 'string' && global.defaultModel.length > 0
|
|
313
|
+
? global.defaultModel
|
|
314
|
+
: 'auto';
|
|
315
|
+
global.defaultModelByEngine = {
|
|
316
|
+
'claude-code': legacyModel,
|
|
317
|
+
codex: 'auto',
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
// Backfill any missing engine entries idempotently.
|
|
322
|
+
const map = existing;
|
|
323
|
+
if (typeof map['claude-code'] !== 'string')
|
|
324
|
+
map['claude-code'] = 'auto';
|
|
325
|
+
if (typeof map.codex !== 'string')
|
|
326
|
+
map.codex = 'auto';
|
|
327
|
+
}
|
|
328
|
+
// Drop the legacy field once migrated.
|
|
329
|
+
delete global.defaultModel;
|
|
330
|
+
},
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
version: 20,
|
|
334
|
+
name: 'split-default-permission-mode-by-engine',
|
|
335
|
+
// Mirrors v19 for permission modes. Both engines accept the full mode set.
|
|
336
|
+
migrate({ global }) {
|
|
337
|
+
const existing = global.defaultPermissionModeByEngine;
|
|
338
|
+
const legacyMode = typeof global.defaultPermissionMode === 'string' && global.defaultPermissionMode.length > 0
|
|
339
|
+
? global.defaultPermissionMode
|
|
340
|
+
: 'plan';
|
|
341
|
+
if (typeof existing !== 'object' || existing === null || Array.isArray(existing)) {
|
|
342
|
+
global.defaultPermissionModeByEngine = {
|
|
343
|
+
'claude-code': legacyMode,
|
|
344
|
+
codex: legacyMode,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
const map = existing;
|
|
349
|
+
if (typeof map['claude-code'] !== 'string')
|
|
350
|
+
map['claude-code'] = legacyMode;
|
|
351
|
+
if (typeof map.codex !== 'string')
|
|
352
|
+
map.codex = legacyMode;
|
|
353
|
+
}
|
|
354
|
+
delete global.defaultPermissionMode;
|
|
355
|
+
},
|
|
356
|
+
},
|
|
269
357
|
];
|
|
270
358
|
/** Current settings schema version — always equals the highest migration version. */
|
|
271
359
|
export const SETTINGS_SCHEMA_VERSION = settingsMigrations.length > 0 ? settingsMigrations[settingsMigrations.length - 1].version : 0;
|
|
@@ -293,7 +381,7 @@ function defaultSettings() {
|
|
|
293
381
|
return {
|
|
294
382
|
schemaVersion: SETTINGS_SCHEMA_VERSION,
|
|
295
383
|
global: {
|
|
296
|
-
|
|
384
|
+
defaultModelByEngine: { 'claude-code': 'auto', codex: 'auto' },
|
|
297
385
|
dangerouslySkipPermissions: true,
|
|
298
386
|
prPromptTemplate: DEFAULT_PR_PROMPT_TEMPLATE,
|
|
299
387
|
reviewPromptTemplate: DEFAULT_REVIEW_PROMPT_TEMPLATE,
|
|
@@ -307,12 +395,22 @@ function defaultSettings() {
|
|
|
307
395
|
audioNotificationVolume: 1,
|
|
308
396
|
notionStatusProperty: '',
|
|
309
397
|
notionInProgressStatus: '',
|
|
310
|
-
|
|
398
|
+
defaultPermissionModeByEngine: { 'claude-code': 'plan', codex: 'plan' },
|
|
311
399
|
notionMcpKey: '',
|
|
312
400
|
sentryMcpKey: '',
|
|
313
401
|
tags: [...DEFAULT_WORKSPACE_TAGS],
|
|
314
402
|
worktreesPath: WORKTREES_PATH,
|
|
315
403
|
worktreesPrefixByProject: false,
|
|
404
|
+
voiceEnabled: false,
|
|
405
|
+
voicePttKey: 'alt',
|
|
406
|
+
voiceLanguage: 'auto',
|
|
407
|
+
voiceModel: null,
|
|
408
|
+
voiceCommandPath: '',
|
|
409
|
+
voiceFfmpegPath: '',
|
|
410
|
+
voiceTemperature: 0,
|
|
411
|
+
voicePrompt: '',
|
|
412
|
+
voiceTranslateToEnglish: false,
|
|
413
|
+
voiceSuppressNonSpeechTokens: true,
|
|
316
414
|
},
|
|
317
415
|
projects: [],
|
|
318
416
|
};
|
|
@@ -497,8 +595,9 @@ export function getEffectiveSettings(projectPath) {
|
|
|
497
595
|
const settings = readSettings();
|
|
498
596
|
const project = settings.projects.find((p) => p.path === projectPath) ?? null;
|
|
499
597
|
if (!project) {
|
|
598
|
+
const claudeCodeDefault = settings.global.defaultModelByEngine?.['claude-code'] ?? 'auto';
|
|
500
599
|
return {
|
|
501
|
-
model:
|
|
600
|
+
model: claudeCodeDefault,
|
|
502
601
|
dangerouslySkipPermissions: settings.global.dangerouslySkipPermissions,
|
|
503
602
|
prPromptTemplate: settings.global.prPromptTemplate,
|
|
504
603
|
reviewPromptTemplate: settings.global.reviewPromptTemplate,
|
|
@@ -512,8 +611,14 @@ export function getEffectiveSettings(projectPath) {
|
|
|
512
611
|
notionInProgressStatus: settings.global.notionInProgressStatus,
|
|
513
612
|
};
|
|
514
613
|
}
|
|
614
|
+
// `model` here is the legacy single-string field exposed via EffectiveSettings
|
|
615
|
+
// for back-compat with existing callers. The engine-aware default lives in
|
|
616
|
+
// `global.defaultModelByEngine[engineId]` and is read directly by the create
|
|
617
|
+
// flow / settings UI. Fall back through claude-code's entry (the historical
|
|
618
|
+
// semantics) so this field never goes empty.
|
|
619
|
+
const claudeCodeDefault = settings.global.defaultModelByEngine?.['claude-code'] ?? 'auto';
|
|
515
620
|
return {
|
|
516
|
-
model: project.defaultModel ||
|
|
621
|
+
model: project.defaultModel || claudeCodeDefault,
|
|
517
622
|
dangerouslySkipPermissions: project.dangerouslySkipPermissions ?? settings.global.dangerouslySkipPermissions,
|
|
518
623
|
prPromptTemplate: project.prPromptTemplate || settings.global.prPromptTemplate,
|
|
519
624
|
reviewPromptTemplate: project.reviewPromptTemplate || settings.global.reviewPromptTemplate,
|
|
@@ -531,7 +636,7 @@ export function getEffectiveSettings(projectPath) {
|
|
|
531
636
|
export function updateGlobalSettings(data) {
|
|
532
637
|
const settings = readSettings();
|
|
533
638
|
const allowedGlobalKeys = [
|
|
534
|
-
'
|
|
639
|
+
'defaultModelByEngine',
|
|
535
640
|
'dangerouslySkipPermissions',
|
|
536
641
|
'prPromptTemplate',
|
|
537
642
|
'reviewPromptTemplate',
|
|
@@ -545,12 +650,22 @@ export function updateGlobalSettings(data) {
|
|
|
545
650
|
'audioNotificationVolume',
|
|
546
651
|
'notionStatusProperty',
|
|
547
652
|
'notionInProgressStatus',
|
|
548
|
-
'
|
|
653
|
+
'defaultPermissionModeByEngine',
|
|
549
654
|
'notionMcpKey',
|
|
550
655
|
'sentryMcpKey',
|
|
551
656
|
'tags',
|
|
552
657
|
'worktreesPath',
|
|
553
658
|
'worktreesPrefixByProject',
|
|
659
|
+
'voiceEnabled',
|
|
660
|
+
'voicePttKey',
|
|
661
|
+
'voiceLanguage',
|
|
662
|
+
'voiceModel',
|
|
663
|
+
'voiceCommandPath',
|
|
664
|
+
'voiceFfmpegPath',
|
|
665
|
+
'voiceTemperature',
|
|
666
|
+
'voicePrompt',
|
|
667
|
+
'voiceTranslateToEnglish',
|
|
668
|
+
'voiceSuppressNonSpeechTokens',
|
|
554
669
|
];
|
|
555
670
|
const filtered = pickKnownKeys(data, allowedGlobalKeys);
|
|
556
671
|
if (filtered.tags !== undefined) {
|
|
@@ -564,6 +679,10 @@ export function updateGlobalSettings(data) {
|
|
|
564
679
|
const v = Number(filtered.audioNotificationVolume);
|
|
565
680
|
filtered.audioNotificationVolume = Number.isFinite(v) ? Math.max(0, Math.min(1, v)) : 1;
|
|
566
681
|
}
|
|
682
|
+
if (filtered.voiceTemperature !== undefined) {
|
|
683
|
+
const t = Number(filtered.voiceTemperature);
|
|
684
|
+
filtered.voiceTemperature = Number.isFinite(t) ? Math.max(0, Math.min(1, t)) : settings.global.voiceTemperature;
|
|
685
|
+
}
|
|
567
686
|
if (filtered.worktreesPath !== undefined) {
|
|
568
687
|
filtered.worktreesPath = validateWorktreesPath(filtered.worktreesPath, { allowEmpty: false });
|
|
569
688
|
ensureGlobalWorktreesRootExists(filtered.worktreesPath);
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { getKoboHome } from '../utils/paths.js';
|
|
7
|
+
import { getGlobalSettings } from './settings-service.js';
|
|
8
|
+
const execFileAsync = promisify(execFile);
|
|
9
|
+
const MAX_LANG_LENGTH = 16;
|
|
10
|
+
export class VoiceError extends Error {
|
|
11
|
+
code;
|
|
12
|
+
status;
|
|
13
|
+
constructor(message, code, status = 400) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.status = status;
|
|
17
|
+
this.name = 'VoiceError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export const VOICE_MODELS = [
|
|
21
|
+
{
|
|
22
|
+
name: 'tiny',
|
|
23
|
+
fileName: 'ggml-tiny.bin',
|
|
24
|
+
url: 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-tiny.bin?download=true',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'base',
|
|
28
|
+
fileName: 'ggml-base.bin',
|
|
29
|
+
url: 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.bin?download=true',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'small',
|
|
33
|
+
fileName: 'ggml-small.bin',
|
|
34
|
+
url: 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-small.bin?download=true',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
name: 'medium',
|
|
38
|
+
fileName: 'ggml-medium.bin',
|
|
39
|
+
url: 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-medium.bin?download=true',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: 'large-v3',
|
|
43
|
+
fileName: 'ggml-large-v3.bin',
|
|
44
|
+
url: 'https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-large-v3.bin?download=true',
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
function voiceHome() {
|
|
48
|
+
return path.join(getKoboHome(), 'voice');
|
|
49
|
+
}
|
|
50
|
+
function modelsDir() {
|
|
51
|
+
return path.join(voiceHome(), 'models', 'whisper');
|
|
52
|
+
}
|
|
53
|
+
function resolveWhisperCommand() {
|
|
54
|
+
const global = getGlobalSettings();
|
|
55
|
+
const fromSettings = (global.voiceCommandPath ?? '').trim();
|
|
56
|
+
if (fromSettings.length > 0)
|
|
57
|
+
return fromSettings;
|
|
58
|
+
return process.env.WHISPER_CPP_COMMAND || 'whisper-cli';
|
|
59
|
+
}
|
|
60
|
+
function resolveFfmpegCommand() {
|
|
61
|
+
const global = getGlobalSettings();
|
|
62
|
+
const fromSettings = (global.voiceFfmpegPath ?? '').trim();
|
|
63
|
+
if (fromSettings.length > 0)
|
|
64
|
+
return fromSettings;
|
|
65
|
+
return 'ffmpeg';
|
|
66
|
+
}
|
|
67
|
+
function ensureVoiceDirs() {
|
|
68
|
+
fs.mkdirSync(modelsDir(), { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
function resolveModel(name) {
|
|
71
|
+
const model = VOICE_MODELS.find((m) => m.name === name);
|
|
72
|
+
if (!model)
|
|
73
|
+
throw new VoiceError(`Unknown voice model '${name}'`, 'MODEL_UNKNOWN', 400);
|
|
74
|
+
return model;
|
|
75
|
+
}
|
|
76
|
+
export function listVoiceModels() {
|
|
77
|
+
ensureVoiceDirs();
|
|
78
|
+
const settings = getGlobalSettings();
|
|
79
|
+
const available = VOICE_MODELS.map((m) => ({
|
|
80
|
+
name: m.name,
|
|
81
|
+
fileName: m.fileName,
|
|
82
|
+
installed: fs.existsSync(path.join(modelsDir(), m.fileName)),
|
|
83
|
+
}));
|
|
84
|
+
return { available, activeModel: settings.voiceModel };
|
|
85
|
+
}
|
|
86
|
+
export async function getVoiceRuntimeStatus() {
|
|
87
|
+
const command = resolveWhisperCommand();
|
|
88
|
+
let ffmpegAvailable = true;
|
|
89
|
+
let ffmpegError;
|
|
90
|
+
try {
|
|
91
|
+
await execFileAsync(resolveFfmpegCommand(), ['-version'], { timeout: 5000 });
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
ffmpegAvailable = false;
|
|
95
|
+
ffmpegError = err instanceof Error ? err.message : String(err);
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await execFileAsync(command, ['-h'], { timeout: 5000 });
|
|
99
|
+
return { available: ffmpegAvailable, command, ffmpegAvailable, ffmpegError };
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
103
|
+
return { available: false, command, error: message, ffmpegAvailable, ffmpegError };
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
export async function downloadVoiceModel(name) {
|
|
107
|
+
ensureVoiceDirs();
|
|
108
|
+
const model = resolveModel(name);
|
|
109
|
+
const res = await fetch(model.url);
|
|
110
|
+
if (!res.ok) {
|
|
111
|
+
throw new VoiceError(`Failed to download model '${name}' (HTTP ${res.status})`, 'MODEL_DOWNLOAD_FAILED', 500);
|
|
112
|
+
}
|
|
113
|
+
const filePath = path.join(modelsDir(), model.fileName);
|
|
114
|
+
const tmpPath = `${filePath}.tmp`;
|
|
115
|
+
try {
|
|
116
|
+
const bytes = Buffer.from(await res.arrayBuffer());
|
|
117
|
+
fs.writeFileSync(tmpPath, bytes);
|
|
118
|
+
fs.renameSync(tmpPath, filePath);
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
if (fs.existsSync(tmpPath))
|
|
122
|
+
fs.rmSync(tmpPath, { force: true });
|
|
123
|
+
}
|
|
124
|
+
return { name, filePath };
|
|
125
|
+
}
|
|
126
|
+
export function deleteVoiceModel(name) {
|
|
127
|
+
const model = resolveModel(name);
|
|
128
|
+
const filePath = path.join(modelsDir(), model.fileName);
|
|
129
|
+
if (fs.existsSync(filePath))
|
|
130
|
+
fs.unlinkSync(filePath);
|
|
131
|
+
}
|
|
132
|
+
function getInstalledModelPath(name) {
|
|
133
|
+
const model = resolveModel(name);
|
|
134
|
+
const fullPath = path.join(modelsDir(), model.fileName);
|
|
135
|
+
if (!fs.existsSync(fullPath)) {
|
|
136
|
+
throw new VoiceError(`Model '${name}' is not installed`, 'MODEL_NOT_INSTALLED', 400);
|
|
137
|
+
}
|
|
138
|
+
return fullPath;
|
|
139
|
+
}
|
|
140
|
+
export async function transcribeAudio(params) {
|
|
141
|
+
const { audioBuffer, modelName } = params;
|
|
142
|
+
const language = params.language && params.language.trim().length > 0 ? params.language : 'auto';
|
|
143
|
+
const temperature = Number.isFinite(Number(params.temperature))
|
|
144
|
+
? Math.max(0, Math.min(1, Number(params.temperature)))
|
|
145
|
+
: 0;
|
|
146
|
+
const prompt = (params.prompt ?? '').trim();
|
|
147
|
+
const translateToEnglish = params.translateToEnglish === true;
|
|
148
|
+
const suppressNst = params.suppressNonSpeechTokens !== false;
|
|
149
|
+
if (language.length > MAX_LANG_LENGTH || !/^[a-z-]+$/i.test(language)) {
|
|
150
|
+
throw new VoiceError(`Invalid language '${language}'`, 'LANGUAGE_INVALID', 400);
|
|
151
|
+
}
|
|
152
|
+
const modelPath = getInstalledModelPath(modelName);
|
|
153
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'kobo-voice-'));
|
|
154
|
+
const audioPath = path.join(tmpDir, 'input.webm');
|
|
155
|
+
const wavPath = path.join(tmpDir, 'input.wav');
|
|
156
|
+
try {
|
|
157
|
+
fs.writeFileSync(audioPath, audioBuffer);
|
|
158
|
+
// Normalize browser-recorded audio (webm/ogg/...) to a mono WAV file that
|
|
159
|
+
// whisper-cli can decode reliably across platforms.
|
|
160
|
+
await execFileAsync(resolveFfmpegCommand(), ['-y', '-i', audioPath, '-ar', '16000', '-ac', '1', wavPath], {
|
|
161
|
+
timeout: 60000,
|
|
162
|
+
});
|
|
163
|
+
const cmd = resolveWhisperCommand();
|
|
164
|
+
const args = [
|
|
165
|
+
'-m',
|
|
166
|
+
modelPath,
|
|
167
|
+
'-f',
|
|
168
|
+
wavPath,
|
|
169
|
+
'-otxt',
|
|
170
|
+
'-of',
|
|
171
|
+
path.join(tmpDir, 'out'),
|
|
172
|
+
'--temperature',
|
|
173
|
+
String(temperature),
|
|
174
|
+
];
|
|
175
|
+
if (language !== 'auto')
|
|
176
|
+
args.push('-l', language);
|
|
177
|
+
if (translateToEnglish)
|
|
178
|
+
args.push('--translate');
|
|
179
|
+
if (suppressNst)
|
|
180
|
+
args.push('--suppress-nst');
|
|
181
|
+
if (prompt.length > 0)
|
|
182
|
+
args.push('--prompt', prompt);
|
|
183
|
+
const start = Date.now();
|
|
184
|
+
const { stderr } = await execFileAsync(cmd, args, { timeout: 120000 });
|
|
185
|
+
const durationMs = Date.now() - start;
|
|
186
|
+
const outTxt = path.join(tmpDir, 'out.txt');
|
|
187
|
+
if (!fs.existsSync(outTxt)) {
|
|
188
|
+
throw new VoiceError(`Transcription output missing (${stderr || 'no stderr'})`, 'TRANSCRIPTION_FAILED', 500);
|
|
189
|
+
}
|
|
190
|
+
const text = fs.readFileSync(outTxt, 'utf-8').trim();
|
|
191
|
+
return { text, durationMs, model: modelName, language };
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
195
|
+
if (message.includes('ENOENT')) {
|
|
196
|
+
throw new VoiceError('Voice runtime missing (whisper-cli or ffmpeg)', 'VOICE_RUNTIME_MISSING', 500);
|
|
197
|
+
}
|
|
198
|
+
if (message.includes('timed out')) {
|
|
199
|
+
throw new VoiceError('Whisper transcription timed out', 'TRANSCRIPTION_TIMEOUT', 500);
|
|
200
|
+
}
|
|
201
|
+
throw err;
|
|
202
|
+
}
|
|
203
|
+
finally {
|
|
204
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -100,8 +100,15 @@ export function getTemplatesPath() {
|
|
|
100
100
|
* Absolute path to the compiled MCP server entry (shipped in the published
|
|
101
101
|
* package as dist/mcp-server/kobo-tasks-server.js). Returns null if not
|
|
102
102
|
* present — callers (orchestrator) then fall back to the TS source for dev.
|
|
103
|
+
*
|
|
104
|
+
* `KOBO_ENFORCE_LOCAL_HOME=1` (set by `npm run dev`) forces the source path
|
|
105
|
+
* even if a stale `dist/mcp-server/` from a prior `npm run build` is hanging
|
|
106
|
+
* around. Without this guard, edits to `kobo-tasks-server.ts` are silently
|
|
107
|
+
* ignored during dev (the orchestrator spawns the months-old compiled binary).
|
|
103
108
|
*/
|
|
104
109
|
export function getCompiledMcpServerPath() {
|
|
110
|
+
if (process.env.KOBO_ENFORCE_LOCAL_HOME === '1')
|
|
111
|
+
return null;
|
|
105
112
|
const compiled = getPackageAssetPath('dist', 'mcp-server', 'kobo-tasks-server.js');
|
|
106
113
|
return fs.existsSync(compiled) ? compiled : null;
|
|
107
114
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex model catalogue — kept in sync with the official roster published at
|
|
3
|
+
* developers.openai.com/codex/models. The Codex CLI accepts arbitrary strings
|
|
4
|
+
* in `--model`, so power users can still pin a model not listed here by
|
|
5
|
+
* editing the workspace `model` field directly. This list reflects the
|
|
6
|
+
* recommended set surfaced in the create-workspace selector.
|
|
7
|
+
*
|
|
8
|
+
* Auth caveat: `gpt-5.5` is currently only reachable when authenticated via
|
|
9
|
+
* ChatGPT (Plus/Pro/Team/Enterprise). API-key auth is limited to `gpt-5.4`
|
|
10
|
+
* and below.
|
|
11
|
+
*/
|
|
12
|
+
export const CODEX_MODELS = [
|
|
13
|
+
{
|
|
14
|
+
id: 'auto',
|
|
15
|
+
label: 'Auto',
|
|
16
|
+
i18nLabelKey: 'model.auto',
|
|
17
|
+
i18nDescriptionKey: 'model.autoDescription',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: 'gpt-5.5',
|
|
21
|
+
label: 'GPT-5.5',
|
|
22
|
+
i18nLabelKey: 'model.gpt55',
|
|
23
|
+
i18nDescriptionKey: 'model.gpt55Description',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'gpt-5.4',
|
|
27
|
+
label: 'GPT-5.4',
|
|
28
|
+
i18nLabelKey: 'model.gpt54',
|
|
29
|
+
i18nDescriptionKey: 'model.gpt54Description',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: 'gpt-5.4-mini',
|
|
33
|
+
label: 'GPT-5.4 mini',
|
|
34
|
+
i18nLabelKey: 'model.gpt54mini',
|
|
35
|
+
i18nDescriptionKey: 'model.gpt54miniDescription',
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
id: 'gpt-5.3-codex',
|
|
39
|
+
label: 'GPT-5.3 Codex',
|
|
40
|
+
i18nLabelKey: 'model.gpt53codex',
|
|
41
|
+
i18nDescriptionKey: 'model.gpt53codexDescription',
|
|
42
|
+
},
|
|
43
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loicngr/kobo",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.8",
|
|
4
4
|
"description": "Kōbō — multi-workspace agent manager for Claude Code. Orchestrates isolated git worktrees with dev servers, Notion integration, and MCP tools.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "GPL-3.0-or-later",
|
|
@@ -66,25 +66,28 @@
|
|
|
66
66
|
"prepublishOnly": "npm run build"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
70
|
-
"@
|
|
69
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.90",
|
|
70
|
+
"@emnapi/core": "^1.10.0",
|
|
71
|
+
"@emnapi/runtime": "^1.10.0",
|
|
72
|
+
"@hono/node-server": "^2.0.2",
|
|
71
73
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
72
|
-
"
|
|
74
|
+
"@openai/codex": "^0.130.0",
|
|
75
|
+
"better-sqlite3": "^12.9.0",
|
|
73
76
|
"cron-parser": "^5.5.0",
|
|
74
|
-
"hono": "^4.12.
|
|
75
|
-
"nanoid": "^5.1.
|
|
77
|
+
"hono": "^4.12.18",
|
|
78
|
+
"nanoid": "^5.1.11",
|
|
76
79
|
"node-pty": "^1.1.0",
|
|
77
80
|
"ws": "^8.20.0"
|
|
78
81
|
},
|
|
79
82
|
"devDependencies": {
|
|
80
83
|
"@biomejs/biome": "2.4.10",
|
|
81
84
|
"@types/better-sqlite3": "^7.6.13",
|
|
82
|
-
"@types/node": "^25.
|
|
85
|
+
"@types/node": "^25.6.2",
|
|
83
86
|
"@types/ws": "^8.18.1",
|
|
84
|
-
"@vitest/runner": "^4.1.
|
|
87
|
+
"@vitest/runner": "^4.1.5",
|
|
85
88
|
"concurrently": "^9.2.1",
|
|
86
89
|
"tsx": "^4.21.0",
|
|
87
|
-
"typescript": "^6.0.
|
|
88
|
-
"vitest": "^
|
|
90
|
+
"typescript": "^6.0.3",
|
|
91
|
+
"vitest": "^4.1.5"
|
|
89
92
|
}
|
|
90
93
|
}
|