@aiworker/web 1.23.2 → 1.23.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/assets/{CodeMirrorCommentWidgets-COGJG0Iv.js → CodeMirrorCommentWidgets-pFeKzADY.js} +1 -1
- package/dist/assets/{DiffView-BviYY3zL.js → DiffView-BYwAHIlW.js} +3 -3
- package/dist/assets/{DirtyBranchSwitchDialog-C26DvfVJ.js → DirtyBranchSwitchDialog-BeyJczAi.js} +1 -1
- package/dist/assets/{FilesView-D0ZU_nCt.js → FilesView-lrP5OlzK.js} +6 -6
- package/dist/assets/{GitView-CYgiDRH-.js → GitView-CCUK73JL.js} +4 -4
- package/dist/assets/{JsonTreeView-GsV9UScz.js → JsonTreeView-CqUi8Ytj.js} +1 -1
- package/dist/assets/{LinearIssuesView-shqUS8nl.js → LinearIssuesView-C0WwIDQN.js} +1 -1
- package/dist/assets/{MarkdownImageGallery-CQSTFp6m.js → MarkdownImageGallery-0NXPad4K.js} +1 -1
- package/dist/assets/{MarkdownRendererImpl-DJWP3A8X.js → MarkdownRendererImpl-q-rUxBkU.js} +4 -4
- package/dist/assets/{OnboardingScreen-CTuu4lgo.js → OnboardingScreen-Cy6xZ-IQ.js} +1 -1
- package/dist/assets/{PatchDiff-Dp53v2pl.js → PatchDiff-DuYWs-7m.js} +1 -1
- package/dist/assets/{PierreDiffViewer-B5l0djPS.js → PierreDiffViewer-C51SEtjJ.js} +1 -1
- package/dist/assets/{PlanView-CLUWkGiG.js → PlanView-k63mHfFF.js} +1 -1
- package/dist/assets/SettingsView-DNJXHPYC.js +28 -0
- package/dist/assets/{SettingsWindow-DbgEnZ6T.js → SettingsWindow-DFMJqIkx.js} +1 -1
- package/dist/assets/{ToolOutputDialog-Du-RZj-a.js → ToolOutputDialog-B4kBDjLK.js} +4 -4
- package/dist/assets/{ToolPartDiffPreview-HBLSokoe.js → ToolPartDiffPreview-DhVBmwis.js} +2 -2
- package/dist/assets/{VirtualizedFileDiff-Bzp3hClU.js → VirtualizedFileDiff-C-MFNoKo.js} +1 -1
- package/dist/assets/{WalkthroughView-CpgA4s9w.js → WalkthroughView-hmnM70S9.js} +1 -1
- package/dist/assets/{computeVirtualFileMetrics-CI1PC_z2.js → computeVirtualFileMetrics-Bv64vrxY.js} +1 -1
- package/dist/assets/{de-BX8b4hDD.js → de-i-51iOMQ.js} +2 -2
- package/dist/assets/{es-e98cS-yM.js → es-CNkqJISC.js} +3 -3
- package/dist/assets/{fr-BOiqxJAs.js → fr-a9t4bHWo.js} +3 -3
- package/dist/assets/index-BvwlOZOg.js +40 -0
- package/dist/assets/{index-Cmn74sw-.js → index-C-KjDH6t.js} +1 -1
- package/dist/assets/index-n5p3SnES.css +1 -0
- package/dist/assets/{isDiffPlainText-DVralkU-.js → isDiffPlainText-B0pMS3gs.js} +1 -1
- package/dist/assets/{isFilePlainText-CgTREsRX.js → isFilePlainText-IXDeiXmH.js} +1 -1
- package/dist/assets/{ja-aoO55hGu.js → ja-C2HQKC-_.js} +3 -3
- package/dist/assets/{ko-BGzFLogk.js → ko-CvVt6P_Q.js} +3 -3
- package/dist/assets/main-6ZT1mvpO.js +930 -0
- package/dist/assets/{main-BgfXQ-4g.js → main-BeWvA5d9.js} +2 -2
- package/dist/assets/markdownCore-DTW1POUM.js +5 -0
- package/dist/assets/{miniChat-DHKXp-_I.js → miniChat-DzMGD1Hv.js} +2 -2
- package/dist/assets/{mobile-lXQ08VnX.js → mobile-CrDSF4dQ.js} +2 -2
- package/dist/assets/{patchFileDiff-i1lTtNbn.js → patchFileDiff-DHG-znZQ.js} +3 -3
- package/dist/assets/{pl-B5x19laq.js → pl-CQi2SXLP.js} +3 -3
- package/dist/assets/{pt-BR-C3oU51Il.js → pt-BR-FdnmJOe8.js} +3 -3
- package/dist/assets/{renderElectronMiniChatApp-CuO4ue6B.js → renderElectronMiniChatApp-BuaS0XYk.js} +2 -2
- package/dist/assets/renderMobileApp-hxXJf_Db.js +25 -0
- package/dist/assets/runtimeEndpointReset-DB4KI-ED.js +41 -0
- package/dist/assets/{scheduledTasksApi-BEuUF_cj.js → scheduledTasksApi-DZaxQ19y.js} +1 -1
- package/dist/assets/{shikiHighlight-ctlcPvvx.js → shikiHighlight-B1goMnV6.js} +1 -1
- package/dist/assets/{tr-C0QQa7et.js → tr-BwD9HLzh.js} +2 -2
- package/dist/assets/{uk-CqLSe9wT.js → uk-BWNCwGJO.js} +3 -3
- package/dist/assets/{useAppFontEffects-B6OfEypo.js → useAppFontEffects-cYK1WODe.js} +130 -130
- package/dist/assets/useGitComparison-DIetwQtj.js +1 -0
- package/dist/assets/{useInlineCommentController-CgbrpEWu.js → useInlineCommentController-DscFZWyI.js} +2 -2
- package/dist/assets/{useRootScrollLock-Me41OjBT.js → useRootScrollLock-CE3HJBOu.js} +1 -1
- package/dist/assets/{vendor-codemirror-language-data-SJuFp0a0.js → vendor-codemirror-language-data-CsvoASYk.js} +2 -2
- package/dist/assets/{vendor-remixicon-react-Dil0zKKT.js → vendor-remixicon-react-BDmGmFCA.js} +1 -1
- package/dist/assets/{web-components-D8GS8Ork.js → web-components-HM6S8xyb.js} +1 -1
- package/dist/assets/{zh-CN-iJEie4d6.js → zh-CN-CH6L_7cX.js} +3 -3
- package/dist/assets/{zh-TW-CyslEwlK.js → zh-TW--py9VFnc.js} +3 -3
- package/dist/index.html +3 -3
- package/dist/mini-chat.html +3 -3
- package/dist/mobile.html +3 -3
- package/package.json +2 -2
- package/server/lib/opencode/DOCUMENTATION.md +14 -0
- package/server/lib/opencode/openchamber-routes.js +72 -14
- package/server/lib/opencode/openchamber-routes.test.js +71 -3
- package/server/lib/opencode/settings-helpers.js +5 -0
- package/server/lib/opencode/settings-helpers.test.js +13 -1
- package/server/lib/opencode/settings-registry.json +3 -0
- package/dist/assets/SettingsView-CojPQVHx.js +0 -28
- package/dist/assets/index-BD8ILbxF.js +0 -40
- package/dist/assets/index-zhgg-SDz.css +0 -1
- package/dist/assets/main-DGYUWmIt.js +0 -930
- package/dist/assets/markdownCore-BesaNs_G.js +0 -5
- package/dist/assets/renderMobileApp-B3zFlHy4.js +0 -25
- package/dist/assets/runtimeEndpointReset-BLzrtrmj.js +0 -41
- package/dist/assets/useGitComparison-BXZXEAW6.js +0 -1
package/dist/index.html
CHANGED
|
@@ -509,16 +509,16 @@
|
|
|
509
509
|
pointer-events: none;
|
|
510
510
|
}
|
|
511
511
|
</style>
|
|
512
|
-
<script type="module" crossorigin src="/assets/main-
|
|
512
|
+
<script type="module" crossorigin src="/assets/main-BeWvA5d9.js"></script>
|
|
513
513
|
<link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
|
|
514
514
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zod-B6551dwG.js">
|
|
515
515
|
<link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
|
|
516
516
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BUCujlKS.js">
|
|
517
517
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-BAGL70zf.js">
|
|
518
518
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-BJ73u-bH.js">
|
|
519
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
519
|
+
<link rel="modulepreload" crossorigin href="/assets/index-BvwlOZOg.js">
|
|
520
520
|
<link rel="modulepreload" crossorigin href="/assets/contextPanelEmbeddedChat-BdA6bdky.js">
|
|
521
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
521
|
+
<link rel="stylesheet" crossorigin href="/assets/index-n5p3SnES.css">
|
|
522
522
|
</head>
|
|
523
523
|
<body class="h-full bg-background text-foreground">
|
|
524
524
|
<div id="root" class="h-full">
|
package/dist/mini-chat.html
CHANGED
|
@@ -74,15 +74,15 @@
|
|
|
74
74
|
}
|
|
75
75
|
</style>
|
|
76
76
|
|
|
77
|
-
<script type="module" crossorigin src="/assets/miniChat-
|
|
77
|
+
<script type="module" crossorigin src="/assets/miniChat-DzMGD1Hv.js"></script>
|
|
78
78
|
<link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
|
|
79
79
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zod-B6551dwG.js">
|
|
80
80
|
<link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
|
|
81
81
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BUCujlKS.js">
|
|
82
82
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-BAGL70zf.js">
|
|
83
83
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-BJ73u-bH.js">
|
|
84
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
85
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
84
|
+
<link rel="modulepreload" crossorigin href="/assets/index-BvwlOZOg.js">
|
|
85
|
+
<link rel="stylesheet" crossorigin href="/assets/index-n5p3SnES.css">
|
|
86
86
|
</head>
|
|
87
87
|
<body class="h-full bg-background text-foreground">
|
|
88
88
|
<div id="root" class="h-full">
|
package/dist/mobile.html
CHANGED
|
@@ -63,15 +63,15 @@
|
|
|
63
63
|
})();
|
|
64
64
|
</script>
|
|
65
65
|
|
|
66
|
-
<script type="module" crossorigin src="/assets/mobile-
|
|
66
|
+
<script type="module" crossorigin src="/assets/mobile-CrDSF4dQ.js"></script>
|
|
67
67
|
<link rel="modulepreload" crossorigin href="/assets/vendor-vite-runtime-DD1OZmLC.js">
|
|
68
68
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zod-B6551dwG.js">
|
|
69
69
|
<link rel="modulepreload" crossorigin href="/assets/vendor-scheduler-7OC5HNn7.js">
|
|
70
70
|
<link rel="modulepreload" crossorigin href="/assets/vendor-react-BUCujlKS.js">
|
|
71
71
|
<link rel="modulepreload" crossorigin href="/assets/vendor-zustand-BAGL70zf.js">
|
|
72
72
|
<link rel="modulepreload" crossorigin href="/assets/vendor-opencode-sdk-BJ73u-bH.js">
|
|
73
|
-
<link rel="modulepreload" crossorigin href="/assets/index-
|
|
74
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
73
|
+
<link rel="modulepreload" crossorigin href="/assets/index-BvwlOZOg.js">
|
|
74
|
+
<link rel="stylesheet" crossorigin href="/assets/index-n5p3SnES.css">
|
|
75
75
|
</head>
|
|
76
76
|
<body class="h-full bg-background text-foreground">
|
|
77
77
|
<div id="root" class="h-full"></div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiworker/web",
|
|
3
|
-
"version": "1.23.
|
|
3
|
+
"version": "1.23.03",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./server/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dependencies": {
|
|
26
26
|
"@clack/prompts": "^1.1.0",
|
|
27
27
|
"@octokit/rest": "^22.0.1",
|
|
28
|
-
"@opencode-ai/sdk": "1.18.
|
|
28
|
+
"@opencode-ai/sdk": "1.18.31",
|
|
29
29
|
"@simplewebauthn/server": "13.3.1",
|
|
30
30
|
"bun-pty": "^0.4.5",
|
|
31
31
|
"compression": "^1.8.1",
|
|
@@ -389,6 +389,20 @@ within a ten-minute overall deadline.
|
|
|
389
389
|
- Foreground servers running under a systemd user unit queue installation in
|
|
390
390
|
a separate transient unit and restart the configured service afterwards.
|
|
391
391
|
`AIWORKER_SYSTEMD_UNIT` overrides the default `aiworker.service`.
|
|
392
|
+
- On Windows the install-and-restart script is written to
|
|
393
|
+
`<data dir>/update-install.cmd` before the response and run with
|
|
394
|
+
`cmd.exe /c <file>`. A newline ends a `cmd.exe /c` command line, so the
|
|
395
|
+
same script passed as an argument ran nothing and exited 0; the batch
|
|
396
|
+
file keeps every line. The package-manager line is `call`ed because
|
|
397
|
+
npm, pnpm and yarn are `.cmd` shims that would otherwise end the script,
|
|
398
|
+
the pre-install pause is a loopback `ping` because `timeout` rejects a
|
|
399
|
+
detached child's stdin, and the file deletes itself on its last line
|
|
400
|
+
because the restart command carries the server's flags. If the file
|
|
401
|
+
cannot be written the route answers 500 and the server keeps running.
|
|
402
|
+
The listener is closed before the batch is spawned: on Windows the
|
|
403
|
+
detached child inherits the listening socket and would hold the port
|
|
404
|
+
for the whole batch, so the restart inside it failed with "port already
|
|
405
|
+
in use" and the update ended with no server.
|
|
392
406
|
- `GET /api/openchamber/models-metadata`
|
|
393
407
|
- `GET /api/zen/models`
|
|
394
408
|
|
|
@@ -16,6 +16,43 @@ function quotePosixShell(value) {
|
|
|
16
16
|
return `'${String(value).replace(/'/g, "'\\''")}'`;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
/**
|
|
20
|
+
* `echo` in batch treats `& | < > ( ) ^` as syntax and `%var%` as expansion,
|
|
21
|
+
* so a line of the log preamble is escaped before it becomes an `echo`.
|
|
22
|
+
*/
|
|
23
|
+
const escapeBatchEchoLine = (line) => {
|
|
24
|
+
if (!line) return 'echo.';
|
|
25
|
+
return `echo ${line.replace(/%/g, '%%').replace(/[&|<>()^]/g, '^$&')}`;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The Windows update runs as a batch file, one command per line, so the
|
|
30
|
+
* `if ... else` block and the multi-line log preamble keep their structure.
|
|
31
|
+
*/
|
|
32
|
+
const buildWindowsUpdateScript = ({ logPreamble, updateCmd, restartCmd }) => [
|
|
33
|
+
'@echo off',
|
|
34
|
+
...logPreamble.split('\n').map(escapeBatchEchoLine),
|
|
35
|
+
// `timeout` refuses redirected stdin, which is what a detached child has;
|
|
36
|
+
// a ping to loopback waits about two seconds without a console.
|
|
37
|
+
'ping -n 3 127.0.0.1 >nul',
|
|
38
|
+
// npm, pnpm and yarn are .cmd shims on Windows. Without `call`, a batch
|
|
39
|
+
// file hands control to them for good and the restart below never runs.
|
|
40
|
+
// Read from a file, cmd expands `%x%` and drops a lone `%`, so a `%` in a
|
|
41
|
+
// host or UI password would change the restart command. Doubling keeps it.
|
|
42
|
+
`call ${updateCmd.replace(/%/g, '%%')}`,
|
|
43
|
+
'if %ERRORLEVEL% EQU 0 (',
|
|
44
|
+
' echo Update successful, restarting AiWorker...',
|
|
45
|
+
` ${restartCmd ? restartCmd.replace(/%/g, '%%') : 'echo Service manager will restart AiWorker.'}`,
|
|
46
|
+
') else (',
|
|
47
|
+
' echo Update failed',
|
|
48
|
+
')',
|
|
49
|
+
// The restart command carries the server's own flags, `--ui-password`
|
|
50
|
+
// included, so the file does not outlive the run. Deleting the running
|
|
51
|
+
// batch file on its last line is safe: cmd has already read it.
|
|
52
|
+
'del "%~f0"',
|
|
53
|
+
'',
|
|
54
|
+
].join('\r\n');
|
|
55
|
+
|
|
19
56
|
export const registerAiWorkerRoutes = (app, dependencies) => {
|
|
20
57
|
const {
|
|
21
58
|
fs,
|
|
@@ -300,6 +337,26 @@ export const registerAiWorkerRoutes = (app, dependencies) => {
|
|
|
300
337
|
`logPath=${updateLogPath}`,
|
|
301
338
|
].join('\n');
|
|
302
339
|
|
|
340
|
+
const shell = isWindows ? (process.env.ComSpec || 'cmd.exe') : 'sh';
|
|
341
|
+
const shellFlag = isWindows ? '/c' : '-c';
|
|
342
|
+
// cmd.exe /c takes one command line: a newline inside it ends the
|
|
343
|
+
// command, so a multi-line script passed as the argument ran nothing
|
|
344
|
+
// and exited 0, and the server shut itself down believing the update
|
|
345
|
+
// was under way (#3084). Batch runs from a file instead, written before
|
|
346
|
+
// the client is told to expect a restart.
|
|
347
|
+
const windowsScriptPath = path.join(aiworkerDataDir, 'update-install.cmd');
|
|
348
|
+
if (isWindows) {
|
|
349
|
+
try {
|
|
350
|
+
fs.mkdirSync(path.dirname(windowsScriptPath), { recursive: true });
|
|
351
|
+
fs.writeFileSync(windowsScriptPath, buildWindowsUpdateScript({ logPreamble, updateCmd, restartCmd }), 'utf8');
|
|
352
|
+
} catch (scriptError) {
|
|
353
|
+
console.error('Failed to write the update script, update not started:', scriptError);
|
|
354
|
+
return res.status(500).json({
|
|
355
|
+
error: `Could not write the update script at ${windowsScriptPath}: ${scriptError instanceof Error ? scriptError.message : String(scriptError)}`,
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
303
360
|
res.json({
|
|
304
361
|
success: true,
|
|
305
362
|
message: 'Update starting, server will restart shortly',
|
|
@@ -314,21 +371,8 @@ export const registerAiWorkerRoutes = (app, dependencies) => {
|
|
|
314
371
|
console.log(`Running: ${updateCmd}`);
|
|
315
372
|
console.log(logPreamble);
|
|
316
373
|
|
|
317
|
-
const shell = isWindows ? (process.env.ComSpec || 'cmd.exe') : 'sh';
|
|
318
|
-
const shellFlag = isWindows ? '/c' : '-c';
|
|
319
374
|
const script = isWindows
|
|
320
|
-
?
|
|
321
|
-
echo ${quoteCmd(logPreamble)}
|
|
322
|
-
timeout /t 2 /nobreak >nul
|
|
323
|
-
${updateCmd}
|
|
324
|
-
if %ERRORLEVEL% EQU 0 (
|
|
325
|
-
echo Update successful, restarting AiWorker...
|
|
326
|
-
${restartCmd || 'echo Service manager will restart AiWorker.'}
|
|
327
|
-
) else (
|
|
328
|
-
echo Update failed
|
|
329
|
-
exit /b 1
|
|
330
|
-
)
|
|
331
|
-
`
|
|
375
|
+
? windowsScriptPath
|
|
332
376
|
: `
|
|
333
377
|
printf '%s\n' ${quotePosix(logPreamble)}
|
|
334
378
|
sleep 2
|
|
@@ -350,10 +394,24 @@ export const registerAiWorkerRoutes = (app, dependencies) => {
|
|
|
350
394
|
console.warn('Failed to open update log file, continuing without log capture:', logError);
|
|
351
395
|
}
|
|
352
396
|
|
|
397
|
+
if (isWindows) {
|
|
398
|
+
// On Windows the detached child inherits this process's listening
|
|
399
|
+
// socket, and keeps the port for as long as the batch runs. The
|
|
400
|
+
// restart inside that batch then fails with "port already in use",
|
|
401
|
+
// and the update ends with no server. Closing the listener first
|
|
402
|
+
// leaves nothing to inherit; the process exits right after anyway.
|
|
403
|
+
try {
|
|
404
|
+
server.close();
|
|
405
|
+
} catch (closeError) {
|
|
406
|
+
console.warn('Failed to close the listener before the update script:', closeError);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
353
410
|
const child = spawnChild(shell, [shellFlag, script], {
|
|
354
411
|
detached: true,
|
|
355
412
|
stdio: logFd !== null ? ['ignore', logFd, logFd] : 'ignore',
|
|
356
413
|
env: process.env,
|
|
414
|
+
windowsHide: true,
|
|
357
415
|
});
|
|
358
416
|
child.unref();
|
|
359
417
|
|
|
@@ -18,11 +18,15 @@ const childProcess = await import('child_process');
|
|
|
18
18
|
const packageManager = await import('../package-manager.js');
|
|
19
19
|
const { registerAiWorkerRoutes } = await import('./openchamber-routes.js');
|
|
20
20
|
|
|
21
|
-
const createApp = ({ environment = {}, storedOptions = {}, desktopUpdater } = {}) => {
|
|
21
|
+
const createApp = ({ environment = {}, storedOptions = {}, desktopUpdater, platform = 'linux', execPath = '/usr/bin/node' } = {}) => {
|
|
22
22
|
const app = express();
|
|
23
23
|
const dependencies = {
|
|
24
24
|
fs: {
|
|
25
25
|
existsSync: vi.fn(() => false),
|
|
26
|
+
mkdirSync: vi.fn(),
|
|
27
|
+
writeFileSync: vi.fn(),
|
|
28
|
+
openSync: vi.fn(() => 7),
|
|
29
|
+
closeSync: vi.fn(),
|
|
26
30
|
promises: {
|
|
27
31
|
readFile: vi.fn(async () => JSON.stringify({
|
|
28
32
|
launchMode: 'foreground',
|
|
@@ -34,11 +38,13 @@ const createApp = ({ environment = {}, storedOptions = {}, desktopUpdater } = {}
|
|
|
34
38
|
path,
|
|
35
39
|
process: {
|
|
36
40
|
env: environment,
|
|
37
|
-
platform
|
|
38
|
-
execPath
|
|
41
|
+
platform,
|
|
42
|
+
execPath,
|
|
43
|
+
exit: vi.fn(),
|
|
39
44
|
},
|
|
40
45
|
server: {
|
|
41
46
|
address: () => ({ port: 7897 }),
|
|
47
|
+
close: vi.fn(),
|
|
42
48
|
},
|
|
43
49
|
__dirname: '/opt/openchamber/server',
|
|
44
50
|
aiworkerDataDir: '/tmp/openchamber',
|
|
@@ -266,3 +272,65 @@ describe('AiWorker foreground update route', () => {
|
|
|
266
272
|
});
|
|
267
273
|
});
|
|
268
274
|
});
|
|
275
|
+
|
|
276
|
+
describe('AiWorker web update route on Windows', () => {
|
|
277
|
+
it('runs the install-and-restart script from a batch file instead of a cmd.exe /c argument', async () => {
|
|
278
|
+
const { app, dependencies } = createApp({
|
|
279
|
+
platform: 'win32',
|
|
280
|
+
execPath: 'C:\\Program Files\\nodejs\\node.exe',
|
|
281
|
+
environment: { ComSpec: 'C:\\Windows\\system32\\cmd.exe' },
|
|
282
|
+
storedOptions: { launchMode: 'daemon', port: 7897, uiPassword: 'pa%ss' },
|
|
283
|
+
});
|
|
284
|
+
childProcess.spawn.mockReturnValue({ unref: vi.fn() });
|
|
285
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
286
|
+
|
|
287
|
+
await request(app).post('/api/openchamber/update-install').expect(200);
|
|
288
|
+
await new Promise((resolve) => setTimeout(resolve, 1300));
|
|
289
|
+
|
|
290
|
+
const scriptPath = path.join('/tmp/openchamber', 'update-install.cmd');
|
|
291
|
+
expect(dependencies.fs.writeFileSync).toHaveBeenCalledWith(scriptPath, expect.any(String), 'utf8');
|
|
292
|
+
const script = dependencies.fs.writeFileSync.mock.calls[0][1];
|
|
293
|
+
const lines = script.split('\r\n');
|
|
294
|
+
expect(lines[0]).toBe('@echo off');
|
|
295
|
+
// Every preamble line is an echo; none is left to run as a command.
|
|
296
|
+
expect(lines.filter((line) => line.startsWith('currentVersion=') || line.startsWith('restartCommand='))).toEqual([]);
|
|
297
|
+
expect(lines).toContain('echo packageManager=npm');
|
|
298
|
+
expect(lines).toContain('echo restartCommand=^("C:\\Program Files\\nodejs\\node.exe" "/opt/openchamber/bin/cli.js" serve --port 7897 --ui-password "pa%%ss"^) ^|^| ^(aiworker serve --port 7897 --ui-password "pa%%ss"^)');
|
|
299
|
+
// A .cmd shim (npm, pnpm, yarn) must be `call`ed or the script ends there.
|
|
300
|
+
expect(lines).toContain('call npm install -g @aiworker/web@latest');
|
|
301
|
+
expect(lines).toContain('ping -n 3 127.0.0.1 >nul');
|
|
302
|
+
expect(lines.some((line) => line.startsWith('timeout '))).toBe(false);
|
|
303
|
+
expect(lines).toContain('if %ERRORLEVEL% EQU 0 (');
|
|
304
|
+
expect(lines.at(-2)).toBe('del "%~f0"');
|
|
305
|
+
// A `%` in the password survives batch expansion only when doubled.
|
|
306
|
+
expect(lines).toContain(' ("C:\\Program Files\\nodejs\\node.exe" "/opt/openchamber/bin/cli.js" serve --port 7897 --ui-password "pa%%ss") || (aiworker serve --port 7897 --ui-password "pa%%ss")');
|
|
307
|
+
|
|
308
|
+
expect(childProcess.spawn).toHaveBeenCalledWith(
|
|
309
|
+
'C:\\Windows\\system32\\cmd.exe',
|
|
310
|
+
['/c', scriptPath],
|
|
311
|
+
expect.objectContaining({ detached: true, windowsHide: true }),
|
|
312
|
+
);
|
|
313
|
+
// The listener is closed before the batch is spawned, so the detached
|
|
314
|
+
// child cannot inherit the socket and hold the port against the restart.
|
|
315
|
+
expect(dependencies.server.close).toHaveBeenCalledOnce();
|
|
316
|
+
expect(dependencies.server.close.mock.invocationCallOrder[0]).toBeLessThan(childProcess.spawn.mock.invocationCallOrder[0]);
|
|
317
|
+
expect(dependencies.process.exit).toHaveBeenCalledWith(0);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
it('answers 500 and keeps the server up when the batch file cannot be written', async () => {
|
|
321
|
+
const { app, dependencies } = createApp({ platform: 'win32', storedOptions: { launchMode: 'daemon', port: 7897 } });
|
|
322
|
+
dependencies.fs.writeFileSync.mockImplementation(() => { throw new Error('EACCES'); });
|
|
323
|
+
vi.spyOn(console, 'log').mockImplementation(() => {});
|
|
324
|
+
const logError = vi.spyOn(console, 'error').mockImplementation(() => {});
|
|
325
|
+
|
|
326
|
+
const response = await request(app).post('/api/openchamber/update-install').expect(500);
|
|
327
|
+
await new Promise((resolve) => setTimeout(resolve, 1300));
|
|
328
|
+
|
|
329
|
+
expect(response.body.error).toContain('update-install.cmd');
|
|
330
|
+
expect(response.body.error).toContain('EACCES');
|
|
331
|
+
expect(childProcess.spawn).not.toHaveBeenCalled();
|
|
332
|
+
expect(dependencies.process.exit).not.toHaveBeenCalled();
|
|
333
|
+
expect(logError).toHaveBeenCalledOnce();
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
@@ -233,6 +233,11 @@ export const createSettingsHelpers = (dependencies) => {
|
|
|
233
233
|
if (typeof candidate.workStatusHiddenSectionsExplicit === 'boolean') {
|
|
234
234
|
result.workStatusHiddenSectionsExplicit = candidate.workStatusHiddenSectionsExplicit;
|
|
235
235
|
}
|
|
236
|
+
if (Array.isArray(candidate.workStatusSectionOrder)) {
|
|
237
|
+
result.workStatusSectionOrder = [
|
|
238
|
+
...new Set(candidate.workStatusSectionOrder.filter((entry) => typeof entry === 'string' && entry.length > 0)),
|
|
239
|
+
];
|
|
240
|
+
}
|
|
236
241
|
if (typeof candidate.desktopLanAccessEnabled === 'boolean') {
|
|
237
242
|
result.desktopLanAccessEnabled = candidate.desktopLanAccessEnabled;
|
|
238
243
|
}
|
|
@@ -69,6 +69,18 @@ const createTestHelpersWithRealSanitizers = () => {
|
|
|
69
69
|
};
|
|
70
70
|
|
|
71
71
|
describe('settings helpers', () => {
|
|
72
|
+
it('round-trips section order and preserves it across unrelated writes', () => {
|
|
73
|
+
const helpers = createTestHelpers();
|
|
74
|
+
const changes = helpers.sanitizeSettingsUpdate({ workStatusSectionOrder: ['mcp', 'session', 'mcp', null, ''] });
|
|
75
|
+
expect(changes.workStatusSectionOrder).toEqual(['mcp', 'session']);
|
|
76
|
+
const saved = helpers.mergePersistedSettings({}, changes);
|
|
77
|
+
const reloaded = helpers.formatSettingsResponse(JSON.parse(JSON.stringify(saved)));
|
|
78
|
+
expect(reloaded.workStatusSectionOrder).toEqual(['mcp', 'session']);
|
|
79
|
+
const next = helpers.mergePersistedSettings(reloaded, helpers.sanitizeSettingsUpdate({ workStatusPanelEnabled: false }));
|
|
80
|
+
expect(helpers.formatSettingsResponse(next).workStatusSectionOrder).toEqual(['mcp', 'session']);
|
|
81
|
+
expect(helpers.sanitizeSettingsUpdate({ workStatusSectionOrder: 'bad' }).workStatusSectionOrder).toBeUndefined();
|
|
82
|
+
expect(helpers.sanitizeSettingsUpdate({ workStatusSectionOrder: [] }).workStatusSectionOrder).toEqual([]);
|
|
83
|
+
});
|
|
72
84
|
it('round-trips telemetry opt-in with the hidden list and preserves it across unrelated writes', () => {
|
|
73
85
|
const helpers = createTestHelpers();
|
|
74
86
|
const legacy = helpers.sanitizeSettingsUpdate({ workStatusHiddenSections: [] });
|
|
@@ -762,7 +774,7 @@ describe('settings registry gate', () => {
|
|
|
762
774
|
managedRemoteTunnelHostname: 'x.example', managedRemoteTunnelToken: 'token', managedRemoteTunnelPresets: [{ id: 'a', name: 'A', hostname: 'a.example' }],
|
|
763
775
|
managedRemoteTunnelSelectedPresetId: 'a', managedRemoteTunnelPresetTokens: { a: 'token' },
|
|
764
776
|
sidebarProjectDisplayMode: 'all', sidebarSessionGroupingMode: 'flat', sidebarProjectSortOrder: 'manual', sidebarShowRecentSection: true,
|
|
765
|
-
workStatusPanelEnabled: true, workStatusHiddenSections: ['mcp'], workStatusHiddenSectionsExplicit: true,
|
|
777
|
+
workStatusPanelEnabled: true, workStatusHiddenSections: ['mcp'], workStatusHiddenSectionsExplicit: true, workStatusSectionOrder: ['mcp', 'session'],
|
|
766
778
|
showReasoningTraces: true, streamingAutoFollowEnabled: true, collapsibleThinkingBlocks: true, showTextJustificationActivity: true,
|
|
767
779
|
chatRenderMode: 'live', activityRenderMode: 'summary', mermaidRenderingMode: 'svg', userMessageRenderingMode: 'markdown', collapsibleUserMessages: true,
|
|
768
780
|
stickyUserHeader: true, promptNavigatorEnabled: true, wideChatLayoutEnabled: true, showSplitAssistantMessageActions: true, showToolFileIcons: true,
|